From 3fd16af5a91e232f1b32c85db79c45d5563ae14f Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi <1591639+s1na@users.noreply.github.com> Date: Thu, 31 Mar 2022 11:51:44 +0200 Subject: [PATCH] core,eth: implement tx-level hooks for tracers (#24510) * core,eth: add empty tx logger hooks * core,eth: add initial and remaining gas to tx hooks * store tx gasLimit in js tracer * use gasLimit to compute intrinsic cost for js tracer * re-use rules in transitiondb * rm logs * rm logs * Mv some fields from Start to TxStart * simplify sender lookup in prestate tracer * mv env to TxStart * Revert "mv env to TxStart" This reverts commit 656939634b9aff19f55a1cd167345faf8b1ec310. * Revert "simplify sender lookup in prestate tracer" This reverts commit ab65bce48007cab99e68232e7aac2fe008338d50. * Revert "Mv some fields from Start to TxStart" This reverts commit aa50d3d9b2559addc80df966111ef5fb5d0c1b6b. * fix intrinsic gas for prestate tracer * add comments * refactor * fix test case * simplify consumedGas calc in prestate tracer --- core/state_transition.go | 28 +++++++++++++++--------- core/vm/logger.go | 10 +++++++-- eth/tracers/js/tracer.go | 27 +++++++++++++---------- eth/tracers/js/tracer_test.go | 4 ++++ eth/tracers/logger/access_list_tracer.go | 4 ++++ eth/tracers/logger/logger.go | 8 +++++++ eth/tracers/logger/logger_json.go | 4 ++++ eth/tracers/native/4byte.go | 4 ++++ eth/tracers/native/call.go | 4 ++++ eth/tracers/native/noop.go | 4 ++++ eth/tracers/native/prestate.go | 26 ++++++++-------------- 11 files changed, 83 insertions(+), 40 deletions(-) diff --git a/core/state_transition.go b/core/state_transition.go index 05d5633075..3b5f81b166 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -287,15 +287,23 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { if err := st.preCheck(); err != nil { return nil, err } - msg := st.msg - sender := vm.AccountRef(msg.From()) - homestead := st.evm.ChainConfig().IsHomestead(st.evm.Context.BlockNumber) - istanbul := st.evm.ChainConfig().IsIstanbul(st.evm.Context.BlockNumber) - london := st.evm.ChainConfig().IsLondon(st.evm.Context.BlockNumber) - contractCreation := msg.To() == nil + + if st.evm.Config.Debug { + st.evm.Config.Tracer.CaptureTxStart(st.initialGas) + defer func() { + st.evm.Config.Tracer.CaptureTxEnd(st.gas) + }() + } + + var ( + msg = st.msg + sender = vm.AccountRef(msg.From()) + rules = st.evm.ChainConfig().Rules(st.evm.Context.BlockNumber, st.evm.Context.Random != nil) + contractCreation = msg.To() == nil + ) // Check clauses 4-5, subtract intrinsic gas if everything is correct - gas, err := IntrinsicGas(st.data, st.msg.AccessList(), contractCreation, homestead, istanbul) + gas, err := IntrinsicGas(st.data, st.msg.AccessList(), contractCreation, rules.IsHomestead, rules.IsIstanbul) if err != nil { return nil, err } @@ -310,7 +318,7 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { } // Set up the initial access list. - if rules := st.evm.ChainConfig().Rules(st.evm.Context.BlockNumber, st.evm.Context.Random != nil); rules.IsBerlin { + if rules.IsBerlin { st.state.PrepareAccessList(msg.From(), msg.To(), vm.ActivePrecompiles(rules), msg.AccessList()) } var ( @@ -325,7 +333,7 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { ret, st.gas, vmerr = st.evm.Call(sender, st.to(), st.data, st.gas, st.value) } - if !london { + if !rules.IsLondon { // Before EIP-3529: refunds were capped to gasUsed / 2 st.refundGas(params.RefundQuotient) } else { @@ -333,7 +341,7 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { st.refundGas(params.RefundQuotientEIP3529) } effectiveTip := st.gasPrice - if london { + if rules.IsLondon { effectiveTip = cmath.BigMin(st.gasTipCap, new(big.Int).Sub(st.gasFeeCap, st.evm.Context.BaseFee)) } st.state.AddBalance(st.evm.Context.Coinbase, new(big.Int).Mul(new(big.Int).SetUint64(st.gasUsed()), effectiveTip)) diff --git a/core/vm/logger.go b/core/vm/logger.go index 3af5aec199..1067947d47 100644 --- a/core/vm/logger.go +++ b/core/vm/logger.go @@ -29,10 +29,16 @@ import ( // Note that reference types are actual VM data structures; make copies // if you need to retain them beyond the current call. type EVMLogger interface { + // Transaction level + CaptureTxStart(gasLimit uint64) + CaptureTxEnd(restGas uint64) + // Top call frame CaptureStart(env *EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) - CaptureState(pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, rData []byte, depth int, err error) + CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) + // Rest of call frames CaptureEnter(typ OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) CaptureExit(output []byte, gasUsed uint64, err error) + // Opcode level + 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) - CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) } diff --git a/eth/tracers/js/tracer.go b/eth/tracers/js/tracer.go index 30c5c2cf14..a71d2920ff 100644 --- a/eth/tracers/js/tracer.go +++ b/eth/tracers/js/tracer.go @@ -30,7 +30,6 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" tracers2 "github.com/ethereum/go-ethereum/eth/tracers" @@ -419,6 +418,7 @@ type jsTracer struct { activePrecompiles []common.Address // Updated on CaptureStart based on given rules traceSteps bool // When true, will invoke step() on each opcode traceCallFrames bool // When true, will invoke enter() and exit() js funcs + gasLimit uint64 // Amount of gas bought for the whole tx } // New instantiates a new tracer instance. code specifies a Javascript snippet, @@ -679,7 +679,18 @@ func wrapError(context string, err error) error { return fmt.Errorf("%v in server-side tracer function '%v'", err, context) } -// CaptureStart implements the Tracer interface to initialize the tracing operation. +// CaptureTxStart implements the Tracer interface and is invoked at the beginning of +// transaction processing. +func (jst *jsTracer) CaptureTxStart(gasLimit uint64) { + jst.gasLimit = gasLimit +} + +// CaptureTxStart implements the Tracer interface and is invoked at the end of +// transaction processing. +func (*jsTracer) CaptureTxEnd(restGas uint64) {} + +// CaptureStart implements the Tracer interface and is invoked before executing the +// top-level call frame of a transaction. func (jst *jsTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) { jst.env = env jst.ctx["type"] = "CALL" @@ -700,14 +711,8 @@ func (jst *jsTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Ad rules := env.ChainConfig().Rules(env.Context.BlockNumber, env.Context.Random != nil) jst.activePrecompiles = vm.ActivePrecompiles(rules) - // Compute intrinsic gas - isHomestead := env.ChainConfig().IsHomestead(env.Context.BlockNumber) - isIstanbul := env.ChainConfig().IsIstanbul(env.Context.BlockNumber) - intrinsicGas, err := core.IntrinsicGas(input, nil, jst.ctx["type"] == "CREATE", isHomestead, isIstanbul) - if err != nil { - return - } - jst.ctx["intrinsicGas"] = intrinsicGas + // Intrinsic costs are the only things reduced from initial gas to this point + jst.ctx["intrinsicGas"] = jst.gasLimit - gas } // CaptureState implements the Tracer interface to trace a single step of VM execution. @@ -760,7 +765,7 @@ func (jst *jsTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, sco } } -// CaptureEnd is called after the call finishes to finalize the tracing. +// CaptureEnd is called after the top-level call finishes. func (jst *jsTracer) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) { jst.ctx["output"] = output jst.ctx["time"] = t.String() diff --git a/eth/tracers/js/tracer_test.go b/eth/tracers/js/tracer_test.go index cf0a4aa828..9f4d6ddd4d 100644 --- a/eth/tracers/js/tracer_test.go +++ b/eth/tracers/js/tracer_test.go @@ -62,15 +62,19 @@ func testCtx() *vmContext { func runTrace(tracer tracers.Tracer, vmctx *vmContext, chaincfg *params.ChainConfig) (json.RawMessage, error) { var ( env = vm.NewEVM(vmctx.blockCtx, vmctx.txCtx, &dummyStatedb{}, chaincfg, vm.Config{Debug: true, Tracer: tracer}) + gasLimit uint64 = 31000 startGas uint64 = 10000 value = big.NewInt(0) contract = vm.NewContract(account{}, account{}, value, startGas) ) contract.Code = []byte{byte(vm.PUSH1), 0x1, byte(vm.PUSH1), 0x1, 0x0} + tracer.CaptureTxStart(gasLimit) tracer.CaptureStart(env, contract.Caller(), contract.Address(), false, []byte{}, startGas, value) ret, err := env.Interpreter().Run(contract, []byte{}, false) tracer.CaptureEnd(ret, startGas-contract.Gas, 1, err) + // Rest gas assumes no refund + tracer.CaptureTxEnd(startGas - contract.Gas) if err != nil { return nil, err } diff --git a/eth/tracers/logger/access_list_tracer.go b/eth/tracers/logger/access_list_tracer.go index 181fc47acb..37f71a05ab 100644 --- a/eth/tracers/logger/access_list_tracer.go +++ b/eth/tracers/logger/access_list_tracer.go @@ -174,6 +174,10 @@ func (*AccessListTracer) CaptureEnter(typ vm.OpCode, from common.Address, to com func (*AccessListTracer) CaptureExit(output []byte, gasUsed uint64, err error) {} +func (*AccessListTracer) CaptureTxStart(gasLimit uint64) {} + +func (*AccessListTracer) CaptureTxEnd(restGas uint64) {} + // 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 d0c7bff893..fe15c97ef7 100644 --- a/eth/tracers/logger/logger.go +++ b/eth/tracers/logger/logger.go @@ -223,6 +223,10 @@ func (l *StructLogger) CaptureEnter(typ vm.OpCode, from common.Address, to commo func (l *StructLogger) CaptureExit(output []byte, gasUsed uint64, err error) {} +func (*StructLogger) CaptureTxStart(gasLimit uint64) {} + +func (*StructLogger) CaptureTxEnd(restGas uint64) {} + // StructLogs returns the captured log entries. func (l *StructLogger) StructLogs() []StructLog { return l.logs } @@ -347,3 +351,7 @@ func (t *mdLogger) CaptureEnter(typ vm.OpCode, from common.Address, to common.Ad } func (t *mdLogger) CaptureExit(output []byte, gasUsed uint64, err error) {} + +func (*mdLogger) CaptureTxStart(gasLimit uint64) {} + +func (*mdLogger) CaptureTxEnd(restGas uint64) {} diff --git a/eth/tracers/logger/logger_json.go b/eth/tracers/logger/logger_json.go index 4a7abacba2..72ad0199c9 100644 --- a/eth/tracers/logger/logger_json.go +++ b/eth/tracers/logger/logger_json.go @@ -98,3 +98,7 @@ func (l *JSONLogger) CaptureEnter(typ vm.OpCode, from common.Address, to common. } func (l *JSONLogger) CaptureExit(output []byte, gasUsed uint64, err error) {} + +func (l *JSONLogger) CaptureTxStart(gasLimit uint64) {} + +func (l *JSONLogger) CaptureTxEnd(restGas uint64) {} diff --git a/eth/tracers/native/4byte.go b/eth/tracers/native/4byte.go index ad1d89071c..5d4057689c 100644 --- a/eth/tracers/native/4byte.go +++ b/eth/tracers/native/4byte.go @@ -131,6 +131,10 @@ func (t *fourByteTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, func (t *fourByteTracer) CaptureEnd(output []byte, gasUsed uint64, _ time.Duration, err error) { } +func (*fourByteTracer) CaptureTxStart(gasLimit uint64) {} + +func (*fourByteTracer) CaptureTxEnd(restGas uint64) {} + // GetResult returns the json-encoded nested list of call traces, and any // error arising from the encoding or forceful termination (via `Stop`). func (t *fourByteTracer) GetResult() (json.RawMessage, error) { diff --git a/eth/tracers/native/call.go b/eth/tracers/native/call.go index 08dc76aa61..843c1a18f4 100644 --- a/eth/tracers/native/call.go +++ b/eth/tracers/native/call.go @@ -142,6 +142,10 @@ func (t *callTracer) CaptureExit(output []byte, gasUsed uint64, err error) { t.callstack[size-1].Calls = append(t.callstack[size-1].Calls, call) } +func (*callTracer) CaptureTxStart(gasLimit uint64) {} + +func (*callTracer) CaptureTxEnd(restGas uint64) {} + // 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) { diff --git a/eth/tracers/native/noop.go b/eth/tracers/native/noop.go index 15b7dbccb7..566a8a652f 100644 --- a/eth/tracers/native/noop.go +++ b/eth/tracers/native/noop.go @@ -64,6 +64,10 @@ func (t *noopTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common. func (t *noopTracer) CaptureExit(output []byte, gasUsed uint64, err error) { } +func (*noopTracer) CaptureTxStart(gasLimit uint64) {} + +func (*noopTracer) CaptureTxEnd(restGas uint64) {} + // GetResult returns an empty json object. func (t *noopTracer) GetResult() (json.RawMessage, error) { return json.RawMessage(`{}`), nil diff --git a/eth/tracers/native/prestate.go b/eth/tracers/native/prestate.go index 598663ac81..c7b00e5da8 100644 --- a/eth/tracers/native/prestate.go +++ b/eth/tracers/native/prestate.go @@ -24,7 +24,6 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/eth/tracers" @@ -47,6 +46,7 @@ type prestateTracer struct { prestate prestate create bool to common.Address + gasLimit uint64 // Amount of gas bought for the whole tx interrupt uint32 // Atomic flag to signal execution interruption reason error // Textual reason for the interruption } @@ -63,14 +63,6 @@ func (t *prestateTracer) CaptureStart(env *vm.EVM, from common.Address, to commo t.create = create t.to = to - // Compute intrinsic gas - isHomestead := env.ChainConfig().IsHomestead(env.Context.BlockNumber) - isIstanbul := env.ChainConfig().IsIstanbul(env.Context.BlockNumber) - intrinsicGas, err := core.IntrinsicGas(input, nil, create, isHomestead, isIstanbul) - if err != nil { - return - } - t.lookupAccount(from) t.lookupAccount(to) @@ -79,17 +71,11 @@ func (t *prestateTracer) CaptureStart(env *vm.EVM, from common.Address, to commo toBal = new(big.Int).Sub(toBal, value) t.prestate[to].Balance = hexutil.EncodeBig(toBal) - // The sender balance is after reducing: value, gasLimit, intrinsicGas. + // The sender balance is after reducing: value and gasLimit. // We need to re-add them to get the pre-tx balance. fromBal := hexutil.MustDecodeBig(t.prestate[from].Balance) gasPrice := env.TxContext.GasPrice - consumedGas := new(big.Int).Mul( - gasPrice, - new(big.Int).Add( - new(big.Int).SetUint64(intrinsicGas), - new(big.Int).SetUint64(gas), - ), - ) + consumedGas := new(big.Int).Mul(gasPrice, new(big.Int).SetUint64(t.gasLimit)) fromBal.Add(fromBal, new(big.Int).Add(value, consumedGas)) t.prestate[from].Balance = hexutil.EncodeBig(fromBal) t.prestate[from].Nonce-- @@ -145,6 +131,12 @@ func (t *prestateTracer) CaptureEnter(typ vm.OpCode, from common.Address, to com func (t *prestateTracer) CaptureExit(output []byte, gasUsed uint64, err error) { } +func (t *prestateTracer) CaptureTxStart(gasLimit uint64) { + t.gasLimit = gasLimit +} + +func (t *prestateTracer) CaptureTxEnd(restGas uint64) {} + // GetResult returns the json-encoded nested list of call traces, and any // error arising from the encoding or forceful termination (via `Stop`). func (t *prestateTracer) GetResult() (json.RawMessage, error) {