core: introduce GasChangeHook v2 (#34946)
Some checks are pending
/ Linux Build (push) Waiting to run
/ Linux Build (arm) (push) Waiting to run
/ Keeper Build (push) Waiting to run
/ Windows Build (push) Waiting to run
/ Docker Image (push) Waiting to run

This PR introduces OnGasChangeV2 tracing hook, as the pre-requisite for landing
EIP-8037.

---------

Co-authored-by: Sina M <1591639+s1na@users.noreply.github.com>
This commit is contained in:
rjl493456442 2026-05-13 16:53:47 +08:00 committed by GitHub
parent 21c5a287f9
commit 0494cdce23
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 164 additions and 63 deletions

View file

@ -420,8 +420,10 @@ func (st *stateTransition) buyGas() error {
return err return err
} }
if st.evm.Config.Tracer != nil && st.evm.Config.Tracer.OnGasChange != nil { if st.evm.Config.Tracer.HasGasHook() {
st.evm.Config.Tracer.OnGasChange(0, st.msg.GasLimit, tracing.GasChangeTxInitialBalance) empty := vm.GasBudget{}
initial := vm.NewGasBudget(st.msg.GasLimit)
st.evm.Config.Tracer.EmitGasChange(empty.AsTracing(), initial.AsTracing(), tracing.GasChangeTxInitialBalance)
} }
st.gasRemaining = vm.NewGasBudget(st.msg.GasLimit) st.gasRemaining = vm.NewGasBudget(st.msg.GasLimit)
st.initialBudget = st.gasRemaining.Copy() st.initialBudget = st.gasRemaining.Copy()
@ -566,8 +568,8 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
if !sufficient { if !sufficient {
return nil, fmt.Errorf("%w: have %d, want %d", ErrIntrinsicGas, st.gasRemaining.RegularGas, cost.RegularGas) return nil, fmt.Errorf("%w: have %d, want %d", ErrIntrinsicGas, st.gasRemaining.RegularGas, cost.RegularGas)
} }
if t := st.evm.Config.Tracer; t != nil && t.OnGasChange != nil { if st.evm.Config.Tracer.HasGasHook() {
t.OnGasChange(prior, st.gasRemaining.RegularGas, tracing.GasChangeTxIntrinsicGas) st.evm.Config.Tracer.EmitGasChange(prior.AsTracing(), st.gasRemaining.AsTracing(), tracing.GasChangeTxIntrinsicGas)
} }
// Gas limit suffices for the floor data cost (EIP-7623) // Gas limit suffices for the floor data cost (EIP-7623)
if rules.IsPrague { if rules.IsPrague {
@ -651,8 +653,8 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
// After EIP-7623: Data-heavy transactions pay the floor gas. // After EIP-7623: Data-heavy transactions pay the floor gas.
if used := st.gasUsed(); used < floorDataGas { if used := st.gasUsed(); used < floorDataGas {
prior, _ := st.gasRemaining.Charge(vm.GasCosts{RegularGas: floorDataGas - used}) prior, _ := st.gasRemaining.Charge(vm.GasCosts{RegularGas: floorDataGas - used})
if t := st.evm.Config.Tracer; t != nil && t.OnGasChange != nil { if st.evm.Config.Tracer.HasGasHook() {
t.OnGasChange(prior, st.gasRemaining.RegularGas, tracing.GasChangeTxDataFloor) st.evm.Config.Tracer.EmitGasChange(prior.AsTracing(), st.gasRemaining.AsTracing(), tracing.GasChangeTxDataFloor)
} }
} }
if peakGasUsed < floorDataGas { if peakGasUsed < floorDataGas {
@ -780,8 +782,11 @@ func (st *stateTransition) calcRefund() vm.GasBudget {
if refund > st.state.GetRefund() { if refund > st.state.GetRefund() {
refund = st.state.GetRefund() refund = st.state.GetRefund()
} }
if st.evm.Config.Tracer != nil && st.evm.Config.Tracer.OnGasChange != nil && refund > 0 { if refund > 0 && st.evm.Config.Tracer.HasGasHook() {
st.evm.Config.Tracer.OnGasChange(st.gasRemaining.RegularGas, st.gasRemaining.RegularGas+refund, tracing.GasChangeTxRefunds) after := st.gasRemaining
after.RegularGas += refund
st.evm.Config.Tracer.EmitGasChange(st.gasRemaining.AsTracing(), after.AsTracing(), tracing.GasChangeTxRefunds)
} }
return vm.NewGasBudget(refund) return vm.NewGasBudget(refund)
} }
@ -793,8 +798,10 @@ func (st *stateTransition) returnGas() {
remaining.Mul(remaining, st.msg.GasPrice) remaining.Mul(remaining, st.msg.GasPrice)
st.state.AddBalance(st.msg.From, remaining, tracing.BalanceIncreaseGasReturn) st.state.AddBalance(st.msg.From, remaining, tracing.BalanceIncreaseGasReturn)
if st.evm.Config.Tracer != nil && st.evm.Config.Tracer.OnGasChange != nil && st.gasRemaining.RegularGas > 0 { if st.gasRemaining.RegularGas > 0 && st.evm.Config.Tracer.HasGasHook() {
st.evm.Config.Tracer.OnGasChange(st.gasRemaining.RegularGas, 0, tracing.GasChangeTxLeftOverReturned) after := st.gasRemaining
after.RegularGas = 0
st.evm.Config.Tracer.EmitGasChange(st.gasRemaining.AsTracing(), after.AsTracing(), tracing.GasChangeTxLeftOverReturned)
} }
} }

View file

@ -164,10 +164,36 @@ type (
// FaultHook is invoked when an error occurs during the execution of an opcode. // FaultHook is invoked when an error occurs during the execution of an opcode.
FaultHook = func(pc uint64, op byte, gas, cost uint64, scope OpContext, depth int, err error) FaultHook = func(pc uint64, op byte, gas, cost uint64, scope OpContext, depth int, err error)
// GasChangeHook is invoked when the gas changes. // GasChangeHook reports changes to the regular execution gas. Tracers
// that don't need visibility into the state-access gas dimension
// introduced by EIP-8037 (Amsterdam) can implement only this hook; it
// will continue to fire across the Amsterdam fork unchanged.
//
// If both this hook and GasChangeHookV2 are implemented on the same
// tracer, only V2 will be invoked. Implement exactly one to avoid
// double-counting.
GasChangeHook = func(old, new uint64, reason GasChangeReason) GasChangeHook = func(old, new uint64, reason GasChangeReason)
// TODO(sina, rjl), please add GasChangeV2Hook by landing the multi-dimensional gas // GasChangeHookV2 is invoked when any gas dimension changes. It is the
// multi-dimensional successor to GasChangeHook, exposing the state-access
// gas dimension introduced by EIP-8037 (Amsterdam) alongside the regular
// dimension.
//
// Compatibility:
// - Post-Amsterdam: fires for changes to either the regular or the
// state-access dimension. The non-changing dimension is passed through
// unchanged in both `old` and `new` so consumers always observe the
// complete gas vector.
// - Pre-Amsterdam: no state-access gas events occur, so the State field
// of both `old` and `new` is always zero. Tracers that register only
// V2 still receive every regular-gas change as Gas{State: 0} and
// behave identically to a V1 tracer; there is no pre-Amsterdam event
// a V2-only tracer misses.
//
// V1 and V2 coexist: when both are registered on a tracer, only V2 is
// invoked. Tracers SHOULD register at most one of the two to avoid
// double-counting.
GasChangeHookV2 = func(old, new Gas, reason GasChangeReason)
/* /*
- Chain events - - Chain events -
@ -250,13 +276,14 @@ type (
type Hooks struct { type Hooks struct {
// VM events // VM events
OnTxStart TxStartHook OnTxStart TxStartHook
OnTxEnd TxEndHook OnTxEnd TxEndHook
OnEnter EnterHook OnEnter EnterHook
OnExit ExitHook OnExit ExitHook
OnOpcode OpcodeHook OnOpcode OpcodeHook
OnFault FaultHook OnFault FaultHook
OnGasChange GasChangeHook OnGasChange GasChangeHook
OnGasChangeV2 GasChangeHookV2
// Chain events // Chain events
OnBlockchainInit BlockchainInitHook OnBlockchainInit BlockchainInitHook
OnClose CloseHook OnClose CloseHook
@ -280,6 +307,35 @@ type Hooks struct {
OnBlockHashRead BlockHashReadHook OnBlockHashRead BlockHashReadHook
} }
// HasGasHook reports whether any gas-change hook is registered. Call sites
// should use this to short-circuit before constructing the Gas / GasBudget
// arguments to EmitGasChange when tracing is off — the dispatch is otherwise
// always paid the cost of evaluating those args.
func (h *Hooks) HasGasHook() bool {
return h != nil && (h.OnGasChangeV2 != nil || h.OnGasChange != nil)
}
// EmitGasChange dispatches a gas change event to the registered hooks. If the
// multi-dimensional OnGasChangeV2 hook is set it is invoked with the full Gas
// vectors; otherwise the single-dimensional OnGasChange hook is invoked with
// the regular-gas dimension only. The call is a no-op when the receiver is
// nil, when neither hook is registered, or when the reason is GasChangeIgnored.
//
// Call sites SHOULD use this helper instead of invoking the hooks directly so
// that both variants stay consistent across the Amsterdam fork boundary.
func (h *Hooks) EmitGasChange(old, new Gas, reason GasChangeReason) {
if h == nil || reason == GasChangeIgnored {
return
}
if h.OnGasChangeV2 != nil {
h.OnGasChangeV2(old, new, reason)
return
}
if h.OnGasChange != nil {
h.OnGasChange(old.Regular, new.Regular, reason)
}
}
// BalanceChangeReason is used to indicate the reason for a balance change, useful // BalanceChangeReason is used to indicate the reason for a balance change, useful
// for tracing and reporting. // for tracing and reporting.
type BalanceChangeReason byte type BalanceChangeReason byte
@ -335,6 +391,19 @@ const (
BalanceChangeRevert BalanceChangeReason = 15 BalanceChangeRevert BalanceChangeReason = 15
) )
// Gas represents a multi-dimensional gas budget introduced by EIP-8037.
// It carries the regular execution gas and the state-access gas, which are
// metered independently from the Amsterdam fork onwards.
//
// Before Amsterdam, gas metering is single-dimensional and only the Regular
// field is meaningful; State is always zero. The struct is shaped so that
// pre-Amsterdam call sites can populate it as Gas{Regular: g} without loss
// of fidelity relative to the legacy single-uint64 hook.
type Gas struct {
Regular uint64 // Regular is the budget for ordinary execution gas.
State uint64 // State is the budget dedicated to state-access gas (zero pre-Amsterdam).
}
// GasChangeReason is used to indicate the reason for a gas change, useful // GasChangeReason is used to indicate the reason for a gas change, useful
// for tracing and reporting. // for tracing and reporting.
// //

View file

@ -131,8 +131,8 @@ func (c *Contract) UseGas(cost GasCosts, logger *tracing.Hooks, reason tracing.G
if !ok { if !ok {
return false return false
} }
if logger != nil && logger.OnGasChange != nil && reason != tracing.GasChangeIgnored { if logger.HasGasHook() && reason != tracing.GasChangeIgnored {
logger.OnGasChange(prior, c.Gas.RegularGas, reason) logger.EmitGasChange(prior.AsTracing(), c.Gas.AsTracing(), reason)
} }
return true return true
} }
@ -143,8 +143,8 @@ func (c *Contract) RefundGas(refund GasBudget, logger *tracing.Hooks, reason tra
if !changed { if !changed {
return return
} }
if logger != nil && logger.OnGasChange != nil && reason != tracing.GasChangeIgnored { if logger.HasGasHook() && reason != tracing.GasChangeIgnored {
logger.OnGasChange(prior, c.Gas.RegularGas, reason) logger.EmitGasChange(prior.AsTracing(), c.Gas.AsTracing(), reason)
} }
} }

View file

@ -269,8 +269,8 @@ func RunPrecompiledContract(stateDB StateDB, p PrecompiledContract, address comm
gas.Exhaust() gas.Exhaust()
return nil, gas, ErrOutOfGas return nil, gas, ErrOutOfGas
} }
if logger != nil && logger.OnGasChange != nil { if logger.HasGasHook() {
logger.OnGasChange(prior, gas.RegularGas, tracing.GasChangeCallPrecompiledContract) logger.EmitGasChange(prior.AsTracing(), gas.AsTracing(), tracing.GasChangeCallPrecompiledContract)
} }
// Touch the precompile for block-level accessList recording once Amsterdam // Touch the precompile for block-level accessList recording once Amsterdam
// fork is activated. // fork is activated.

View file

@ -317,8 +317,8 @@ func (evm *EVM) Call(caller common.Address, addr common.Address, input []byte, g
if err != nil { if err != nil {
evm.StateDB.RevertToSnapshot(snapshot) evm.StateDB.RevertToSnapshot(snapshot)
if err != ErrExecutionReverted { if err != ErrExecutionReverted {
if evm.Config.Tracer != nil && evm.Config.Tracer.OnGasChange != nil { if evm.Config.Tracer.HasGasHook() {
evm.Config.Tracer.OnGasChange(gas.RegularGas, 0, tracing.GasChangeCallFailedExecution) evm.Config.Tracer.EmitGasChange(gas.AsTracing(), tracing.Gas{}, tracing.GasChangeCallFailedExecution)
} }
gas.Exhaust() gas.Exhaust()
} }
@ -371,8 +371,8 @@ func (evm *EVM) CallCode(caller common.Address, addr common.Address, input []byt
if err != nil { if err != nil {
evm.StateDB.RevertToSnapshot(snapshot) evm.StateDB.RevertToSnapshot(snapshot)
if err != ErrExecutionReverted { if err != ErrExecutionReverted {
if evm.Config.Tracer != nil && evm.Config.Tracer.OnGasChange != nil { if evm.Config.Tracer.HasGasHook() {
evm.Config.Tracer.OnGasChange(gas.RegularGas, 0, tracing.GasChangeCallFailedExecution) evm.Config.Tracer.EmitGasChange(gas.AsTracing(), tracing.Gas{}, tracing.GasChangeCallFailedExecution)
} }
gas.Exhaust() gas.Exhaust()
} }
@ -415,8 +415,8 @@ func (evm *EVM) DelegateCall(originCaller common.Address, caller common.Address,
if err != nil { if err != nil {
evm.StateDB.RevertToSnapshot(snapshot) evm.StateDB.RevertToSnapshot(snapshot)
if err != ErrExecutionReverted { if err != ErrExecutionReverted {
if evm.Config.Tracer != nil && evm.Config.Tracer.OnGasChange != nil { if evm.Config.Tracer.HasGasHook() {
evm.Config.Tracer.OnGasChange(gas.RegularGas, 0, tracing.GasChangeCallFailedExecution) evm.Config.Tracer.EmitGasChange(gas.AsTracing(), tracing.Gas{}, tracing.GasChangeCallFailedExecution)
} }
gas.Exhaust() gas.Exhaust()
} }
@ -470,8 +470,8 @@ func (evm *EVM) StaticCall(caller common.Address, addr common.Address, input []b
if err != nil { if err != nil {
evm.StateDB.RevertToSnapshot(snapshot) evm.StateDB.RevertToSnapshot(snapshot)
if err != ErrExecutionReverted { if err != ErrExecutionReverted {
if evm.Config.Tracer != nil && evm.Config.Tracer.OnGasChange != nil { if evm.Config.Tracer.HasGasHook() {
evm.Config.Tracer.OnGasChange(gas.RegularGas, 0, tracing.GasChangeCallFailedExecution) evm.Config.Tracer.EmitGasChange(gas.AsTracing(), tracing.Gas{}, tracing.GasChangeCallFailedExecution)
} }
gas.Exhaust() gas.Exhaust()
} }
@ -509,8 +509,8 @@ func (evm *EVM) create(caller common.Address, code []byte, gas GasBudget, value
gas.Exhaust() gas.Exhaust()
return nil, common.Address{}, gas, ErrOutOfGas return nil, common.Address{}, gas, ErrOutOfGas
} }
if evm.Config.Tracer != nil && evm.Config.Tracer.OnGasChange != nil { if evm.Config.Tracer.HasGasHook() {
evm.Config.Tracer.OnGasChange(prior, gas.RegularGas, tracing.GasChangeWitnessContractCollisionCheck) evm.Config.Tracer.EmitGasChange(prior.AsTracing(), gas.AsTracing(), tracing.GasChangeWitnessContractCollisionCheck)
} }
} }
@ -528,8 +528,8 @@ func (evm *EVM) create(caller common.Address, code []byte, gas GasBudget, value
if evm.StateDB.GetNonce(address) != 0 || if evm.StateDB.GetNonce(address) != 0 ||
(contractHash != (common.Hash{}) && contractHash != types.EmptyCodeHash) || // non-empty code (contractHash != (common.Hash{}) && contractHash != types.EmptyCodeHash) || // non-empty code
isEIP7610RejectedAccount(evm.ChainConfig().ChainID, address, evm.chainRules.IsEIP158) { isEIP7610RejectedAccount(evm.ChainConfig().ChainID, address, evm.chainRules.IsEIP158) {
if evm.Config.Tracer != nil && evm.Config.Tracer.OnGasChange != nil { if evm.Config.Tracer.HasGasHook() {
evm.Config.Tracer.OnGasChange(gas.RegularGas, 0, tracing.GasChangeCallFailedExecution) evm.Config.Tracer.EmitGasChange(gas.AsTracing(), tracing.Gas{}, tracing.GasChangeCallFailedExecution)
} }
gas.Exhaust() gas.Exhaust()
return nil, common.Address{}, gas, ErrContractAddressCollision return nil, common.Address{}, gas, ErrContractAddressCollision
@ -558,8 +558,8 @@ func (evm *EVM) create(caller common.Address, code []byte, gas GasBudget, value
return nil, common.Address{}, gas, ErrOutOfGas return nil, common.Address{}, gas, ErrOutOfGas
} }
prior, _ := gas.Charge(GasCosts{RegularGas: consumed}) prior, _ := gas.Charge(GasCosts{RegularGas: consumed})
if evm.Config.Tracer != nil && evm.Config.Tracer.OnGasChange != nil { if evm.Config.Tracer.HasGasHook() {
evm.Config.Tracer.OnGasChange(prior, gas.RegularGas, tracing.GasChangeWitnessContractInit) evm.Config.Tracer.EmitGasChange(prior.AsTracing(), gas.AsTracing(), tracing.GasChangeWitnessContractInit)
} }
} }
evm.Context.Transfer(evm.StateDB, caller, address, value, &evm.chainRules) evm.Context.Transfer(evm.StateDB, caller, address, value, &evm.chainRules)
@ -673,15 +673,17 @@ func (evm *EVM) captureBegin(depth int, typ OpCode, from common.Address, to comm
if tracer.OnEnter != nil { if tracer.OnEnter != nil {
tracer.OnEnter(depth, byte(typ), from, to, input, startGas, value) tracer.OnEnter(depth, byte(typ), from, to, input, startGas, value)
} }
if tracer.OnGasChange != nil { if tracer.HasGasHook() {
tracer.OnGasChange(0, startGas, tracing.GasChangeCallInitialBalance) initial := NewGasBudget(startGas)
tracer.EmitGasChange(tracing.Gas{}, initial.AsTracing(), tracing.GasChangeCallInitialBalance)
} }
} }
func (evm *EVM) captureEnd(depth int, startGas uint64, leftOverGas uint64, ret []byte, err error) { func (evm *EVM) captureEnd(depth int, startGas uint64, leftOverGas uint64, ret []byte, err error) {
tracer := evm.Config.Tracer tracer := evm.Config.Tracer
if leftOverGas != 0 && tracer.OnGasChange != nil { if leftOverGas != 0 && tracer.HasGasHook() {
tracer.OnGasChange(leftOverGas, 0, tracing.GasChangeCallLeftOverReturned) leftover := NewGasBudget(leftOverGas)
tracer.EmitGasChange(leftover.AsTracing(), tracing.Gas{}, tracing.GasChangeCallLeftOverReturned)
} }
var reverted bool var reverted bool
if err != nil { if err != nil {

View file

@ -16,7 +16,11 @@
package vm package vm
import "fmt" import (
"fmt"
"github.com/ethereum/go-ethereum/core/tracing"
)
// GasCosts denotes a vector of gas costs in the // GasCosts denotes a vector of gas costs in the
// multidimensional metering paradigm. It represents the cost // multidimensional metering paradigm. It represents the cost
@ -77,21 +81,26 @@ func (g GasBudget) CanAfford(cost GasCosts) bool {
} }
// Charge deducts the given gas cost from the budget. It returns the // Charge deducts the given gas cost from the budget. It returns the
// pre-charge gas value and false if the budget does not have sufficient // pre-charge budget and false if the budget does not have sufficient
// gas to cover the cost. // gas to cover the cost.
func (g *GasBudget) Charge(cost GasCosts) (uint64, bool) { func (g *GasBudget) Charge(cost GasCosts) (GasBudget, bool) {
prior := g.RegularGas prior := *g
if prior < cost.RegularGas { if g.RegularGas < cost.RegularGas {
return prior, false return prior, false
} }
g.RegularGas -= cost.RegularGas g.RegularGas -= cost.RegularGas
return prior, true return prior, true
} }
// Refund adds the given gas budget back. It returns the pre-refund gas // Refund adds the given gas budget back. It returns the pre-refund budget
// value and whether the budget was actually changed. // and whether the budget was actually changed.
func (g *GasBudget) Refund(other GasBudget) (uint64, bool) { func (g *GasBudget) Refund(other GasBudget) (GasBudget, bool) {
prior := g.RegularGas prior := *g
g.RegularGas += other.RegularGas g.RegularGas += other.RegularGas
return prior, g.RegularGas != prior return prior, g.RegularGas != prior.RegularGas
}
// AsTracing converts the GasBudget into the tracing-facing Gas vector.
func (g GasBudget) AsTracing() tracing.Gas {
return tracing.Gas{Regular: g.RegularGas, State: g.StateGas}
} }

View file

@ -234,8 +234,12 @@ func (evm *EVM) Run(contract *Contract, input []byte, readOnly bool) (ret []byte
// Do tracing before potential memory expansion // Do tracing before potential memory expansion
if debug { if debug {
if evm.Config.Tracer.OnGasChange != nil { if evm.Config.Tracer.HasGasHook() {
evm.Config.Tracer.OnGasChange(gasCopy, gasCopy-cost, tracing.GasChangeCallOpCode) evm.Config.Tracer.EmitGasChange(
tracing.Gas{Regular: gasCopy, State: contract.Gas.StateGas},
tracing.Gas{Regular: gasCopy - cost, State: contract.Gas.StateGas},
tracing.GasChangeCallOpCode,
)
} }
if evm.Config.Tracer.OnOpcode != nil { if evm.Config.Tracer.OnOpcode != nil {
evm.Config.Tracer.OnOpcode(pc, byte(op), gasCopy, cost, callContext, evm.returnData, evm.depth, VMErrorFromErr(err)) evm.Config.Tracer.OnOpcode(pc, byte(op), gasCopy, cost, callContext, evm.returnData, evm.depth, VMErrorFromErr(err))

View file

@ -47,6 +47,7 @@ func newNoopTracer(_ json.RawMessage) (*tracing.Hooks, error) {
OnOpcode: t.OnOpcode, OnOpcode: t.OnOpcode,
OnFault: t.OnFault, OnFault: t.OnFault,
OnGasChange: t.OnGasChange, OnGasChange: t.OnGasChange,
OnGasChangeV2: t.OnGasChangeV2,
OnBlockchainInit: t.OnBlockchainInit, OnBlockchainInit: t.OnBlockchainInit,
OnBlockStart: t.OnBlockStart, OnBlockStart: t.OnBlockStart,
OnBlockEnd: t.OnBlockEnd, OnBlockEnd: t.OnBlockEnd,
@ -113,3 +114,6 @@ func (t *noop) OnBlockHashRead(number uint64, hash common.Hash) {}
func (t *noop) OnGasChange(old, new uint64, reason tracing.GasChangeReason) { func (t *noop) OnGasChange(old, new uint64, reason tracing.GasChangeReason) {
} }
func (t *noop) OnGasChangeV2(old, new tracing.Gas, reason tracing.GasChangeReason) {
}

View file

@ -65,10 +65,11 @@ func newMuxTracerFromConfig(ctx *tracers.Context, cfg json.RawMessage, chainConf
// the aggregated JSON result returned by GetResult. // the aggregated JSON result returned by GetResult.
// //
// For hooks that have both a V1 and V2 form (OnCodeChange / OnCodeChangeV2, // For hooks that have both a V1 and V2 form (OnCodeChange / OnCodeChangeV2,
// OnNonceChange / OnNonceChangeV2, OnSystemCallStart / OnSystemCallStartV2), // OnNonceChange / OnNonceChangeV2, OnGasChange / OnGasChangeV2,
// the mux exposes only the V2 variant upward. The fanout then prefers each // OnSystemCallStart / OnSystemCallStartV2), the mux exposes only the V2
// child's V2 hook and falls back to V1 if only V1 is set, mirroring the // variant upward. The fanout then prefers each child's V2 hook and falls
// precedence already used in core/state_processor.go. // back to V1 if only V1 is set, mirroring the precedence already used in
// core/state_processor.go.
func NewMuxTracer(names []string, objects []*tracers.Tracer) (*tracers.Tracer, error) { func NewMuxTracer(names []string, objects []*tracers.Tracer) (*tracers.Tracer, error) {
t := &muxTracer{names: names, tracers: objects} t := &muxTracer{names: names, tracers: objects}
return &tracers.Tracer{ return &tracers.Tracer{
@ -79,7 +80,7 @@ func NewMuxTracer(names []string, objects []*tracers.Tracer) (*tracers.Tracer, e
OnExit: t.OnExit, OnExit: t.OnExit,
OnOpcode: t.OnOpcode, OnOpcode: t.OnOpcode,
OnFault: t.OnFault, OnFault: t.OnFault,
OnGasChange: t.OnGasChange, OnGasChangeV2: t.OnGasChangeV2,
OnBalanceChange: t.OnBalanceChange, OnBalanceChange: t.OnBalanceChange,
OnNonceChangeV2: t.OnNonceChangeV2, OnNonceChangeV2: t.OnNonceChangeV2,
OnCodeChangeV2: t.OnCodeChangeV2, OnCodeChangeV2: t.OnCodeChangeV2,
@ -109,10 +110,12 @@ func (t *muxTracer) OnFault(pc uint64, op byte, gas, cost uint64, scope tracing.
} }
} }
func (t *muxTracer) OnGasChange(old, new uint64, reason tracing.GasChangeReason) { func (t *muxTracer) OnGasChangeV2(old, new tracing.Gas, reason tracing.GasChangeReason) {
for _, t := range t.tracers { for _, t := range t.tracers {
if t.OnGasChange != nil { if t.OnGasChangeV2 != nil {
t.OnGasChange(old, new, reason) t.OnGasChangeV2(old, new, reason)
} else if t.OnGasChange != nil {
t.OnGasChange(old.Regular, new.Regular, reason)
} }
} }
} }

View file

@ -47,6 +47,7 @@ func newNoopTracer(ctx *tracers.Context, cfg json.RawMessage, chainConfig *param
OnOpcode: t.OnOpcode, OnOpcode: t.OnOpcode,
OnFault: t.OnFault, OnFault: t.OnFault,
OnGasChange: t.OnGasChange, OnGasChange: t.OnGasChange,
OnGasChangeV2: t.OnGasChangeV2,
OnBalanceChange: t.OnBalanceChange, OnBalanceChange: t.OnBalanceChange,
OnNonceChange: t.OnNonceChange, OnNonceChange: t.OnNonceChange,
OnCodeChange: t.OnCodeChange, OnCodeChange: t.OnCodeChange,
@ -66,6 +67,8 @@ func (t *noopTracer) OnFault(pc uint64, op byte, gas, cost uint64, _ tracing.OpC
func (t *noopTracer) OnGasChange(old, new uint64, reason tracing.GasChangeReason) {} func (t *noopTracer) OnGasChange(old, new uint64, reason tracing.GasChangeReason) {}
func (t *noopTracer) OnGasChangeV2(old, new tracing.Gas, reason tracing.GasChangeReason) {}
func (t *noopTracer) OnEnter(depth int, typ byte, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { func (t *noopTracer) OnEnter(depth int, typ byte, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
} }