From 0bc25c35b427d490230a46b0b964f76a80de6604 Mon Sep 17 00:00:00 2001 From: Daniel Liu <139250065@qq.com> Date: Mon, 8 Sep 2025 17:53:21 +0800 Subject: [PATCH] core,eth: implement tx-level hooks for tracers #24510 (#1277) * 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 Co-authored-by: Sina Mahmoodi <1591639+s1na@users.noreply.github.com> --- core/state_transition.go | 13 ++++++++---- 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/contract.go | 4 ++++ eth/tracers/native/noop.go | 5 +++++ eth/tracers/native/prestate.go | 26 ++++++++--------------- 12 files changed, 79 insertions(+), 34 deletions(-) diff --git a/core/state_transition.go b/core/state_transition.go index 854ce14cec..6d9aa2b75f 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -329,17 +329,22 @@ func (st *StateTransition) TransitionDb(owner common.Address) (*ExecutionResult, return nil, err } + if tracer := st.evm.Config.Tracer; tracer != nil { + st.evm.Config.Tracer.CaptureTxStart(st.initialGas) + defer func() { + st.evm.Config.Tracer.CaptureTxEnd(st.gas) + }() + } + var ( msg = st.msg sender = st.from() // err checked in preCheck rules = st.evm.ChainConfig().Rules(st.evm.Context.BlockNumber) - homestead = rules.IsHomestead - eip3529 = rules.IsEIP1559 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, rules.IsEIP1559) + gas, err := IntrinsicGas(st.data, st.msg.AccessList(), contractCreation, rules.IsHomestead, rules.IsEIP1559) if err != nil { return nil, err } @@ -378,7 +383,7 @@ func (st *StateTransition) TransitionDb(owner common.Address) (*ExecutionResult, st.state.SetNonce(sender.Address(), st.state.GetNonce(sender.Address())+1) ret, st.gas, vmerr = st.evm.Call(sender, st.to().Address(), st.data, st.gas, st.value) } - if !eip3529 { + if !rules.IsEIP1559 { // Before EIP-3529: refunds were capped to gasUsed / 2 st.refundGas(params.RefundQuotient) } else { diff --git a/core/vm/logger.go b/core/vm/logger.go index f6d3e8e703..367ca543c7 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 38fcd40aa7..7f7c8a3b1d 100644 --- a/eth/tracers/js/tracer.go +++ b/eth/tracers/js/tracer.go @@ -30,7 +30,6 @@ import ( "github.com/XinFinOrg/XDPoSChain/common" "github.com/XinFinOrg/XDPoSChain/common/hexutil" - "github.com/XinFinOrg/XDPoSChain/core" "github.com/XinFinOrg/XDPoSChain/core/vm" "github.com/XinFinOrg/XDPoSChain/crypto" tracers2 "github.com/XinFinOrg/XDPoSChain/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) jst.activePrecompiles = vm.ActivePrecompiles(rules) - // Compute intrinsic gas - isHomestead := env.ChainConfig().IsHomestead(env.Context.BlockNumber) - isEIP1559 := env.ChainConfig().IsEIP1559(env.Context.BlockNumber) - intrinsicGas, err := core.IntrinsicGas(input, nil, jst.ctx["type"] == "CREATE", isHomestead, isEIP1559) - 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 04c5dadea3..09581f86ec 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.ctx, vmctx.txContext, &dummyStatedb{}, nil, chaincfg, vm.Config{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 e005466498..526efcf7ad 100644 --- a/eth/tracers/logger/access_list_tracer.go +++ b/eth/tracers/logger/access_list_tracer.go @@ -169,6 +169,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 360eafd610..165d6a96a7 100644 --- a/eth/tracers/logger/logger.go +++ b/eth/tracers/logger/logger.go @@ -224,6 +224,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 } @@ -348,3 +352,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 d53dd355f5..2a739c9295 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 aa7f462883..97282198db 100644 --- a/eth/tracers/native/4byte.go +++ b/eth/tracers/native/4byte.go @@ -132,6 +132,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 00057fbc0c..c394458419 100644 --- a/eth/tracers/native/call.go +++ b/eth/tracers/native/call.go @@ -158,6 +158,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/contract.go b/eth/tracers/native/contract.go index 62906a811b..18b5e8ae40 100644 --- a/eth/tracers/native/contract.go +++ b/eth/tracers/native/contract.go @@ -60,6 +60,10 @@ func (t *contractTracer) CaptureStart(env *vm.EVM, from common.Address, to commo func (t *contractTracer) CaptureEnd(output []byte, gasUsed uint64, _ time.Duration, err error) { } +func (*contractTracer) CaptureTxStart(gasLimit uint64) {} + +func (*contractTracer) CaptureTxEnd(restGas uint64) {} + func (t *contractTracer) CaptureState(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 { diff --git a/eth/tracers/native/noop.go b/eth/tracers/native/noop.go index df7a1df08d..6414aeb8f9 100644 --- a/eth/tracers/native/noop.go +++ b/eth/tracers/native/noop.go @@ -54,6 +54,11 @@ 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 eca3998d2a..6a40e2b6b5 100644 --- a/eth/tracers/native/prestate.go +++ b/eth/tracers/native/prestate.go @@ -24,7 +24,6 @@ import ( "github.com/XinFinOrg/XDPoSChain/common" "github.com/XinFinOrg/XDPoSChain/common/hexutil" - "github.com/XinFinOrg/XDPoSChain/core" "github.com/XinFinOrg/XDPoSChain/core/vm" "github.com/XinFinOrg/XDPoSChain/crypto" "github.com/XinFinOrg/XDPoSChain/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) {