Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

eth/tracers: add onlyTopCall option to callTracer #25430

Merged
merged 7 commits into from Aug 9, 2022
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 3 additions & 3 deletions core/vm/runtime/runtime_test.go
Expand Up @@ -333,7 +333,7 @@ func benchmarkNonModifyingCode(gas uint64, code []byte, name string, tracerCode
cfg.State, _ = state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil)
cfg.GasLimit = gas
if len(tracerCode) > 0 {
tracer, err := tracers.New(tracerCode, new(tracers.Context))
tracer, err := tracers.New(tracerCode, new(tracers.Context), nil)
if err != nil {
b.Fatal(err)
}
Expand Down Expand Up @@ -832,7 +832,7 @@ func TestRuntimeJSTracer(t *testing.T) {
statedb.SetCode(common.HexToAddress("0xee"), calleeCode)
statedb.SetCode(common.HexToAddress("0xff"), depressedCode)

tracer, err := tracers.New(jsTracer, new(tracers.Context))
tracer, err := tracers.New(jsTracer, new(tracers.Context), nil)
if err != nil {
t.Fatal(err)
}
Expand Down Expand Up @@ -868,7 +868,7 @@ func TestJSTracerCreateTx(t *testing.T) {
code := []byte{byte(vm.PUSH1), 0, byte(vm.PUSH1), 0, byte(vm.RETURN)}

statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil)
tracer, err := tracers.New(jsTracer, new(tracers.Context))
tracer, err := tracers.New(jsTracer, new(tracers.Context), nil)
if err != nil {
t.Fatal(err)
}
Expand Down
11 changes: 6 additions & 5 deletions eth/tracers/api.go
Expand Up @@ -20,6 +20,7 @@ import (
"bufio"
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"os"
Expand Down Expand Up @@ -169,15 +170,15 @@ type TraceConfig struct {
Tracer *string
Timeout *string
Reexec *uint64
// Config specific to given tracer. Note struct logger
// config are historically embedded in main object.
TracerConfig json.RawMessage
}

// TraceCallConfig is the config for traceCall API. It holds one more
// field to override the state for tracing.
type TraceCallConfig struct {
*logger.Config
Tracer *string
Timeout *string
Reexec *uint64
TraceConfig
StateOverrides *ethapi.StateOverride
BlockOverrides *ethapi.BlockOverrides
}
Expand Down Expand Up @@ -882,7 +883,7 @@ func (api *API) traceTx(ctx context.Context, message core.Message, txctx *Contex
// Default tracer is the struct logger
tracer = logger.NewStructLogger(config.Config)
if config.Tracer != nil {
tracer, err = New(*config.Tracer, txctx)
tracer, err = New(*config.Tracer, txctx, config.TracerConfig)
if err != nil {
return nil, err
}
Expand Down
6 changes: 3 additions & 3 deletions eth/tracers/internal/tracetest/calltrace_test.go
Expand Up @@ -179,7 +179,7 @@ func testCallTracer(tracerName string, dirPath string, t *testing.T) {
}
_, statedb = tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc, false)
)
tracer, err := tracers.New(tracerName, new(tracers.Context))
tracer, err := tracers.New(tracerName, new(tracers.Context), nil)
if err != nil {
t.Fatalf("failed to create call tracer: %v", err)
}
Expand Down Expand Up @@ -293,7 +293,7 @@ func benchTracer(tracerName string, test *callTracerTest, b *testing.B) {
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
tracer, err := tracers.New(tracerName, new(tracers.Context))
tracer, err := tracers.New(tracerName, new(tracers.Context), nil)
if err != nil {
b.Fatalf("failed to create call tracer: %v", err)
}
Expand Down Expand Up @@ -359,7 +359,7 @@ func TestZeroValueToNotExitCall(t *testing.T) {
}
_, statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), alloc, false)
// Create the tracer, the EVM environment and run it
tracer, err := tracers.New("callTracer", nil)
tracer, err := tracers.New("callTracer", nil, nil)
if err != nil {
t.Fatalf("failed to create call tracer: %v", err)
}
Expand Down
2 changes: 1 addition & 1 deletion eth/tracers/js/goja.go
Expand Up @@ -125,7 +125,7 @@ type jsTracer struct {
// The methods `result` and `fault` are required to be present.
// The methods `step`, `enter`, and `exit` are optional, but note that
// `enter` and `exit` always go together.
func newJsTracer(code string, ctx *tracers.Context) (tracers.Tracer, error) {
func newJsTracer(code string, ctx *tracers.Context, _ json.RawMessage) (tracers.Tracer, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems wrong to just drop it on the floor here. I think we should maybe invoke some init(msg) with it. Can save that for some other PR though, if you want

if c, ok := assetTracers[code]; ok {
code = c
}
Expand Down
18 changes: 9 additions & 9 deletions eth/tracers/js/tracer_test.go
Expand Up @@ -85,7 +85,7 @@ func runTrace(tracer tracers.Tracer, vmctx *vmContext, chaincfg *params.ChainCon
func TestTracer(t *testing.T) {
execTracer := func(code string) ([]byte, string) {
t.Helper()
tracer, err := newJsTracer(code, nil)
tracer, err := newJsTracer(code, nil, nil)
if err != nil {
t.Fatal(err)
}
Expand Down Expand Up @@ -149,7 +149,7 @@ func TestTracer(t *testing.T) {

func TestHalt(t *testing.T) {
timeout := errors.New("stahp")
tracer, err := newJsTracer("{step: function() { while(1); }, result: function() { return null; }, fault: function(){}}", nil)
tracer, err := newJsTracer("{step: function() { while(1); }, result: function() { return null; }, fault: function(){}}", nil, nil)
if err != nil {
t.Fatal(err)
}
Expand All @@ -163,7 +163,7 @@ func TestHalt(t *testing.T) {
}

func TestHaltBetweenSteps(t *testing.T) {
tracer, err := newJsTracer("{step: function() {}, fault: function() {}, result: function() { return null; }}", nil)
tracer, err := newJsTracer("{step: function() {}, fault: function() {}, result: function() { return null; }}", nil, nil)
if err != nil {
t.Fatal(err)
}
Expand All @@ -187,7 +187,7 @@ func TestHaltBetweenSteps(t *testing.T) {
func TestNoStepExec(t *testing.T) {
execTracer := func(code string) []byte {
t.Helper()
tracer, err := newJsTracer(code, nil)
tracer, err := newJsTracer(code, nil, nil)
if err != nil {
t.Fatal(err)
}
Expand Down Expand Up @@ -221,7 +221,7 @@ func TestIsPrecompile(t *testing.T) {
chaincfg.IstanbulBlock = big.NewInt(200)
chaincfg.BerlinBlock = big.NewInt(300)
txCtx := vm.TxContext{GasPrice: big.NewInt(100000)}
tracer, err := newJsTracer("{addr: toAddress('0000000000000000000000000000000000000009'), res: null, step: function() { this.res = isPrecompiled(this.addr); }, fault: function() {}, result: function() { return this.res; }}", nil)
tracer, err := newJsTracer("{addr: toAddress('0000000000000000000000000000000000000009'), res: null, step: function() { this.res = isPrecompiled(this.addr); }, fault: function() {}, result: function() { return this.res; }}", nil, nil)
if err != nil {
t.Fatal(err)
}
Expand All @@ -235,7 +235,7 @@ func TestIsPrecompile(t *testing.T) {
t.Errorf("tracer should not consider blake2f as precompile in byzantium")
}

tracer, _ = newJsTracer("{addr: toAddress('0000000000000000000000000000000000000009'), res: null, step: function() { this.res = isPrecompiled(this.addr); }, fault: function() {}, result: function() { return this.res; }}", nil)
tracer, _ = newJsTracer("{addr: toAddress('0000000000000000000000000000000000000009'), res: null, step: function() { this.res = isPrecompiled(this.addr); }, fault: function() {}, result: function() { return this.res; }}", nil, nil)
blockCtx = vm.BlockContext{BlockNumber: big.NewInt(250)}
res, err = runTrace(tracer, &vmContext{blockCtx, txCtx}, chaincfg)
if err != nil {
Expand All @@ -248,14 +248,14 @@ func TestIsPrecompile(t *testing.T) {

func TestEnterExit(t *testing.T) {
// test that either both or none of enter() and exit() are defined
if _, err := newJsTracer("{step: function() {}, fault: function() {}, result: function() { return null; }, enter: function() {}}", new(tracers.Context)); err == nil {
if _, err := newJsTracer("{step: function() {}, fault: function() {}, result: function() { return null; }, enter: function() {}}", new(tracers.Context), nil); err == nil {
t.Fatal("tracer creation should've failed without exit() definition")
}
if _, err := newJsTracer("{step: function() {}, fault: function() {}, result: function() { return null; }, enter: function() {}, exit: function() {}}", new(tracers.Context)); err != nil {
if _, err := newJsTracer("{step: function() {}, fault: function() {}, result: function() { return null; }, enter: function() {}, exit: function() {}}", new(tracers.Context), nil); err != nil {
t.Fatal(err)
}
// test that the enter and exit method are correctly invoked and the values passed
tracer, err := newJsTracer("{enters: 0, exits: 0, enterGas: 0, gasUsed: 0, step: function() {}, fault: function() {}, result: function() { return {enters: this.enters, exits: this.exits, enterGas: this.enterGas, gasUsed: this.gasUsed} }, enter: function(frame) { this.enters++; this.enterGas = frame.getGas(); }, exit: function(res) { this.exits++; this.gasUsed = res.getGasUsed(); }}", new(tracers.Context))
tracer, err := newJsTracer("{enters: 0, exits: 0, enterGas: 0, gasUsed: 0, step: function() {}, fault: function() {}, result: function() { return {enters: this.enters, exits: this.exits, enterGas: this.enterGas, gasUsed: this.gasUsed} }, enter: function(frame) { this.enters++; this.enterGas = frame.getGas(); }, exit: function(res) { this.exits++; this.gasUsed = res.getGasUsed(); }}", new(tracers.Context), nil)
if err != nil {
t.Fatal(err)
}
Expand Down
4 changes: 2 additions & 2 deletions eth/tracers/native/4byte.go
Expand Up @@ -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
Expand Down
21 changes: 19 additions & 2 deletions eth/tracers/native/call.go
Expand Up @@ -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.
Expand Down Expand Up @@ -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()
Expand All @@ -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
Expand Down
4 changes: 2 additions & 2 deletions eth/tracers/native/noop.go
Expand Up @@ -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.
Expand Down
4 changes: 2 additions & 2 deletions eth/tracers/native/prestate.go
Expand Up @@ -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.
Expand Down
4 changes: 2 additions & 2 deletions eth/tracers/native/revertreason.go
Expand Up @@ -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.
Expand Down
7 changes: 4 additions & 3 deletions eth/tracers/native/tracer.go
Expand Up @@ -35,6 +35,7 @@ func init() {
package native

import (
"encoding/json"
"errors"

"github.com/ethereum/go-ethereum/eth/tracers"
Expand All @@ -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.
Expand All @@ -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")
}
6 changes: 3 additions & 3 deletions eth/tracers/tracers.go
Expand Up @@ -42,7 +42,7 @@ type Tracer interface {
Stop(err error)
}

type lookupFunc func(string, *Context) (Tracer, error)
type lookupFunc func(string, *Context, json.RawMessage) (Tracer, error)

var (
lookups []lookupFunc
Expand All @@ -62,9 +62,9 @@ func RegisterLookup(wildcard bool, lookup lookupFunc) {

// New returns a new instance of a tracer, by iterating through the
// registered lookups.
func New(code string, ctx *Context) (Tracer, error) {
func New(code string, ctx *Context, cfg json.RawMessage) (Tracer, error) {
for _, lookup := range lookups {
if tracer, err := lookup(code, ctx); err == nil {
if tracer, err := lookup(code, ctx, cfg); err == nil {
return tracer, nil
}
}
Expand Down