diff --git a/core/state_transition.go b/core/state_transition.go index 022238c8d6..cce83ecf3c 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -348,6 +348,9 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { if st.gasRemaining < gas { return nil, fmt.Errorf("%w: have %d, want %d", ErrIntrinsicGas, st.gasRemaining, gas) } + if t := st.evm.Config.Tracer; t != nil { + t.OnGasConsumed(st.gasRemaining, gas) + } st.gasRemaining -= gas // Check clause 6 diff --git a/core/vm/contract.go b/core/vm/contract.go index bb0902969e..1d88b0e1b9 100644 --- a/core/vm/contract.go +++ b/core/vm/contract.go @@ -159,10 +159,13 @@ func (c *Contract) Caller() common.Address { } // UseGas attempts the use gas and subtracts it and returns true on success -func (c *Contract) UseGas(gas uint64) (ok bool) { +func (c *Contract) UseGas(gas uint64, logger EVMLogger) (ok bool) { if c.Gas < gas { return false } + if logger != nil { + logger.OnGasConsumed(c.Gas, gas) + } c.Gas -= gas return true } diff --git a/core/vm/contracts.go b/core/vm/contracts.go index 6041be6c9f..cb4cc76c02 100644 --- a/core/vm/contracts.go +++ b/core/vm/contracts.go @@ -168,11 +168,14 @@ func ActivePrecompiles(rules params.Rules) []common.Address { // - the returned bytes, // - the _remaining_ gas, // - any error that occurred -func RunPrecompiledContract(p PrecompiledContract, input []byte, suppliedGas uint64) (ret []byte, remainingGas uint64, err error) { +func RunPrecompiledContract(p PrecompiledContract, input []byte, suppliedGas uint64, logger EVMLogger) (ret []byte, remainingGas uint64, err error) { gasCost := p.RequiredGas(input) if suppliedGas < gasCost { return nil, 0, ErrOutOfGas } + if logger != nil { + logger.OnGasConsumed(suppliedGas, gasCost) + } suppliedGas -= gasCost output, err := p.Run(input) return output, suppliedGas, err diff --git a/core/vm/contracts_test.go b/core/vm/contracts_test.go index 5b1e874e91..6a17f5b7d5 100644 --- a/core/vm/contracts_test.go +++ b/core/vm/contracts_test.go @@ -97,7 +97,7 @@ func testPrecompiled(addr string, test precompiledTest, t *testing.T) { in := common.Hex2Bytes(test.Input) gas := p.RequiredGas(in) t.Run(fmt.Sprintf("%s-Gas=%d", test.Name, gas), func(t *testing.T) { - if res, _, err := RunPrecompiledContract(p, in, gas); err != nil { + if res, _, err := RunPrecompiledContract(p, in, gas, nil); err != nil { t.Error(err) } else if common.Bytes2Hex(res) != test.Expected { t.Errorf("Expected %v, got %v", test.Expected, common.Bytes2Hex(res)) @@ -119,7 +119,7 @@ func testPrecompiledOOG(addr string, test precompiledTest, t *testing.T) { gas := p.RequiredGas(in) - 1 t.Run(fmt.Sprintf("%s-Gas=%d", test.Name, gas), func(t *testing.T) { - _, _, err := RunPrecompiledContract(p, in, gas) + _, _, err := RunPrecompiledContract(p, in, gas, nil) if err.Error() != "out of gas" { t.Errorf("Expected error [out of gas], got [%v]", err) } @@ -136,7 +136,7 @@ func testPrecompiledFailure(addr string, test precompiledFailureTest, t *testing in := common.Hex2Bytes(test.Input) gas := p.RequiredGas(in) t.Run(test.Name, func(t *testing.T) { - _, _, err := RunPrecompiledContract(p, in, gas) + _, _, err := RunPrecompiledContract(p, in, gas, nil) if err.Error() != test.ExpectedError { t.Errorf("Expected error [%v], got [%v]", test.ExpectedError, err) } @@ -168,7 +168,7 @@ func benchmarkPrecompiled(addr string, test precompiledTest, bench *testing.B) { bench.ResetTimer() for i := 0; i < bench.N; i++ { copy(data, in) - res, _, err = RunPrecompiledContract(p, data, reqGas) + res, _, err = RunPrecompiledContract(p, data, reqGas, nil) } bench.StopTimer() elapsed := uint64(time.Since(start)) diff --git a/core/vm/evm.go b/core/vm/evm.go index 36336d8cbd..30cfd2b9a8 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -222,7 +222,7 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas } if isPrecompile { - ret, gas, err = RunPrecompiledContract(p, input, gas) + ret, gas, err = RunPrecompiledContract(p, input, gas, evm.Config.Tracer) } else { // Initialise a new contract and set the code that is to be used by the EVM. // The contract is a scoped environment for this execution context only. @@ -285,7 +285,7 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte, // It is allowed to call precompiles, even via delegatecall if p, isPrecompile := evm.precompile(addr); isPrecompile { - ret, gas, err = RunPrecompiledContract(p, input, gas) + ret, gas, err = RunPrecompiledContract(p, input, gas, evm.Config.Tracer) } else { addrCopy := addr // Initialise a new contract and set the code that is to be used by the EVM. @@ -330,7 +330,7 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by // It is allowed to call precompiles, even via delegatecall if p, isPrecompile := evm.precompile(addr); isPrecompile { - ret, gas, err = RunPrecompiledContract(p, input, gas) + ret, gas, err = RunPrecompiledContract(p, input, gas, evm.Config.Tracer) } else { addrCopy := addr // Initialise a new contract and make initialise the delegate values @@ -379,7 +379,7 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte } if p, isPrecompile := evm.precompile(addr); isPrecompile { - ret, gas, err = RunPrecompiledContract(p, input, gas) + ret, gas, err = RunPrecompiledContract(p, input, gas, evm.Config.Tracer) } else { // At this point, we use a copy of address. If we don't, the go compiler will // leak the 'contract' to the outer scope, and make allocation for 'contract' @@ -480,7 +480,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, // by the error checking condition below. if err == nil { createDataGas := uint64(len(ret)) * params.CreateDataGas - if contract.UseGas(createDataGas) { + if contract.UseGas(createDataGas, evm.Config.Tracer) { evm.StateDB.SetCode(address, ret) } else { err = ErrCodeStoreOutOfGas @@ -493,7 +493,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, if err != nil && (evm.chainRules.IsHomestead || err != ErrCodeStoreOutOfGas) { evm.StateDB.RevertToSnapshot(snapshot) if err != ErrExecutionReverted { - contract.UseGas(contract.Gas) + contract.UseGas(contract.Gas, evm.Config.Tracer) } } diff --git a/core/vm/instructions.go b/core/vm/instructions.go index aaa12e4361..65f8ea1930 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -591,7 +591,7 @@ func opCreate(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]b // reuse size int for stackvalue stackvalue := size - scope.Contract.UseGas(gas) + scope.Contract.UseGas(gas, interpreter.evm.Config.Tracer) //TODO: use uint256.Int instead of converting with toBig() var bigVal = big0 if !value.IsZero() { @@ -634,7 +634,7 @@ func opCreate2(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([] ) // Apply EIP150 gas -= gas / 64 - scope.Contract.UseGas(gas) + scope.Contract.UseGas(gas, interpreter.evm.Config.Tracer) // reuse size int for stackvalue stackvalue := size //TODO: use uint256.Int instead of converting with toBig() diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index 873337850e..14aa9cee21 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -185,7 +185,7 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( } else if sLen > operation.maxStack { return nil, &ErrStackOverflow{stackLen: sLen, limit: operation.maxStack} } - if !contract.UseGas(cost) { + if !contract.UseGas(cost, in.evm.Config.Tracer) { return nil, ErrOutOfGas } if operation.dynamicGas != nil { @@ -211,7 +211,7 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( var dynamicCost uint64 dynamicCost, err = operation.dynamicGas(in.evm, contract, stack, mem, memorySize) cost += dynamicCost // for tracing - if err != nil || !contract.UseGas(dynamicCost) { + if err != nil || !contract.UseGas(dynamicCost, in.evm.Config.Tracer) { return nil, ErrOutOfGas } // Do tracing before memory expansion diff --git a/core/vm/logger.go b/core/vm/logger.go index d4b91ccc71..c4955d0c27 100644 --- a/core/vm/logger.go +++ b/core/vm/logger.go @@ -41,4 +41,6 @@ type EVMLogger interface { CaptureState(pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, rData []byte, depth int, err error) CaptureFault(pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, depth int, err error) CaptureKeccakPreimage(hash common.Hash, data []byte) + // Misc + OnGasConsumed(gas, amount uint64) } diff --git a/core/vm/operations_acl.go b/core/vm/operations_acl.go index 551e1f5f11..92520972c4 100644 --- a/core/vm/operations_acl.go +++ b/core/vm/operations_acl.go @@ -169,7 +169,7 @@ func makeCallVariantGasCallEIP2929(oldCalculator gasFunc) gasFunc { evm.StateDB.AddAddressToAccessList(addr) // Charge the remaining difference here already, to correctly calculate available // gas for call - if !contract.UseGas(coldCost) { + if !contract.UseGas(coldCost, evm.Config.Tracer) { return 0, ErrOutOfGas } } diff --git a/eth/tracers/js/goja.go b/eth/tracers/js/goja.go index ac8822e98f..d6ac946fea 100644 --- a/eth/tracers/js/goja.go +++ b/eth/tracers/js/goja.go @@ -95,6 +95,8 @@ func fromBuf(vm *goja.Runtime, bufType goja.Value, buf goja.Value, allowString b // jsTracer is an implementation of the Tracer interface which evaluates // JS functions on the relevant EVM hooks. It uses Goja as its JS engine. type jsTracer struct { + tracers.NoopTracer + vm *goja.Runtime env *vm.EVM toBig toBigFn // Converts a hex string into a JS bigint @@ -286,9 +288,6 @@ func (t *jsTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope } } -// CaptureKeccakPreimage is called during the KECCAK256 opcode. -func (t *jsTracer) CaptureKeccakPreimage(hash common.Hash, data []byte) {} - // CaptureEnd is called after the call finishes to finalize the tracing. func (t *jsTracer) CaptureEnd(output []byte, gasUsed uint64, err error) { t.ctx["output"] = t.vm.ToValue(output) diff --git a/eth/tracers/logger/access_list_tracer.go b/eth/tracers/logger/access_list_tracer.go index c6a7bfddbe..316c104278 100644 --- a/eth/tracers/logger/access_list_tracer.go +++ b/eth/tracers/logger/access_list_tracer.go @@ -163,6 +163,8 @@ func (*AccessListTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, func (*AccessListTracer) CaptureKeccakPreimage(hash common.Hash, data []byte) {} +func (*AccessListTracer) OnGasConsumed(gas, amount uint64) {} + func (*AccessListTracer) CaptureEnd(output []byte, gasUsed uint64, err error) {} func (*AccessListTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { diff --git a/eth/tracers/logger/logger.go b/eth/tracers/logger/logger.go index 0c9ce9aec1..572e4e4c4b 100644 --- a/eth/tracers/logger/logger.go +++ b/eth/tracers/logger/logger.go @@ -220,6 +220,8 @@ func (l *StructLogger) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, s // CaptureKeccakPreimage is called during the KECCAK256 opcode. func (l *StructLogger) CaptureKeccakPreimage(hash common.Hash, data []byte) {} +func (l *StructLogger) OnGasConsumed(gas, amount uint64) {} + // CaptureEnd is called after the call finishes to finalize the tracing. func (l *StructLogger) CaptureEnd(output []byte, gasUsed uint64, err error) { l.output = output @@ -389,6 +391,8 @@ func (t *mdLogger) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope func (t *mdLogger) CaptureKeccakPreimage(hash common.Hash, data []byte) {} +func (t *mdLogger) OnGasConsumed(gas, amount uint64) {} + func (t *mdLogger) CaptureEnd(output []byte, gasUsed uint64, err error) { fmt.Fprintf(t.out, "\nOutput: `%#x`\nConsumed gas: `%d`\nError: `%v`\n", output, gasUsed, err) diff --git a/eth/tracers/logger/logger_json.go b/eth/tracers/logger/logger_json.go index 91def6f389..7237865ee3 100644 --- a/eth/tracers/logger/logger_json.go +++ b/eth/tracers/logger/logger_json.go @@ -81,6 +81,8 @@ func (l *JSONLogger) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, sco // CaptureKeccakPreimage is called during the KECCAK256 opcode. func (l *JSONLogger) CaptureKeccakPreimage(hash common.Hash, data []byte) {} +func (l *JSONLogger) OnGasConsumed(gas, amount uint64) {} + // CaptureEnd is triggered at end of execution. func (l *JSONLogger) CaptureEnd(output []byte, gasUsed uint64, err error) { type endLog struct { diff --git a/eth/tracers/native/4byte.go b/eth/tracers/native/4byte.go index 5a2c4f9111..0699ac887d 100644 --- a/eth/tracers/native/4byte.go +++ b/eth/tracers/native/4byte.go @@ -46,7 +46,7 @@ func init() { // 0xc281d19e-0: 1 // } type fourByteTracer struct { - noopTracer + tracers.NoopTracer ids map[string]int // ids aggregates the 4byte ids found interrupt atomic.Bool // Atomic flag to signal execution interruption reason error // Textual reason for the interruption diff --git a/eth/tracers/native/call.go b/eth/tracers/native/call.go index 4ac03e512f..8e084ad37e 100644 --- a/eth/tracers/native/call.go +++ b/eth/tracers/native/call.go @@ -98,7 +98,7 @@ type callFrameMarshaling struct { } type callTracer struct { - noopTracer + tracers.NoopTracer callstack []callFrame config callTracerConfig gasLimit uint64 diff --git a/eth/tracers/native/call_flat.go b/eth/tracers/native/call_flat.go index ca5d2e6aec..dedc01cf26 100644 --- a/eth/tracers/native/call_flat.go +++ b/eth/tracers/native/call_flat.go @@ -108,7 +108,7 @@ type flatCallResultMarshaling struct { // flatCallTracer reports call frame information of a tx in a flat format, i.e. // as opposed to the nested format of `callTracer`. type flatCallTracer struct { - noopTracer + tracers.NoopTracer tracer *callTracer config flatCallTracerConfig ctx *tracers.Context // Holds tracer context data diff --git a/eth/tracers/native/mux.go b/eth/tracers/native/mux.go index 16ae47e5fa..8680fdda3b 100644 --- a/eth/tracers/native/mux.go +++ b/eth/tracers/native/mux.go @@ -93,6 +93,13 @@ func (t *muxTracer) CaptureKeccakPreimage(hash common.Hash, data []byte) { } } +// CaptureGasConsumed is called when gas is consumed. +func (t *muxTracer) OnGasConsumed(gas, amount uint64) { + for _, t := range t.tracers { + t.OnGasConsumed(gas, amount) + } +} + // CaptureEnter is called when EVM enters a new scope (via call, create or selfdestruct). func (t *muxTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { for _, t := range t.tracers { diff --git a/eth/tracers/native/prestate.go b/eth/tracers/native/prestate.go index b71d5d6215..78c76166e3 100644 --- a/eth/tracers/native/prestate.go +++ b/eth/tracers/native/prestate.go @@ -54,7 +54,7 @@ type accountMarshaling struct { } type prestateTracer struct { - noopTracer + tracers.NoopTracer env *vm.EVM pre state post state diff --git a/eth/tracers/native/noop.go b/eth/tracers/noop.go similarity index 67% rename from eth/tracers/native/noop.go rename to eth/tracers/noop.go index f60f3873a0..a22ff0c56c 100644 --- a/eth/tracers/native/noop.go +++ b/eth/tracers/noop.go @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -package native +package tracers import ( "encoding/json" @@ -22,59 +22,61 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/vm" - "github.com/ethereum/go-ethereum/eth/tracers" ) func init() { - tracers.DefaultDirectory.Register("noopTracer", newNoopTracer, false) + DefaultDirectory.Register("noopTracer", newNoopTracer, false) } -// noopTracer is a go implementation of the Tracer interface which +// NoopTracer is a go implementation of the Tracer interface which // performs no action. It's mostly useful for testing purposes. -type noopTracer struct{} +type NoopTracer struct{} // newNoopTracer returns a new noop tracer. -func newNoopTracer(ctx *tracers.Context, _ json.RawMessage) (tracers.Tracer, error) { - return &noopTracer{}, nil +func newNoopTracer(ctx *Context, _ json.RawMessage) (Tracer, error) { + return &NoopTracer{}, nil } // CaptureStart implements the EVMLogger interface to initialize the tracing operation. -func (t *noopTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) { +func (t *NoopTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) { } // CaptureEnd is called after the call finishes to finalize the tracing. -func (t *noopTracer) CaptureEnd(output []byte, gasUsed uint64, err error) { +func (t *NoopTracer) CaptureEnd(output []byte, gasUsed uint64, err error) { } // CaptureState implements the EVMLogger interface to trace a single step of VM execution. -func (t *noopTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) { +func (t *NoopTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) { } // CaptureFault implements the EVMLogger interface to trace an execution fault. -func (t *noopTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, _ *vm.ScopeContext, depth int, err error) { +func (t *NoopTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, _ *vm.ScopeContext, depth int, err error) { } // CaptureKeccakPreimage is called during the KECCAK256 opcode. -func (t *noopTracer) CaptureKeccakPreimage(hash common.Hash, data []byte) {} +func (t *NoopTracer) CaptureKeccakPreimage(hash common.Hash, data []byte) {} + +// OnGasConsumed is called when gas is consumed. +func (t *NoopTracer) OnGasConsumed(gas, amount uint64) {} // CaptureEnter is called when EVM enters a new scope (via call, create or selfdestruct). -func (t *noopTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { +func (t *NoopTracer) CaptureEnter(typ vm.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) { +func (t *NoopTracer) CaptureExit(output []byte, gasUsed uint64, err error) { } -func (*noopTracer) CaptureTxStart(gasLimit uint64) {} +func (*NoopTracer) CaptureTxStart(gasLimit uint64) {} -func (*noopTracer) CaptureTxEnd(restGas uint64) {} +func (*NoopTracer) CaptureTxEnd(restGas uint64) {} // GetResult returns an empty json object. -func (t *noopTracer) GetResult() (json.RawMessage, error) { +func (t *NoopTracer) GetResult() (json.RawMessage, error) { return json.RawMessage(`{}`), nil } // Stop terminates execution of the tracer at the first opportune moment. -func (t *noopTracer) Stop(err error) { +func (t *NoopTracer) Stop(err error) { } diff --git a/eth/tracers/printer.go b/eth/tracers/printer.go index e77df83365..65f749b684 100644 --- a/eth/tracers/printer.go +++ b/eth/tracers/printer.go @@ -93,3 +93,7 @@ func (p *Printer) OnLog(l *types.Log) { func (p *Printer) OnNewAccount(a common.Address) { fmt.Printf("OnNewAccount: a=%v\n", a) } + +func (p *Printer) OnGasConsumed(gas, amount uint64) { + fmt.Printf("OnGasConsumed: gas=%v, amount=%v\n", gas, amount) +}