From 619ac2948c27d8b8c309f31d2e3b5dad6401a355 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Wed, 28 Jun 2023 18:00:44 +0200 Subject: [PATCH] provide StateLogger for short-lived tracers --- eth/tracers/api.go | 1 + .../internal/tracetest/calltrace_test.go | 2 + .../internal/tracetest/flat_calltrace_test.go | 1 + .../internal/tracetest/prestate_test.go | 1 + eth/tracers/logger/access_list_tracer.go | 13 ++++ eth/tracers/logger/logger.go | 26 ++++++++ eth/tracers/logger/logger_json.go | 13 ++++ eth/tracers/native/call.go | 62 ++++++------------- eth/tracers/native/mux.go | 36 +++++++++++ eth/tracers/noop.go | 13 ++++ eth/tracers/tracers.go | 8 ++- 11 files changed, 132 insertions(+), 44 deletions(-) diff --git a/eth/tracers/api.go b/eth/tracers/api.go index e4c3bb2cb4..479ca58de6 100644 --- a/eth/tracers/api.go +++ b/eth/tracers/api.go @@ -952,6 +952,7 @@ func (api *API) traceTx(ctx context.Context, tx *types.Transaction, message *cor } } vmenv := vm.NewEVM(vmctx, txContext, statedb, api.backend.ChainConfig(), vm.Config{Tracer: tracer, NoBaseFee: true}) + statedb.SetLogger(tracer) // Define a meaningful timeout of a single transaction trace if config.Timeout != nil { diff --git a/eth/tracers/internal/tracetest/calltrace_test.go b/eth/tracers/internal/tracetest/calltrace_test.go index 695480f86b..8dbcacb3bc 100644 --- a/eth/tracers/internal/tracetest/calltrace_test.go +++ b/eth/tracers/internal/tracetest/calltrace_test.go @@ -145,6 +145,7 @@ func testCallTracer(tracerName string, dirPath string, t *testing.T) { if err != nil { t.Fatalf("failed to create call tracer: %v", err) } + statedb.SetLogger(tracer) evm := vm.NewEVM(context, txContext, statedb, test.Genesis.Config, vm.Config{Tracer: tracer}) msg, err := core.TransactionToMessage(tx, signer, nil) if err != nil { @@ -360,6 +361,7 @@ func TestInternals(t *testing.T) { Balance: big.NewInt(500000000000000), }, }, false) + statedb.SetLogger(tc.tracer) evm := vm.NewEVM(context, txContext, statedb, params.MainnetChainConfig, vm.Config{Tracer: tc.tracer}) tx, err := types.SignNewTx(key, signer, &types.LegacyTx{ To: &to, diff --git a/eth/tracers/internal/tracetest/flat_calltrace_test.go b/eth/tracers/internal/tracetest/flat_calltrace_test.go index dc3cdde261..4f191b1487 100644 --- a/eth/tracers/internal/tracetest/flat_calltrace_test.go +++ b/eth/tracers/internal/tracetest/flat_calltrace_test.go @@ -107,6 +107,7 @@ func flatCallTracerTestRunner(tracerName string, filename string, dirPath string if err != nil { return fmt.Errorf("failed to create call tracer: %v", err) } + statedb.SetLogger(tracer) evm := vm.NewEVM(context, txContext, statedb, test.Genesis.Config, vm.Config{Tracer: tracer}) msg, err := core.TransactionToMessage(tx, signer, nil) diff --git a/eth/tracers/internal/tracetest/prestate_test.go b/eth/tracers/internal/tracetest/prestate_test.go index 93839ef3be..8f6edd8e80 100644 --- a/eth/tracers/internal/tracetest/prestate_test.go +++ b/eth/tracers/internal/tracetest/prestate_test.go @@ -114,6 +114,7 @@ func testPrestateDiffTracer(tracerName string, dirPath string, t *testing.T) { if err != nil { t.Fatalf("failed to create call tracer: %v", err) } + statedb.SetLogger(tracer) evm := vm.NewEVM(context, txContext, statedb, test.Genesis.Config, vm.Config{Tracer: tracer}) msg, err := core.TransactionToMessage(tx, signer, nil) if err != nil { diff --git a/eth/tracers/logger/access_list_tracer.go b/eth/tracers/logger/access_list_tracer.go index 89692591cf..3f98de306d 100644 --- a/eth/tracers/logger/access_list_tracer.go +++ b/eth/tracers/logger/access_list_tracer.go @@ -176,6 +176,19 @@ func (*AccessListTracer) CaptureTxStart(tx *types.Transaction) {} func (*AccessListTracer) CaptureTxEnd(receipt *types.Receipt) {} +func (*AccessListTracer) OnBalanceChange(a common.Address, prev, new *big.Int) {} + +func (*AccessListTracer) OnNonceChange(a common.Address, prev, new uint64) {} + +func (*AccessListTracer) OnCodeChange(a common.Address, prevCodeHash common.Hash, prev []byte, codeHash common.Hash, code []byte) { +} + +func (*AccessListTracer) OnStorageChange(a common.Address, k, prev, new common.Hash) {} + +func (*AccessListTracer) OnLog(log *types.Log) {} + +func (*AccessListTracer) OnNewAccount(a common.Address) {} + // AccessList returns the current accesslist maintained by the tracer. func (a *AccessListTracer) AccessList() types.AccessList { return a.list.accessList() diff --git a/eth/tracers/logger/logger.go b/eth/tracers/logger/logger.go index 709fe64629..b06e8d0d03 100644 --- a/eth/tracers/logger/logger.go +++ b/eth/tracers/logger/logger.go @@ -271,6 +271,19 @@ func (l *StructLogger) CaptureTxEnd(receipt *types.Receipt) { l.usedGas = receipt.GasUsed } +func (l *StructLogger) OnBalanceChange(a common.Address, prev, new *big.Int) {} + +func (l *StructLogger) OnNonceChange(a common.Address, prev, new uint64) {} + +func (l *StructLogger) OnCodeChange(a common.Address, prevCodeHash common.Hash, prev []byte, codeHash common.Hash, code []byte) { +} + +func (l *StructLogger) OnStorageChange(a common.Address, k, prev, new common.Hash) {} + +func (l *StructLogger) OnLog(log *types.Log) {} + +func (l *StructLogger) OnNewAccount(a common.Address) {} + // StructLogs returns the captured log entries. func (l *StructLogger) StructLogs() []StructLog { return l.logs } @@ -404,6 +417,19 @@ func (*mdLogger) CaptureTxStart(tx *types.Transaction) {} func (*mdLogger) CaptureTxEnd(receipt *types.Receipt) {} +func (*mdLogger) OnBalanceChange(a common.Address, prev, new *big.Int) {} + +func (*mdLogger) OnNonceChange(a common.Address, prev, new uint64) {} + +func (*mdLogger) OnCodeChange(a common.Address, prevCodeHash common.Hash, prev []byte, codeHash common.Hash, code []byte) { +} + +func (*mdLogger) OnStorageChange(a common.Address, k, prev, new common.Hash) {} + +func (*mdLogger) OnLog(log *types.Log) {} + +func (*mdLogger) OnNewAccount(a common.Address) {} + // ExecutionResult groups all structured logs emitted by the EVM // while replaying a transaction in debug mode as well as transaction // execution status, the amount of gas used and the return value diff --git a/eth/tracers/logger/logger_json.go b/eth/tracers/logger/logger_json.go index f91ef2bb4b..99620a9f16 100644 --- a/eth/tracers/logger/logger_json.go +++ b/eth/tracers/logger/logger_json.go @@ -106,3 +106,16 @@ func (l *JSONLogger) CaptureExit(output []byte, gasUsed uint64, err error) {} func (l *JSONLogger) CaptureTxStart(tx *types.Transaction) {} func (l *JSONLogger) CaptureTxEnd(receipt *types.Receipt) {} + +func (*JSONLogger) OnBalanceChange(a common.Address, prev, new *big.Int) {} + +func (*JSONLogger) OnNonceChange(a common.Address, prev, new uint64) {} + +func (*JSONLogger) OnCodeChange(a common.Address, prevCodeHash common.Hash, prev []byte, codeHash common.Hash, code []byte) { +} + +func (*JSONLogger) OnStorageChange(a common.Address, k, prev, new common.Hash) {} + +func (*JSONLogger) OnLog(log *types.Log) {} + +func (*JSONLogger) OnNewAccount(a common.Address) {} diff --git a/eth/tracers/native/call.go b/eth/tracers/native/call.go index a3b3fb788c..f3a6eca1bb 100644 --- a/eth/tracers/native/call.go +++ b/eth/tracers/native/call.go @@ -103,6 +103,7 @@ type callTracer struct { callstack []callFrame config callTracerConfig gasLimit uint64 + depth int interrupt atomic.Bool // Atomic flag to signal execution interruption reason error // Textual reason for the interruption } @@ -149,51 +150,11 @@ func (t *callTracer) CaptureEnd(output []byte, gasUsed uint64, err error) { // CaptureState implements the EVMLogger interface to trace a single step of VM execution. func (t *callTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) { - // skip if the previous op caused an error - if err != nil { - return - } - // Only logs need to be captured via opcode processing - if !t.config.WithLog { - return - } - // Avoid processing nested calls when only caring about top call - if t.config.OnlyTopCall && depth > 0 { - return - } - // Skip if tracing was interrupted - if t.interrupt.Load() { - return - } - switch op { - case vm.LOG0, vm.LOG1, vm.LOG2, vm.LOG3, vm.LOG4: - size := int(op - vm.LOG0) - - stack := scope.Stack - stackData := stack.Data() - - // Don't modify the stack - mStart := stackData[len(stackData)-1] - mSize := stackData[len(stackData)-2] - topics := make([]common.Hash, size) - for i := 0; i < size; i++ { - topic := stackData[len(stackData)-2-(i+1)] - topics[i] = common.Hash(topic.Bytes32()) - } - - data, err := tracers.GetMemoryCopyPadded(scope.Memory, int64(mStart.Uint64()), int64(mSize.Uint64())) - if err != nil { - // mSize was unrealistically large - return - } - - log := callLog{Address: scope.Contract.Address(), Topics: topics, Data: hexutil.Bytes(data)} - t.callstack[len(t.callstack)-1].Logs = append(t.callstack[len(t.callstack)-1].Logs, log) - } } // 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) { + t.depth++ if t.config.OnlyTopCall { return } @@ -217,6 +178,7 @@ 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) { + t.depth-- if t.config.OnlyTopCall { return } @@ -246,6 +208,22 @@ func (t *callTracer) CaptureTxEnd(receipt *types.Receipt) { } } +func (t *callTracer) OnLog(log *types.Log) { + // Only logs need to be captured via opcode processing + if !t.config.WithLog { + return + } + // Avoid processing nested calls when only caring about top call + if t.config.OnlyTopCall && t.depth > 0 { + return + } + // Skip if tracing was interrupted + if t.interrupt.Load() { + return + } + t.callstack[len(t.callstack)-1].Logs = append(t.callstack[len(t.callstack)-1].Logs, callLog{Address: log.Address, Topics: log.Topics, Data: log.Data}) +} + // GetResult returns the json-encoded nested list of call traces, and any // error arising from the encoding or forceful termination (via `Stop`). func (t *callTracer) GetResult() (json.RawMessage, error) { @@ -257,7 +235,7 @@ func (t *callTracer) GetResult() (json.RawMessage, error) { if err != nil { return nil, err } - return json.RawMessage(res), t.reason + return res, t.reason } // Stop terminates execution of the tracer at the first opportune moment. diff --git a/eth/tracers/native/mux.go b/eth/tracers/native/mux.go index 583930ed77..9abbea3152 100644 --- a/eth/tracers/native/mux.go +++ b/eth/tracers/native/mux.go @@ -128,6 +128,42 @@ func (t *muxTracer) CaptureTxEnd(receipt *types.Receipt) { } } +func (t *muxTracer) OnBalanceChange(a common.Address, prev, new *big.Int) { + for _, t := range t.tracers { + t.OnBalanceChange(a, prev, new) + } +} + +func (t *muxTracer) OnNonceChange(a common.Address, prev, new uint64) { + for _, t := range t.tracers { + t.OnNonceChange(a, prev, new) + } +} + +func (t *muxTracer) OnCodeChange(a common.Address, prevCodeHash common.Hash, prev []byte, codeHash common.Hash, code []byte) { + for _, t := range t.tracers { + t.OnCodeChange(a, prevCodeHash, prev, codeHash, code) + } +} + +func (t *muxTracer) OnStorageChange(a common.Address, k, prev, new common.Hash) { + for _, t := range t.tracers { + t.OnStorageChange(a, k, prev, new) + } +} + +func (t *muxTracer) OnLog(log *types.Log) { + for _, t := range t.tracers { + t.OnLog(log) + } +} + +func (t *muxTracer) OnNewAccount(a common.Address) { + for _, t := range t.tracers { + t.OnNewAccount(a) + } +} + // GetResult returns an empty json object. func (t *muxTracer) GetResult() (json.RawMessage, error) { resObject := make(map[string]json.RawMessage) diff --git a/eth/tracers/noop.go b/eth/tracers/noop.go index d15e1b23a9..082717c3fe 100644 --- a/eth/tracers/noop.go +++ b/eth/tracers/noop.go @@ -73,6 +73,19 @@ func (*NoopTracer) CaptureTxStart(tx *types.Transaction) {} func (*NoopTracer) CaptureTxEnd(receipt *types.Receipt) {} +func (*NoopTracer) OnBalanceChange(a common.Address, prev, new *big.Int) {} + +func (*NoopTracer) OnNonceChange(a common.Address, prev, new uint64) {} + +func (*NoopTracer) OnCodeChange(a common.Address, prevCodeHash common.Hash, prev []byte, codeHash common.Hash, code []byte) { +} + +func (*NoopTracer) OnStorageChange(a common.Address, k, prev, new common.Hash) {} + +func (*NoopTracer) OnLog(log *types.Log) {} + +func (*NoopTracer) OnNewAccount(a common.Address) {} + // GetResult returns an empty json object. func (t *NoopTracer) GetResult() (json.RawMessage, error) { return json.RawMessage(`{}`), nil diff --git a/eth/tracers/tracers.go b/eth/tracers/tracers.go index 7b43b7cf83..c82fcf92ac 100644 --- a/eth/tracers/tracers.go +++ b/eth/tracers/tracers.go @@ -24,6 +24,7 @@ import ( "math/big" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/vm" ) @@ -36,10 +37,13 @@ type Context struct { TxHash common.Hash // Hash of the transaction being traced (zero if dangling call) } -// Tracer interface extends vm.EVMLogger and additionally -// allows collecting the tracing result. +// The set of methods that must be exposed by a tracer +// for it to be available through the RPC interface. +// This involves a method to retrieve results and one to +// stop tracing. type Tracer interface { vm.EVMLogger + state.StateLogger GetResult() (json.RawMessage, error) // Stop terminates execution of the tracer at the first opportune moment. Stop(err error)