Skip to content

core/tracing: state journal wrapper #30441

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 61 commits into from
Feb 5, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
8659e68
core/tracing: add vm context to system call hook
s1na Aug 26, 2024
b4e0174
core/tracing: add GetCodeHash to statedb interface
s1na Aug 26, 2024
f670a7f
core/tracing: emit state change events for journal reverts
s1na Aug 26, 2024
cf873c3
core/tracing: add hook for reverted out blocks
s1na Aug 26, 2024
365b715
log selfdestructs balance revert
s1na Aug 27, 2024
aac4024
Add state read hooks
s1na Sep 2, 2024
dbe5f83
add tracing journal
s1na Sep 15, 2024
b87c4fe
update changelog
s1na Sep 16, 2024
702a42f
fix indent
s1na Sep 16, 2024
c915bed
add block hash read hook
s1na Oct 3, 2024
838fc25
resolve merge conflict
s1na Oct 4, 2024
1cc58cf
fix code and nonce param order
s1na Oct 4, 2024
3c58155
update test
s1na Oct 4, 2024
501f302
pass-through non-journaled hooks
s1na Oct 5, 2024
1a64297
missed two hooks
s1na Oct 5, 2024
1862333
fix journal cur rev Id
s1na Oct 8, 2024
6650000
add note on balanceChangeRevert reason
s1na Oct 9, 2024
d9de74e
refactor WrapWithJournal to use reflection
s1na Oct 9, 2024
d2ba76f
add license to journal_test
s1na Oct 10, 2024
a2ca5f8
add desc for revert change reason
s1na Oct 10, 2024
85a85d0
add OnSystemCallStartV2
s1na Oct 14, 2024
2754b41
drop OnReorg
s1na Oct 17, 2024
92337d8
rm newline
s1na Oct 17, 2024
36b4194
Merge branch 'master' into tracing/v1.1
s1na Oct 17, 2024
efed5a6
fix OnTxEnd
s1na Oct 24, 2024
ea92ef4
Merge branch 'master' into tracing/v1.1
s1na Oct 24, 2024
fbd1d19
fix pre-post block process fns
s1na Oct 24, 2024
0f005af
mv read hooks to statedb_hooked
s1na Oct 24, 2024
5e4d6b8
add whitespace
s1na Oct 24, 2024
4d2fb0e
update changelog
s1na Oct 24, 2024
b37f2ac
Merge branch 'master' into tracing/v1.1
s1na Oct 25, 2024
a0f7cd6
Add test for all underlying hooks being called
s1na Oct 25, 2024
87582a4
Merge branch 'master' into tracing/v1.1
s1na Nov 11, 2024
6e4d14c
resolve merge conflict
s1na Nov 26, 2024
553f023
handle creation nonce in journal
s1na Nov 26, 2024
be93d72
Merge branch 'master' into tracing/v1.1
s1na Dec 2, 2024
1dda30d
Merge branch 'master' into tracing/v1.1
s1na Dec 3, 2024
4acea3b
rm OnCodeSizeRead
s1na Dec 3, 2024
018df6b
rm onreorg type
s1na Dec 3, 2024
60b2222
wrapper func for OnSystemCallStart
s1na Dec 3, 2024
6c56ea5
update changelog
s1na Dec 3, 2024
f4cf2a5
Merge branch 'master' into tracing/v1.1
s1na Dec 4, 2024
7fb2688
run go generate
s1na Dec 4, 2024
3228063
rm read hooks
s1na Dec 9, 2024
95b82cf
lint issue
s1na Dec 9, 2024
de48d55
fix changelog
s1na Dec 10, 2024
9cae376
un-expose hooks copy
s1na Dec 10, 2024
bf51dde
Merge branch 'master' into tracing/v1.1
s1na Dec 16, 2024
459c50f
refactor copy
s1na Feb 4, 2025
831524a
Merge branch 'master' into tracing/v1.1
fjl Feb 4, 2025
6f5e74b
Use nonce reason in journal
s1na Feb 4, 2025
bca2e2c
resolve conflict
s1na Feb 4, 2025
8a2230e
resolve conflict
s1na Feb 4, 2025
59a5022
fix test
s1na Feb 4, 2025
4ba05e9
core/tracing: add logging in journal test
fjl Feb 4, 2025
2795c0e
core/tracing: simplify journal implementation
fjl Feb 4, 2025
51720dc
core/tracing: further improve journal tests
fjl Feb 4, 2025
4787f31
core/tracing: remove Hooks.copy
fjl Feb 4, 2025
8a44029
core/tracing: add note about WrapWithJournal in comments
fjl Feb 4, 2025
eaacae4
core/tracing: add a package-level doc comment
fjl Feb 4, 2025
93432fc
license year
s1na Feb 5, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cmd/evm/internal/t8ntool/execution.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
4 changes: 2 additions & 2 deletions core/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand All @@ -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)
}
Expand Down
2 changes: 1 addition & 1 deletion core/state/statedb.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion core/state/statedb_fuzz_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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),
},
Expand Down
11 changes: 7 additions & 4 deletions core/state/statedb_hooked.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}

Expand Down
4 changes: 2 additions & 2 deletions core/state/statedb_hooked_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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"))
Expand Down
6 changes: 3 additions & 3 deletions core/state/statedb_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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}))
}
Expand Down Expand Up @@ -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})
Expand Down Expand Up @@ -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),
},
Expand Down
4 changes: 2 additions & 2 deletions core/state_transition.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
Expand Down
47 changes: 47 additions & 0 deletions core/tracing/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
5 changes: 3 additions & 2 deletions core/tracing/gen_balance_change_reason_stringer.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

47 changes: 47 additions & 0 deletions core/tracing/hooks.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 <http://www.gnu.org/licenses/>.

// 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 (
Expand Down Expand Up @@ -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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The V2 isn't helpful, we should use NonceChangeHokWithReason. Function names should be explict in what they do.

Copy link
Contributor

@fjl fjl Feb 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have chosen this naming scheme explicitly. The intent is communicating which version of the hook is the newest one. When we introduce a new hook version, the old one becomes deprecated and will eventually be unsupported. Also, if we were to introduce another revision of this hook, would it be called NonceChangeHookWithReasonAndBellsAndWhistles? The *Vx naming scheme avoids this problem.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When the old one is removed, you can remove the WithReason part, but in the meantime, you know at a glance why the two methods differ. I don't think there's a big risk of that function being rewritten many times and, therefore, having the names getting longer and longer. But sure, just giving my opinion on what could make the code readable, not going to hold the release for that one 🤷

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We cannot rename the function because it is a stable, user-exposed API. If we could just change it, we wouldn't go through the trouble of having multiple versions of the hook.


// 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)

Expand All @@ -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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this needed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The use-case is to have access to the headers of hashes that are accessed by the EVM. Alternative would be if we added a GetHeaderByHash method somewhere. But getting the hash from OnOpcode is also tricky since the hash will be put on the stack after OnOpcode is invoked.

)

type Hooks struct {
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
)
Loading