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.
pull/25506/head
Sina Mahmoodi 2 years ago committed by GitHub
parent 759d795c56
commit 86de2e516e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      core/vm/runtime/runtime_test.go
  2. 11
      eth/tracers/api.go
  3. 7
      eth/tracers/internal/tracetest/calltrace_test.go
  4. 72
      eth/tracers/internal/tracetest/testdata/call_tracer/simple_onlytop.json
  5. 13
      eth/tracers/js/goja.go
  6. 48
      eth/tracers/js/tracer_test.go
  7. 4
      eth/tracers/native/4byte.go
  8. 21
      eth/tracers/native/call.go
  9. 4
      eth/tracers/native/noop.go
  10. 4
      eth/tracers/native/prestate.go
  11. 4
      eth/tracers/native/revertreason.go
  12. 7
      eth/tracers/native/tracer.go
  13. 6
      eth/tracers/tracers.go

@ -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.State, _ = state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil)
cfg.GasLimit = gas cfg.GasLimit = gas
if len(tracerCode) > 0 { if len(tracerCode) > 0 {
tracer, err := tracers.New(tracerCode, new(tracers.Context)) tracer, err := tracers.New(tracerCode, new(tracers.Context), nil)
if err != nil { if err != nil {
b.Fatal(err) b.Fatal(err)
} }
@ -832,7 +832,7 @@ func TestRuntimeJSTracer(t *testing.T) {
statedb.SetCode(common.HexToAddress("0xee"), calleeCode) statedb.SetCode(common.HexToAddress("0xee"), calleeCode)
statedb.SetCode(common.HexToAddress("0xff"), depressedCode) 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 { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -868,7 +868,7 @@ func TestJSTracerCreateTx(t *testing.T) {
code := []byte{byte(vm.PUSH1), 0, byte(vm.PUSH1), 0, byte(vm.RETURN)} code := []byte{byte(vm.PUSH1), 0, byte(vm.PUSH1), 0, byte(vm.RETURN)}
statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) 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 { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

@ -20,6 +20,7 @@ import (
"bufio" "bufio"
"bytes" "bytes"
"context" "context"
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"os" "os"
@ -169,15 +170,15 @@ type TraceConfig struct {
Tracer *string Tracer *string
Timeout *string Timeout *string
Reexec *uint64 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 // TraceCallConfig is the config for traceCall API. It holds one more
// field to override the state for tracing. // field to override the state for tracing.
type TraceCallConfig struct { type TraceCallConfig struct {
*logger.Config TraceConfig
Tracer *string
Timeout *string
Reexec *uint64
StateOverrides *ethapi.StateOverride StateOverrides *ethapi.StateOverride
BlockOverrides *ethapi.BlockOverrides BlockOverrides *ethapi.BlockOverrides
} }
@ -882,7 +883,7 @@ func (api *API) traceTx(ctx context.Context, message core.Message, txctx *Contex
// Default tracer is the struct logger // Default tracer is the struct logger
tracer = logger.NewStructLogger(config.Config) tracer = logger.NewStructLogger(config.Config)
if config.Tracer != nil { if config.Tracer != nil {
tracer, err = New(*config.Tracer, txctx) tracer, err = New(*config.Tracer, txctx, config.TracerConfig)
if err != nil { if err != nil {
return nil, err return nil, err
} }

@ -121,6 +121,7 @@ type callTracerTest struct {
Genesis *core.Genesis `json:"genesis"` Genesis *core.Genesis `json:"genesis"`
Context *callContext `json:"context"` Context *callContext `json:"context"`
Input string `json:"input"` Input string `json:"input"`
TracerConfig json.RawMessage `json:"tracerConfig"`
Result *callTrace `json:"result"` Result *callTrace `json:"result"`
} }
@ -179,7 +180,7 @@ func testCallTracer(tracerName string, dirPath string, t *testing.T) {
} }
_, statedb = tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc, false) _, 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), test.TracerConfig)
if err != nil { if err != nil {
t.Fatalf("failed to create call tracer: %v", err) t.Fatalf("failed to create call tracer: %v", err)
} }
@ -293,7 +294,7 @@ func benchTracer(tracerName string, test *callTracerTest, b *testing.B) {
b.ReportAllocs() b.ReportAllocs()
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ { 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 { if err != nil {
b.Fatalf("failed to create call tracer: %v", err) b.Fatalf("failed to create call tracer: %v", err)
} }
@ -359,7 +360,7 @@ func TestZeroValueToNotExitCall(t *testing.T) {
} }
_, statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), alloc, false) _, statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), alloc, false)
// Create the tracer, the EVM environment and run it // 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 { if err != nil {
t.Fatalf("failed to create call tracer: %v", err) t.Fatalf("failed to create call tracer: %v", err)
} }

@ -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"
}
}

