Skip to content

Commit

Permalink
eth/tracers: add TracerConfig option to Tracer (ethereum#25430)
Browse files Browse the repository at this point in the history
  • Loading branch information
JukLee0ira committed Oct 21, 2024
1 parent 22c5420 commit ec24a94
Show file tree
Hide file tree
Showing 8 changed files with 132 additions and 34 deletions.
6 changes: 5 additions & 1 deletion eth/api_tracer.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package eth
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"math/big"
Expand Down Expand Up @@ -60,6 +61,9 @@ 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
Expand Down Expand Up @@ -719,7 +723,7 @@ func (api *PrivateDebugAPI) traceTx(ctx context.Context, message core.Message, t
return nil, err
}
}
if t, err := tracers.New(*config.Tracer, txctx); err != nil {
if t, err := tracers.New(*config.Tracer, txctx, config.TracerConfig); err != nil {
return nil, err
} else {
deadlineCtx, cancel := context.WithTimeout(ctx, timeout)
Expand Down
24 changes: 21 additions & 3 deletions eth/tracers/native/call.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,17 +48,28 @@ type callFrame struct {

type callTracer struct {
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() tracers.Tracer {
func NewCallTracer(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.
t := &callTracer{callstack: make([]callFrame, 1)}
return t
t := &callTracer{callstack: make([]callFrame, 1), config: config}
return t, nil
}

func (t *callTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
Expand Down Expand Up @@ -94,9 +105,13 @@ func (t *callTracer) CaptureFault(env *vm.EVM, pc uint64, op vm.OpCode, gas, cos
}

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 {
// TODO: env.Cancel()

return
}

Expand All @@ -112,6 +127,9 @@ func (t *callTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.
}

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
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ func init() {

type noopTracer struct{}

func NewNoopTracer() tracers.Tracer {
return &noopTracer{}
func NewNoopTracer(_ json.RawMessage) (tracers.Tracer, error) {
return &noopTracer{}, nil
}

func (t *noopTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
Expand Down
72 changes: 72 additions & 0 deletions eth/tracers/testdata/call_tracer/simple_onlytop.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
{
"context": {
"difficulty": "3502894804",
"gasLimit": "4722976",
"miner": "0x1585936b53834b021f68cc13eeefdec2efc8e724",
"number": "2289806",
"timestamp": "1513601314"
},
"genesis": {
"alloc": {
"0x0024f658a46fbb89d8ac105e98d7ac7cbbaf27c5": {
"balance": "0x0",
"code": "0x",
"nonce": "22",
"storage": {}
},
"0x3b873a919aa0512d5a0f09e6dcceaa4a6727fafe": {
"balance": "0x4d87094125a369d9bd5",
"code": "0x606060405236156100935763ffffffff60e060020a60003504166311ee8382811461009c57806313af4035146100be5780631f5e8f4c146100ee57806324daddc5146101125780634921a91a1461013b57806363e4bff414610157578063764978f91461017f578063893d20e8146101a1578063ba40aaa1146101cd578063cebc9a82146101f4578063e177246e14610216575b61009a5b5b565b005b34156100a457fe5b6100ac61023d565b60408051918252519081900360200190f35b34156100c657fe5b6100da600160a060020a0360043516610244565b604080519115158252519081900360200190f35b34156100f657fe5b6100da610307565b604080519115158252519081900360200190f35b341561011a57fe5b6100da6004351515610318565b604080519115158252519081900360200190f35b6100da6103d6565b604080519115158252519081900360200190f35b6100da600160a060020a0360043516610420565b604080519115158252519081900360200190f35b341561018757fe5b6100ac61046c565b60408051918252519081900360200190f35b34156101a957fe5b6101b1610473565b60408051600160a060020a039092168252519081900360200190f35b34156101d557fe5b6100da600435610483565b604080519115158252519081900360200190f35b34156101fc57fe5b6100ac61050d565b60408051918252519081900360200190f35b341561021e57fe5b6100da600435610514565b604080519115158252519081900360200190f35b6003545b90565b60006000610250610473565b600160a060020a031633600160a060020a03161415156102705760006000fd5b600160a060020a03831615156102865760006000fd5b50600054600160a060020a0390811690831681146102fb57604051600160a060020a0380851691908316907ffcf23a92150d56e85e3a3d33b357493246e55783095eb6a733eb8439ffc752c890600090a360008054600160a060020a031916600160a060020a03851617905560019150610300565b600091505b5b50919050565b60005460a060020a900460ff165b90565b60006000610324610473565b600160a060020a031633600160a060020a03161415156103445760006000fd5b5060005460a060020a900460ff16801515831515146102fb576000546040805160a060020a90920460ff1615158252841515602083015280517fe6cd46a119083b86efc6884b970bfa30c1708f53ba57b86716f15b2f4551a9539281900390910190a16000805460a060020a60ff02191660a060020a8515150217905560019150610300565b600091505b5b50919050565b60006103e0610307565b801561040557506103ef610473565b600160a060020a031633600160a060020a031614155b156104105760006000fd5b610419336105a0565b90505b5b90565b600061042a610307565b801561044f5750610439610473565b600160a060020a031633600160a060020a031614155b1561045a5760006000fd5b610463826105a0565b90505b5b919050565b6001545b90565b600054600160a060020a03165b90565b6000600061048f610473565b600160a060020a031633600160a060020a03161415156104af5760006000fd5b506001548281146102fb57604080518281526020810185905281517f79a3746dde45672c9e8ab3644b8bb9c399a103da2dc94b56ba09777330a83509929181900390910190a160018381559150610300565b600091505b5b50919050565b6002545b90565b60006000610520610473565b600160a060020a031633600160a060020a03161415156105405760006000fd5b506002548281146102fb57604080518281526020810185905281517ff6991a728965fedd6e927fdf16bdad42d8995970b4b31b8a2bf88767516e2494929181900390910190a1600283905560019150610300565b600091505b5b50919050565b60006000426105ad61023d565b116102fb576105c46105bd61050d565b4201610652565b6105cc61046c565b604051909150600160a060020a038416908290600081818185876187965a03f1925050501561063d57604080518281529051600160a060020a038516917f9bca65ce52fdef8a470977b51f247a2295123a4807dfa9e502edf0d30722da3b919081900360200190a260019150610300565b6102fb42610652565b5b600091505b50919050565b60038190555b505600a165627a7a72305820f3c973c8b7ed1f62000b6701bd5b708469e19d0f1d73fde378a56c07fd0b19090029",
"nonce": "1",
"storage": {
"0x0000000000000000000000000000000000000000000000000000000000000000": "0x000000000000000000000001b436ba50d378d4bbc8660d312a13df6af6e89dfb",
"0x0000000000000000000000000000000000000000000000000000000000000001": "0x00000000000000000000000000000000000000000000000006f05b59d3b20000",
"0x0000000000000000000000000000000000000000000000000000000000000002": "0x000000000000000000000000000000000000000000000000000000000000003c",
"0x0000000000000000000000000000000000000000000000000000000000000003": "0x000000000000000000000000000000000000000000000000000000005a37b834"
}
},
"0xb436ba50d378d4bbc8660d312a13df6af6e89dfb": {
"balance": "0x1780d77678137ac1b775",
"code": "0x",
"nonce": "29072",
"storage": {}
}
},
"config": {
"byzantiumBlock": 1700000,
"chainId": 3,
"daoForkSupport": true,
"eip150Block": 0,
"eip150Hash": "0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d",
"eip155Block": 10,
"eip158Block": 10,
"ethash": {},
"homesteadBlock": 0
},
"difficulty": "3509749784",
"extraData": "0x4554482e45544846414e532e4f52472d4641313738394444",
"gasLimit": "4727564",
"hash": "0x609948ac3bd3c00b7736b933248891d6c901ee28f066241bddb28f4e00a9f440",
"miner": "0xbbf5029fd710d227630c8b7d338051b8e76d50b3",
"mixHash": "0xb131e4507c93c7377de00e7c271bf409ec7492767142ff0f45c882f8068c2ada",
"nonce": "0x4eb12e19c16d43da",
"number": "2289805",
"stateRoot": "0xc7f10f352bff82fac3c2999d3085093d12652e19c7fd32591de49dc5d91b4f1f",
"timestamp": "1513601261",
"totalDifficulty": "7143276353481064"
},
"input": "0xf88b8271908506fc23ac0083015f90943b873a919aa0512d5a0f09e6dcceaa4a6727fafe80a463e4bff40000000000000000000000000024f658a46fbb89d8ac105e98d7ac7cbbaf27c52aa0bdce0b59e8761854e857fe64015f06dd08a4fbb7624f6094893a79a72e6ad6bea01d9dde033cff7bb235a3163f348a6d7ab8d6b52bc0963a95b91612e40ca766a4",
"tracerConfig": {
"onlyTopCall": true
},
"result": {
"from": "0xb436ba50d378d4bbc8660d312a13df6af6e89dfb",
"gas": "0x10738",
"gasUsed": "0x3ef9",
"input": "0x63e4bff40000000000000000000000000024f658a46fbb89d8ac105e98d7ac7cbbaf27c5",
"output": "0x0000000000000000000000000000000000000000000000000000000000000001",
"to": "0x3b873a919aa0512d5a0f09e6dcceaa4a6727fafe",
"type": "CALL",
"value": "0x0"
}
}
13 changes: 7 additions & 6 deletions eth/tracers/testing/calltrace_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,11 @@ type callTrace struct {

// callTracerTest defines a single test to check the call tracer against.
type callTracerTest struct {
Genesis *core.Genesis `json:"genesis"`
Context *callContext `json:"context"`
Input string `json:"input"`
Result *callTrace `json:"result"`
Genesis *core.Genesis `json:"genesis"`
Context *callContext `json:"context"`
Input string `json:"input"`
TracerConfig json.RawMessage `json:"tracerConfig"`
Result *callTrace `json:"result"`
}

// Iterates over all the input-output datasets in the tracer test harness and
Expand Down Expand Up @@ -110,7 +111,7 @@ func testCallTracer(tracerName string, dirPath string, t *testing.T) {
}
statedb = tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc)
)
tracer, err := tracers.New(tracerName, new(tracers.Context))
tracer, err := tracers.New(tracerName, new(tracers.Context), test.TracerConfig)
if err != nil {
t.Fatalf("failed to create call tracer: %v", err)
}
Expand Down Expand Up @@ -224,7 +225,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
30 changes: 15 additions & 15 deletions eth/tracers/tracer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ func runTrace(tracer Tracer, blockNumber *big.Int, chaincfg *params.ChainConfig)
func TestTracer(t *testing.T) {
execTracer := func(code string) ([]byte, string) {
t.Helper()
tracer, err := New(code, new(Context))
tracer, err := New(code, new(Context), nil)
if err != nil {
t.Fatal(err)
}
Expand Down Expand Up @@ -131,7 +131,7 @@ func TestHalt(t *testing.T) {
t.Skip("duktape doesn't support abortion")

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

func TestHaltBetweenSteps(t *testing.T) {
tracer, err := New("{step: function() {}, fault: function() {}, result: function() { return null; }}", new(Context))
tracer, err := New("{step: function() {}, fault: function() {}, result: function() { return null; }}", new(Context), nil)
if err != nil {
t.Fatal(err)
}
Expand Down Expand Up @@ -178,7 +178,7 @@ func TestNoStepExec(t *testing.T) {
}
execTracer := func(code string) []byte {
t.Helper()
tracer, err := New(code, new(Context))
tracer, err := New(code, new(Context), nil)
if err != nil {
t.Fatal(err)
}
Expand Down Expand Up @@ -208,7 +208,7 @@ func TestIsPrecompile(t *testing.T) {
chaincfg.ByzantiumBlock = big.NewInt(100)
chaincfg.IstanbulBlock = big.NewInt(200)
chaincfg.BerlinBlock = big.NewInt(300)
tracer, err := New("{addr: toAddress('0000000000000000000000000000000000000009'), res: null, step: function() { this.res = isPrecompiled(this.addr); }, fault: function() {}, result: function() { return this.res; }}", new(Context))
tracer, err := New("{addr: toAddress('0000000000000000000000000000000000000009'), res: null, step: function() { this.res = isPrecompiled(this.addr); }, fault: function() {}, result: function() { return this.res; }}", new(Context), nil)
if err != nil {
t.Fatal(err)
}
Expand All @@ -220,7 +220,7 @@ func TestIsPrecompile(t *testing.T) {
t.Errorf("Tracer should not consider blake2f as precompile in byzantium")
}

tracer, _ = New("{addr: toAddress('0000000000000000000000000000000000000009'), res: null, step: function() { this.res = isPrecompiled(this.addr); }, fault: function() {}, result: function() { return this.res; }}", new(Context))
tracer, _ = New("{addr: toAddress('0000000000000000000000000000000000000009'), res: null, step: function() { this.res = isPrecompiled(this.addr); }, fault: function() {}, result: function() { return this.res; }}", new(Context), nil)
res, err = runTrace(tracer, big.NewInt(250), chaincfg)
if err != nil {
t.Error(err)
Expand All @@ -232,15 +232,15 @@ func TestIsPrecompile(t *testing.T) {

func TestEnterExit(t *testing.T) {
// test that either both or none of enter() and exit() are defined
if _, err := New("{step: function() {}, fault: function() {}, result: function() { return null; }, enter: function() {}}", new(Context)); err == nil {
if _, err := New("{step: function() {}, fault: function() {}, result: function() { return null; }, enter: function() {}}", new(Context), nil); err == nil {
t.Fatal("tracer creation should've failed without exit() definition")
}
if _, err := New("{step: function() {}, fault: function() {}, result: function() { return null; }, enter: function() {}, exit: function() {}}", new(Context)); err != nil {
if _, err := New("{step: function() {}, fault: function() {}, result: function() { return null; }, enter: function() {}, exit: function() {}}", new(Context), nil); err != nil {
t.Fatal(err)
}

// test that the enter and exit method are correctly invoked and the values passed
tracer, err := New("{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(Context))
tracer, err := New("{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(Context), nil)
if err != nil {
t.Fatal(err)
}
Expand All @@ -264,7 +264,7 @@ func TestEnterExit(t *testing.T) {

// TestRegressionPanicSlice tests that we don't panic on bad arguments to memory access
func TestRegressionPanicSlice(t *testing.T) {
tracer, err := New("{depths: [], step: function(log) { this.depths.push(log.memory.slice(-1,-2)); }, fault: function() {}, result: function() { return this.depths; }}", new(Context))
tracer, err := New("{depths: [], step: function(log) { this.depths.push(log.memory.slice(-1,-2)); }, fault: function() {}, result: function() { return this.depths; }}", new(Context), nil)
if err != nil {
t.Fatal(err)
}
Expand All @@ -275,7 +275,7 @@ func TestRegressionPanicSlice(t *testing.T) {

// TestRegressionPanicSlice tests that we don't panic on bad arguments to stack peeks
func TestRegressionPanicPeek(t *testing.T) {
tracer, err := New("{depths: [], step: function(log) { this.depths.push(log.stack.peek(-1)); }, fault: function() {}, result: function() { return this.depths; }}", new(Context))
tracer, err := New("{depths: [], step: function(log) { this.depths.push(log.stack.peek(-1)); }, fault: function() {}, result: function() { return this.depths; }}", new(Context), nil)
if err != nil {
t.Fatal(err)
}
Expand All @@ -286,7 +286,7 @@ func TestRegressionPanicPeek(t *testing.T) {

// TestRegressionPanicSlice tests that we don't panic on bad arguments to memory getUint
func TestRegressionPanicGetUint(t *testing.T) {
tracer, err := New("{ depths: [], step: function(log, db) { this.depths.push(log.memory.getUint(-64));}, fault: function() {}, result: function() { return this.depths; }}", new(Context))
tracer, err := New("{ depths: [], step: function(log, db) { this.depths.push(log.memory.getUint(-64));}, fault: function() {}, result: function() { return this.depths; }}", new(Context), nil)
if err != nil {
t.Fatal(err)
}
Expand All @@ -296,7 +296,7 @@ func TestRegressionPanicGetUint(t *testing.T) {
}

func TestTracing(t *testing.T) {
tracer, err := New("{count: 0, step: function() { this.count += 1; }, fault: function() {}, result: function() { return this.count; }}", new(Context))
tracer, err := New("{count: 0, step: function() { this.count += 1; }, fault: function() {}, result: function() { return this.count; }}", new(Context), nil)
if err != nil {
t.Fatal(err)
}
Expand All @@ -311,7 +311,7 @@ func TestTracing(t *testing.T) {
}

func TestStack(t *testing.T) {
tracer, err := New("{depths: [], step: function(log) { this.depths.push(log.stack.length()); }, fault: function() {}, result: function() { return this.depths; }}", new(Context))
tracer, err := New("{depths: [], step: function(log) { this.depths.push(log.stack.length()); }, fault: function() {}, result: function() { return this.depths; }}", new(Context), nil)
if err != nil {
t.Fatal(err)
}
Expand All @@ -326,7 +326,7 @@ func TestStack(t *testing.T) {
}

func TestOpcodes(t *testing.T) {
tracer, err := New("{opcodes: [], step: function(log) { this.opcodes.push(log.op.toString()); }, fault: function() {}, result: function() { return this.opcodes; }}", new(Context))
tracer, err := New("{opcodes: [], step: function(log) { this.opcodes.push(log.op.toString()); }, fault: function() {}, result: function() { return this.opcodes; }}", new(Context), nil)
if err != nil {
t.Fatal(err)
}
Expand Down
13 changes: 8 additions & 5 deletions eth/tracers/tracers.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,17 @@ type Tracer interface {
}

var (
nativeTracers map[string]func() Tracer = make(map[string]func() Tracer)
jsTracers = make(map[string]string)
nativeTracers map[string]ctorFn = make(map[string]ctorFn)
jsTracers = make(map[string]string)
)

// ctorFn is the constructor signature of a native tracer.
type ctorFn = func(json.RawMessage) (Tracer, error)

// RegisterNativeTracer makes native tracers which adhere
// to the `Tracer` interface available to the rest of the codebase.
// It is typically invoked in the `init()` function, e.g. see the `native/call.go`.
func RegisterNativeTracer(name string, ctor func() Tracer) {
func RegisterNativeTracer(name string, ctor ctorFn) {
nativeTracers[name] = ctor
}

Expand All @@ -54,10 +57,10 @@ func RegisterNativeTracer(name string, ctor func() Tracer) {
// instantiated and returned
// 3. Otherwise, the code is interpreted as the js code of a js-tracer, and
// is evaluated and returned.
func New(code string, ctx *Context) (Tracer, error) {
func New(code string, ctx *Context, cfg json.RawMessage) (Tracer, error) {
// Resolve native tracer
if fn, ok := nativeTracers[code]; ok {
return fn(), nil
return fn(cfg)
}
// Resolve js-tracers by name and assemble the tracer object
if tracer, ok := jsTracers[code]; ok {
Expand Down
Loading

0 comments on commit ec24a94

Please sign in to comment.