diff --git a/cmd/evm/internal/t8ntool/execution.go b/cmd/evm/internal/t8ntool/execution.go index 793b2d425b..16c8808360 100644 --- a/cmd/evm/internal/t8ntool/execution.go +++ b/cmd/evm/internal/t8ntool/execution.go @@ -419,7 +419,7 @@ func MakePreState(db ethdb.Database, accounts types.GenesisAlloc) *state.StateDB statedb, _ := state.New(types.EmptyRootHash, sdb) for addr, a := range accounts { statedb.SetCode(addr, a.Code) - statedb.SetNonce(addr, a.Nonce) + statedb.SetNonce(addr, a.Nonce, tracing.NonceChangeGenesis) statedb.SetBalance(addr, uint256.MustFromBig(a.Balance), tracing.BalanceIncreaseGenesisBalance) for k, v := range a.Storage { statedb.SetState(addr, k, v) diff --git a/core/genesis.go b/core/genesis.go index 8f71f9ef1e..8546f4e37e 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -154,7 +154,7 @@ func hashAlloc(ga *types.GenesisAlloc, isVerkle bool) (common.Hash, error) { statedb.AddBalance(addr, uint256.MustFromBig(account.Balance), tracing.BalanceIncreaseGenesisBalance) } statedb.SetCode(addr, account.Code) - statedb.SetNonce(addr, account.Nonce) + statedb.SetNonce(addr, account.Nonce, tracing.NonceChangeGenesis) for key, value := range account.Storage { statedb.SetState(addr, key, value) } @@ -180,7 +180,7 @@ func flushAlloc(ga *types.GenesisAlloc, triedb *triedb.Database) (common.Hash, e statedb.AddBalance(addr, uint256.MustFromBig(account.Balance), tracing.BalanceIncreaseGenesisBalance) } statedb.SetCode(addr, account.Code) - statedb.SetNonce(addr, account.Nonce) + statedb.SetNonce(addr, account.Nonce, tracing.NonceChangeGenesis) for key, value := range account.Storage { statedb.SetState(addr, key, value) } diff --git a/core/state/statedb.go b/core/state/statedb.go index 0310ee6973..efafdc1aa2 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -432,7 +432,7 @@ func (s *StateDB) SetBalance(addr common.Address, amount *uint256.Int, reason tr } } -func (s *StateDB) SetNonce(addr common.Address, nonce uint64) { +func (s *StateDB) SetNonce(addr common.Address, nonce uint64, reason tracing.NonceChangeReason) { stateObject := s.getOrNewStateObject(addr) if stateObject != nil { stateObject.SetNonce(nonce) diff --git a/core/state/statedb_fuzz_test.go b/core/state/statedb_fuzz_test.go index ed99cf687c..2923a2c224 100644 --- a/core/state/statedb_fuzz_test.go +++ b/core/state/statedb_fuzz_test.go @@ -69,7 +69,7 @@ func newStateTestAction(addr common.Address, r *rand.Rand, index int) testAction { name: "SetNonce", fn: func(a testAction, s *StateDB) { - s.SetNonce(addr, uint64(a.args[0])) + s.SetNonce(addr, uint64(a.args[0]), tracing.NonceChangeUnspecified) }, args: make([]int64, 1), }, diff --git a/core/state/statedb_hooked.go b/core/state/statedb_hooked.go index 25d823cc87..a2fdfe9a21 100644 --- a/core/state/statedb_hooked.go +++ b/core/state/statedb_hooked.go @@ -179,10 +179,13 @@ func (s *hookedStateDB) AddBalance(addr common.Address, amount *uint256.Int, rea return prev } -func (s *hookedStateDB) SetNonce(address common.Address, nonce uint64) { - s.inner.SetNonce(address, nonce) - if s.hooks.OnNonceChange != nil { - s.hooks.OnNonceChange(address, nonce-1, nonce) +func (s *hookedStateDB) SetNonce(address common.Address, nonce uint64, reason tracing.NonceChangeReason) { + prev := s.inner.GetNonce(address) + s.inner.SetNonce(address, nonce, reason) + if s.hooks.OnNonceChangeV2 != nil { + s.hooks.OnNonceChangeV2(address, prev, nonce, reason) + } else if s.hooks.OnNonceChange != nil { + s.hooks.OnNonceChange(address, prev, nonce) } } diff --git a/core/state/statedb_hooked_test.go b/core/state/statedb_hooked_test.go index 874a275993..ce42e96409 100644 --- a/core/state/statedb_hooked_test.go +++ b/core/state/statedb_hooked_test.go @@ -85,7 +85,7 @@ func TestHooks(t *testing.T) { var wants = []string{ "0xaa00000000000000000000000000000000000000.balance: 0->100 (BalanceChangeUnspecified)", "0xaa00000000000000000000000000000000000000.balance: 100->50 (BalanceChangeTransfer)", - "0xaa00000000000000000000000000000000000000.nonce: 1336->1337", + "0xaa00000000000000000000000000000000000000.nonce: 0->1337", "0xaa00000000000000000000000000000000000000.code: (0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470) ->0x1325 (0xa12ae05590de0c93a00bc7ac773c2fdb621e44f814985e72194f921c0050f728)", "0xaa00000000000000000000000000000000000000.storage slot 0x0000000000000000000000000000000000000000000000000000000000000001: 0x0000000000000000000000000000000000000000000000000000000000000000 ->0x0000000000000000000000000000000000000000000000000000000000000011", "0xaa00000000000000000000000000000000000000.storage slot 0x0000000000000000000000000000000000000000000000000000000000000001: 0x0000000000000000000000000000000000000000000000000000000000000011 ->0x0000000000000000000000000000000000000000000000000000000000000022", @@ -113,7 +113,7 @@ func TestHooks(t *testing.T) { }) sdb.AddBalance(common.Address{0xaa}, uint256.NewInt(100), tracing.BalanceChangeUnspecified) sdb.SubBalance(common.Address{0xaa}, uint256.NewInt(50), tracing.BalanceChangeTransfer) - sdb.SetNonce(common.Address{0xaa}, 1337) + sdb.SetNonce(common.Address{0xaa}, 1337, tracing.NonceChangeGenesis) sdb.SetCode(common.Address{0xaa}, []byte{0x13, 37}) sdb.SetState(common.Address{0xaa}, common.HexToHash("0x01"), common.HexToHash("0x11")) sdb.SetState(common.Address{0xaa}, common.HexToHash("0x01"), common.HexToHash("0x22")) diff --git a/core/state/statedb_test.go b/core/state/statedb_test.go index 67eb9cbdc6..e740c64faa 100644 --- a/core/state/statedb_test.go +++ b/core/state/statedb_test.go @@ -60,7 +60,7 @@ func TestUpdateLeaks(t *testing.T) { for i := byte(0); i < 255; i++ { addr := common.BytesToAddress([]byte{i}) state.AddBalance(addr, uint256.NewInt(uint64(11*i)), tracing.BalanceChangeUnspecified) - state.SetNonce(addr, uint64(42*i)) + state.SetNonce(addr, uint64(42*i), tracing.NonceChangeUnspecified) if i%2 == 0 { state.SetState(addr, common.BytesToHash([]byte{i, i, i}), common.BytesToHash([]byte{i, i, i, i})) } @@ -95,7 +95,7 @@ func TestIntermediateLeaks(t *testing.T) { modify := func(state *StateDB, addr common.Address, i, tweak byte) { state.SetBalance(addr, uint256.NewInt(uint64(11*i)+uint64(tweak)), tracing.BalanceChangeUnspecified) - state.SetNonce(addr, uint64(42*i+tweak)) + state.SetNonce(addr, uint64(42*i+tweak), tracing.NonceChangeUnspecified) if i%2 == 0 { state.SetState(addr, common.Hash{i, i, i, 0}, common.Hash{}) state.SetState(addr, common.Hash{i, i, i, tweak}, common.Hash{i, i, i, i, tweak}) @@ -357,7 +357,7 @@ func newTestAction(addr common.Address, r *rand.Rand) testAction { { name: "SetNonce", fn: func(a testAction, s *StateDB) { - s.SetNonce(addr, uint64(a.args[0])) + s.SetNonce(addr, uint64(a.args[0]), tracing.NonceChangeUnspecified) }, args: make([]int64, 1), }, diff --git a/core/state_transition.go b/core/state_transition.go index e9c88eaedf..e7edd76ced 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -487,7 +487,7 @@ func (st *stateTransition) execute() (*ExecutionResult, error) { ret, _, st.gasRemaining, vmerr = st.evm.Create(sender, msg.Data, st.gasRemaining, value) } else { // Increment the nonce for the next transaction. - st.state.SetNonce(msg.From, st.state.GetNonce(msg.From)+1) + st.state.SetNonce(msg.From, st.state.GetNonce(msg.From)+1, tracing.NonceChangeEoACall) // Apply EIP-7702 authorizations. if msg.SetCodeAuthorizations != nil { @@ -602,7 +602,7 @@ func (st *stateTransition) applyAuthorization(auth *types.SetCodeAuthorization) } // Update nonce and account code. - st.state.SetNonce(authority, auth.Nonce+1) + st.state.SetNonce(authority, auth.Nonce+1, tracing.NonceChangeAuthorization) if auth.Address == (common.Address{}) { // Delegation to zero address means clear. st.state.SetCode(authority, nil) diff --git a/core/tracing/CHANGELOG.md b/core/tracing/CHANGELOG.md index 270e0a30bf..a14e123d99 100644 --- a/core/tracing/CHANGELOG.md +++ b/core/tracing/CHANGELOG.md @@ -4,6 +4,53 @@ All notable changes to the tracing interface will be documented in this file. ## [Unreleased] +The tracing interface has been extended with backwards-compatible changes to support more use-cases and simplify tracer code. The most notable change is a state journaling library which emits reverse events when a call is reverted. + +### Deprecated methods + +- `OnSystemCallStart()`: This hook is deprecated in favor of `OnSystemCallStartV2(vm *VMContext)`. +- `OnNonceChange(addr common.Address, prev, new uint64)`: This hook is deprecated in favor of `OnNonceChangeV2(addr common.Address, prev, new uint64, reason NonceChangeReason)`. + +### New methods + +- `OnBlockHashRead(blockNum uint64, hash common.Hash)`: This hook is called when a block hash is read by EVM. +- `OnSystemCallStartV2(vm *VMContext)`. This allows access to EVM context during system calls. It is a successor to `OnSystemCallStart`. +- `OnNonceChangeV2(addr common.Address, prev, new uint64, reason NonceChangeReason)`: This hook is called when a nonce change occurs. It is a successor to `OnNonceChange`. + +### New types + +- `NonceChangeReason` is a new type used to provide a reason for nonce changes. Notably it includes `NonceChangeRevert` which will be emitted by the state journaling library when a nonce change is due to a revert. + +### Modified types + +- `VMContext.StateDB` has been extended with `GetCodeHash(addr common.Address) common.Hash` method used to retrieve the code hash an account. +- `BalanceChangeReason` has been extended with the `BalanceChangeRevert` reason. More on that below. + +### State journaling + +Tracers receive state changes events from the node. The tracer was so far expected to keep track of modified accounts and slots and revert those changes when a call frame failed. Now a utility tracer wrapper is provided which will emit "reverse change" events when a call frame fails. To use this feature the hooks have to be wrapped prior to registering the tracer. The following example demonstrates how to use the state journaling library: + +```go +func init() { + tracers.LiveDirectory.Register("test", func (cfg json.RawMessage) (*tracing.Hooks, error) { + hooks, err := newTestTracer(cfg) + if err != nil { + return nil, err + } + return tracing.WrapWithJournal(hooks) + }) +} +``` + +The state changes that are covered by the journaling library are: + +- `OnBalanceChange`. Note that `OnBalanceChange` will carry the `BalanceChangeRevert` reason. +- `OnNonceChange`, `OnNonceChangeV2` +- `OnCodeChange` +- `OnStorageChange` + +## [v1.14.9](https://github.com/ethereum/go-ethereum/releases/tag/v1.14.9) + ### Modified types - `GasChangeReason` has been extended with the following reasons which will be enabled only post-Verkle. There shouldn't be any gas changes with those reasons prior to the fork. diff --git a/core/tracing/gen_balance_change_reason_stringer.go b/core/tracing/gen_balance_change_reason_stringer.go index d3a515a12d..4f094efb4f 100644 --- a/core/tracing/gen_balance_change_reason_stringer.go +++ b/core/tracing/gen_balance_change_reason_stringer.go @@ -23,11 +23,12 @@ func _() { _ = x[BalanceIncreaseSelfdestruct-12] _ = x[BalanceDecreaseSelfdestruct-13] _ = x[BalanceDecreaseSelfdestructBurn-14] + _ = x[BalanceChangeRevert-15] } -const _BalanceChangeReason_name = "BalanceChangeUnspecifiedBalanceIncreaseRewardMineUncleBalanceIncreaseRewardMineBlockBalanceIncreaseWithdrawalBalanceIncreaseGenesisBalanceBalanceIncreaseRewardTransactionFeeBalanceDecreaseGasBuyBalanceIncreaseGasReturnBalanceIncreaseDaoContractBalanceDecreaseDaoAccountBalanceChangeTransferBalanceChangeTouchAccountBalanceIncreaseSelfdestructBalanceDecreaseSelfdestructBalanceDecreaseSelfdestructBurn" +const _BalanceChangeReason_name = "BalanceChangeUnspecifiedBalanceIncreaseRewardMineUncleBalanceIncreaseRewardMineBlockBalanceIncreaseWithdrawalBalanceIncreaseGenesisBalanceBalanceIncreaseRewardTransactionFeeBalanceDecreaseGasBuyBalanceIncreaseGasReturnBalanceIncreaseDaoContractBalanceDecreaseDaoAccountBalanceChangeTransferBalanceChangeTouchAccountBalanceIncreaseSelfdestructBalanceDecreaseSelfdestructBalanceDecreaseSelfdestructBurnBalanceChangeRevert" -var _BalanceChangeReason_index = [...]uint16{0, 24, 54, 84, 109, 138, 173, 194, 218, 244, 269, 290, 315, 342, 369, 400} +var _BalanceChangeReason_index = [...]uint16{0, 24, 54, 84, 109, 138, 173, 194, 218, 244, 269, 290, 315, 342, 369, 400, 419} func (i BalanceChangeReason) String() string { if i >= BalanceChangeReason(len(_BalanceChangeReason_index)-1) { diff --git a/core/tracing/hooks.go b/core/tracing/hooks.go index 167bcb5c16..4002b57207 100644 --- a/core/tracing/hooks.go +++ b/core/tracing/hooks.go @@ -14,6 +14,14 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . +// Package tracing defines hooks for 'live tracing' of block processing and transaction +// execution. Here we define the low-level [Hooks] object that carries hooks which are +// invoked by the go-ethereum core at various points in the state transition. +// +// To create a tracer that can be invoked with Geth, you need to register it using +// [github.com/ethereum/go-ethereum/eth/tracers.LiveDirectory.Register]. +// +// See https://geth.ethereum.org/docs/developers/evm-tracing/live-tracing for a tutorial. package tracing import ( @@ -163,6 +171,9 @@ type ( // NonceChangeHook is called when the nonce of an account changes. NonceChangeHook = func(addr common.Address, prev, new uint64) + // NonceChangeHookV2 is called when the nonce of an account changes. + NonceChangeHookV2 = func(addr common.Address, prev, new uint64, reason NonceChangeReason) + // CodeChangeHook is called when the code of an account changes. CodeChangeHook = func(addr common.Address, prevCodeHash common.Hash, prevCode []byte, codeHash common.Hash, code []byte) @@ -171,6 +182,9 @@ type ( // LogHook is called when a log is emitted. LogHook = func(log *types.Log) + + // BlockHashReadHook is called when EVM reads the blockhash of a block. + BlockHashReadHook = func(blockNumber uint64, hash common.Hash) ) type Hooks struct { @@ -195,9 +209,12 @@ type Hooks struct { // State events OnBalanceChange BalanceChangeHook OnNonceChange NonceChangeHook + OnNonceChangeV2 NonceChangeHookV2 OnCodeChange CodeChangeHook OnStorageChange StorageChangeHook OnLog LogHook + // Block hash read + OnBlockHashRead BlockHashReadHook } // BalanceChangeReason is used to indicate the reason for a balance change, useful @@ -249,6 +266,10 @@ const ( // account within the same tx (captured at end of tx). // Note it doesn't account for a self-destruct which appoints itself as recipient. BalanceDecreaseSelfdestructBurn BalanceChangeReason = 14 + + // BalanceChangeRevert is emitted when the balance is reverted back to a previous value due to call failure. + // It is only emitted when the tracer has opted in to use the journaling wrapper (WrapWithJournal). + BalanceChangeRevert BalanceChangeReason = 15 ) // GasChangeReason is used to indicate the reason for a gas change, useful @@ -321,3 +342,29 @@ const ( // it will be "manually" tracked by a direct emit of the gas change event. GasChangeIgnored GasChangeReason = 0xFF ) + +// NonceChangeReason is used to indicate the reason for a nonce change. +type NonceChangeReason byte + +const ( + NonceChangeUnspecified NonceChangeReason = 0 + + // NonceChangeGenesis is the nonce allocated to accounts at genesis. + NonceChangeGenesis NonceChangeReason = 1 + + // NonceChangeEoACall is the nonce change due to an EoA call. + NonceChangeEoACall NonceChangeReason = 2 + + // NonceChangeContractCreator is the nonce change of an account creating a contract. + NonceChangeContractCreator NonceChangeReason = 3 + + // NonceChangeNewContract is the nonce change of a newly created contract. + NonceChangeNewContract NonceChangeReason = 4 + + // NonceChangeTransaction is the nonce change due to a EIP-7702 authorization. + NonceChangeAuthorization NonceChangeReason = 5 + + // NonceChangeRevert is emitted when the nonce is reverted back to a previous value due to call failure. + // It is only emitted when the tracer has opted in to use the journaling wrapper (WrapWithJournal). + NonceChangeRevert NonceChangeReason = 6 +) diff --git a/core/tracing/journal.go b/core/tracing/journal.go new file mode 100644 index 0000000000..8937d4c5ae --- /dev/null +++ b/core/tracing/journal.go @@ -0,0 +1,237 @@ +// Copyright 2025 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 . + +package tracing + +import ( + "fmt" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" +) + +// journal is a state change journal to be wrapped around a tracer. +// It will emit the state change hooks with reverse values when a call reverts. +type journal struct { + hooks *Hooks + entries []entry + revisions []int +} + +type entry interface { + revert(tracer *Hooks) +} + +// WrapWithJournal wraps the given tracer with a journaling layer. +func WrapWithJournal(hooks *Hooks) (*Hooks, error) { + if hooks == nil { + return nil, fmt.Errorf("wrapping nil tracer") + } + // No state change to journal, return the wrapped hooks as is + if hooks.OnBalanceChange == nil && hooks.OnNonceChange == nil && hooks.OnNonceChangeV2 == nil && hooks.OnCodeChange == nil && hooks.OnStorageChange == nil { + return hooks, nil + } + if hooks.OnNonceChange != nil && hooks.OnNonceChangeV2 != nil { + return nil, fmt.Errorf("cannot have both OnNonceChange and OnNonceChangeV2") + } + + // Create a new Hooks instance and copy all hooks + wrapped := *hooks + + // Create journal + j := &journal{hooks: hooks} + // Scope hooks need to be re-implemented. + wrapped.OnTxEnd = j.OnTxEnd + wrapped.OnEnter = j.OnEnter + wrapped.OnExit = j.OnExit + // Wrap state change hooks. + if hooks.OnBalanceChange != nil { + wrapped.OnBalanceChange = j.OnBalanceChange + } + if hooks.OnNonceChange != nil || hooks.OnNonceChangeV2 != nil { + // Regardless of which hook version is used in the tracer, + // the journal will want to capture the nonce change reason. + wrapped.OnNonceChangeV2 = j.OnNonceChangeV2 + // A precaution to ensure EVM doesn't call both hooks. + wrapped.OnNonceChange = nil + } + if hooks.OnCodeChange != nil { + wrapped.OnCodeChange = j.OnCodeChange + } + if hooks.OnStorageChange != nil { + wrapped.OnStorageChange = j.OnStorageChange + } + + return &wrapped, nil +} + +// reset clears the journal, after this operation the journal can be used anew. +// It is semantically similar to calling 'NewJournal', but the underlying slices +// can be reused. +func (j *journal) reset() { + j.entries = j.entries[:0] + j.revisions = j.revisions[:0] +} + +// snapshot records a revision and stores it to the revision stack. +func (j *journal) snapshot() { + rev := len(j.entries) + j.revisions = append(j.revisions, rev) +} + +// revert reverts all state changes up to the last tracked revision. +func (j *journal) revert(hooks *Hooks) { + // Replay the journal entries above the last revision to undo changes, + // then remove the reverted changes from the journal. + rev := j.revisions[len(j.revisions)-1] + for i := len(j.entries) - 1; i >= rev; i-- { + j.entries[i].revert(hooks) + } + j.entries = j.entries[:rev] + j.popRevision() +} + +// popRevision removes an item from the revision stack. This basically forgets about +// the last call to snapshot() and moves to the one prior. +func (j *journal) popRevision() { + j.revisions = j.revisions[:len(j.revisions)-1] +} + +// OnTxEnd resets the journal since each transaction has its own EVM call stack. +func (j *journal) OnTxEnd(receipt *types.Receipt, err error) { + j.reset() + if j.hooks.OnTxEnd != nil { + j.hooks.OnTxEnd(receipt, err) + } +} + +// OnEnter is invoked for each EVM call frame and records a journal revision. +func (j *journal) OnEnter(depth int, typ byte, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { + j.snapshot() + if j.hooks.OnEnter != nil { + j.hooks.OnEnter(depth, typ, from, to, input, gas, value) + } +} + +// OnExit is invoked when an EVM call frame ends. +// If the call has reverted, all state changes made by that frame are undone. +// If the call did not revert, we forget about changes in that revision. +func (j *journal) OnExit(depth int, output []byte, gasUsed uint64, err error, reverted bool) { + if reverted { + j.revert(j.hooks) + } else { + j.popRevision() + } + if j.hooks.OnExit != nil { + j.hooks.OnExit(depth, output, gasUsed, err, reverted) + } +} + +func (j *journal) OnBalanceChange(addr common.Address, prev, new *big.Int, reason BalanceChangeReason) { + j.entries = append(j.entries, balanceChange{addr: addr, prev: prev, new: new}) + if j.hooks.OnBalanceChange != nil { + j.hooks.OnBalanceChange(addr, prev, new, reason) + } +} + +func (j *journal) OnNonceChangeV2(addr common.Address, prev, new uint64, reason NonceChangeReason) { + // When a contract is created, the nonce of the creator is incremented. + // This change is not reverted when the creation fails. + if reason != NonceChangeContractCreator { + j.entries = append(j.entries, nonceChange{addr: addr, prev: prev, new: new}) + } + if j.hooks.OnNonceChangeV2 != nil { + j.hooks.OnNonceChangeV2(addr, prev, new, reason) + } else if j.hooks.OnNonceChange != nil { + j.hooks.OnNonceChange(addr, prev, new) + } +} + +func (j *journal) OnCodeChange(addr common.Address, prevCodeHash common.Hash, prevCode []byte, codeHash common.Hash, code []byte) { + j.entries = append(j.entries, codeChange{ + addr: addr, + prevCodeHash: prevCodeHash, + prevCode: prevCode, + newCodeHash: codeHash, + newCode: code, + }) + if j.hooks.OnCodeChange != nil { + j.hooks.OnCodeChange(addr, prevCodeHash, prevCode, codeHash, code) + } +} + +func (j *journal) OnStorageChange(addr common.Address, slot common.Hash, prev, new common.Hash) { + j.entries = append(j.entries, storageChange{addr: addr, slot: slot, prev: prev, new: new}) + if j.hooks.OnStorageChange != nil { + j.hooks.OnStorageChange(addr, slot, prev, new) + } +} + +type ( + balanceChange struct { + addr common.Address + prev *big.Int + new *big.Int + } + + nonceChange struct { + addr common.Address + prev uint64 + new uint64 + } + + codeChange struct { + addr common.Address + prevCodeHash common.Hash + prevCode []byte + newCodeHash common.Hash + newCode []byte + } + + storageChange struct { + addr common.Address + slot common.Hash + prev common.Hash + new common.Hash + } +) + +func (b balanceChange) revert(hooks *Hooks) { + if hooks.OnBalanceChange != nil { + hooks.OnBalanceChange(b.addr, b.new, b.prev, BalanceChangeRevert) + } +} + +func (n nonceChange) revert(hooks *Hooks) { + if hooks.OnNonceChangeV2 != nil { + hooks.OnNonceChangeV2(n.addr, n.new, n.prev, NonceChangeRevert) + } else if hooks.OnNonceChange != nil { + hooks.OnNonceChange(n.addr, n.new, n.prev) + } +} + +func (c codeChange) revert(hooks *Hooks) { + if hooks.OnCodeChange != nil { + hooks.OnCodeChange(c.addr, c.newCodeHash, c.newCode, c.prevCodeHash, c.prevCode) + } +} + +func (s storageChange) revert(hooks *Hooks) { + if hooks.OnStorageChange != nil { + hooks.OnStorageChange(s.addr, s.slot, s.new, s.prev) + } +} diff --git a/core/tracing/journal_test.go b/core/tracing/journal_test.go new file mode 100644 index 0000000000..d9616a2ce8 --- /dev/null +++ b/core/tracing/journal_test.go @@ -0,0 +1,335 @@ +// Copyright 2025 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 . + +package tracing + +import ( + "errors" + "math/big" + "reflect" + "testing" + + "github.com/ethereum/go-ethereum/common" +) + +type testTracer struct { + t *testing.T + bal *big.Int + nonce uint64 + code []byte + storage map[common.Hash]common.Hash +} + +func (t *testTracer) OnBalanceChange(addr common.Address, prev *big.Int, new *big.Int, reason BalanceChangeReason) { + t.t.Logf("OnBalanceChange(%v, %v -> %v, %v)", addr, prev, new, reason) + if t.bal != nil && t.bal.Cmp(prev) != 0 { + t.t.Errorf(" !! wrong prev balance (expected %v)", t.bal) + } + t.bal = new +} + +func (t *testTracer) OnNonceChange(addr common.Address, prev uint64, new uint64) { + t.t.Logf("OnNonceChange(%v, %v -> %v)", addr, prev, new) + t.nonce = new +} + +func (t *testTracer) OnNonceChangeV2(addr common.Address, prev uint64, new uint64, reason NonceChangeReason) { + t.t.Logf("OnNonceChangeV2(%v, %v -> %v, %v)", addr, prev, new, reason) + t.nonce = new +} + +func (t *testTracer) OnCodeChange(addr common.Address, prevCodeHash common.Hash, prevCode []byte, codeHash common.Hash, code []byte) { + t.t.Logf("OnCodeChange(%v, %v -> %v)", addr, prevCodeHash, codeHash) + t.code = code +} + +func (t *testTracer) OnStorageChange(addr common.Address, slot common.Hash, prev common.Hash, new common.Hash) { + t.t.Logf("OnStorageCodeChange(%v, %v, %v -> %v)", addr, slot, prev, new) + if t.storage == nil { + t.storage = make(map[common.Hash]common.Hash) + } + if new == (common.Hash{}) { + delete(t.storage, slot) + } else { + t.storage[slot] = new + } +} + +func TestJournalIntegration(t *testing.T) { + tr := &testTracer{t: t} + wr, err := WrapWithJournal(&Hooks{OnBalanceChange: tr.OnBalanceChange, OnNonceChange: tr.OnNonceChange, OnCodeChange: tr.OnCodeChange, OnStorageChange: tr.OnStorageChange}) + if err != nil { + t.Fatalf("failed to wrap test tracer: %v", err) + } + + addr := common.HexToAddress("0x1234") + { + wr.OnEnter(0, 0, addr, addr, nil, 1000, big.NewInt(0)) + wr.OnBalanceChange(addr, nil, big.NewInt(100), BalanceChangeUnspecified) + wr.OnCodeChange(addr, common.Hash{}, nil, common.Hash{}, []byte{1, 2, 3}) + wr.OnStorageChange(addr, common.Hash{1}, common.Hash{}, common.Hash{2}) + { + wr.OnEnter(1, 0, addr, addr, nil, 1000, big.NewInt(0)) + wr.OnNonceChangeV2(addr, 0, 1, NonceChangeUnspecified) + wr.OnBalanceChange(addr, big.NewInt(100), big.NewInt(200), BalanceChangeUnspecified) + wr.OnBalanceChange(addr, big.NewInt(200), big.NewInt(250), BalanceChangeUnspecified) + wr.OnStorageChange(addr, common.Hash{1}, common.Hash{2}, common.Hash{3}) + wr.OnStorageChange(addr, common.Hash{2}, common.Hash{}, common.Hash{4}) + wr.OnExit(1, nil, 100, errors.New("revert"), true) + } + wr.OnExit(0, nil, 150, nil, false) + } + + if tr.bal.Cmp(big.NewInt(100)) != 0 { + t.Fatalf("unexpected balance: %v", tr.bal) + } + if tr.nonce != 0 { + t.Fatalf("unexpected nonce: %v", tr.nonce) + } + if len(tr.code) != 3 { + t.Fatalf("unexpected code: %v", tr.code) + } + if len(tr.storage) != 1 { + t.Fatalf("unexpected storage len. want %d, have %d", 1, len(tr.storage)) + } + if tr.storage[common.Hash{1}] != (common.Hash{2}) { + t.Fatalf("unexpected storage. want %v, have %v", common.Hash{2}, tr.storage[common.Hash{1}]) + } +} + +func TestJournalTopRevert(t *testing.T) { + tr := &testTracer{t: t} + wr, err := WrapWithJournal(&Hooks{OnBalanceChange: tr.OnBalanceChange, OnNonceChange: tr.OnNonceChange}) + if err != nil { + t.Fatalf("failed to wrap test tracer: %v", err) + } + + addr := common.HexToAddress("0x1234") + { + wr.OnEnter(0, 0, addr, addr, nil, 1000, big.NewInt(0)) + wr.OnBalanceChange(addr, big.NewInt(0), big.NewInt(100), BalanceChangeUnspecified) + { + wr.OnEnter(1, 0, addr, addr, nil, 1000, big.NewInt(0)) + wr.OnNonceChangeV2(addr, 0, 1, NonceChangeUnspecified) + wr.OnBalanceChange(addr, big.NewInt(100), big.NewInt(200), BalanceChangeUnspecified) + wr.OnBalanceChange(addr, big.NewInt(200), big.NewInt(250), BalanceChangeUnspecified) + wr.OnExit(1, nil, 100, errors.New("revert"), true) + } + wr.OnExit(0, nil, 150, errors.New("revert"), true) + } + + if tr.bal.Cmp(big.NewInt(0)) != 0 { + t.Fatalf("unexpected balance: %v", tr.bal) + } + if tr.nonce != 0 { + t.Fatalf("unexpected nonce: %v", tr.nonce) + } +} + +// This test checks that changes in nested calls are reverted properly. +func TestJournalNestedCalls(t *testing.T) { + tr := &testTracer{t: t} + wr, err := WrapWithJournal(&Hooks{OnBalanceChange: tr.OnBalanceChange, OnNonceChange: tr.OnNonceChange}) + if err != nil { + t.Fatalf("failed to wrap test tracer: %v", err) + } + + addr := common.HexToAddress("0x1234") + { + wr.OnEnter(0, 0, addr, addr, nil, 1000, big.NewInt(0)) + wr.OnBalanceChange(addr, big.NewInt(0), big.NewInt(100), BalanceChangeUnspecified) + { + wr.OnEnter(1, 0, addr, addr, nil, 1000, big.NewInt(0)) + wr.OnBalanceChange(addr, big.NewInt(100), big.NewInt(200), BalanceChangeUnspecified) + { + wr.OnEnter(2, 0, addr, addr, nil, 1000, big.NewInt(0)) + wr.OnExit(2, nil, 100, nil, false) + } + { + wr.OnEnter(2, 0, addr, addr, nil, 1000, big.NewInt(0)) + wr.OnBalanceChange(addr, big.NewInt(200), big.NewInt(300), BalanceChangeUnspecified) + wr.OnExit(2, nil, 100, nil, false) + } + { + wr.OnEnter(2, 0, addr, addr, nil, 1000, big.NewInt(0)) + wr.OnExit(2, nil, 100, nil, false) + } + wr.OnBalanceChange(addr, big.NewInt(300), big.NewInt(400), BalanceChangeUnspecified) + { + wr.OnEnter(2, 0, addr, addr, nil, 1000, big.NewInt(0)) + wr.OnBalanceChange(addr, big.NewInt(400), big.NewInt(500), BalanceChangeUnspecified) + wr.OnExit(2, nil, 100, errors.New("revert"), true) + } + { + wr.OnEnter(2, 0, addr, addr, nil, 1000, big.NewInt(0)) + wr.OnExit(2, nil, 100, errors.New("revert"), true) + } + { + wr.OnEnter(2, 0, addr, addr, nil, 1000, big.NewInt(0)) + wr.OnBalanceChange(addr, big.NewInt(400), big.NewInt(600), BalanceChangeUnspecified) + wr.OnExit(2, nil, 100, nil, false) + } + wr.OnExit(1, nil, 100, errors.New("revert"), true) + } + wr.OnExit(0, nil, 150, nil, false) + } + + if tr.bal.Uint64() != 100 { + t.Fatalf("unexpected balance: %v", tr.bal) + } +} + +func TestNonceIncOnCreate(t *testing.T) { + const opCREATE = 0xf0 + + tr := &testTracer{t: t} + wr, err := WrapWithJournal(&Hooks{OnNonceChange: tr.OnNonceChange}) + if err != nil { + t.Fatalf("failed to wrap test tracer: %v", err) + } + + addr := common.HexToAddress("0x1234") + { + wr.OnEnter(0, opCREATE, addr, addr, nil, 1000, big.NewInt(0)) + wr.OnNonceChangeV2(addr, 0, 1, NonceChangeContractCreator) + wr.OnExit(0, nil, 100, errors.New("revert"), true) + } + + if tr.nonce != 1 { + t.Fatalf("unexpected nonce: %v", tr.nonce) + } +} + +func TestOnNonceChangeV2(t *testing.T) { + tr := &testTracer{t: t} + wr, err := WrapWithJournal(&Hooks{OnNonceChangeV2: tr.OnNonceChangeV2}) + if err != nil { + t.Fatalf("failed to wrap test tracer: %v", err) + } + + addr := common.HexToAddress("0x1234") + { + wr.OnEnter(2, 0, addr, addr, nil, 1000, big.NewInt(0)) + wr.OnNonceChangeV2(addr, 0, 1, NonceChangeEoACall) + wr.OnExit(2, nil, 100, nil, true) + } + + if tr.nonce != 0 { + t.Fatalf("unexpected nonce: %v", tr.nonce) + } +} + +func TestAllHooksCalled(t *testing.T) { + tracer := newTracerAllHooks() + hooks := tracer.hooks() + + wrapped, err := WrapWithJournal(hooks) + if err != nil { + t.Fatalf("failed to wrap hooks with journal: %v", err) + } + + // Get the underlying value of the wrapped hooks + wrappedValue := reflect.ValueOf(wrapped).Elem() + wrappedType := wrappedValue.Type() + + // Iterate over all fields of the wrapped hooks + for i := 0; i < wrappedType.NumField(); i++ { + field := wrappedType.Field(i) + + // Skip fields that are not function types + if field.Type.Kind() != reflect.Func { + continue + } + // Skip non-hooks, i.e. Copy + if field.Name == "copy" { + continue + } + // Skip if field is not set + if wrappedValue.Field(i).IsNil() { + continue + } + + // Get the method + method := wrappedValue.Field(i) + + // Call the method with zero values + params := make([]reflect.Value, method.Type().NumIn()) + for j := 0; j < method.Type().NumIn(); j++ { + params[j] = reflect.Zero(method.Type().In(j)) + } + method.Call(params) + } + + // Check if all hooks were called + if tracer.numCalled() != tracer.hooksCount() { + t.Errorf("Not all hooks were called. Expected %d, got %d", tracer.hooksCount(), tracer.numCalled()) + } + + for hookName, called := range tracer.hooksCalled { + if !called { + t.Errorf("Hook %s was not called", hookName) + } + } +} + +type tracerAllHooks struct { + hooksCalled map[string]bool +} + +func newTracerAllHooks() *tracerAllHooks { + t := &tracerAllHooks{hooksCalled: make(map[string]bool)} + // Initialize all hooks to false. We will use this to + // get total count of hooks. + hooksType := reflect.TypeOf((*Hooks)(nil)).Elem() + for i := 0; i < hooksType.NumField(); i++ { + t.hooksCalled[hooksType.Field(i).Name] = false + } + delete(t.hooksCalled, "OnNonceChange") + return t +} + +func (t *tracerAllHooks) hooksCount() int { + return len(t.hooksCalled) +} + +func (t *tracerAllHooks) numCalled() int { + count := 0 + for _, called := range t.hooksCalled { + if called { + count++ + } + } + return count +} + +func (t *tracerAllHooks) hooks() *Hooks { + h := &Hooks{} + // Create a function for each hook that sets the + // corresponding hooksCalled field to true. + hooksValue := reflect.ValueOf(h).Elem() + for i := 0; i < hooksValue.NumField(); i++ { + field := hooksValue.Type().Field(i) + if field.Name == "OnNonceChange" { + continue + } + hookMethod := reflect.MakeFunc(field.Type, func(args []reflect.Value) []reflect.Value { + t.hooksCalled[field.Name] = true + return nil + }) + hooksValue.Field(i).Set(hookMethod) + } + return h +} diff --git a/core/txpool/blobpool/blobpool_test.go b/core/txpool/blobpool/blobpool_test.go index 88d68eb307..f7c9e4844b 100644 --- a/core/txpool/blobpool/blobpool_test.go +++ b/core/txpool/blobpool/blobpool_test.go @@ -680,9 +680,9 @@ func TestOpenDrops(t *testing.T) { statedb.AddBalance(crypto.PubkeyToAddress(gapper.PublicKey), uint256.NewInt(1000000), tracing.BalanceChangeUnspecified) statedb.AddBalance(crypto.PubkeyToAddress(dangler.PublicKey), uint256.NewInt(1000000), tracing.BalanceChangeUnspecified) statedb.AddBalance(crypto.PubkeyToAddress(filler.PublicKey), uint256.NewInt(1000000), tracing.BalanceChangeUnspecified) - statedb.SetNonce(crypto.PubkeyToAddress(filler.PublicKey), 3) + statedb.SetNonce(crypto.PubkeyToAddress(filler.PublicKey), 3, tracing.NonceChangeUnspecified) statedb.AddBalance(crypto.PubkeyToAddress(overlapper.PublicKey), uint256.NewInt(1000000), tracing.BalanceChangeUnspecified) - statedb.SetNonce(crypto.PubkeyToAddress(overlapper.PublicKey), 2) + statedb.SetNonce(crypto.PubkeyToAddress(overlapper.PublicKey), 2, tracing.NonceChangeUnspecified) statedb.AddBalance(crypto.PubkeyToAddress(underpayer.PublicKey), uint256.NewInt(1000000), tracing.BalanceChangeUnspecified) statedb.AddBalance(crypto.PubkeyToAddress(outpricer.PublicKey), uint256.NewInt(1000000), tracing.BalanceChangeUnspecified) statedb.AddBalance(crypto.PubkeyToAddress(exceeder.PublicKey), uint256.NewInt(1000000), tracing.BalanceChangeUnspecified) @@ -1526,7 +1526,7 @@ func TestAdd(t *testing.T) { // Seed the state database with this account statedb.AddBalance(addrs[acc], new(uint256.Int).SetUint64(seed.balance), tracing.BalanceChangeUnspecified) - statedb.SetNonce(addrs[acc], seed.nonce) + statedb.SetNonce(addrs[acc], seed.nonce, tracing.NonceChangeUnspecified) // Sign the seed transactions and store them in the data store for _, tx := range seed.txs { @@ -1581,7 +1581,7 @@ func TestAdd(t *testing.T) { // Apply the nonce updates to the state db for _, tx := range txs { sender, _ := types.Sender(types.LatestSigner(params.MainnetChainConfig), tx) - chain.statedb.SetNonce(sender, tx.Nonce()+1) + chain.statedb.SetNonce(sender, tx.Nonce()+1, tracing.NonceChangeUnspecified) } pool.Reset(chain.CurrentBlock(), header) verifyPoolInternals(t, pool) diff --git a/core/txpool/legacypool/legacypool_test.go b/core/txpool/legacypool/legacypool_test.go index 55699e93ee..fdbcad3d4f 100644 --- a/core/txpool/legacypool/legacypool_test.go +++ b/core/txpool/legacypool/legacypool_test.go @@ -251,7 +251,7 @@ func (c *testChain) State() (*state.StateDB, error) { if *c.trigger { c.statedb, _ = state.New(types.EmptyRootHash, state.NewDatabaseForTesting()) // simulate that the new head block included tx0 and tx1 - c.statedb.SetNonce(c.address, 2) + c.statedb.SetNonce(c.address, 2, tracing.NonceChangeUnspecified) c.statedb.SetBalance(c.address, new(uint256.Int).SetUint64(params.Ether), tracing.BalanceChangeUnspecified) *c.trigger = false } @@ -312,7 +312,7 @@ func testAddBalance(pool *LegacyPool, addr common.Address, amount *big.Int) { func testSetNonce(pool *LegacyPool, addr common.Address, nonce uint64) { pool.mu.Lock() - pool.currentState.SetNonce(addr, nonce) + pool.currentState.SetNonce(addr, nonce, tracing.NonceChangeUnspecified) pool.mu.Unlock() } @@ -1011,7 +1011,7 @@ func TestQueueTimeLimiting(t *testing.T) { } // remove current transactions and increase nonce to prepare for a reset and cleanup - statedb.SetNonce(crypto.PubkeyToAddress(remote.PublicKey), 2) + statedb.SetNonce(crypto.PubkeyToAddress(remote.PublicKey), 2, tracing.NonceChangeUnspecified) <-pool.requestReset(nil, nil) // make sure queue, pending are cleared diff --git a/core/verkle_witness_test.go b/core/verkle_witness_test.go index 4e873eedff..de2280ced1 100644 --- a/core/verkle_witness_test.go +++ b/core/verkle_witness_test.go @@ -29,6 +29,7 @@ import ( "github.com/ethereum/go-ethereum/consensus/ethash" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" @@ -226,7 +227,7 @@ func TestProcessParentBlockHash(t *testing.T) { // block 2 parent hash is 0x0200.... // etc checkBlockHashes := func(statedb *state.StateDB, isVerkle bool) { - statedb.SetNonce(params.HistoryStorageAddress, 1) + statedb.SetNonce(params.HistoryStorageAddress, 1, tracing.NonceChangeUnspecified) statedb.SetCode(params.HistoryStorageAddress, params.HistoryStorageCode) // Process n blocks, from 1 .. num var num = 2 diff --git a/core/vm/evm.go b/core/vm/evm.go index 3f9ae621a3..442441e9ae 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -439,7 +439,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, if nonce+1 < nonce { return nil, common.Address{}, gas, ErrNonceUintOverflow } - evm.StateDB.SetNonce(caller.Address(), nonce+1) + evm.StateDB.SetNonce(caller.Address(), nonce+1, tracing.NonceChangeContractCreator) // Charge the contract creation init gas in verkle mode if evm.chainRules.IsEIP4762 { @@ -487,7 +487,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, evm.StateDB.CreateContract(address) if evm.chainRules.IsEIP158 { - evm.StateDB.SetNonce(address, 1) + evm.StateDB.SetNonce(address, 1, tracing.NonceChangeNewContract) } // Charge the contract creation init gas in verkle mode if evm.chainRules.IsEIP4762 { diff --git a/core/vm/instructions.go b/core/vm/instructions.go index 9b9a31a855..c9eea33507 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -448,6 +448,9 @@ func opBlockhash(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ( if witness := interpreter.evm.StateDB.Witness(); witness != nil { witness.AddBlockHash(num64) } + if tracer := interpreter.evm.Config.Tracer; tracer != nil && tracer.OnBlockHashRead != nil { + tracer.OnBlockHashRead(num64, res) + } num.SetBytes(res[:]) } else { num.Clear() diff --git a/core/vm/interface.go b/core/vm/interface.go index 3488526fc4..0d7862a66e 100644 --- a/core/vm/interface.go +++ b/core/vm/interface.go @@ -39,7 +39,7 @@ type StateDB interface { GetBalance(common.Address) *uint256.Int GetNonce(common.Address) uint64 - SetNonce(common.Address, uint64) + SetNonce(common.Address, uint64, tracing.NonceChangeReason) GetCodeHash(common.Address) common.Hash GetCode(common.Address) []byte diff --git a/core/vm/runtime/runtime_test.go b/core/vm/runtime/runtime_test.go index 44f5bc8273..bde230b6da 100644 --- a/core/vm/runtime/runtime_test.go +++ b/core/vm/runtime/runtime_test.go @@ -414,7 +414,7 @@ func benchmarkNonModifyingCode(gas uint64, code []byte, name string, tracerCode eoa := common.HexToAddress("E0") { cfg.State.CreateAccount(eoa) - cfg.State.SetNonce(eoa, 100) + cfg.State.SetNonce(eoa, 100, tracing.NonceChangeUnspecified) } reverting := common.HexToAddress("EE") { diff --git a/eth/tracers/live/noop.go b/eth/tracers/live/noop.go index 46c5700d25..f3def85606 100644 --- a/eth/tracers/live/noop.go +++ b/eth/tracers/live/noop.go @@ -57,6 +57,7 @@ func newNoopTracer(_ json.RawMessage) (*tracing.Hooks, error) { OnCodeChange: t.OnCodeChange, OnStorageChange: t.OnStorageChange, OnLog: t.OnLog, + OnBlockHashRead: t.OnBlockHashRead, }, nil } @@ -108,5 +109,7 @@ func (t *noop) OnLog(l *types.Log) { } +func (t *noop) OnBlockHashRead(number uint64, hash common.Hash) {} + func (t *noop) OnGasChange(old, new uint64, reason tracing.GasChangeReason) { } diff --git a/internal/ethapi/override/override.go b/internal/ethapi/override/override.go index 70b6210275..f6a8a94ffd 100644 --- a/internal/ethapi/override/override.go +++ b/internal/ethapi/override/override.go @@ -86,7 +86,7 @@ func (diff *StateOverride) Apply(statedb *state.StateDB, precompiles vm.Precompi } // Override account nonce. if account.Nonce != nil { - statedb.SetNonce(addr, uint64(*account.Nonce)) + statedb.SetNonce(addr, uint64(*account.Nonce), tracing.NonceChangeUnspecified) } // Override account(contract) code. if account.Code != nil { diff --git a/tests/state_test_util.go b/tests/state_test_util.go index 24caf41ed9..a22e470ad8 100644 --- a/tests/state_test_util.go +++ b/tests/state_test_util.go @@ -512,7 +512,7 @@ func MakePreState(db ethdb.Database, accounts types.GenesisAlloc, snapshotter bo statedb, _ := state.New(types.EmptyRootHash, sdb) for addr, a := range accounts { statedb.SetCode(addr, a.Code) - statedb.SetNonce(addr, a.Nonce) + statedb.SetNonce(addr, a.Nonce, tracing.NonceChangeUnspecified) statedb.SetBalance(addr, uint256.MustFromBig(a.Balance), tracing.BalanceChangeUnspecified) for k, v := range a.Storage { statedb.SetState(addr, k, v)