Skip to content
Merged
Show file tree
Hide file tree
Changes from 18 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
4 changes: 4 additions & 0 deletions core/blockchain.go
Original file line number Diff line number Diff line change
Expand Up @@ -2358,6 +2358,10 @@ func (bc *BlockChain) reorg(oldHead *types.Header, newHead *types.Block) error {
if len(rebirthLogs) > 0 {
bc.logsFeed.Send(rebirthLogs)
}

if bc.logger != nil && bc.logger.OnReorg != nil {
bc.logger.OnReorg(oldChain)
}
return nil
}

Expand Down
49 changes: 36 additions & 13 deletions core/state/statedb.go
Original file line number Diff line number Diff line change
Expand Up @@ -317,21 +317,28 @@ func (s *StateDB) Empty(addr common.Address) bool {

// GetBalance retrieves the balance from the given address or 0 if object not found
func (s *StateDB) GetBalance(addr common.Address) *uint256.Int {
bal := common.U2560
stateObject := s.getStateObject(addr)
if stateObject != nil {
return stateObject.Balance()
bal = stateObject.Balance()
}
return common.U2560
if s.logger != nil && s.logger.OnBalanceRead != nil {
s.logger.OnBalanceRead(addr, bal.ToBig())
}
return bal
}

// GetNonce retrieves the nonce from the given address or 0 if object not found
func (s *StateDB) GetNonce(addr common.Address) uint64 {
var nonce uint64
stateObject := s.getStateObject(addr)
if stateObject != nil {
return stateObject.Nonce()
nonce = stateObject.Nonce()
}

return 0
if s.logger != nil && s.logger.OnNonceRead != nil {
s.logger.OnNonceRead(addr, nonce)
}
return nonce
}

// GetStorageRoot retrieves the storage root from the given address or empty
Expand All @@ -350,36 +357,52 @@ func (s *StateDB) TxIndex() int {
}

func (s *StateDB) GetCode(addr common.Address) []byte {
var code []byte
stateObject := s.getStateObject(addr)
if stateObject != nil {
return stateObject.Code()
code = stateObject.Code()
}
return nil
if s.logger != nil && s.logger.OnCodeRead != nil {
s.logger.OnCodeRead(addr, code)
}
return code
}

func (s *StateDB) GetCodeSize(addr common.Address) int {
var size int
stateObject := s.getStateObject(addr)
if stateObject != nil {
return stateObject.CodeSize()
size = stateObject.CodeSize()
}
if s.logger != nil && s.logger.OnCodeSizeRead != nil {
s.logger.OnCodeSizeRead(addr, size)
}
return 0
return size
}

func (s *StateDB) GetCodeHash(addr common.Address) common.Hash {
hash := common.Hash{}
stateObject := s.getStateObject(addr)
if stateObject != nil {
return common.BytesToHash(stateObject.CodeHash())
hash = common.BytesToHash(stateObject.CodeHash())
}
return common.Hash{}
if s.logger != nil && s.logger.OnCodeHashRead != nil {
s.logger.OnCodeHashRead(addr, hash)
}
return hash
}

// GetState retrieves the value associated with the specific key.
func (s *StateDB) GetState(addr common.Address, hash common.Hash) common.Hash {
val := common.Hash{}
stateObject := s.getStateObject(addr)
if stateObject != nil {
return stateObject.GetState(hash)
val = stateObject.GetState(hash)
}
return common.Hash{}
if s.logger != nil && s.logger.OnStorageRead != nil {
s.logger.OnStorageRead(addr, hash, val)
}
return val
}

// GetCommittedState retrieves the value associated with the specific key
Expand Down
4 changes: 2 additions & 2 deletions core/state_processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *commo
func ProcessBeaconBlockRoot(beaconRoot common.Hash, vmenv *vm.EVM, statedb *state.StateDB) {
if tracer := vmenv.Config.Tracer; tracer != nil {
if tracer.OnSystemCallStart != nil {
tracer.OnSystemCallStart()
tracer.OnSystemCallStart(vmenv.GetVMContext())
}
if tracer.OnSystemCallEnd != nil {
defer tracer.OnSystemCallEnd()
Expand Down Expand Up @@ -238,7 +238,7 @@ func ProcessBeaconBlockRoot(beaconRoot common.Hash, vmenv *vm.EVM, statedb *stat
func ProcessParentBlockHash(prevHash common.Hash, vmenv *vm.EVM, statedb *state.StateDB) {
if tracer := vmenv.Config.Tracer; tracer != nil {
if tracer.OnSystemCallStart != nil {
tracer.OnSystemCallStart()
tracer.OnSystemCallStart(vmenv.GetVMContext())
}
if tracer.OnSystemCallEnd != nil {
defer tracer.OnSystemCallEnd()
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 changes are state read hooks as well a state journaling library which emits events when a call is reverted.

### New methods

- `OnReorg(reverted []*types.Block)`: This hook is called when a reorg is detected. The `reverted` slice contains the blocks that are no longer part of the canonical chain.
Copy link
Member

Choose a reason for hiding this comment

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

Here types block is very very heavy. You should at most pass headers and allow chain access to pull the blocks on demand (chain access in someconstructor, ha)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

On second thought what is the issue? it is a slice so passed by reference and the memory can be freed as soon as OnReorg processing is done.

Copy link
Member

Choose a reason for hiding this comment

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

Ugh, this is annoying. So reorg in the blockchain at some point in the past used to collect blocks. Turned out that sometimes it became insanely heavy and we've switched so it operates on headers. I guess later someone refactored it back to operate on blocks again. This is an issue when you do setHead or any similar operation; of even if finality fails for a while and you have blocks reorging back and forth. It's very very bad to pull all the block in from disk IMO.

CC @holiman @rjl493456442 ?

Copy link
Contributor

Choose a reason for hiding this comment

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

I agree. I don't particularly recall switching from headers to blocks....

- `OnBalanceRead(addr common.Address, balance *big.Int)`: This hook is called when an account balance is read.
- `OnNonceRead(addr common.Address, nonce uint64)`: This hook is called when an account nonce is read.
- `OnCodeRead(addr common.Address, code []byte)`: This hook is called when an account code is read.
- `OnCodeSizeRead(addr common.Address, size int)`: This hook is called when an account code size is read.
- `OnCodeHashRead(addr common.Address, codeHash common.Hash)`: This hook is called when an account code hash is read.
- `OnStorageRead(addr common.Address, slot common.Hash, value common.Hash)`: This hook is called when an account storage slot is read.
- `OnBlockHashRead(blockNum uint64, hash common.Hash)`: This hook is called when a block hash is read by EVM.

### Modified methods

- `OnSystemCallStart()` -> `OnSystemCallStart(vm *VMContext)`. This allows access to EVM context during system calls.

### 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`
- `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
56 changes: 55 additions & 1 deletion core/tracing/hooks.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package tracing

import (
"math/big"
"reflect"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
Expand All @@ -42,6 +43,7 @@ type StateDB interface {
GetBalance(common.Address) *uint256.Int
GetNonce(common.Address) uint64
GetCode(common.Address) []byte
GetCodeHash(common.Address) common.Hash
GetState(common.Address, common.Hash) common.Hash
Exist(common.Address) bool
GetRefund() uint64
Expand Down Expand Up @@ -134,6 +136,9 @@ type (
// GenesisBlockHook is called when the genesis block is being processed.
GenesisBlockHook = func(genesis *types.Block, alloc types.GenesisAlloc)

// ReorgHook is called when a segment of the chain is reverted.
ReorgHook = func(reverted []*types.Block)
Copy link
Member

Choose a reason for hiding this comment

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

The consensus was to drop Reorg hook for now because it's not clear what the best API would be. We can shit the rest and then iterate on this one with whoever wants to use it before comitting.


// OnSystemCallStartHook is called when a system call is about to be executed. Today,
// this hook is invoked when the EIP-4788 system call is about to be executed to set the
// beacon block root.
Expand All @@ -143,7 +148,7 @@ type (
//
// Note that system call happens outside normal transaction execution, so the `OnTxStart/OnTxEnd` hooks
// will not be invoked.
OnSystemCallStartHook = func()
OnSystemCallStartHook = func(vm *VMContext)

// OnSystemCallEndHook is called when a system call has finished executing. Today,
// this hook is invoked when the EIP-4788 system call is about to be executed to set the
Expand All @@ -168,6 +173,27 @@ type (

// LogHook is called when a log is emitted.
LogHook = func(log *types.Log)

// BalanceReadHook is called when EVM reads the balance of an account.
BalanceReadHook = func(addr common.Address, bal *big.Int)

// NonceReadHook is called when EVM reads the nonce of an account.
NonceReadHook = func(addr common.Address, nonce uint64)

// CodeReadHook is called when EVM reads the code of an account.
CodeReadHook = func(addr common.Address, code []byte)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Open question: should we add codeHash here to be consistent with OnCodeChange?


// CodeSizeReadHook is called when EVM reads the code size of an account.
CodeSizeReadHook = func(addr common.Address, size int)

// CodeHashReadHook is called when EVM reads the code hash of an account.
CodeHashReadHook = func(addr common.Address, hash common.Hash)

// StorageReadHook is called when EVM reads a storage slot of an account.
StorageReadHook = func(addr common.Address, slot, value common.Hash)

// 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 @@ -186,6 +212,7 @@ type Hooks struct {
OnBlockEnd BlockEndHook
OnSkippedBlock SkippedBlockHook
OnGenesisBlock GenesisBlockHook
OnReorg ReorgHook
OnSystemCallStart OnSystemCallStartHook
OnSystemCallEnd OnSystemCallEndHook
// State events
Expand All @@ -194,6 +221,30 @@ type Hooks struct {
OnCodeChange CodeChangeHook
OnStorageChange StorageChangeHook
OnLog LogHook
// State reads
OnBalanceRead BalanceReadHook
OnNonceRead NonceReadHook
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Question from triage: how exactly is OnNonceRead used?

OnCodeRead CodeReadHook
OnCodeSizeRead CodeSizeReadHook
OnCodeHashRead CodeHashReadHook
OnStorageRead StorageReadHook
// Block hash read
OnBlockHashRead BlockHashReadHook
}

// Copy creates a new Hooks instance with all implemented hooks copied from the original.
func (h *Hooks) Copy() *Hooks {
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 method needed? It's not obvious to me what the side-effects are. Typically, the hooks might be closures, and the closures are still referenced as if they were not copied.

func TestHooks(t *testing.T) {
	counter := 0
	a := &Hooks{
		OnClose: func() {
			counter++
		},
	}
	a.OnClose()
	t.Logf("counter is %d", counter)
	a.Copy().OnClose()
	t.Logf("counter is %d", counter)
}

outputs:

    hooks_test.go:13: counter is 1
    hooks_test.go:15: counter is 2

I'm curious why you'd ever need to use this Copy method.

Copy link
Contributor

Choose a reason for hiding this comment

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

Is it because you want to copy all, but not have to specify all manually?

Copy link
Contributor Author

@s1na s1na Dec 10, 2024

Choose a reason for hiding this comment

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

Typically, the hooks might be closures, and the closures are still referenced as if they were not copied.

Right you are correct I had not foreseen that. After thinking a bit, it feels like for my use-case that is fine. Essentially the clone will replace some of the methods to add some pre-processing logic. The rest are supposed to execute as in the original tracer.

I have un-exported the Copy method to avoid people to shoot themselves in the foot and added a comment to clarify this point.

Edit: right what I want is to add the journal in front of the tracer and process some of the hooks first before proxying back to tracer. And this without having to iterate the list of all hooks which I find very error-prone. I have already had to fix bugs because of missing some hook in there.

copied := &Hooks{}
srcValue := reflect.ValueOf(h).Elem()
dstValue := reflect.ValueOf(copied).Elem()

for i := 0; i < srcValue.NumField(); i++ {
field := srcValue.Field(i)
if !field.IsNil() {
dstValue.Field(i).Set(field)
}
}
return copied
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this not equivalent to

copied := *h
return &copied

}

// BalanceChangeReason is used to indicate the reason for a balance change, useful
Expand Down Expand Up @@ -245,6 +296,9 @@ 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.
BalanceChangeRevert BalanceChangeReason = 15
)

// GasChangeReason is used to indicate the reason for a gas change, useful
Expand Down
Loading