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)