go-ethereum/eth/tracers/native/mux_test.go
rayoo 2c846b5601 eth/tracers: forward V2 state hooks through mux tracer
The mux tracer never exposed OnNonceChangeV2 or OnCodeChangeV2 on the
Hooks struct it returns. A child tracer that only implements the V2
variants (which include a reason parameter - CodeChangeReason /
NonceChangeReason) silently received no events when wrapped behind the
mux.

Worse, OnCodeChangeV2 had a fanout method defined (added in #33148) but
was never wired into the outer Hooks{} literal, so it was dead code.
OnNonceChangeV2 had neither the fanout nor the wire.

Mirror the precedence already used in core/state_processor.go and the
OnSystemCall fix from #34862: expose only the V2 variants on the Hooks
struct, and have the fanout prefer each child's V2 hook, falling back
to V1 when only V1 is set. Exposing both V1 and V2 simultaneously would
have tripped the "cannot have both" guard in WrapWithJournal and made
statedb_hooked pick only V2 (so V1-only children would still lose
events).

Callers: a child tracer registered via muxTracer config that tracks
nonce changes with NonceChangeReason (e.g. to detect EIP-7702
authorizations vs contract creation) or code changes with
CodeChangeReason (e.g. to filter self-destruct from creation) was
functionally broken - no events reached the child at all for
OnNonceChangeV2, and OnCodeChangeV2 specifically became dead code
after #33148.

Add a regression test covering both V2-only and V1-only children.
2026-05-04 21:35:14 +08:00

87 lines
3.2 KiB
Go

// Copyright 2026 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package native
import (
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/eth/tracers"
)
// TestMuxForwardsV2StateHooks verifies that the mux tracer fans out the V2
// variants of state-change hooks to child tracers. A child tracer that only
// implements OnCodeChangeV2 / OnNonceChangeV2 must still receive events when
// wrapped behind the mux. The mux must also fall back to the V1 hook when a
// child only implements V1, mirroring the precedence used in
// core/state_processor.go.
func TestMuxForwardsV2StateHooks(t *testing.T) {
var (
codeV2Calls int
nonceV2Calls int
codeV1Calls int
nonceV1Calls int
)
v2Child := &tracers.Tracer{
Hooks: &tracing.Hooks{
OnCodeChangeV2: func(addr common.Address, prevCodeHash common.Hash, prevCode []byte, codeHash common.Hash, code []byte, reason tracing.CodeChangeReason) {
codeV2Calls++
},
OnNonceChangeV2: func(addr common.Address, prev, new uint64, reason tracing.NonceChangeReason) {
nonceV2Calls++
},
},
}
v1Child := &tracers.Tracer{
Hooks: &tracing.Hooks{
OnCodeChange: func(addr common.Address, prevCodeHash common.Hash, prevCode []byte, codeHash common.Hash, code []byte) {
codeV1Calls++
},
OnNonceChange: func(addr common.Address, prev, new uint64) {
nonceV1Calls++
},
},
}
mux, err := NewMuxTracer([]string{"v2", "v1"}, []*tracers.Tracer{v2Child, v1Child})
if err != nil {
t.Fatalf("NewMuxTracer: %v", err)
}
if mux.Hooks.OnCodeChangeV2 == nil {
t.Fatal("mux does not expose OnCodeChangeV2; V2-only child tracers will miss code changes")
}
if mux.Hooks.OnNonceChangeV2 == nil {
t.Fatal("mux does not expose OnNonceChangeV2; V2-only child tracers will miss nonce changes")
}
mux.Hooks.OnCodeChangeV2(common.Address{}, common.Hash{}, nil, common.Hash{}, nil, tracing.CodeChangeContractCreation)
mux.Hooks.OnNonceChangeV2(common.Address{}, 0, 1, tracing.NonceChangeEoACall)
if codeV2Calls != 1 {
t.Fatalf("V2 child OnCodeChangeV2 got %d calls, want 1", codeV2Calls)
}
if nonceV2Calls != 1 {
t.Fatalf("V2 child OnNonceChangeV2 got %d calls, want 1", nonceV2Calls)
}
if codeV1Calls != 1 {
t.Fatalf("V1 child OnCodeChange got %d calls, want 1 (mux should fall back from V2 to V1)", codeV1Calls)
}
if nonceV1Calls != 1 {
t.Fatalf("V1 child OnNonceChange got %d calls, want 1 (mux should fall back from V2 to V1)", nonceV1Calls)
}
}