From 7ca83ab0ec54a9e476c7c33aad1093efa008ad7b Mon Sep 17 00:00:00 2001 From: JukLee0ira Date: Wed, 16 Oct 2024 15:04:32 +0800 Subject: [PATCH 1/3] eth/tracers: add TracerConfig option to Tracer (#25430) --- eth/api_tracer.go | 6 +- eth/tracers/native/call.go | 24 ++++++- eth/tracers/native/noop.go | 4 +- .../testdata/call_tracer/simple_onlytop.json | 72 +++++++++++++++++++ eth/tracers/testing/calltrace_test.go | 13 ++-- eth/tracers/tracer_test.go | 30 ++++---- eth/tracers/tracers.go | 13 ++-- eth/tracers/tracers_test.go | 4 +- 8 files changed, 132 insertions(+), 34 deletions(-) create mode 100644 eth/tracers/testdata/call_tracer/simple_onlytop.json diff --git a/eth/api_tracer.go b/eth/api_tracer.go index 18c0fc0109..11ed917a48 100644 --- a/eth/api_tracer.go +++ b/eth/api_tracer.go @@ -19,6 +19,7 @@ package eth import ( "bytes" "context" + "encoding/json" "errors" "fmt" "math/big" @@ -59,6 +60,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 @@ -726,7 +730,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) diff --git a/eth/tracers/native/call.go b/eth/tracers/native/call.go index 532068e6ae..f0d8e771de 100644 --- a/eth/tracers/native/call.go +++ b/eth/tracers/native/call.go @@ -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) { @@ -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 } @@ -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 diff --git a/eth/tracers/native/noop.go b/eth/tracers/native/noop.go index 8dd2405bc6..20e70e152b 100644 --- a/eth/tracers/native/noop.go +++ b/eth/tracers/native/noop.go @@ -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) { diff --git a/eth/tracers/testdata/call_tracer/simple_onlytop.json b/eth/tracers/testdata/call_tracer/simple_onlytop.json new file mode 100644 index 0000000000..1685379b20 --- /dev/null +++ b/eth/tracers/testdata/call_tracer/simple_onlytop.json @@ -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" + } +} \ No newline at end of file diff --git a/eth/tracers/testing/calltrace_test.go b/eth/tracers/testing/calltrace_test.go index 102e6f7416..9b8ebf043c 100644 --- a/eth/tracers/testing/calltrace_test.go +++ b/eth/tracers/testing/calltrace_test.go @@ -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 @@ -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) } @@ -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) } diff --git a/eth/tracers/tracer_test.go b/eth/tracers/tracer_test.go index 7721dbed30..a1d4cea511 100644 --- a/eth/tracers/tracer_test.go +++ b/eth/tracers/tracer_test.go @@ -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) } @@ -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) } @@ -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) } @@ -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) } @@ -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) } @@ -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) @@ -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) } @@ -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) } @@ -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) } @@ -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) } @@ -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) } @@ -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) } @@ -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) } diff --git a/eth/tracers/tracers.go b/eth/tracers/tracers.go index 65469d46df..dd9276af12 100644 --- a/eth/tracers/tracers.go +++ b/eth/tracers/tracers.go @@ -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 } @@ -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 { diff --git a/eth/tracers/tracers_test.go b/eth/tracers/tracers_test.go index af01acda67..53d3b9dcaf 100644 --- a/eth/tracers/tracers_test.go +++ b/eth/tracers/tracers_test.go @@ -148,7 +148,7 @@ func TestZeroValueToNotExitCall(t *testing.T) { } statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), alloc) // Create the tracer, the EVM environment and run it - tracer, err := New("callTracer", new(Context)) + tracer, err := New("callTracer", new(Context), nil) if err != nil { t.Fatalf("failed to create call tracer: %v", err) } @@ -233,7 +233,7 @@ func TestPrestateTracerCreate2(t *testing.T) { statedb := tests.MakePreState(db, alloc) // Create the tracer, the EVM environment and run it - tracer, err := New("prestateTracer", new(Context)) + tracer, err := New("prestateTracer", new(Context), nil) if err != nil { t.Fatalf("failed to create call tracer: %v", err) } From f7b591ffe33d4007fde90d292cf8becb8991e847 Mon Sep 17 00:00:00 2001 From: JukLee0ira Date: Mon, 21 Oct 2024 11:28:50 +0800 Subject: [PATCH 2/3] eth/tracers: add a golang tracer to locate contracts (#23708) --- core/vm/opcodes.go | 1 + eth/tracers/native/contract.go | 120 +++++++++++++++++++++++++++++++++ 2 files changed, 121 insertions(+) create mode 100644 eth/tracers/native/contract.go diff --git a/core/vm/opcodes.go b/core/vm/opcodes.go index c050f64e79..b14ef63b59 100644 --- a/core/vm/opcodes.go +++ b/core/vm/opcodes.go @@ -449,6 +449,7 @@ var stringToOp = map[string]OpCode{ "TIMESTAMP": TIMESTAMP, "NUMBER": NUMBER, "DIFFICULTY": DIFFICULTY, + "PREVRANDAO": PREVRANDAO, "GASLIMIT": GASLIMIT, "SELFBALANCE": SELFBALANCE, "BASEFEE": BASEFEE, diff --git a/eth/tracers/native/contract.go b/eth/tracers/native/contract.go new file mode 100644 index 0000000000..25483b137b --- /dev/null +++ b/eth/tracers/native/contract.go @@ -0,0 +1,120 @@ +// Copyright 2021 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package native + +import ( + "encoding/json" + "fmt" + "math/big" + "sync/atomic" + "time" + + "github.com/XinFinOrg/XDPoSChain/common" + "github.com/XinFinOrg/XDPoSChain/core/vm" + "github.com/XinFinOrg/XDPoSChain/eth/tracers" +) + +func init() { + tracers.RegisterNativeTracer("contractTracer", NewContractTracer) +} + +type contractTracer struct { + Addrs map[string]string + config contractTracerConfig + interrupt uint32 // Atomic flag to signal execution interruption + reason error // Textual reason for the interruption +} + +type contractTracerConfig struct { + OpCode string `json:"opCode"` // Target opcode to trace +} + +// NewContractTracer returns a native go tracer which tracks the contracr was created +func NewContractTracer(cfg json.RawMessage) (tracers.Tracer, error) { + var config contractTracerConfig + if cfg != nil { + if err := json.Unmarshal(cfg, &config); err != nil { + return nil, err + } + } + t := &contractTracer{ + Addrs: make(map[string]string, 1), + config: config, + } + // handle invalid opcode case + op := vm.StringToOp(t.config.OpCode) + if op == 0 && t.config.OpCode != "STOP" && t.config.OpCode != "" { + t.reason = fmt.Errorf("opcode %s not defined", t.config.OpCode) + return nil, t.reason + } + return t, nil +} + +func (t *contractTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) { + //When not searching for opcodes, record the contract address. + if create && t.config.OpCode == "" { + t.Addrs[addrToHex(to)] = "" + } +} + +func (t *contractTracer) CaptureEnd(output []byte, gasUsed uint64, _ time.Duration, err error) { +} + +func (t *contractTracer) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) { + // Skip if tracing was interrupted + if atomic.LoadUint32(&t.interrupt) > 0 { + // TODO: env.Cancel() + return + } + // If the OpCode is empty , exit early. + if t.config.OpCode == "" { + return + } + targetOp := vm.StringToOp(t.config.OpCode) + if op == targetOp { + addr := scope.Contract.Address() + t.Addrs[addrToHex(addr)] = "" + } +} + +func (t *contractTracer) CaptureFault(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, _ *vm.ScopeContext, depth int, err error) { +} + +func (t *contractTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { +} + +func (t *contractTracer) CaptureExit(output []byte, gasUsed uint64, err error) { +} + +func (t *contractTracer) GetResult() (json.RawMessage, error) { + // return Address array without duplicate address + AddrArray := make([]string, 0, len(t.Addrs)) + for addr := range t.Addrs { + AddrArray = append(AddrArray, addr) + } + + res, err := json.Marshal(AddrArray) + if err != nil { + return nil, err + } + return json.RawMessage(res), t.reason +} + +func (t *contractTracer) Stop(err error) { + t.reason = err + atomic.StoreUint32(&t.interrupt, 1) +} From 412133b97769b93e8682057a9fa0ccca80b3716f Mon Sep 17 00:00:00 2001 From: JukLee0ira Date: Wed, 30 Oct 2024 09:54:13 +0800 Subject: [PATCH 3/3] eth/tracers: add test for contractTracer --- .../contract_tracer/simple_opCode.json | 67 ++++++++++++++ eth/tracers/testing/calltrace_test.go | 91 +++++++++++++++++++ 2 files changed, 158 insertions(+) create mode 100644 eth/tracers/testdata/contract_tracer/simple_opCode.json diff --git a/eth/tracers/testdata/contract_tracer/simple_opCode.json b/eth/tracers/testdata/contract_tracer/simple_opCode.json new file mode 100644 index 0000000000..d8daa188e4 --- /dev/null +++ b/eth/tracers/testdata/contract_tracer/simple_opCode.json @@ -0,0 +1,67 @@ +{ + "context": { + "difficulty": "3502894804", + "gasLimit": "4722976", + "miner": "0x1585936b53834b021f68cc13eeefdec2efc8e724", + "number": "2289806", + "timestamp": "1513601314" + }, + "genesis": { + "alloc": { + "0x0024f658a46fbb89d8ac105e98d7ac7cbbaf27c5": { + "balance": "0x0", + "code": "0x", + "nonce": "9", + "storage": {} + }, + "0x7ef6c7dc8f8e9dbb026382537906991b07f2da86": { + "balance": "0x4d87094125a369d9bd5", + "code": "0x6080604052348015600f57600080fd5b506004361060325760003560e01c8063773a1154146037578063ccbac9f514603f575b600080fd5b603d6059565b005b60456099565b6040516050919060b6565b60405180910390f35b446000819055507facb85192b17e57cdd6ffdc2af021cc70c3a2269771b37b82dd36695fec903af5600054604051608f919060b6565b60405180910390a1565b60005481565b6000819050919050565b60b081609f565b82525050565b600060208201905060c9600083018460a9565b9291505056fea26469706673582212206c5f0ead8b2711f212408856a646c0e33b66df9c87c135b64651e31409588e7864736f6c63430008120033", + "nonce": "1", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000000": "0x0000000000000000000000000000000000000000000000000000000000000003", + "0x3e77b453e919df1d5620826b6e3709443da2cb6fa3f01166311eebd8013e02bb": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000003": "0x000000000000000000000000000000000000000000000000000000005a37b834" + } + }, + "0x873c36f9fd02e0c57a393afe80d14f244fe04378": { + "balance": "0x1780d77678137ac1b775", + "code": "0x", + "nonce": "0x10", + "storage": {} + } + }, + "config": { + "byzantiumBlock": 0, + "constantinopleBlock": 0, + "petersburgBlock": 0, + "IstanbulBlock":1561651, + "chainId": 4761, + "daoForkSupport": true, + "eip150Block": 0, + "eip150Hash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "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": "0xf86910840ee6b28082a72e947ef6c7dc8f8e9dbb026382537906991b07f2da868084773a1154822555a0a90b352128680ed1277fd0819acdfb1da9e71558854effa68358d36e41e0d170a0685835cae1e3a0b5a57019683cb3150fe22bfab465d0b2568e7266ace66042cd", + "tracerConfig": { + "opCode": "PREVRANDAO" + }, + "result": + ["0x7ef6c7dc8f8e9dbb026382537906991b07f2da86"] + +} \ No newline at end of file diff --git a/eth/tracers/testing/calltrace_test.go b/eth/tracers/testing/calltrace_test.go index 9b8ebf043c..3924ae184d 100644 --- a/eth/tracers/testing/calltrace_test.go +++ b/eth/tracers/testing/calltrace_test.go @@ -241,3 +241,94 @@ func benchTracer(tracerName string, test *callTracerTest, b *testing.B) { statedb.RevertToSnapshot(snap) } } + +type contractTracerTest struct { + Genesis *core.Genesis `json:"genesis"` + Context *callContext `json:"context"` + Input string `json:"input"` + TracerConfig json.RawMessage `json:"tracerConfig"` + Result []string `json:"result"` +} + +func testContractTracer(tracerName string, dirPath string, t *testing.T) { + files, err := os.ReadDir(filepath.Join("..", "testdata", dirPath)) + if err != nil { + t.Fatalf("failed to retrieve tracer test suite: %v", err) + } + for _, file := range files { + if !strings.HasSuffix(file.Name(), ".json") { + continue + } + file := file // capture range variable + t.Run(camel(strings.TrimSuffix(file.Name(), ".json")), func(t *testing.T) { + t.Parallel() + + var ( + test = new(contractTracerTest) + tx = new(types.Transaction) + ) + // Call tracer test found, read if from disk + if blob, err := os.ReadFile(filepath.Join("..", "testdata", dirPath, file.Name())); err != nil { + t.Fatalf("failed to read testcase: %v", err) + } else if err := json.Unmarshal(blob, test); err != nil { + t.Fatalf("failed to parse testcase: %v", err) + } + if err := rlp.DecodeBytes(common.FromHex(test.Input), tx); err != nil { + t.Fatalf("failed to parse testcase input: %v", err) + } + // Configure a blockchain with the given prestate + var ( + signer = types.MakeSigner(test.Genesis.Config, new(big.Int).SetUint64(uint64(test.Context.Number))) + origin, _ = signer.Sender(tx) + txContext = vm.TxContext{ + Origin: origin, + GasPrice: tx.GasPrice(), + } + context = vm.BlockContext{ + CanTransfer: core.CanTransfer, + Transfer: core.Transfer, + Coinbase: test.Context.Miner, + BlockNumber: new(big.Int).SetUint64(uint64(test.Context.Number)), + Time: new(big.Int).SetUint64(uint64(test.Context.Time)), + Difficulty: (*big.Int)(test.Context.Difficulty), + GasLimit: uint64(test.Context.GasLimit), + } + statedb = tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc) + ) + tracer, err := tracers.New(tracerName, new(tracers.Context), test.TracerConfig) + if err != nil { + t.Fatalf("failed to create call tracer: %v", err) + } + evm := vm.NewEVM(context, txContext, statedb, nil, test.Genesis.Config, vm.Config{Debug: true, Tracer: tracer}) + msg, err := tx.AsMessage(signer, nil, nil, nil) + if err != nil { + t.Fatalf("failed to prepare transaction for tracing: %v", err) + } + st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas())) + if _, err = st.TransitionDb(common.Address{}); err != nil { + t.Fatalf("failed to execute transaction: %v", err) + } + // Retrieve the trace result and compare against the etalon + res, err := tracer.GetResult() + if err != nil { + t.Fatalf("failed to retrieve trace result: %v", err) + } + ret := new([]string) + if err := json.Unmarshal(res, ret); err != nil { + t.Fatalf("failed to unmarshal trace result: %v", err) + } + + if !reflect.DeepEqual(*ret, test.Result) { + // uncomment this for easier debugging + //have, _ := json.MarshalIndent(ret, "", " ") + //want, _ := json.MarshalIndent(test.Result, "", " ") + //t.Fatalf("trace mismatch: \nhave %+v\nwant %+v", string(have), string(want)) + t.Fatalf("trace mismatch: \nhave %+v\nwant %+v", *ret, test.Result) + } + }) + } +} + +func TestContractTracer(t *testing.T) { + testContractTracer("contractTracer", "contract_tracer", t) +}