From 37ce159500dbdcd403200473b59efb3eae45b623 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Wed, 6 Mar 2024 15:49:49 +0100 Subject: [PATCH] rm OnStart and onEnd --- core/tracing/hooks.go | 13 ++----- core/vm/evm.go | 43 +++++++++------------- core/vm/instructions.go | 8 ++--- eth/tracers/directory/noop.go | 6 ++-- eth/tracers/js/goja.go | 18 ++++++---- eth/tracers/js/tracer_test.go | 14 ++++---- eth/tracers/live/noop.go | 6 ++-- eth/tracers/logger/logger.go | 31 ++++++++++------ eth/tracers/logger/logger_json.go | 7 ++-- eth/tracers/native/4byte.go | 11 +----- eth/tracers/native/call.go | 59 +++++++++++++------------------ eth/tracers/native/call_flat.go | 26 ++++++-------- eth/tracers/native/mux.go | 28 +++------------ 13 files changed, 109 insertions(+), 161 deletions(-) diff --git a/core/tracing/hooks.go b/core/tracing/hooks.go index f912a6c87d..daf2243299 100644 --- a/core/tracing/hooks.go +++ b/core/tracing/hooks.go @@ -91,15 +91,8 @@ type ( // TxEndHook is called after the execution of a transaction ends. TxEndHook = func(receipt *types.Receipt, err error) - // StartHook is invoked when the processing of the root call starts. - StartHook = func(from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) - - // EndHook is invoked when the processing of the top call ends. - // See docs for `ExitHook` for info on the `reverted` parameter. - EndHook = func(output []byte, gasUsed uint64, err error, reverted bool) - // EnterHook is invoked when the processing of a message starts. - EnterHook = func(typ OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) + EnterHook = func(depth int, typ OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) // ExitHook is invoked when the processing of a message ends. // `revert` is true when there was an error during the execution. @@ -107,7 +100,7 @@ type ( // ran out of gas when attempting to persist the code to database did not // count as a call failure and did not cause a revert of the call. This will // be indicated by `reverted == false` and `err == ErrCodeStoreOutOfGas`. - ExitHook = func(output []byte, gasUsed uint64, err error, reverted bool) + ExitHook = func(depth int, output []byte, gasUsed uint64, err error, reverted bool) // OpcodeHook is invoked just prior to the execution of an opcode. OpcodeHook = func(pc uint64, op OpCode, gas, cost uint64, scope OpContext, rData []byte, depth int, err error) @@ -167,8 +160,6 @@ type Hooks struct { // VM events OnTxStart TxStartHook OnTxEnd TxEndHook - OnStart StartHook - OnEnd EndHook OnEnter EnterHook OnExit ExitHook OnOpcode OpcodeHook diff --git a/core/vm/evm.go b/core/vm/evm.go index 1a1600a0f5..c1ad917d0e 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -181,9 +181,9 @@ func (evm *EVM) Interpreter() *EVMInterpreter { func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas uint64, value *uint256.Int) (ret []byte, leftOverGas uint64, err error) { // Capture the tracer start/end events in debug mode if evm.Config.Tracer != nil { - evm.captureBegin(evm.depth == 0, CALL, caller.Address(), addr, input, gas, value.ToBig()) + evm.captureBegin(evm.depth, CALL, caller.Address(), addr, input, gas, value.ToBig()) defer func(startGas uint64) { - evm.captureEnd(evm.depth == 0, CALL, startGas, leftOverGas, ret, err) + evm.captureEnd(evm.depth, CALL, startGas, leftOverGas, ret, err) }(gas) } // Fail if we're trying to execute above the call depth limit @@ -253,9 +253,9 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte, gas uint64, value *uint256.Int) (ret []byte, leftOverGas uint64, err error) { // Invoke tracer hooks that signal entering/exiting a call frame if evm.Config.Tracer != nil { - evm.captureBegin(false, CALLCODE, caller.Address(), addr, input, gas, value.ToBig()) + evm.captureBegin(evm.depth, CALLCODE, caller.Address(), addr, input, gas, value.ToBig()) defer func(startGas uint64) { - evm.captureEnd(false, CALLCODE, startGas, leftOverGas, ret, err) + evm.captureEnd(evm.depth, CALLCODE, startGas, leftOverGas, ret, err) }(gas) } // Fail if we're trying to execute above the call depth limit @@ -308,9 +308,9 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by // that caller is something other than a Contract. parent := caller.(*Contract) // DELEGATECALL inherits value from parent call - evm.captureBegin(false, DELEGATECALL, caller.Address(), addr, input, gas, parent.value.ToBig()) + evm.captureBegin(evm.depth, DELEGATECALL, caller.Address(), addr, input, gas, parent.value.ToBig()) defer func(startGas uint64) { - evm.captureEnd(false, DELEGATECALL, startGas, leftOverGas, ret, err) + evm.captureEnd(evm.depth, DELEGATECALL, startGas, leftOverGas, ret, err) }(gas) } // Fail if we're trying to execute above the call depth limit @@ -349,9 +349,9 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte, gas uint64) (ret []byte, leftOverGas uint64, err error) { // Invoke tracer hooks that signal entering/exiting a call frame if evm.Config.Tracer != nil { - evm.captureBegin(false, STATICCALL, caller.Address(), addr, input, gas, nil) + evm.captureBegin(evm.depth, STATICCALL, caller.Address(), addr, input, gas, nil) defer func(startGas uint64) { - evm.captureEnd(false, STATICCALL, startGas, leftOverGas, ret, err) + evm.captureEnd(evm.depth, STATICCALL, startGas, leftOverGas, ret, err) }(gas) } // Fail if we're trying to execute above the call depth limit @@ -416,9 +416,9 @@ func (c *codeAndHash) Hash() common.Hash { // create creates a new contract using code as deployment code. func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, value *uint256.Int, address common.Address, typ OpCode) (ret []byte, createAddress common.Address, leftOverGas uint64, err error) { if evm.Config.Tracer != nil { - evm.captureBegin(evm.depth == 0, typ, caller.Address(), address, codeAndHash.code, gas, value.ToBig()) + evm.captureBegin(evm.depth, typ, caller.Address(), address, codeAndHash.code, gas, value.ToBig()) defer func(startGas uint64) { - evm.captureEnd(evm.depth == 0, typ, startGas, leftOverGas, ret, err) + evm.captureEnd(evm.depth, typ, startGas, leftOverGas, ret, err) }(gas) } // Depth check execution. Fail if we're trying to execute above the @@ -518,25 +518,18 @@ func (evm *EVM) Create2(caller ContractRef, code []byte, gas uint64, endowment * // ChainConfig returns the environment's chain configuration func (evm *EVM) ChainConfig() *params.ChainConfig { return evm.chainConfig } -func (evm *EVM) captureBegin(isRoot bool, typ OpCode, from common.Address, to common.Address, input []byte, startGas uint64, value *big.Int) { +func (evm *EVM) captureBegin(depth int, typ OpCode, from common.Address, to common.Address, input []byte, startGas uint64, value *big.Int) { tracer := evm.Config.Tracer - - if isRoot { - if tracer.OnStart != nil { - tracer.OnStart(from, to, typ == CREATE || typ == CREATE2, input, startGas, value) - } - } else if tracer.OnEnter != nil { - tracer.OnEnter(tracing.OpCode(typ), from, to, input, startGas, value) + if tracer.OnEnter != nil { + tracer.OnEnter(depth, tracing.OpCode(typ), from, to, input, startGas, value) } - if tracer.OnGasChange != nil { tracer.OnGasChange(0, startGas, tracing.GasChangeCallInitialBalance) } } -func (evm *EVM) captureEnd(isRoot bool, typ OpCode, startGas uint64, leftOverGas uint64, ret []byte, err error) { +func (evm *EVM) captureEnd(depth int, typ OpCode, startGas uint64, leftOverGas uint64, ret []byte, err error) { tracer := evm.Config.Tracer - if leftOverGas != 0 && tracer.OnGasChange != nil { tracer.OnGasChange(leftOverGas, 0, tracing.GasChangeCallLeftOverReturned) } @@ -547,12 +540,8 @@ func (evm *EVM) captureEnd(isRoot bool, typ OpCode, startGas uint64, leftOverGas if !evm.chainRules.IsHomestead && errors.Is(err, ErrCodeStoreOutOfGas) { reverted = false } - if isRoot { - if tracer.OnEnd != nil { - tracer.OnEnd(ret, startGas-leftOverGas, VMErrorFromErr(err), reverted) - } - } else if tracer.OnExit != nil { - tracer.OnExit(ret, startGas-leftOverGas, VMErrorFromErr(err), reverted) + if tracer.OnExit != nil { + tracer.OnExit(depth, ret, startGas-leftOverGas, VMErrorFromErr(err), reverted) } } diff --git a/core/vm/instructions.go b/core/vm/instructions.go index 9daba0c957..3578d412ea 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -837,10 +837,10 @@ func opSelfdestruct(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext interpreter.evm.StateDB.SelfDestruct(scope.Contract.Address()) if tracer := interpreter.evm.Config.Tracer; tracer != nil { if tracer.OnEnter != nil { - tracer.OnEnter(tracing.OpCode(SELFDESTRUCT), scope.Contract.Address(), beneficiary.Bytes20(), []byte{}, 0, balance.ToBig()) + tracer.OnEnter(interpreter.evm.depth, tracing.OpCode(SELFDESTRUCT), scope.Contract.Address(), beneficiary.Bytes20(), []byte{}, 0, balance.ToBig()) } if tracer.OnExit != nil { - tracer.OnExit([]byte{}, 0, nil, false) + tracer.OnExit(interpreter.evm.depth, []byte{}, 0, nil, false) } } return nil, errStopToken @@ -857,10 +857,10 @@ func opSelfdestruct6780(pc *uint64, interpreter *EVMInterpreter, scope *ScopeCon interpreter.evm.StateDB.Selfdestruct6780(scope.Contract.Address()) if tracer := interpreter.evm.Config.Tracer; tracer != nil { if tracer.OnEnter != nil { - tracer.OnEnter(tracing.OpCode(SELFDESTRUCT), scope.Contract.Address(), beneficiary.Bytes20(), []byte{}, 0, balance.ToBig()) + tracer.OnEnter(interpreter.evm.depth, tracing.OpCode(SELFDESTRUCT), scope.Contract.Address(), beneficiary.Bytes20(), []byte{}, 0, balance.ToBig()) } if tracer.OnExit != nil { - tracer.OnExit([]byte{}, 0, nil, false) + tracer.OnExit(interpreter.evm.depth, []byte{}, 0, nil, false) } } return nil, errStopToken diff --git a/eth/tracers/directory/noop.go b/eth/tracers/directory/noop.go index 637f0da326..7fffcdd610 100644 --- a/eth/tracers/directory/noop.go +++ b/eth/tracers/directory/noop.go @@ -40,8 +40,6 @@ func newNoopTracer(ctx *Context, _ json.RawMessage) (*Tracer, error) { Hooks: &tracing.Hooks{ OnTxStart: t.CaptureTxStart, OnTxEnd: t.CaptureTxEnd, - OnStart: t.CaptureStart, - OnEnd: t.CaptureEnd, OnEnter: t.CaptureEnter, OnExit: t.CaptureExit, OnOpcode: t.CaptureState, @@ -82,12 +80,12 @@ func (t *NoopTracer) CaptureKeccakPreimage(hash common.Hash, data []byte) {} func (t *NoopTracer) OnGasChange(old, new uint64, reason tracing.GasChangeReason) {} // CaptureEnter is called when EVM enters a new scope (via call, create or selfdestruct). -func (t *NoopTracer) CaptureEnter(typ tracing.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { +func (t *NoopTracer) CaptureEnter(depth int, typ tracing.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { } // CaptureExit is called when EVM exits a scope, even if the scope didn't // execute any code. -func (t *NoopTracer) CaptureExit(output []byte, gasUsed uint64, err error, reverted bool) { +func (t *NoopTracer) CaptureExit(depth int, output []byte, gasUsed uint64, err error, reverted bool) { } func (*NoopTracer) CaptureTxStart(env *tracing.VMContext, tx *types.Transaction, from common.Address) { diff --git a/eth/tracers/js/goja.go b/eth/tracers/js/goja.go index 1a02ca1c87..f3e0f5415a 100644 --- a/eth/tracers/js/goja.go +++ b/eth/tracers/js/goja.go @@ -226,8 +226,6 @@ func newJsTracer(code string, ctx *directory.Context, cfg json.RawMessage) (*dir Hooks: &tracing.Hooks{ OnTxStart: t.CaptureTxStart, OnTxEnd: t.CaptureTxEnd, - OnStart: t.CaptureStart, - OnEnd: t.CaptureEnd, OnEnter: t.CaptureEnter, OnExit: t.CaptureExit, OnOpcode: t.CaptureState, @@ -273,7 +271,7 @@ func (t *jsTracer) CaptureTxEnd(receipt *types.Receipt, err error) { } // CaptureStart implements the Tracer interface to initialize the tracing operation. -func (t *jsTracer) CaptureStart(from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) { +func (t *jsTracer) captureStart(from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) { cancel := func(err error) { t.err = err t.env.VM.Cancel() @@ -347,7 +345,7 @@ func (t *jsTracer) CaptureFault(pc uint64, op tracing.OpCode, gas, cost uint64, } // CaptureEnd is called after the call finishes to finalize the tracing. -func (t *jsTracer) CaptureEnd(output []byte, gasUsed uint64, err error, reverted bool) { +func (t *jsTracer) captureEnd(output []byte, gasUsed uint64, err error, reverted bool) { if err != nil { t.ctx["error"] = t.vm.ToValue(err.Error()) } @@ -360,7 +358,11 @@ func (t *jsTracer) CaptureEnd(output []byte, gasUsed uint64, err error, reverted } // CaptureEnter is called when EVM enters a new scope (via call, create or selfdestruct). -func (t *jsTracer) CaptureEnter(typ tracing.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { +func (t *jsTracer) CaptureEnter(depth int, typ tracing.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { + if depth == 0 { + t.captureStart(from, to, vm.OpCode(typ) == vm.CREATE, input, gas, value) + return + } if !t.traceFrame { return } @@ -385,7 +387,11 @@ func (t *jsTracer) CaptureEnter(typ tracing.OpCode, from common.Address, to comm // CaptureExit is called when EVM exits a scope, even if the scope didn't // execute any code. -func (t *jsTracer) CaptureExit(output []byte, gasUsed uint64, err error, reverted bool) { +func (t *jsTracer) CaptureExit(depth int, output []byte, gasUsed uint64, err error, reverted bool) { + if depth == 0 { + t.captureEnd(output, gasUsed, err, reverted) + return + } if !t.traceFrame { return } diff --git a/eth/tracers/js/tracer_test.go b/eth/tracers/js/tracer_test.go index 07536e14e1..3a0505455e 100644 --- a/eth/tracers/js/tracer_test.go +++ b/eth/tracers/js/tracer_test.go @@ -77,9 +77,9 @@ func runTrace(tracer *directory.Tracer, vmctx *vmContext, chaincfg *params.Chain } tracer.OnTxStart(env.GetVMContext(), types.NewTx(&types.LegacyTx{Gas: gasLimit}), contract.Caller()) - tracer.OnStart(contract.Caller(), contract.Address(), false, []byte{}, startGas, value.ToBig()) + tracer.OnEnter(0, tracing.OpCode(vm.CALL), contract.Caller(), contract.Address(), []byte{}, startGas, value.ToBig()) ret, err := env.Interpreter().Run(contract, []byte{}, false) - tracer.OnEnd(ret, startGas-contract.Gas, err, true) + tracer.OnExit(0, ret, startGas-contract.Gas, err, true) // Rest gas assumes no refund tracer.OnTxEnd(&types.Receipt{GasUsed: gasLimit - contract.Gas}, nil) if err != nil { @@ -188,7 +188,7 @@ func TestHaltBetweenSteps(t *testing.T) { } env := vm.NewEVM(vm.BlockContext{BlockNumber: big.NewInt(1)}, vm.TxContext{GasPrice: big.NewInt(1)}, &dummyStatedb{}, params.TestChainConfig, vm.Config{Tracer: tracer.Hooks}) tracer.OnTxStart(env.GetVMContext(), types.NewTx(&types.LegacyTx{}), common.Address{}) - tracer.OnStart(common.Address{}, common.Address{}, false, []byte{}, 0, big.NewInt(0)) + tracer.OnEnter(0, tracing.OpCode(vm.CALL), common.Address{}, common.Address{}, []byte{}, 0, big.NewInt(0)) tracer.OnOpcode(0, 0, 0, 0, scope, nil, 0, nil) timeout := errors.New("stahp") tracer.Stop(timeout) @@ -210,8 +210,8 @@ func TestNoStepExec(t *testing.T) { } env := vm.NewEVM(vm.BlockContext{BlockNumber: big.NewInt(1)}, vm.TxContext{GasPrice: big.NewInt(100)}, &dummyStatedb{}, params.TestChainConfig, vm.Config{Tracer: tracer.Hooks}) tracer.OnTxStart(env.GetVMContext(), types.NewTx(&types.LegacyTx{}), common.Address{}) - tracer.OnStart(common.Address{}, common.Address{}, false, []byte{}, 1000, big.NewInt(0)) - tracer.OnEnd(nil, 0, nil, false) + tracer.OnEnter(0, tracing.OpCode(vm.CALL), common.Address{}, common.Address{}, []byte{}, 1000, big.NewInt(0)) + tracer.OnExit(0, nil, 0, nil, false) ret, err := tracer.GetResult() if err != nil { t.Fatal(err) @@ -280,8 +280,8 @@ func TestEnterExit(t *testing.T) { scope := &vm.ScopeContext{ Contract: vm.NewContract(&account{}, &account{}, uint256.NewInt(0), 0), } - tracer.OnEnter(tracing.OpCode(vm.CALL), scope.Contract.Caller(), scope.Contract.Address(), []byte{}, 1000, new(big.Int)) - tracer.OnExit([]byte{}, 400, nil, false) + tracer.OnEnter(1, tracing.OpCode(vm.CALL), scope.Contract.Caller(), scope.Contract.Address(), []byte{}, 1000, new(big.Int)) + tracer.OnExit(1, []byte{}, 400, nil, false) have, err := tracer.GetResult() if err != nil { diff --git a/eth/tracers/live/noop.go b/eth/tracers/live/noop.go index 095217e47c..35b070b122 100644 --- a/eth/tracers/live/noop.go +++ b/eth/tracers/live/noop.go @@ -26,8 +26,6 @@ func newNoopTracer(_ json.RawMessage) (*tracing.Hooks, error) { return &tracing.Hooks{ OnTxStart: t.CaptureTxStart, OnTxEnd: t.CaptureTxEnd, - OnStart: t.CaptureStart, - OnEnd: t.CaptureEnd, OnEnter: t.CaptureEnter, OnExit: t.CaptureExit, OnOpcode: t.CaptureState, @@ -67,12 +65,12 @@ func (t *noop) CaptureFault(pc uint64, op tracing.OpCode, gas, cost uint64, _ tr func (t *noop) CaptureKeccakPreimage(hash common.Hash, data []byte) {} // CaptureEnter is called when EVM enters a new scope (via call, create or selfdestruct). -func (t *noop) CaptureEnter(typ tracing.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { +func (t *noop) CaptureEnter(depth int, typ tracing.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { } // CaptureExit is called when EVM exits a scope, even if the scope didn't // execute any code. -func (t *noop) CaptureExit(output []byte, gasUsed uint64, err error, reverted bool) { +func (t *noop) CaptureExit(depth int, output []byte, gasUsed uint64, err error, reverted bool) { } func (t *noop) CaptureTxStart(vm *tracing.VMContext, tx *types.Transaction, from common.Address) { diff --git a/eth/tracers/logger/logger.go b/eth/tracers/logger/logger.go index 1fb014cb34..85f5f5a538 100644 --- a/eth/tracers/logger/logger.go +++ b/eth/tracers/logger/logger.go @@ -138,7 +138,7 @@ func (l *StructLogger) Hooks() *tracing.Hooks { return &tracing.Hooks{ OnTxStart: l.CaptureTxStart, OnTxEnd: l.CaptureTxEnd, - OnEnd: l.CaptureEnd, + OnExit: l.CaptureExit, OnOpcode: l.CaptureState, } } @@ -227,8 +227,11 @@ func (l *StructLogger) CaptureState(pc uint64, opcode tracing.OpCode, gas, cost l.logs = append(l.logs, log) } -// CaptureEnd is called after the call finishes to finalize the tracing. -func (l *StructLogger) CaptureEnd(output []byte, gasUsed uint64, err error, reverted bool) { +// CaptureExit is called a call frame finishes processing. +func (l *StructLogger) CaptureExit(depth int, output []byte, gasUsed uint64, err error, reverted bool) { + if depth != 0 { + return + } l.output = output l.err = err if l.cfg.Debug { @@ -356,10 +359,10 @@ func NewMarkdownLogger(cfg *Config, writer io.Writer) *mdLogger { func (t *mdLogger) Hooks() *tracing.Hooks { return &tracing.Hooks{ OnTxStart: t.CaptureTxStart, - OnStart: t.CaptureStart, + OnEnter: t.CaptureEnter, + OnExit: t.CaptureExit, OnOpcode: t.CaptureState, OnFault: t.CaptureFault, - OnEnd: t.CaptureEnd, } } @@ -367,7 +370,11 @@ func (t *mdLogger) CaptureTxStart(env *tracing.VMContext, tx *types.Transaction, t.env = env } -func (t *mdLogger) CaptureStart(from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) { +func (t *mdLogger) CaptureEnter(depth int, typ tracing.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { + if depth != 0 { + return + } + create := vm.OpCode(typ) == vm.CREATE if !create { fmt.Fprintf(t.out, "From: `%v`\nTo: `%v`\nData: `%#x`\nGas: `%d`\nValue `%v` wei\n", from.String(), to.String(), @@ -384,6 +391,13 @@ func (t *mdLogger) CaptureStart(from common.Address, to common.Address, create b `) } +func (t *mdLogger) CaptureExit(depth int, output []byte, gasUsed uint64, err error, reverted bool) { + if depth == 0 { + fmt.Fprintf(t.out, "\nOutput: `%#x`\nConsumed gas: `%d`\nError: `%v`\n", + output, gasUsed, err) + } +} + // CaptureState also tracks SLOAD/SSTORE ops to track storage change. func (t *mdLogger) CaptureState(pc uint64, op tracing.OpCode, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error) { stack := scope.StackData() @@ -409,11 +423,6 @@ func (t *mdLogger) CaptureFault(pc uint64, op tracing.OpCode, gas, cost uint64, fmt.Fprintf(t.out, "\nError: at pc=%d, op=%v: %v\n", pc, op, err) } -func (t *mdLogger) CaptureEnd(output []byte, gasUsed uint64, err error, reverted bool) { - fmt.Fprintf(t.out, "\nOutput: `%#x`\nConsumed gas: `%d`\nError: `%v`\n", - output, gasUsed, err) -} - // 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 5067fb8a58..ab2c35348d 100644 --- a/eth/tracers/logger/logger_json.go +++ b/eth/tracers/logger/logger_json.go @@ -48,7 +48,7 @@ func NewJSONLogger(cfg *Config, writer io.Writer) *JSONLogger { func (l *JSONLogger) Hooks() *tracing.Hooks { return &tracing.Hooks{ OnTxStart: l.CaptureTxStart, - OnEnd: l.CaptureEnd, + OnExit: l.CaptureExit, OnOpcode: l.CaptureState, OnFault: l.CaptureFault, } @@ -87,7 +87,10 @@ func (l *JSONLogger) CaptureState(pc uint64, op tracing.OpCode, gas, cost uint64 } // CaptureEnd is triggered at end of execution. -func (l *JSONLogger) CaptureEnd(output []byte, gasUsed uint64, err error, reverted bool) { +func (l *JSONLogger) CaptureExit(depth int, output []byte, gasUsed uint64, err error, reverted bool) { + if depth > 0 { + return + } type endLog struct { Output string `json:"output"` GasUsed math.HexOrDecimal64 `json:"gasUsed"` diff --git a/eth/tracers/native/4byte.go b/eth/tracers/native/4byte.go index a0a4a6ff4f..271a0b6bda 100644 --- a/eth/tracers/native/4byte.go +++ b/eth/tracers/native/4byte.go @@ -64,7 +64,6 @@ func newFourByteTracer(ctx *directory.Context, _ json.RawMessage) (*directory.Tr return &directory.Tracer{ Hooks: &tracing.Hooks{ OnTxStart: t.CaptureTxStart, - OnStart: t.CaptureStart, OnEnter: t.CaptureEnter, }, GetResult: t.GetResult, @@ -94,16 +93,8 @@ func (t *fourByteTracer) CaptureTxStart(env *tracing.VMContext, tx *types.Transa t.activePrecompiles = vm.ActivePrecompiles(rules) } -// CaptureStart implements the EVMLogger interface to initialize the tracing operation. -func (t *fourByteTracer) CaptureStart(from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) { - // Save the outer calldata - if len(input) >= 4 { - t.store(input[0:4], len(input)-4) - } -} - // CaptureEnter is called when EVM enters a new scope (via call, create or selfdestruct). -func (t *fourByteTracer) CaptureEnter(opcode tracing.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { +func (t *fourByteTracer) CaptureEnter(depth int, opcode tracing.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { // Skip if tracing was interrupted if t.interrupt.Load() { return diff --git a/eth/tracers/native/call.go b/eth/tracers/native/call.go index 8fd2b883f0..b4059b8ce5 100644 --- a/eth/tracers/native/call.go +++ b/eth/tracers/native/call.go @@ -130,11 +130,8 @@ func newCallTracer(ctx *directory.Context, cfg json.RawMessage) (*directory.Trac Hooks: &tracing.Hooks{ OnTxStart: t.CaptureTxStart, OnTxEnd: t.CaptureTxEnd, - OnStart: t.CaptureStart, - OnEnd: t.CaptureEnd, OnEnter: t.CaptureEnter, OnExit: t.CaptureExit, - OnOpcode: t.CaptureState, OnLog: t.OnLog, }, GetResult: t.GetResult, @@ -151,38 +148,13 @@ func newCallTracerObject(ctx *directory.Context, cfg json.RawMessage) (*callTrac } // First callframe contains tx context info // and is populated on start and end. - return &callTracer{callstack: make([]callFrame, 1), config: config}, nil -} - -// CaptureStart implements the EVMLogger interface to initialize the tracing operation. -func (t *callTracer) CaptureStart(from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) { - toCopy := to - t.callstack[0] = callFrame{ - Type: vm.CALL, - From: from, - To: &toCopy, - Input: common.CopyBytes(input), - Gas: t.gasLimit, - Value: value, - } - if create { - t.callstack[0].Type = vm.CREATE - } -} - -// CaptureEnd is called after the call finishes to finalize the tracing. -func (t *callTracer) CaptureEnd(output []byte, gasUsed uint64, err error, reverted bool) { - t.callstack[0].processOutput(output, err, reverted) -} - -// CaptureState implements the EVMLogger interface to trace a single step of VM execution. -func (t *callTracer) CaptureState(pc uint64, op tracing.OpCode, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error) { + return &callTracer{callstack: make([]callFrame, 0, 1), config: config}, nil } // CaptureEnter is called when EVM enters a new scope (via call, create or selfdestruct). -func (t *callTracer) CaptureEnter(typ tracing.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { - t.depth++ - if t.config.OnlyTopCall { +func (t *callTracer) CaptureEnter(depth int, typ tracing.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { + t.depth = depth + if t.config.OnlyTopCall && depth > 0 { return } // Skip if tracing was interrupted @@ -199,30 +171,47 @@ func (t *callTracer) CaptureEnter(typ tracing.OpCode, from common.Address, to co Gas: gas, Value: value, } + if depth == 0 { + call.Gas = t.gasLimit + } t.callstack = append(t.callstack, call) } // 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, reverted bool) { - t.depth-- +func (t *callTracer) CaptureExit(depth int, output []byte, gasUsed uint64, err error, reverted bool) { + if depth == 0 { + t.captureEnd(output, gasUsed, err, reverted) + return + } + + t.depth = depth - 1 if t.config.OnlyTopCall { return } + size := len(t.callstack) if size <= 1 { return } - // pop call + // Pop call. call := t.callstack[size-1] t.callstack = t.callstack[:size-1] size -= 1 call.GasUsed = gasUsed call.processOutput(output, err, reverted) + // Nest call into parent. t.callstack[size-1].Calls = append(t.callstack[size-1].Calls, call) } +func (t *callTracer) captureEnd(output []byte, gasUsed uint64, err error, reverted bool) { + if len(t.callstack) != 1 { + return + } + t.callstack[0].processOutput(output, err, reverted) +} + func (t *callTracer) CaptureTxStart(env *tracing.VMContext, tx *types.Transaction, from common.Address) { t.gasLimit = tx.Gas() } diff --git a/eth/tracers/native/call_flat.go b/eth/tracers/native/call_flat.go index c0ec5c312e..eca4325644 100644 --- a/eth/tracers/native/call_flat.go +++ b/eth/tracers/native/call_flat.go @@ -144,8 +144,6 @@ func newFlatCallTracer(ctx *directory.Context, cfg json.RawMessage) (*directory. Hooks: &tracing.Hooks{ OnTxStart: ft.CaptureTxStart, OnTxEnd: ft.CaptureTxEnd, - OnStart: ft.CaptureStart, - OnEnd: ft.CaptureEnd, OnEnter: ft.CaptureEnter, OnExit: ft.CaptureExit, OnOpcode: ft.CaptureState, @@ -156,16 +154,6 @@ func newFlatCallTracer(ctx *directory.Context, cfg json.RawMessage) (*directory. }, nil } -// CaptureStart implements the EVMLogger interface to initialize the tracing operation. -func (t *flatCallTracer) CaptureStart(from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) { - t.tracer.CaptureStart(from, to, create, input, gas, value) -} - -// CaptureEnd is called after the call finishes to finalize the tracing. -func (t *flatCallTracer) CaptureEnd(output []byte, gasUsed uint64, err error, reverted bool) { - t.tracer.CaptureEnd(output, gasUsed, err, reverted) -} - // CaptureState implements the EVMLogger interface to trace a single step of VM execution. func (t *flatCallTracer) CaptureState(pc uint64, op tracing.OpCode, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error) { t.tracer.CaptureState(pc, op, gas, cost, scope, rData, depth, err) @@ -177,9 +165,12 @@ func (t *flatCallTracer) CaptureFault(pc uint64, op tracing.OpCode, gas, cost ui } // CaptureEnter is called when EVM enters a new scope (via call, create or selfdestruct). -func (t *flatCallTracer) CaptureEnter(typ tracing.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { - t.tracer.CaptureEnter(typ, from, to, input, gas, value) +func (t *flatCallTracer) CaptureEnter(depth int, typ tracing.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { + t.tracer.CaptureEnter(depth, typ, from, to, input, gas, value) + if depth == 0 { + return + } // Child calls must have a value, even if it's zero. // Practically speaking, only STATICCALL has nil value. Set it to zero. if t.tracer.callstack[len(t.tracer.callstack)-1].Value == nil && value == nil { @@ -189,9 +180,12 @@ func (t *flatCallTracer) CaptureEnter(typ tracing.OpCode, from common.Address, t // CaptureExit is called when EVM exits a scope, even if the scope didn't // execute any code. -func (t *flatCallTracer) CaptureExit(output []byte, gasUsed uint64, err error, reverted bool) { - t.tracer.CaptureExit(output, gasUsed, err, reverted) +func (t *flatCallTracer) CaptureExit(depth int, output []byte, gasUsed uint64, err error, reverted bool) { + t.tracer.CaptureExit(depth, output, gasUsed, err, reverted) + if depth == 0 { + return + } // Parity traces don't include CALL/STATICCALLs to precompiles. // By default we remove them from the callstack. if t.config.IncludePrecompiles { diff --git a/eth/tracers/native/mux.go b/eth/tracers/native/mux.go index 4516ab1dff..c3416c1c97 100644 --- a/eth/tracers/native/mux.go +++ b/eth/tracers/native/mux.go @@ -61,8 +61,6 @@ func newMuxTracer(ctx *directory.Context, cfg json.RawMessage) (*directory.Trace Hooks: &tracing.Hooks{ OnTxStart: t.CaptureTxStart, OnTxEnd: t.CaptureTxEnd, - OnStart: t.CaptureStart, - OnEnd: t.CaptureEnd, OnEnter: t.CaptureEnter, OnExit: t.CaptureExit, OnOpcode: t.CaptureState, @@ -80,24 +78,6 @@ func newMuxTracer(ctx *directory.Context, cfg json.RawMessage) (*directory.Trace }, nil } -// CaptureStart implements the EVMLogger interface to initialize the tracing operation. -func (t *muxTracer) CaptureStart(from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) { - for _, t := range t.tracers { - if t.OnStart != nil { - t.OnStart(from, to, create, input, gas, value) - } - } -} - -// CaptureEnd is called after the call finishes to finalize the tracing. -func (t *muxTracer) CaptureEnd(output []byte, gasUsed uint64, err error, reverted bool) { - for _, t := range t.tracers { - if t.OnEnd != nil { - t.OnEnd(output, gasUsed, err, reverted) - } - } -} - // CaptureState implements the EVMLogger interface to trace a single step of VM execution. func (t *muxTracer) CaptureState(pc uint64, op tracing.OpCode, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error) { for _, t := range t.tracers { @@ -135,20 +115,20 @@ func (t *muxTracer) OnGasChange(old, new uint64, reason tracing.GasChangeReason) } // CaptureEnter is called when EVM enters a new scope (via call, create or selfdestruct). -func (t *muxTracer) CaptureEnter(typ tracing.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { +func (t *muxTracer) CaptureEnter(depth int, typ tracing.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { for _, t := range t.tracers { if t.OnEnter != nil { - t.OnEnter(typ, from, to, input, gas, value) + t.OnEnter(depth, typ, from, to, input, gas, value) } } } // CaptureExit is called when EVM exits a scope, even if the scope didn't // execute any code. -func (t *muxTracer) CaptureExit(output []byte, gasUsed uint64, err error, reverted bool) { +func (t *muxTracer) CaptureExit(depth int, output []byte, gasUsed uint64, err error, reverted bool) { for _, t := range t.tracers { if t.OnExit != nil { - t.OnExit(output, gasUsed, err, reverted) + t.OnExit(depth, output, gasUsed, err, reverted) } } }