Skip to content

Commit

Permalink
core/tracing: state journal wrapper (#30441)
Browse files Browse the repository at this point in the history
Here we add some more changes for live tracing API v1.1:

- Hook `OnSystemCallStartV2` was introduced with `VMContext` as parameter.
- Hook `OnBlockHashRead` was introduced.
- `GetCodeHash` was added to the state interface
- The new `WrapWithJournal` construction helps with tracking EVM reverts in the tracer.

---------

Co-authored-by: Felix Lange <[email protected]>
  • Loading branch information
s1na and fjl authored Feb 5, 2025
1 parent ed1d46b commit aaaf01d
Show file tree
Hide file tree
Showing 23 changed files with 709 additions and 32 deletions.
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)

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

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

0 comments on commit aaaf01d

Please sign in to comment.