@ -125,7 +125,7 @@ type jsTracer struct {
// The methods `result` and `fault` are required to be present. // The methods `result` and `fault` are required to be present.
// The methods `step`, `enter`, and `exit` are optional, but note that // The methods `step`, `enter`, and `exit` are optional, but note that
// `enter` and `exit` always go together. // `enter` and `exit` always go together.
func newJsTracer(code string, ctx *tracers.Context) (tracers.Tracer, error) { func newJsTracer(code string, ctx *tracers.Context, cfg json.RawMessage) (tracers.Tracer, error) {
if c, ok := assetTracers[code]; ok { if c, ok := assetTracers[code]; ok {
code = c code = c
} }
@ -177,6 +177,17 @@ func newJsTracer(code string, ctx *tracers.Context) (tracers.Tracer, error) {
t.exit = exit t.exit = exit
t.result = result t.result = result
t.fault = fault t.fault = fault
// Pass in config
if setup, ok := goja.AssertFunction(obj.Get("setup")); ok {
cfgStr := "{}"
if cfg != nil {
cfgStr = string(cfg)
}
if _, err := setup(obj, vm.ToValue(cfgStr)); err != nil {
return nil, err
}
}
// Setup objects carrying data to JS. These are created once and re-used. // Setup objects carrying data to JS. These are created once and re-used.
t.log = &steplog{ t.log = &steplog{
vm: vm, vm: vm,

@ -85,7 +85,7 @@ func runTrace(tracer tracers.Tracer, vmctx *vmContext, chaincfg *params.ChainCon
func TestTracer(t *testing.T) { func TestTracer(t *testing.T) {
execTracer := func(code string) ([]byte, string) { execTracer := func(code string) ([]byte, string) {
t.Helper() t.Helper()
tracer, err := newJsTracer(code, nil) tracer, err := newJsTracer(code, nil, nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -149,7 +149,7 @@ func TestTracer(t *testing.T) {
func TestHalt(t *testing.T) { func TestHalt(t *testing.T) {
timeout := errors.New("stahp") 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 { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -163,7 +163,7 @@ func TestHalt(t *testing.T) {
} }
func TestHaltBetweenSteps(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 { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -187,7 +187,7 @@ func TestHaltBetweenSteps(t *testing.T) {
func TestNoStepExec(t *testing.T) { func TestNoStepExec(t *testing.T) {
execTracer := func(code string) []byte { execTracer := func(code string) []byte {
t.Helper() t.Helper()
tracer, err := newJsTracer(code, nil) tracer, err := newJsTracer(code, nil, nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -221,7 +221,7 @@ func TestIsPrecompile(t *testing.T) {
chaincfg.IstanbulBlock = big.NewInt(200) chaincfg.IstanbulBlock = big.NewInt(200)
chaincfg.BerlinBlock = big.NewInt(300) chaincfg.BerlinBlock = big.NewInt(300)
txCtx := vm.TxContext{GasPrice: big.NewInt(100000)} 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 { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -235,7 +235,7 @@ func TestIsPrecompile(t *testing.T) {
t.Errorf("tracer should not consider blake2f as precompile in byzantium") 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)} blockCtx = vm.BlockContext{BlockNumber: big.NewInt(250)}
res, err = runTrace(tracer, &vmContext{blockCtx, txCtx}, chaincfg) res, err = runTrace(tracer, &vmContext{blockCtx, txCtx}, chaincfg)
if err != nil { if err != nil {
@ -248,14 +248,14 @@ func TestIsPrecompile(t *testing.T) {
func TestEnterExit(t *testing.T) { func TestEnterExit(t *testing.T) {
// test that either both or none of enter() and exit() are defined // 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") 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) t.Fatal(err)
} }
// test that the enter and exit method are correctly invoked and the values passed // 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 { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -274,3 +274,33 @@ func TestEnterExit(t *testing.T) {
t.Errorf("Number of invocations of enter() and exit() is wrong. Have %s, want %s\n", have, want) t.Errorf("Number of invocations of enter() and exit() is wrong. Have %s, want %s\n", have, want)
} }
} }
func TestSetup(t *testing.T) {
// Test empty config
_, err := newJsTracer(`{setup: function(cfg) { if (cfg !== "{}") { throw("invalid empty config") } }, fault: function() {}, result: function() {}}`, new(tracers.Context), nil)
if err != nil {
t.Error(err)
}
cfg, err := json.Marshal(map[string]string{"foo": "bar"})
if err != nil {
t.Fatal(err)
}
// Test no setup func
_, err = newJsTracer(`{fault: function() {}, result: function() {}}`, new(tracers.Context), cfg)
if err != nil {
t.Fatal(err)
}
// Test config value
tracer, err := newJsTracer("{config: null, setup: function(cfg) { this.config = JSON.parse(cfg) }, step: function() {}, fault: function() {}, result: function() { return this.config.foo }}", new(tracers.Context), cfg)
if err != nil {
t.Fatal(err)
}
have, err := tracer.GetResult()
if err != nil {
t.Fatal(err)
}
if string(have) != `"bar"` {
t.Errorf("tracer returned wrong result. have: %s, want: \"bar\"\n", string(have))
}
}

@ -55,11 +55,11 @@ type fourByteTracer struct {
// newFourByteTracer returns a native go tracer which collects // newFourByteTracer returns a native go tracer which collects
// 4 byte-identifiers of a tx, and implements vm.EVMLogger. // 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{ t := &fourByteTracer{
ids: make(map[string]int), 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 // 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 { type callTracer struct {
env *vm.EVM env *vm.EVM
callstack []callFrame callstack []callFrame
config callTracerConfig
interrupt uint32 // Atomic flag to signal execution interruption interrupt uint32 // Atomic flag to signal execution interruption
reason error // Textual reason for the 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 // newCallTracer returns a native go tracer which tracks
// call frames of a tx, and implements vm.EVMLogger. // 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 // First callframe contains tx context info
// and is populated on start and end. // 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. // 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). // 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) { 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 // Skip if tracing was interrupted
if atomic.LoadUint32(&t.interrupt) > 0 { if atomic.LoadUint32(&t.interrupt) > 0 {
t.env.Cancel() 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 // CaptureExit is called when EVM exits a scope, even if the scope didn't
// execute any code. // execute any code.
func (t *callTracer) CaptureExit(output []byte, gasUsed uint64, err error) { func (t *callTracer) CaptureExit(output []byte, gasUsed uint64, err error) {
if t.config.OnlyTopCall {
return
}
size := len(t.callstack) size := len(t.callstack)
if size <= 1 { if size <= 1 {
return return

@ -35,8 +35,8 @@ func init() {
type noopTracer struct{} type noopTracer struct{}
// newNoopTracer returns a new noop tracer. // newNoopTracer returns a new noop tracer.
func newNoopTracer(ctx *tracers.Context) tracers.Tracer { func newNoopTracer(ctx *tracers.Context, _ json.RawMessage) (tracers.Tracer, error) {
return &noopTracer{} return &noopTracer{}, nil
} }
// CaptureStart implements the EVMLogger interface to initialize the tracing operation. // CaptureStart implements the EVMLogger interface to initialize the tracing operation.

@ -51,10 +51,10 @@ type prestateTracer struct {
reason error // Textual reason for the interruption 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 // First callframe contains tx context info
// and is populated on start and end. // 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. // CaptureStart implements the EVMLogger interface to initialize the tracing operation.

@ -46,8 +46,8 @@ type revertReasonTracer struct {
} }
// newRevertReasonTracer returns a new revert reason tracer. // newRevertReasonTracer returns a new revert reason tracer.
func newRevertReasonTracer(_ *tracers.Context) tracers.Tracer { func newRevertReasonTracer(_ *tracers.Context, _ json.RawMessage) (tracers.Tracer, error) {
return &revertReasonTracer{} return &revertReasonTracer{}, nil
} }
// CaptureStart implements the EVMLogger interface to initialize the tracing operation. // CaptureStart implements the EVMLogger interface to initialize the tracing operation.

@ -35,6 +35,7 @@ func init() {
package native package native
import ( import (
"encoding/json"
"errors" "errors"
"github.com/ethereum/go-ethereum/eth/tracers" "github.com/ethereum/go-ethereum/eth/tracers"
@ -46,7 +47,7 @@ func init() {
} }
// ctorFn is the constructor signature of a native tracer. // 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. 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. // 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 { if ctors == nil {
ctors = make(map[string]ctorFn) ctors = make(map[string]ctorFn)
} }
if ctor, ok := ctors[name]; ok { if ctor, ok := ctors[name]; ok {
return ctor(ctx), nil return ctor(ctx, cfg)
} }
return nil, errors.New("no tracer found") return nil, errors.New("no tracer found")
} }

@ -42,7 +42,7 @@ type Tracer interface {
Stop(err error) Stop(err error)
} }
type lookupFunc func(string, *Context) (Tracer, error) type lookupFunc func(string, *Context, json.RawMessage) (Tracer, error)
var ( var (
lookups []lookupFunc lookups []lookupFunc
@ -62,9 +62,9 @@ func RegisterLookup(wildcard bool, lookup lookupFunc) {
// New returns a new instance of a tracer, by iterating through the // New returns a new instance of a tracer, by iterating through the
// registered lookups. // 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 { for _, lookup := range lookups {
if tracer, err := lookup(code, ctx); err == nil { if tracer, err := lookup(code, ctx, cfg); err == nil {
return tracer, nil return tracer, nil
} }
} }

Loading…
Cancel
Save