eth/tracers: add onlyTopCall option to callTracer (#25430)
This PR allows users to pass in a config object directly to the tracers. Previously only the struct logger was configurable. It also adds an option to the call tracer which if enabled makes it ignore any subcall and collect only information about the top-level call. See #25419 for discussion. The tracers will silently ignore if they are passed a config they don't care about.
This commit is contained in:
@@ -55,11 +55,11 @@ type fourByteTracer struct {
|
||||
|
||||
// newFourByteTracer returns a native go tracer which collects
|
||||
// 4 byte-identifiers of a tx, and implements vm.EVMLogger.
|
||||
func newFourByteTracer(ctx *tracers.Context) tracers.Tracer {
|
||||
func newFourByteTracer(ctx *tracers.Context, _ json.RawMessage) (tracers.Tracer, error) {
|
||||
t := &fourByteTracer{
|
||||
ids: make(map[string]int),
|
||||
}
|
||||
return t
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// isPrecompiled returns whether the addr is a precompile. Logic borrowed from newJsTracer in eth/tracers/js/tracer.go
|
||||
|
||||
@@ -50,16 +50,27 @@ type callFrame struct {
|
||||
type callTracer struct {
|
||||
env *vm.EVM
|
||||
callstack []callFrame
|
||||
config callTracerConfig
|
||||
interrupt uint32 // Atomic flag to signal execution interruption
|
||||
reason error // Textual reason for the interruption
|
||||
}
|
||||
|
||||
type callTracerConfig struct {
|
||||
OnlyTopCall bool `json:"onlyTopCall"` // If true, call tracer won't collect any subcalls
|
||||
}
|
||||
|
||||
// newCallTracer returns a native go tracer which tracks
|
||||
// call frames of a tx, and implements vm.EVMLogger.
|
||||
func newCallTracer(ctx *tracers.Context) tracers.Tracer {
|
||||
func newCallTracer(ctx *tracers.Context, cfg json.RawMessage) (tracers.Tracer, error) {
|
||||
var config callTracerConfig
|
||||
if cfg != nil {
|
||||
if err := json.Unmarshal(cfg, &config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
// First callframe contains tx context info
|
||||
// and is populated on start and end.
|
||||
return &callTracer{callstack: make([]callFrame, 1)}
|
||||
return &callTracer{callstack: make([]callFrame, 1), config: config}, nil
|
||||
}
|
||||
|
||||
// CaptureStart implements the EVMLogger interface to initialize the tracing operation.
|
||||
@@ -101,6 +112,9 @@ func (t *callTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, _ *
|
||||
|
||||
// CaptureEnter is called when EVM enters a new scope (via call, create or selfdestruct).
|
||||
func (t *callTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
|
||||
if t.config.OnlyTopCall {
|
||||
return
|
||||
}
|
||||
// Skip if tracing was interrupted
|
||||
if atomic.LoadUint32(&t.interrupt) > 0 {
|
||||
t.env.Cancel()
|
||||
@@ -121,6 +135,9 @@ func (t *callTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.
|
||||
// CaptureExit is called when EVM exits a scope, even if the scope didn't
|
||||
// execute any code.
|
||||
func (t *callTracer) CaptureExit(output []byte, gasUsed uint64, err error) {
|
||||
if t.config.OnlyTopCall {
|
||||
return
|
||||
}
|
||||
size := len(t.callstack)
|
||||
if size <= 1 {
|
||||
return
|
||||
|
||||
@@ -35,8 +35,8 @@ func init() {
|
||||
type noopTracer struct{}
|
||||
|
||||
// newNoopTracer returns a new noop tracer.
|
||||
func newNoopTracer(ctx *tracers.Context) tracers.Tracer {
|
||||
return &noopTracer{}
|
||||
func newNoopTracer(ctx *tracers.Context, _ json.RawMessage) (tracers.Tracer, error) {
|
||||
return &noopTracer{}, nil
|
||||
}
|
||||
|
||||
// CaptureStart implements the EVMLogger interface to initialize the tracing operation.
|
||||
|
||||
@@ -51,10 +51,10 @@ type prestateTracer struct {
|
||||
reason error // Textual reason for the interruption
|
||||
}
|
||||
|
||||
func newPrestateTracer(ctx *tracers.Context) tracers.Tracer {
|
||||
func newPrestateTracer(ctx *tracers.Context, _ json.RawMessage) (tracers.Tracer, error) {
|
||||
// First callframe contains tx context info
|
||||
// and is populated on start and end.
|
||||
return &prestateTracer{prestate: prestate{}}
|
||||
return &prestateTracer{prestate: prestate{}}, nil
|
||||
}
|
||||
|
||||
// CaptureStart implements the EVMLogger interface to initialize the tracing operation.
|
||||
|
||||
@@ -46,8 +46,8 @@ type revertReasonTracer struct {
|
||||
}
|
||||
|
||||
// newRevertReasonTracer returns a new revert reason tracer.
|
||||
func newRevertReasonTracer(_ *tracers.Context) tracers.Tracer {
|
||||
return &revertReasonTracer{}
|
||||
func newRevertReasonTracer(_ *tracers.Context, _ json.RawMessage) (tracers.Tracer, error) {
|
||||
return &revertReasonTracer{}, nil
|
||||
}
|
||||
|
||||
// CaptureStart implements the EVMLogger interface to initialize the tracing operation.
|
||||
|
||||
@@ -35,6 +35,7 @@ func init() {
|
||||
package native
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"github.com/ethereum/go-ethereum/eth/tracers"
|
||||
@@ -46,7 +47,7 @@ func init() {
|
||||
}
|
||||
|
||||
// ctorFn is the constructor signature of a native tracer.
|
||||
type ctorFn = func(*tracers.Context) tracers.Tracer
|
||||
type ctorFn = func(*tracers.Context, json.RawMessage) (tracers.Tracer, error)
|
||||
|
||||
/*
|
||||
ctors is a map of package-local tracer constructors.
|
||||
@@ -71,12 +72,12 @@ func register(name string, ctor ctorFn) {
|
||||
}
|
||||
|
||||
// lookup returns a tracer, if one can be matched to the given name.
|
||||
func lookup(name string, ctx *tracers.Context) (tracers.Tracer, error) {
|
||||
func lookup(name string, ctx *tracers.Context, cfg json.RawMessage) (tracers.Tracer, error) {
|
||||
if ctors == nil {
|
||||
ctors = make(map[string]ctorFn)
|
||||
}
|
||||
if ctor, ok := ctors[name]; ok {
|
||||
return ctor(ctx), nil
|
||||
return ctor(ctx, cfg)
|
||||
}
|
||||
return nil, errors.New("no tracer found")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user