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 -
@ -257,6 +283,7 @@ type Hooks struct {
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) {
} }