From 8659e68ad58cd4308fee1aa5f5915b9adfb1b01f Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Mon, 26 Aug 2024 15:45:56 +0200 Subject: [PATCH 01/48] core/tracing: add vm context to system call hook --- core/state_processor.go | 4 ++-- core/tracing/hooks.go | 2 +- eth/tracers/logger/logger_json.go | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/core/state_processor.go b/core/state_processor.go index 44224958ddce..6972f6d9f388 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -183,7 +183,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() @@ -212,7 +212,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() diff --git a/core/tracing/hooks.go b/core/tracing/hooks.go index aa66dc49ff09..43a72162e8d6 100644 --- a/core/tracing/hooks.go +++ b/core/tracing/hooks.go @@ -142,7 +142,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 diff --git a/eth/tracers/logger/logger_json.go b/eth/tracers/logger/logger_json.go index 797f7ac65821..de021e74bef8 100644 --- a/eth/tracers/logger/logger_json.go +++ b/eth/tracers/logger/logger_json.go @@ -127,7 +127,7 @@ func (l *jsonLogger) OnOpcode(pc uint64, op byte, gas, cost uint64, scope tracin l.encoder.Encode(log) } -func (l *jsonLogger) onSystemCallStart() { +func (l *jsonLogger) onSystemCallStart(_ *tracing.VMContext) { // Process no events while in system call. hooks := *l.hooks *l.hooks = tracing.Hooks{ From b4e01743eaaf28236751367ac9f31b74063d3d4c Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Mon, 26 Aug 2024 15:47:21 +0200 Subject: [PATCH 02/48] core/tracing: add GetCodeHash to statedb interface --- core/tracing/hooks.go | 1 + 1 file changed, 1 insertion(+) diff --git a/core/tracing/hooks.go b/core/tracing/hooks.go index 43a72162e8d6..0c5e512f3e90 100644 --- a/core/tracing/hooks.go +++ b/core/tracing/hooks.go @@ -41,6 +41,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 From f670a7fd93e6ddd542727ac8fad24836ba323d9e Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Mon, 26 Aug 2024 20:59:59 +0200 Subject: [PATCH 03/48] core/tracing: emit state change events for journal reverts --- core/state/journal.go | 9 +++++---- core/state/state_object.go | 18 ++++++++++++++++++ core/tracing/hooks.go | 3 +++ 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/core/state/journal.go b/core/state/journal.go index ad4a654fc6a2..49d29cce2899 100644 --- a/core/state/journal.go +++ b/core/state/journal.go @@ -20,6 +20,7 @@ import ( "maps" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/tracing" "github.com/holiman/uint256" ) @@ -233,7 +234,7 @@ func (ch touchChange) copy() journalEntry { } func (ch balanceChange) revert(s *StateDB) { - s.getStateObject(*ch.account).setBalance(ch.prev) + s.getStateObject(*ch.account).setBalanceLogged(ch.prev, tracing.BalanceChangeRevert) } func (ch balanceChange) dirtied() *common.Address { @@ -248,7 +249,7 @@ func (ch balanceChange) copy() journalEntry { } func (ch nonceChange) revert(s *StateDB) { - s.getStateObject(*ch.account).setNonce(ch.prev) + s.getStateObject(*ch.account).setNonceLogged(ch.prev) } func (ch nonceChange) dirtied() *common.Address { @@ -263,7 +264,7 @@ func (ch nonceChange) copy() journalEntry { } func (ch codeChange) revert(s *StateDB) { - s.getStateObject(*ch.account).setCode(common.BytesToHash(ch.prevhash), ch.prevcode) + s.getStateObject(*ch.account).setCodeLogged(common.BytesToHash(ch.prevhash), ch.prevcode) } func (ch codeChange) dirtied() *common.Address { @@ -279,7 +280,7 @@ func (ch codeChange) copy() journalEntry { } func (ch storageChange) revert(s *StateDB) { - s.getStateObject(*ch.account).setState(ch.key, ch.prevvalue, ch.origvalue) + s.getStateObject(*ch.account).setStateLogged(ch.key, ch.prevvalue, ch.origvalue) } func (ch storageChange) dirtied() *common.Address { diff --git a/core/state/state_object.go b/core/state/state_object.go index 880b715b4b37..fc0f6ddbf482 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -257,6 +257,11 @@ func (s *stateObject) SetState(key, value common.Hash) { prevvalue: prev, origvalue: origin, }) + s.setStateLogged(key, value, origin) +} + +func (s *stateObject) setStateLogged(key, value, origin common.Hash) { + prev, _ := s.getState(key) if s.db.logger != nil && s.db.logger.OnStorageChange != nil { s.db.logger.OnStorageChange(s.address, key, prev, value) } @@ -514,6 +519,10 @@ func (s *stateObject) SetBalance(amount *uint256.Int, reason tracing.BalanceChan account: &s.address, prev: new(uint256.Int).Set(s.data.Balance), }) + s.setBalanceLogged(amount, reason) +} + +func (s *stateObject) setBalanceLogged(amount *uint256.Int, reason tracing.BalanceChangeReason) { if s.db.logger != nil && s.db.logger.OnBalanceChange != nil { s.db.logger.OnBalanceChange(s.address, s.Balance().ToBig(), amount.ToBig(), reason) } @@ -595,6 +604,11 @@ func (s *stateObject) SetCode(codeHash common.Hash, code []byte) { prevhash: s.CodeHash(), prevcode: prevcode, }) + s.setCodeLogged(codeHash, code) +} + +func (s *stateObject) setCodeLogged(codeHash common.Hash, code []byte) { + prevcode := s.Code() if s.db.logger != nil && s.db.logger.OnCodeChange != nil { s.db.logger.OnCodeChange(s.address, common.BytesToHash(s.CodeHash()), prevcode, codeHash, code) } @@ -612,6 +626,10 @@ func (s *stateObject) SetNonce(nonce uint64) { account: &s.address, prev: s.data.Nonce, }) + s.setNonceLogged(nonce) +} + +func (s *stateObject) setNonceLogged(nonce uint64) { if s.db.logger != nil && s.db.logger.OnNonceChange != nil { s.db.logger.OnNonceChange(s.address, s.data.Nonce, nonce) } diff --git a/core/tracing/hooks.go b/core/tracing/hooks.go index 0c5e512f3e90..9019724a8cb5 100644 --- a/core/tracing/hooks.go +++ b/core/tracing/hooks.go @@ -245,6 +245,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 From cf873c376738b8ee3b2ae53a100f2ce5cf5683d9 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Mon, 26 Aug 2024 21:32:08 +0200 Subject: [PATCH 04/48] core/tracing: add hook for reverted out blocks --- core/blockchain.go | 4 ++++ core/tracing/hooks.go | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/core/blockchain.go b/core/blockchain.go index 05ebfd18b830..ee801972f8d4 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -2375,6 +2375,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 } diff --git a/core/tracing/hooks.go b/core/tracing/hooks.go index 9019724a8cb5..1a2b0c86a147 100644 --- a/core/tracing/hooks.go +++ b/core/tracing/hooks.go @@ -134,6 +134,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) + // 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. @@ -186,6 +189,7 @@ type Hooks struct { OnBlockEnd BlockEndHook OnSkippedBlock SkippedBlockHook OnGenesisBlock GenesisBlockHook + OnReorg ReorgHook OnSystemCallStart OnSystemCallStartHook OnSystemCallEnd OnSystemCallEndHook // State events From 365b715b88728cfeaed22401ea49f8a82da227b3 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Tue, 27 Aug 2024 08:46:51 +0200 Subject: [PATCH 05/48] log selfdestructs balance revert --- core/state/journal.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/state/journal.go b/core/state/journal.go index 49d29cce2899..6131e4c0a825 100644 --- a/core/state/journal.go +++ b/core/state/journal.go @@ -202,7 +202,7 @@ func (ch selfDestructChange) revert(s *StateDB) { obj := s.getStateObject(*ch.account) if obj != nil { obj.selfDestructed = ch.prev - obj.setBalance(ch.prevbalance) + obj.setBalanceLogged(ch.prevbalance, tracing.BalanceChangeRevert) } } From aac40243b71d3849be02651fedf05662e12be404 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Mon, 2 Sep 2024 10:08:09 +0200 Subject: [PATCH 06/48] Add state read hooks --- core/state/statedb.go | 49 +++++++++++++++++++++++++++++----------- core/tracing/hooks.go | 25 ++++++++++++++++++++ eth/tracers/live/noop.go | 21 +++++++++++++++++ 3 files changed, 82 insertions(+), 13 deletions(-) diff --git a/core/state/statedb.go b/core/state/statedb.go index 80a53dbb1772..db4d0b2b2ab4 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -331,21 +331,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 @@ -364,36 +371,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 diff --git a/core/tracing/hooks.go b/core/tracing/hooks.go index 1a2b0c86a147..8bd57903f5f4 100644 --- a/core/tracing/hooks.go +++ b/core/tracing/hooks.go @@ -171,6 +171,24 @@ 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) + + // 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) ) type Hooks struct { @@ -198,6 +216,13 @@ type Hooks struct { OnCodeChange CodeChangeHook OnStorageChange StorageChangeHook OnLog LogHook + // State reads + OnBalanceRead BalanceReadHook + OnNonceRead NonceReadHook + OnCodeRead CodeReadHook + OnCodeSizeRead CodeSizeReadHook + OnCodeHashRead CodeHashReadHook + OnStorageRead StorageReadHook } // BalanceChangeReason is used to indicate the reason for a balance change, useful diff --git a/eth/tracers/live/noop.go b/eth/tracers/live/noop.go index 7433c288408f..83ea74c24411 100644 --- a/eth/tracers/live/noop.go +++ b/eth/tracers/live/noop.go @@ -36,11 +36,18 @@ func newNoopTracer(_ json.RawMessage) (*tracing.Hooks, error) { OnBlockEnd: t.OnBlockEnd, OnSkippedBlock: t.OnSkippedBlock, OnGenesisBlock: t.OnGenesisBlock, + OnReorg: t.OnReorg, OnBalanceChange: t.OnBalanceChange, OnNonceChange: t.OnNonceChange, OnCodeChange: t.OnCodeChange, OnStorageChange: t.OnStorageChange, OnLog: t.OnLog, + OnBalanceRead: t.OnBalanceRead, + OnNonceRead: t.OnNonceRead, + OnCodeRead: t.OnCodeRead, + OnCodeSizeRead: t.OnCodeSizeRead, + OnCodeHashRead: t.OnCodeHashRead, + OnStorageRead: t.OnStorageRead, }, nil } @@ -76,6 +83,8 @@ func (t *noop) OnBlockchainInit(chainConfig *params.ChainConfig) { func (t *noop) OnGenesisBlock(b *types.Block, alloc types.GenesisAlloc) { } +func (t *noop) OnReorg(reverted []*types.Block) {} + func (t *noop) OnBalanceChange(a common.Address, prev, new *big.Int, reason tracing.BalanceChangeReason) { } @@ -92,5 +101,17 @@ func (t *noop) OnLog(l *types.Log) { } +func (t *noop) OnBalanceRead(addr common.Address, bal *big.Int) {} + +func (t *noop) OnNonceRead(addr common.Address, nonce uint64) {} + +func (t *noop) OnCodeRead(addr common.Address, code []byte) {} + +func (t *noop) OnCodeSizeRead(addr common.Address, size int) {} + +func (t *noop) OnCodeHashRead(addr common.Address, hash common.Hash) {} + +func (t *noop) OnStorageRead(addr common.Address, slot, val common.Hash) {} + func (t *noop) OnGasChange(old, new uint64, reason tracing.GasChangeReason) { } From dbe5f83b0a53e073d430a4687b03d8e467e4dbeb Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Sun, 15 Sep 2024 22:18:39 +0200 Subject: [PATCH 07/48] add tracing journal --- core/state/journal.go | 11 +- core/state/state_object.go | 18 --- core/tracing/journal.go | 233 +++++++++++++++++++++++++++++++++++ core/tracing/journal_test.go | 68 ++++++++++ 4 files changed, 306 insertions(+), 24 deletions(-) create mode 100644 core/tracing/journal.go create mode 100644 core/tracing/journal_test.go diff --git a/core/state/journal.go b/core/state/journal.go index 6131e4c0a825..ad4a654fc6a2 100644 --- a/core/state/journal.go +++ b/core/state/journal.go @@ -20,7 +20,6 @@ import ( "maps" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/tracing" "github.com/holiman/uint256" ) @@ -202,7 +201,7 @@ func (ch selfDestructChange) revert(s *StateDB) { obj := s.getStateObject(*ch.account) if obj != nil { obj.selfDestructed = ch.prev - obj.setBalanceLogged(ch.prevbalance, tracing.BalanceChangeRevert) + obj.setBalance(ch.prevbalance) } } @@ -234,7 +233,7 @@ func (ch touchChange) copy() journalEntry { } func (ch balanceChange) revert(s *StateDB) { - s.getStateObject(*ch.account).setBalanceLogged(ch.prev, tracing.BalanceChangeRevert) + s.getStateObject(*ch.account).setBalance(ch.prev) } func (ch balanceChange) dirtied() *common.Address { @@ -249,7 +248,7 @@ func (ch balanceChange) copy() journalEntry { } func (ch nonceChange) revert(s *StateDB) { - s.getStateObject(*ch.account).setNonceLogged(ch.prev) + s.getStateObject(*ch.account).setNonce(ch.prev) } func (ch nonceChange) dirtied() *common.Address { @@ -264,7 +263,7 @@ func (ch nonceChange) copy() journalEntry { } func (ch codeChange) revert(s *StateDB) { - s.getStateObject(*ch.account).setCodeLogged(common.BytesToHash(ch.prevhash), ch.prevcode) + s.getStateObject(*ch.account).setCode(common.BytesToHash(ch.prevhash), ch.prevcode) } func (ch codeChange) dirtied() *common.Address { @@ -280,7 +279,7 @@ func (ch codeChange) copy() journalEntry { } func (ch storageChange) revert(s *StateDB) { - s.getStateObject(*ch.account).setStateLogged(ch.key, ch.prevvalue, ch.origvalue) + s.getStateObject(*ch.account).setState(ch.key, ch.prevvalue, ch.origvalue) } func (ch storageChange) dirtied() *common.Address { diff --git a/core/state/state_object.go b/core/state/state_object.go index fc0f6ddbf482..880b715b4b37 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -257,11 +257,6 @@ func (s *stateObject) SetState(key, value common.Hash) { prevvalue: prev, origvalue: origin, }) - s.setStateLogged(key, value, origin) -} - -func (s *stateObject) setStateLogged(key, value, origin common.Hash) { - prev, _ := s.getState(key) if s.db.logger != nil && s.db.logger.OnStorageChange != nil { s.db.logger.OnStorageChange(s.address, key, prev, value) } @@ -519,10 +514,6 @@ func (s *stateObject) SetBalance(amount *uint256.Int, reason tracing.BalanceChan account: &s.address, prev: new(uint256.Int).Set(s.data.Balance), }) - s.setBalanceLogged(amount, reason) -} - -func (s *stateObject) setBalanceLogged(amount *uint256.Int, reason tracing.BalanceChangeReason) { if s.db.logger != nil && s.db.logger.OnBalanceChange != nil { s.db.logger.OnBalanceChange(s.address, s.Balance().ToBig(), amount.ToBig(), reason) } @@ -604,11 +595,6 @@ func (s *stateObject) SetCode(codeHash common.Hash, code []byte) { prevhash: s.CodeHash(), prevcode: prevcode, }) - s.setCodeLogged(codeHash, code) -} - -func (s *stateObject) setCodeLogged(codeHash common.Hash, code []byte) { - prevcode := s.Code() if s.db.logger != nil && s.db.logger.OnCodeChange != nil { s.db.logger.OnCodeChange(s.address, common.BytesToHash(s.CodeHash()), prevcode, codeHash, code) } @@ -626,10 +612,6 @@ func (s *stateObject) SetNonce(nonce uint64) { account: &s.address, prev: s.data.Nonce, }) - s.setNonceLogged(nonce) -} - -func (s *stateObject) setNonceLogged(nonce uint64) { if s.db.logger != nil && s.db.logger.OnNonceChange != nil { s.db.logger.OnNonceChange(s.address, s.data.Nonce, nonce) } diff --git a/core/tracing/journal.go b/core/tracing/journal.go new file mode 100644 index 000000000000..75d83b47a0f6 --- /dev/null +++ b/core/tracing/journal.go @@ -0,0 +1,233 @@ +// Copyright 2024 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" + "sort" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" +) + +type revision struct { + id int + journalIndex int +} + +// 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 { + entries []entry + hooks *Hooks + + validRevisions []revision + nextRevisionId int + curRevisionId 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. + if hooks.OnBalanceChange == nil && hooks.OnNonceChange == nil && hooks.OnCodeChange == nil && hooks.OnStorageChange == nil { + return hooks, nil + } + var ( + j = &journal{entries: make([]entry, 0), hooks: hooks} + wrapped = &Hooks{ + OnTxEnd: j.OnTxEnd, + OnEnter: j.OnEnter, + OnExit: j.OnExit, + } + ) + if hooks.OnBalanceChange != nil { + wrapped.OnBalanceChange = j.OnBalanceChange + } + if hooks.OnNonceChange != nil { + wrapped.OnNonceChange = j.OnNonceChange + } + 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.validRevisions = j.validRevisions[:0] + j.nextRevisionId = 0 +} + +// snapshot returns an identifier for the current revision of the state. +func (j *journal) snapshot() int { + id := j.nextRevisionId + j.nextRevisionId++ + j.validRevisions = append(j.validRevisions, revision{id, j.length()}) + return id +} + +// revertToSnapshot reverts all state changes made since the given revision. +func (j *journal) revertToSnapshot(revid int, hooks *Hooks) { + // Find the snapshot in the stack of valid snapshots. + idx := sort.Search(len(j.validRevisions), func(i int) bool { + return j.validRevisions[i].id >= revid + }) + if idx == len(j.validRevisions) || j.validRevisions[idx].id != revid { + panic(fmt.Errorf("revision id %v cannot be reverted", revid)) + } + snapshot := j.validRevisions[idx].journalIndex + + // Replay the journal to undo changes and remove invalidated snapshots + j.revert(hooks, snapshot) + j.validRevisions = j.validRevisions[:idx] +} + +// revert undoes a batch of journaled modifications. +func (j *journal) revert(hooks *Hooks, snapshot int) { + for i := len(j.entries) - 1; i >= snapshot; i-- { + // Undo the changes made by the operation + j.entries[i].revert(hooks) + } + j.entries = j.entries[:snapshot] +} + +// length returns the current number of entries in the journal. +func (j *journal) length() int { + return len(j.entries) +} + +func (j *journal) OnTxEnd(receipt *types.Receipt, err error) { + j.reset() +} + +func (j *journal) OnEnter(depth int, typ byte, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { + j.curRevisionId = j.snapshot() + if j.hooks != nil && j.hooks.OnEnter != nil { + j.hooks.OnEnter(depth, typ, from, to, input, gas, value) + } +} + +func (j *journal) OnExit(depth int, output []byte, gasUsed uint64, err error, reverted bool) { + if reverted { + j.revertToSnapshot(j.curRevisionId, j.hooks) + } + j.curRevisionId-- + if j.hooks != nil && 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 != nil && j.hooks.OnBalanceChange != nil { + j.hooks.OnBalanceChange(addr, prev, new, reason) + } +} + +func (j *journal) OnNonceChange(addr common.Address, prev, new uint64) { + j.entries = append(j.entries, nonceChange{addr: addr, prev: prev, new: new}) + if j.hooks != nil && 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 != nil && j.hooks.OnCodeChange != nil { + j.hooks.OnCodeChange(addr, codeHash, code, prevCodeHash, prevCode) + } +} + +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 != nil && j.hooks.OnStorageChange != nil { + j.hooks.OnStorageChange(addr, slot, new, prev) + } +} + +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.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 000000000000..f9b613dbc6d9 --- /dev/null +++ b/core/tracing/journal_test.go @@ -0,0 +1,68 @@ +package tracing + +import ( + "errors" + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" +) + +type testTracer struct { + bal *big.Int + nonce uint64 +} + +func (t *testTracer) OnBalanceChange(addr common.Address, prev *big.Int, new *big.Int, reason BalanceChangeReason) { + t.bal = new +} + +func (t *testTracer) OnNonceChange(addr common.Address, prev uint64, new uint64) { + t.nonce = new +} + +func TestJournalIntegration(t *testing.T) { + tr := &testTracer{} + 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, nil, big.NewInt(100), BalanceChangeUnspecified) + wr.OnEnter(1, 0, addr, addr, nil, 1000, big.NewInt(0)) + wr.OnNonceChange(addr, 0, 1) + wr.OnBalanceChange(addr, big.NewInt(100), big.NewInt(200), BalanceChangeUnspecified) + wr.OnBalanceChange(addr, big.NewInt(200), big.NewInt(250), BalanceChangeUnspecified) + wr.OnExit(0, 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) + } +} + +func TestJournalTopRevert(t *testing.T) { + tr := &testTracer{} + 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.OnNonceChange(addr, 0, 1) + wr.OnBalanceChange(addr, big.NewInt(100), big.NewInt(200), BalanceChangeUnspecified) + wr.OnBalanceChange(addr, big.NewInt(200), big.NewInt(250), BalanceChangeUnspecified) + wr.OnExit(0, 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) + } +} From b87c4fe3427494b4ea36fb61e8023a11bb07b6a3 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Mon, 16 Sep 2024 13:26:43 +0200 Subject: [PATCH 08/48] update changelog --- core/tracing/CHANGELOG.md | 48 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/core/tracing/CHANGELOG.md b/core/tracing/CHANGELOG.md index cddc728fc0f1..85d09bb1a0a8 100644 --- a/core/tracing/CHANGELOG.md +++ b/core/tracing/CHANGELOG.md @@ -2,6 +2,52 @@ 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. +- `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. + +### 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` +- `OnNonceChange` +- `OnCodeChange` +- `OnStorageChange` + ## [v1.14.3] There have been minor backwards-compatible changes to the tracing interface to explicitly mark the execution of **system** contracts. As of now the only system call updates the parent beacon block root as per [EIP-4788](https://eips.ethereum.org/EIPS/eip-4788). Other system calls are being considered for the future hardfork. @@ -75,6 +121,6 @@ The hooks `CaptureStart` and `CaptureEnd` have been removed. These hooks signale - `CaptureState` -> `OnOpcode(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error)`. `op` is of type `byte` which can be cast to `vm.OpCode` when necessary. A `*vm.ScopeContext` is not passed anymore. It is replaced by `tracing.OpContext` which offers access to the memory, stack and current contract. - `CaptureFault` -> `OnFault(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, depth int, err error)`. Similar to above. -[unreleased]: https://github.com/ethereum/go-ethereum/compare/v1.14.0...master +[unreleased]: https://github.com/ethereum/go-ethereum/compare/v1.14.8...master [v1.14.0]: https://github.com/ethereum/go-ethereum/releases/tag/v1.14.0 [v1.14.3]: https://github.com/ethereum/go-ethereum/releases/tag/v1.14.3 From 702a42feea70084f883743a57daca92789cfdf97 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Mon, 16 Sep 2024 13:29:10 +0200 Subject: [PATCH 09/48] fix indent --- core/tracing/CHANGELOG.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/tracing/CHANGELOG.md b/core/tracing/CHANGELOG.md index 85d09bb1a0a8..4b22d3076f10 100644 --- a/core/tracing/CHANGELOG.md +++ b/core/tracing/CHANGELOG.md @@ -32,11 +32,11 @@ Tracers receive state changes events from the node. The tracer was so far expect ```go func init() { tracers.LiveDirectory.Register("test", func (cfg json.RawMessage) (*tracing.Hooks, error) { - hooks, err := newTestTracer(cfg) + hooks, err := newTestTracer(cfg) if err != nil { - return nil, err + return nil, err } - return tracing.WrapWithJournal(hooks) + return tracing.WrapWithJournal(hooks) }) } ``` From c915bed5e8277bd731171d3bf0de21b49749dcbd Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Thu, 3 Oct 2024 14:07:30 +0200 Subject: [PATCH 10/48] add block hash read hook --- core/tracing/hooks.go | 5 +++++ core/vm/instructions.go | 3 +++ eth/tracers/live/noop.go | 3 +++ 3 files changed, 11 insertions(+) diff --git a/core/tracing/hooks.go b/core/tracing/hooks.go index 8bd57903f5f4..59e3f92a9de9 100644 --- a/core/tracing/hooks.go +++ b/core/tracing/hooks.go @@ -189,6 +189,9 @@ type ( // 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) ) type Hooks struct { @@ -223,6 +226,8 @@ type Hooks struct { OnCodeSizeRead CodeSizeReadHook OnCodeHashRead CodeHashReadHook OnStorageRead StorageReadHook + // Block hash read + OnBlockHashRead BlockHashReadHook } // BalanceChangeReason is used to indicate the reason for a balance change, useful diff --git a/core/vm/instructions.go b/core/vm/instructions.go index 35d6393fba0e..40b26c1cab4d 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -455,6 +455,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/eth/tracers/live/noop.go b/eth/tracers/live/noop.go index 83ea74c24411..614a2c554b18 100644 --- a/eth/tracers/live/noop.go +++ b/eth/tracers/live/noop.go @@ -48,6 +48,7 @@ func newNoopTracer(_ json.RawMessage) (*tracing.Hooks, error) { OnCodeSizeRead: t.OnCodeSizeRead, OnCodeHashRead: t.OnCodeHashRead, OnStorageRead: t.OnStorageRead, + OnBlockHashRead: t.OnBlockHashRead, }, nil } @@ -113,5 +114,7 @@ func (t *noop) OnCodeHashRead(addr common.Address, hash common.Hash) {} func (t *noop) OnStorageRead(addr common.Address, slot, val common.Hash) {} +func (t *noop) OnBlockHashRead(number uint64, hash common.Hash) {} + func (t *noop) OnGasChange(old, new uint64, reason tracing.GasChangeReason) { } From 1cc58cf42e06c35bafbe0b518d1accf2f1b6d1c1 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Fri, 4 Oct 2024 20:54:32 +0200 Subject: [PATCH 11/48] fix code and nonce param order --- core/tracing/journal.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/tracing/journal.go b/core/tracing/journal.go index 75d83b47a0f6..4a4d6be4c16b 100644 --- a/core/tracing/journal.go +++ b/core/tracing/journal.go @@ -168,14 +168,14 @@ func (j *journal) OnCodeChange(addr common.Address, prevCodeHash common.Hash, pr newCode: code, }) if j.hooks != nil && j.hooks.OnCodeChange != nil { - j.hooks.OnCodeChange(addr, codeHash, code, prevCodeHash, prevCode) + 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 != nil && j.hooks.OnStorageChange != nil { - j.hooks.OnStorageChange(addr, slot, new, prev) + j.hooks.OnStorageChange(addr, slot, prev, new) } } From 3c581556ab954100f34db60c3d78a70c11cbaf53 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Fri, 4 Oct 2024 21:01:39 +0200 Subject: [PATCH 12/48] update test --- core/tracing/journal_test.go | 38 ++++++++++++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/core/tracing/journal_test.go b/core/tracing/journal_test.go index f9b613dbc6d9..8681817b171b 100644 --- a/core/tracing/journal_test.go +++ b/core/tracing/journal_test.go @@ -9,8 +9,10 @@ import ( ) type testTracer struct { - bal *big.Int - nonce uint64 + 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) { @@ -21,20 +23,39 @@ func (t *testTracer) OnNonceChange(addr common.Address, prev uint64, new uint64) t.nonce = new } +func (t *testTracer) OnCodeChange(addr common.Address, prevCodeHash common.Hash, prevCode []byte, codeHash common.Hash, code []byte) { + t.code = code +} + +func (t *testTracer) OnStorageChange(addr common.Address, slot common.Hash, prev common.Hash, new common.Hash) { + 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{} - wr, err := WrapWithJournal(&Hooks{OnBalanceChange: tr.OnBalanceChange, OnNonceChange: tr.OnNonceChange}) + 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.OnNonceChange(addr, 0, 1) wr.OnBalanceChange(addr, big.NewInt(100), big.NewInt(200), BalanceChangeUnspecified) wr.OnBalanceChange(addr, big.NewInt(200), big.NewInt(250), BalanceChangeUnspecified) - wr.OnExit(0, nil, 100, errors.New("revert"), true) + 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) @@ -42,6 +63,15 @@ func TestJournalIntegration(t *testing.T) { 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) { From 501f302b750ac8f22bbe027f45610b83c900ffc1 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Sat, 5 Oct 2024 10:15:07 +0200 Subject: [PATCH 13/48] pass-through non-journaled hooks --- core/tracing/journal.go | 59 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/core/tracing/journal.go b/core/tracing/journal.go index 4a4d6be4c16b..0d83499c14bc 100644 --- a/core/tracing/journal.go +++ b/core/tracing/journal.go @@ -62,6 +62,7 @@ func WrapWithJournal(hooks *Hooks) (*Hooks, error) { OnExit: j.OnExit, } ) + // State change hooks. if hooks.OnBalanceChange != nil { wrapped.OnBalanceChange = j.OnBalanceChange } @@ -74,6 +75,64 @@ func WrapWithJournal(hooks *Hooks) (*Hooks, error) { if hooks.OnStorageChange != nil { wrapped.OnStorageChange = j.OnStorageChange } + // Pass through the remaining hooks. + if hooks.OnTxStart != nil { + wrapped.OnTxStart = hooks.OnTxStart + } + if hooks.OnOpcode != nil { + wrapped.OnOpcode = hooks.OnOpcode + } + if hooks.OnFault != nil { + wrapped.OnFault = hooks.OnFault + } + if hooks.OnGasChange != nil { + wrapped.OnGasChange = hooks.OnGasChange + } + if hooks.OnBlockchainInit != nil { + wrapped.OnBlockchainInit = hooks.OnBlockchainInit + } + if hooks.OnClose != nil { + wrapped.OnClose = hooks.OnClose + } + if hooks.OnBlockStart != nil { + wrapped.OnBlockStart = hooks.OnBlockStart + } + if hooks.OnBlockEnd != nil { + wrapped.OnBlockEnd = hooks.OnBlockEnd + } + if hooks.OnSkippedBlock != nil { + wrapped.OnSkippedBlock = hooks.OnSkippedBlock + } + if hooks.OnGenesisBlock != nil { + wrapped.OnGenesisBlock = hooks.OnGenesisBlock + } + if hooks.OnReorg != nil { + wrapped.OnReorg = hooks.OnReorg + } + if hooks.OnSystemCallStart != nil { + wrapped.OnSystemCallStart = hooks.OnSystemCallStart + } + if hooks.OnSystemCallEnd != nil { + wrapped.OnSystemCallEnd = hooks.OnSystemCallEnd + } + if hooks.OnLog != nil { + wrapped.OnLog = hooks.OnLog + } + if hooks.OnBalanceRead != nil { + wrapped.OnBalanceRead = hooks.OnBalanceRead + } + if hooks.OnNonceRead != nil { + wrapped.OnNonceRead = hooks.OnNonceRead + } + if hooks.OnCodeRead != nil { + wrapped.OnCodeRead = hooks.OnCodeRead + } + if hooks.OnStorageRead != nil { + wrapped.OnStorageRead = hooks.OnStorageRead + } + if hooks.OnBlockHashRead != nil { + wrapped.OnBlockHashRead = hooks.OnBlockHashRead + } return wrapped, nil } From 1a64297b1d89f974e8abcefd7d1b4cdfce8ae252 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Sat, 5 Oct 2024 10:17:56 +0200 Subject: [PATCH 14/48] missed two hooks --- core/tracing/journal.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/core/tracing/journal.go b/core/tracing/journal.go index 0d83499c14bc..b5391eb8d0b6 100644 --- a/core/tracing/journal.go +++ b/core/tracing/journal.go @@ -127,6 +127,12 @@ func WrapWithJournal(hooks *Hooks) (*Hooks, error) { if hooks.OnCodeRead != nil { wrapped.OnCodeRead = hooks.OnCodeRead } + if hooks.OnCodeSizeRead != nil { + wrapped.OnCodeSizeRead = hooks.OnCodeSizeRead + } + if hooks.OnCodeHashRead != nil { + wrapped.OnCodeHashRead = hooks.OnCodeHashRead + } if hooks.OnStorageRead != nil { wrapped.OnStorageRead = hooks.OnStorageRead } From 1862333296965edeac5d0b47d97f878dd2e3d1a0 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Tue, 8 Oct 2024 20:09:28 +0200 Subject: [PATCH 15/48] fix journal cur rev Id --- core/tracing/journal.go | 9 +++++---- core/tracing/journal_test.go | 28 ++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/core/tracing/journal.go b/core/tracing/journal.go index b5391eb8d0b6..bc6fb9ccbc4b 100644 --- a/core/tracing/journal.go +++ b/core/tracing/journal.go @@ -38,7 +38,7 @@ type journal struct { validRevisions []revision nextRevisionId int - curRevisionId int + revIds []int } type entry interface { @@ -194,17 +194,18 @@ func (j *journal) OnTxEnd(receipt *types.Receipt, err error) { } func (j *journal) OnEnter(depth int, typ byte, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { - j.curRevisionId = j.snapshot() + j.revIds = append(j.revIds, j.snapshot()) if j.hooks != nil && j.hooks.OnEnter != nil { j.hooks.OnEnter(depth, typ, from, to, input, gas, value) } } func (j *journal) OnExit(depth int, output []byte, gasUsed uint64, err error, reverted bool) { + revId := j.revIds[len(j.revIds)-1] + j.revIds = j.revIds[:len(j.revIds)-1] if reverted { - j.revertToSnapshot(j.curRevisionId, j.hooks) + j.revertToSnapshot(revId, j.hooks) } - j.curRevisionId-- if j.hooks != nil && j.hooks.OnExit != nil { j.hooks.OnExit(depth, output, gasUsed, err, reverted) } diff --git a/core/tracing/journal_test.go b/core/tracing/journal_test.go index 8681817b171b..bcc47f06218c 100644 --- a/core/tracing/journal_test.go +++ b/core/tracing/journal_test.go @@ -96,3 +96,31 @@ func TestJournalTopRevert(t *testing.T) { t.Fatalf("unexpected nonce: %v", tr.nonce) } } + +func TestJournalNestedCalls(t *testing.T) { + tr := &testTracer{} + 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.OnEnter(1, 0, addr, addr, nil, 1000, big.NewInt(0)) + wr.OnBalanceChange(addr, big.NewInt(0), big.NewInt(100), 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.OnExit(2, nil, 100, nil, false) + wr.OnEnter(2, 0, addr, addr, nil, 1000, big.NewInt(0)) + wr.OnBalanceChange(addr, big.NewInt(100), big.NewInt(200), BalanceChangeUnspecified) + wr.OnExit(2, nil, 100, nil, false) + 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.OnExit(2, nil, 100, errors.New("revert"), true) + wr.OnExit(1, nil, 100, errors.New("revert"), true) + wr.OnExit(0, nil, 150, nil, false) + if tr.bal.Cmp(big.NewInt(0)) != 0 { + t.Fatalf("unexpected balance: %v", tr.bal) + } +} From 66500003885263f172b3a426aa17e87ab91236dc Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Wed, 9 Oct 2024 12:30:16 +0200 Subject: [PATCH 16/48] add note on balanceChangeRevert reason --- core/tracing/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/tracing/CHANGELOG.md b/core/tracing/CHANGELOG.md index 9e077fe86f69..56e28b37169c 100644 --- a/core/tracing/CHANGELOG.md +++ b/core/tracing/CHANGELOG.md @@ -44,7 +44,7 @@ func init() { The state changes that are covered by the journaling library are: -- `OnBalanceChange` +- `OnBalanceChange`. Note that `OnBalanceChange` will carry the `BalanceChangeRevert` reason. - `OnNonceChange` - `OnCodeChange` - `OnStorageChange` From d9de74e9833bc2484a6907c6c7348ca461c9e862 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Wed, 9 Oct 2024 17:33:04 +0200 Subject: [PATCH 17/48] refactor WrapWithJournal to use reflection --- core/tracing/hooks.go | 16 ++++++++ core/tracing/journal.go | 86 ++++++----------------------------------- 2 files changed, 28 insertions(+), 74 deletions(-) diff --git a/core/tracing/hooks.go b/core/tracing/hooks.go index 355843c87bbd..3b69359f98b9 100644 --- a/core/tracing/hooks.go +++ b/core/tracing/hooks.go @@ -18,6 +18,7 @@ package tracing import ( "math/big" + "reflect" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" @@ -231,6 +232,21 @@ type Hooks struct { OnBlockHashRead BlockHashReadHook } +// Copy creates a new Hooks instance with all implemented hooks copied from the original. +func (h *Hooks) Copy() *Hooks { + 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 +} + // BalanceChangeReason is used to indicate the reason for a balance change, useful // for tracing and reporting. type BalanceChangeReason byte diff --git a/core/tracing/journal.go b/core/tracing/journal.go index bc6fb9ccbc4b..5ec405edf27f 100644 --- a/core/tracing/journal.go +++ b/core/tracing/journal.go @@ -50,19 +50,20 @@ func WrapWithJournal(hooks *Hooks) (*Hooks, error) { if hooks == nil { return nil, fmt.Errorf("wrapping nil tracer") } - // No state change to journal. + // No state change to journal, return the wrapped hooks as is if hooks.OnBalanceChange == nil && hooks.OnNonceChange == nil && hooks.OnCodeChange == nil && hooks.OnStorageChange == nil { return hooks, nil } - var ( - j = &journal{entries: make([]entry, 0), hooks: hooks} - wrapped = &Hooks{ - OnTxEnd: j.OnTxEnd, - OnEnter: j.OnEnter, - OnExit: j.OnExit, - } - ) - // State change hooks. + + // Create a new Hooks instance and copy all hooks + wrapped := hooks.Copy() + // Create journal + j := &journal{entries: make([]entry, 0), 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 } @@ -75,70 +76,7 @@ func WrapWithJournal(hooks *Hooks) (*Hooks, error) { if hooks.OnStorageChange != nil { wrapped.OnStorageChange = j.OnStorageChange } - // Pass through the remaining hooks. - if hooks.OnTxStart != nil { - wrapped.OnTxStart = hooks.OnTxStart - } - if hooks.OnOpcode != nil { - wrapped.OnOpcode = hooks.OnOpcode - } - if hooks.OnFault != nil { - wrapped.OnFault = hooks.OnFault - } - if hooks.OnGasChange != nil { - wrapped.OnGasChange = hooks.OnGasChange - } - if hooks.OnBlockchainInit != nil { - wrapped.OnBlockchainInit = hooks.OnBlockchainInit - } - if hooks.OnClose != nil { - wrapped.OnClose = hooks.OnClose - } - if hooks.OnBlockStart != nil { - wrapped.OnBlockStart = hooks.OnBlockStart - } - if hooks.OnBlockEnd != nil { - wrapped.OnBlockEnd = hooks.OnBlockEnd - } - if hooks.OnSkippedBlock != nil { - wrapped.OnSkippedBlock = hooks.OnSkippedBlock - } - if hooks.OnGenesisBlock != nil { - wrapped.OnGenesisBlock = hooks.OnGenesisBlock - } - if hooks.OnReorg != nil { - wrapped.OnReorg = hooks.OnReorg - } - if hooks.OnSystemCallStart != nil { - wrapped.OnSystemCallStart = hooks.OnSystemCallStart - } - if hooks.OnSystemCallEnd != nil { - wrapped.OnSystemCallEnd = hooks.OnSystemCallEnd - } - if hooks.OnLog != nil { - wrapped.OnLog = hooks.OnLog - } - if hooks.OnBalanceRead != nil { - wrapped.OnBalanceRead = hooks.OnBalanceRead - } - if hooks.OnNonceRead != nil { - wrapped.OnNonceRead = hooks.OnNonceRead - } - if hooks.OnCodeRead != nil { - wrapped.OnCodeRead = hooks.OnCodeRead - } - if hooks.OnCodeSizeRead != nil { - wrapped.OnCodeSizeRead = hooks.OnCodeSizeRead - } - if hooks.OnCodeHashRead != nil { - wrapped.OnCodeHashRead = hooks.OnCodeHashRead - } - if hooks.OnStorageRead != nil { - wrapped.OnStorageRead = hooks.OnStorageRead - } - if hooks.OnBlockHashRead != nil { - wrapped.OnBlockHashRead = hooks.OnBlockHashRead - } + return wrapped, nil } From d2ba76f7a6fb867196c6c0589847202e646248e6 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Thu, 10 Oct 2024 12:44:13 +0200 Subject: [PATCH 18/48] add license to journal_test --- core/tracing/journal_test.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/core/tracing/journal_test.go b/core/tracing/journal_test.go index bcc47f06218c..4ae26df7e6df 100644 --- a/core/tracing/journal_test.go +++ b/core/tracing/journal_test.go @@ -1,3 +1,19 @@ +// Copyright 2024 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 ( From a2ca5f81ff1d2f766cb8846aaddce5020280a666 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Thu, 10 Oct 2024 12:47:37 +0200 Subject: [PATCH 19/48] add desc for revert change reason --- core/tracing/hooks.go | 1 + 1 file changed, 1 insertion(+) diff --git a/core/tracing/hooks.go b/core/tracing/hooks.go index 3b69359f98b9..51bac8e7a674 100644 --- a/core/tracing/hooks.go +++ b/core/tracing/hooks.go @@ -298,6 +298,7 @@ const ( 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. BalanceChangeRevert BalanceChangeReason = 15 ) From 85a85d09c7f83e57b4065b0c03c4c775ae7ea101 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Mon, 14 Oct 2024 06:47:39 +0200 Subject: [PATCH 20/48] add OnSystemCallStartV2 --- core/state_processor.go | 12 ++++++++---- core/tracing/hooks.go | 25 +++++++++++++++---------- eth/tracers/logger/logger_json.go | 2 +- 3 files changed, 24 insertions(+), 15 deletions(-) diff --git a/core/state_processor.go b/core/state_processor.go index 08961ba74fa8..2ae39dab29d6 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -208,8 +208,10 @@ func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *commo // contract. This method is exported to be used in tests. func ProcessBeaconBlockRoot(beaconRoot common.Hash, vmenv *vm.EVM, statedb *state.StateDB) { if tracer := vmenv.Config.Tracer; tracer != nil { - if tracer.OnSystemCallStart != nil { - tracer.OnSystemCallStart(vmenv.GetVMContext()) + if tracer.OnSystemCallStartV2 != nil { + tracer.OnSystemCallStartV2(vmenv.GetVMContext()) + } else if tracer.OnSystemCallStart != nil { + tracer.OnSystemCallStart() } if tracer.OnSystemCallEnd != nil { defer tracer.OnSystemCallEnd() @@ -237,8 +239,10 @@ func ProcessBeaconBlockRoot(beaconRoot common.Hash, vmenv *vm.EVM, statedb *stat // as per EIP-2935. func ProcessParentBlockHash(prevHash common.Hash, vmenv *vm.EVM, statedb *state.StateDB) { if tracer := vmenv.Config.Tracer; tracer != nil { - if tracer.OnSystemCallStart != nil { - tracer.OnSystemCallStart(vmenv.GetVMContext()) + if tracer.OnSystemCallStartV2 != nil { + tracer.OnSystemCallStartV2(vmenv.GetVMContext()) + } else if tracer.OnSystemCallStart != nil { + tracer.OnSystemCallStart() } if tracer.OnSystemCallEnd != nil { defer tracer.OnSystemCallEnd() diff --git a/core/tracing/hooks.go b/core/tracing/hooks.go index 51bac8e7a674..5ddba2b74b67 100644 --- a/core/tracing/hooks.go +++ b/core/tracing/hooks.go @@ -148,7 +148,11 @@ type ( // // Note that system call happens outside normal transaction execution, so the `OnTxStart/OnTxEnd` hooks // will not be invoked. - OnSystemCallStartHook = func(vm *VMContext) + OnSystemCallStartHook = func() + + // OnSystemCallStartHookV2 is called when a system call is about to be executed. Refer + // to `OnSystemCallStartHook` for more information. + OnSystemCallStartHookV2 = 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 @@ -206,15 +210,16 @@ type Hooks struct { OnFault FaultHook OnGasChange GasChangeHook // Chain events - OnBlockchainInit BlockchainInitHook - OnClose CloseHook - OnBlockStart BlockStartHook - OnBlockEnd BlockEndHook - OnSkippedBlock SkippedBlockHook - OnGenesisBlock GenesisBlockHook - OnReorg ReorgHook - OnSystemCallStart OnSystemCallStartHook - OnSystemCallEnd OnSystemCallEndHook + OnBlockchainInit BlockchainInitHook + OnClose CloseHook + OnBlockStart BlockStartHook + OnBlockEnd BlockEndHook + OnSkippedBlock SkippedBlockHook + OnGenesisBlock GenesisBlockHook + OnReorg ReorgHook + OnSystemCallStart OnSystemCallStartHook + OnSystemCallStartV2 OnSystemCallStartHookV2 + OnSystemCallEnd OnSystemCallEndHook // State events OnBalanceChange BalanceChangeHook OnNonceChange NonceChangeHook diff --git a/eth/tracers/logger/logger_json.go b/eth/tracers/logger/logger_json.go index de021e74bef8..797f7ac65821 100644 --- a/eth/tracers/logger/logger_json.go +++ b/eth/tracers/logger/logger_json.go @@ -127,7 +127,7 @@ func (l *jsonLogger) OnOpcode(pc uint64, op byte, gas, cost uint64, scope tracin l.encoder.Encode(log) } -func (l *jsonLogger) onSystemCallStart(_ *tracing.VMContext) { +func (l *jsonLogger) onSystemCallStart() { // Process no events while in system call. hooks := *l.hooks *l.hooks = tracing.Hooks{ From 2754b414edfe707b93d9cb59d527c1c95a93f929 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Thu, 17 Oct 2024 11:07:58 +0200 Subject: [PATCH 21/48] drop OnReorg --- core/blockchain.go | 3 --- core/tracing/hooks.go | 1 - eth/tracers/live/noop.go | 3 --- 3 files changed, 7 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index 0b8e1b7ad2e4..112ad888ebfa 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -2359,9 +2359,6 @@ func (bc *BlockChain) reorg(oldHead *types.Header, newHead *types.Block) error { bc.logsFeed.Send(rebirthLogs) } - if bc.logger != nil && bc.logger.OnReorg != nil { - bc.logger.OnReorg(oldChain) - } return nil } diff --git a/core/tracing/hooks.go b/core/tracing/hooks.go index 5ddba2b74b67..8c4797891b0e 100644 --- a/core/tracing/hooks.go +++ b/core/tracing/hooks.go @@ -216,7 +216,6 @@ type Hooks struct { OnBlockEnd BlockEndHook OnSkippedBlock SkippedBlockHook OnGenesisBlock GenesisBlockHook - OnReorg ReorgHook OnSystemCallStart OnSystemCallStartHook OnSystemCallStartV2 OnSystemCallStartHookV2 OnSystemCallEnd OnSystemCallEndHook diff --git a/eth/tracers/live/noop.go b/eth/tracers/live/noop.go index 614a2c554b18..328407420d50 100644 --- a/eth/tracers/live/noop.go +++ b/eth/tracers/live/noop.go @@ -36,7 +36,6 @@ func newNoopTracer(_ json.RawMessage) (*tracing.Hooks, error) { OnBlockEnd: t.OnBlockEnd, OnSkippedBlock: t.OnSkippedBlock, OnGenesisBlock: t.OnGenesisBlock, - OnReorg: t.OnReorg, OnBalanceChange: t.OnBalanceChange, OnNonceChange: t.OnNonceChange, OnCodeChange: t.OnCodeChange, @@ -84,8 +83,6 @@ func (t *noop) OnBlockchainInit(chainConfig *params.ChainConfig) { func (t *noop) OnGenesisBlock(b *types.Block, alloc types.GenesisAlloc) { } -func (t *noop) OnReorg(reverted []*types.Block) {} - func (t *noop) OnBalanceChange(a common.Address, prev, new *big.Int, reason tracing.BalanceChangeReason) { } From 92337d87d9a6cdadf29298501d3f6643d4369284 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Thu, 17 Oct 2024 11:18:13 +0200 Subject: [PATCH 22/48] rm newline --- core/blockchain.go | 1 - 1 file changed, 1 deletion(-) diff --git a/core/blockchain.go b/core/blockchain.go index 112ad888ebfa..f7c921fe64fe 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -2358,7 +2358,6 @@ func (bc *BlockChain) reorg(oldHead *types.Header, newHead *types.Block) error { if len(rebirthLogs) > 0 { bc.logsFeed.Send(rebirthLogs) } - return nil } From efed5a6de24b7db57c78b8575610d92f24f26d65 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Thu, 24 Oct 2024 04:47:11 +0200 Subject: [PATCH 23/48] fix OnTxEnd --- core/tracing/journal.go | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/core/tracing/journal.go b/core/tracing/journal.go index 5ec405edf27f..88d70362977f 100644 --- a/core/tracing/journal.go +++ b/core/tracing/journal.go @@ -129,11 +129,14 @@ func (j *journal) length() int { func (j *journal) OnTxEnd(receipt *types.Receipt, err error) { j.reset() + if j.hooks.OnTxEnd != nil { + j.hooks.OnTxEnd(receipt, err) + } } func (j *journal) OnEnter(depth int, typ byte, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { j.revIds = append(j.revIds, j.snapshot()) - if j.hooks != nil && j.hooks.OnEnter != nil { + if j.hooks.OnEnter != nil { j.hooks.OnEnter(depth, typ, from, to, input, gas, value) } } @@ -144,21 +147,21 @@ func (j *journal) OnExit(depth int, output []byte, gasUsed uint64, err error, re if reverted { j.revertToSnapshot(revId, j.hooks) } - if j.hooks != nil && j.hooks.OnExit != nil { + 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 != nil && j.hooks.OnBalanceChange != nil { + if j.hooks.OnBalanceChange != nil { j.hooks.OnBalanceChange(addr, prev, new, reason) } } func (j *journal) OnNonceChange(addr common.Address, prev, new uint64) { j.entries = append(j.entries, nonceChange{addr: addr, prev: prev, new: new}) - if j.hooks != nil && j.hooks.OnNonceChange != nil { + if j.hooks.OnNonceChange != nil { j.hooks.OnNonceChange(addr, prev, new) } } @@ -171,14 +174,14 @@ func (j *journal) OnCodeChange(addr common.Address, prevCodeHash common.Hash, pr newCodeHash: codeHash, newCode: code, }) - if j.hooks != nil && j.hooks.OnCodeChange != nil { + 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 != nil && j.hooks.OnStorageChange != nil { + if j.hooks.OnStorageChange != nil { j.hooks.OnStorageChange(addr, slot, prev, new) } } From fbd1d19cdb136c9510fd5cf80302560b650e3e1c Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Thu, 24 Oct 2024 05:59:57 +0200 Subject: [PATCH 24/48] fix pre-post block process fns --- core/state_processor.go | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/core/state_processor.go b/core/state_processor.go index 631e2a87cf28..bd0ffb86403a 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -77,11 +77,15 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg context = NewEVMBlockContext(header, p.chain, nil) vmenv := vm.NewEVM(context, vm.TxContext{}, statedb, p.config, cfg) + var tracingStateDB = vm.StateDB(statedb) + if hooks := cfg.Tracer; hooks != nil { + tracingStateDB = state.NewHookedState(statedb, hooks) + } if beaconRoot := block.BeaconRoot(); beaconRoot != nil { - ProcessBeaconBlockRoot(*beaconRoot, vmenv, statedb) + ProcessBeaconBlockRoot(*beaconRoot, vmenv, tracingStateDB) } if p.config.IsPrague(block.Number(), block.Time()) { - ProcessParentBlockHash(block.ParentHash(), vmenv, statedb) + ProcessParentBlockHash(block.ParentHash(), vmenv, tracingStateDB) } // Iterate over and process the individual transactions @@ -99,10 +103,6 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg receipts = append(receipts, receipt) allLogs = append(allLogs, receipt.Logs...) } - var tracingStateDB = vm.StateDB(statedb) - if hooks := cfg.Tracer; hooks != nil { - tracingStateDB = state.NewHookedState(statedb, hooks) - } // Read requests if Prague is enabled. var requests [][]byte if p.config.IsPrague(block.Number(), block.Time()) { @@ -292,14 +292,15 @@ func ProcessConsolidationQueue(vmenv *vm.EVM, statedb vm.StateDB) []byte { func processRequestsSystemCall(vmenv *vm.EVM, statedb vm.StateDB, requestType byte, addr common.Address) []byte { if tracer := vmenv.Config.Tracer; tracer != nil { - if tracer.OnSystemCallStart != nil { + if tracer.OnSystemCallStartV2 != nil { + tracer.OnSystemCallStartV2(vmenv.GetVMContext()) + } else if tracer.OnSystemCallStart != nil { tracer.OnSystemCallStart() } if tracer.OnSystemCallEnd != nil { defer tracer.OnSystemCallEnd() } } - msg := &Message{ From: params.SystemAddress, GasLimit: 30_000_000, From 0f005af66a63aba6e1363453306a04e1d5ecf4c9 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Thu, 24 Oct 2024 06:09:44 +0200 Subject: [PATCH 25/48] mv read hooks to statedb_hooked --- core/state/statedb.go | 48 ++++++++----------------------- core/state/statedb_hooked.go | 36 +++++++++++++++++++---- core/state/statedb_hooked_test.go | 30 +++++++++++++++++++ 3 files changed, 72 insertions(+), 42 deletions(-) diff --git a/core/state/statedb.go b/core/state/statedb.go index 754892493f57..ef159a4c221f 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -306,28 +306,20 @@ 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 { - bal = stateObject.Balance() + return stateObject.Balance() } - if s.logger != nil && s.logger.OnBalanceRead != nil { - s.logger.OnBalanceRead(addr, bal.ToBig()) - } - return bal + return common.U2560 } // 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 { - nonce = stateObject.Nonce() - } - if s.logger != nil && s.logger.OnNonceRead != nil { - s.logger.OnNonceRead(addr, nonce) + return stateObject.Nonce() } - return nonce + return 0 } // GetStorageRoot retrieves the storage root from the given address or empty @@ -346,52 +338,36 @@ func (s *StateDB) TxIndex() int { } func (s *StateDB) GetCode(addr common.Address) []byte { - var code []byte stateObject := s.getStateObject(addr) if stateObject != nil { - code = stateObject.Code() - } - if s.logger != nil && s.logger.OnCodeRead != nil { - s.logger.OnCodeRead(addr, code) + return stateObject.Code() } - return code + return nil } func (s *StateDB) GetCodeSize(addr common.Address) int { - var size int stateObject := s.getStateObject(addr) if stateObject != nil { - size = stateObject.CodeSize() + return stateObject.CodeSize() } - if s.logger != nil && s.logger.OnCodeSizeRead != nil { - s.logger.OnCodeSizeRead(addr, size) - } - return size + return 0 } func (s *StateDB) GetCodeHash(addr common.Address) common.Hash { - hash := common.Hash{} stateObject := s.getStateObject(addr) if stateObject != nil { - hash = common.BytesToHash(stateObject.CodeHash()) - } - if s.logger != nil && s.logger.OnCodeHashRead != nil { - s.logger.OnCodeHashRead(addr, hash) + return common.BytesToHash(stateObject.CodeHash()) } - return hash + return common.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 { - val = stateObject.GetState(hash) + return stateObject.GetState(hash) } - if s.logger != nil && s.logger.OnStorageRead != nil { - s.logger.OnStorageRead(addr, hash, val) - } - return val + return common.Hash{} } // GetCommittedState retrieves the value associated with the specific key diff --git a/core/state/statedb_hooked.go b/core/state/statedb_hooked.go index 55b53ded40ff..e7ec0228f0f7 100644 --- a/core/state/statedb_hooked.go +++ b/core/state/statedb_hooked.go @@ -54,23 +54,43 @@ func (s *hookedStateDB) CreateContract(addr common.Address) { } func (s *hookedStateDB) GetBalance(addr common.Address) *uint256.Int { - return s.inner.GetBalance(addr) + bal := s.inner.GetBalance(addr) + if s.hooks.OnBalanceRead != nil { + s.hooks.OnBalanceRead(addr, bal.ToBig()) + } + return bal } func (s *hookedStateDB) GetNonce(addr common.Address) uint64 { - return s.inner.GetNonce(addr) + nonce := s.inner.GetNonce(addr) + if s.hooks.OnNonceRead != nil { + s.hooks.OnNonceRead(addr, nonce) + } + return nonce } func (s *hookedStateDB) GetCodeHash(addr common.Address) common.Hash { - return s.inner.GetCodeHash(addr) + codeHash := s.inner.GetCodeHash(addr) + if s.hooks.OnCodeHashRead != nil { + s.hooks.OnCodeHashRead(addr, codeHash) + } + return codeHash } func (s *hookedStateDB) GetCode(addr common.Address) []byte { - return s.inner.GetCode(addr) + code := s.inner.GetCode(addr) + if s.hooks.OnCodeRead != nil { + s.hooks.OnCodeRead(addr, code) + } + return code } func (s *hookedStateDB) GetCodeSize(addr common.Address) int { - return s.inner.GetCodeSize(addr) + size := s.inner.GetCodeSize(addr) + if s.hooks.OnCodeSizeRead != nil { + s.hooks.OnCodeSizeRead(addr, size) + } + return size } func (s *hookedStateDB) AddRefund(u uint64) { @@ -90,7 +110,11 @@ func (s *hookedStateDB) GetCommittedState(addr common.Address, hash common.Hash) } func (s *hookedStateDB) GetState(addr common.Address, hash common.Hash) common.Hash { - return s.inner.GetState(addr, hash) + val := s.inner.GetState(addr, hash) + if s.hooks.OnStorageRead != nil { + s.hooks.OnStorageRead(addr, hash, val) + } + return val } func (s *hookedStateDB) GetStorageRoot(addr common.Address) common.Hash { diff --git a/core/state/statedb_hooked_test.go b/core/state/statedb_hooked_test.go index 9abd76b02db8..9fd3ebc95e6e 100644 --- a/core/state/statedb_hooked_test.go +++ b/core/state/statedb_hooked_test.go @@ -90,6 +90,12 @@ func TestHooks(t *testing.T) { "0xaa00000000000000000000000000000000000000.storage slot 0x0000000000000000000000000000000000000000000000000000000000000001: 0x0000000000000000000000000000000000000000000000000000000000000000 ->0x0000000000000000000000000000000000000000000000000000000000000011", "0xaa00000000000000000000000000000000000000.storage slot 0x0000000000000000000000000000000000000000000000000000000000000001: 0x0000000000000000000000000000000000000000000000000000000000000011 ->0x0000000000000000000000000000000000000000000000000000000000000022", "log 100", + "0xaa00000000000000000000000000000000000000.balance read: 50", + "0xaa00000000000000000000000000000000000000.nonce read: 1337", + "0xaa00000000000000000000000000000000000000.code read: [19 37]", + "0xaa00000000000000000000000000000000000000.storage read 0x0000000000000000000000000000000000000000000000000000000000000001: 0x0000000000000000000000000000000000000000000000000000000000000022", + "0xaa00000000000000000000000000000000000000.code size read: 2", + "0xaa00000000000000000000000000000000000000.code hash read: 0xa12ae05590de0c93a00bc7ac773c2fdb621e44f814985e72194f921c0050f728", } emitF := func(format string, a ...any) { result = append(result, fmt.Sprintf(format, a...)) @@ -110,6 +116,24 @@ func TestHooks(t *testing.T) { OnLog: func(log *types.Log) { emitF("log %v", log.TxIndex) }, + OnBalanceRead: func(addr common.Address, bal *big.Int) { + emitF("%v.balance read: %v", addr, bal) + }, + OnNonceRead: func(addr common.Address, nonce uint64) { + emitF("%v.nonce read: %v", addr, nonce) + }, + OnCodeRead: func(addr common.Address, code []byte) { + emitF("%v.code read: %v", addr, code) + }, + OnStorageRead: func(addr common.Address, slot common.Hash, value common.Hash) { + emitF("%v.storage read %v: %v", addr, slot, value) + }, + OnCodeSizeRead: func(addr common.Address, size int) { + emitF("%v.code size read: %v", addr, size) + }, + OnCodeHashRead: func(addr common.Address, hash common.Hash) { + emitF("%v.code hash read: %v", addr, hash) + }, }) sdb.AddBalance(common.Address{0xaa}, uint256.NewInt(100), tracing.BalanceChangeUnspecified) sdb.SubBalance(common.Address{0xaa}, uint256.NewInt(50), tracing.BalanceChangeTransfer) @@ -122,6 +146,12 @@ func TestHooks(t *testing.T) { sdb.AddLog(&types.Log{ Address: common.Address{0xbb}, }) + sdb.GetBalance(common.Address{0xaa}) + sdb.GetNonce(common.Address{0xaa}) + sdb.GetCode(common.Address{0xaa}) + sdb.GetState(common.Address{0xaa}, common.HexToHash("0x01")) + sdb.GetCodeSize(common.Address{0xaa}) + sdb.GetCodeHash(common.Address{0xaa}) for i, want := range wants { if have := result[i]; have != want { t.Fatalf("error event %d, have\n%v\nwant%v\n", i, have, want) From 5e4d6b86f49681cfbbb42f0533426744123a0c66 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Thu, 24 Oct 2024 06:17:51 +0200 Subject: [PATCH 26/48] add whitespace --- core/state/statedb.go | 1 + 1 file changed, 1 insertion(+) diff --git a/core/state/statedb.go b/core/state/statedb.go index ef159a4c221f..0183c14480df 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -319,6 +319,7 @@ func (s *StateDB) GetNonce(addr common.Address) uint64 { if stateObject != nil { return stateObject.Nonce() } + return 0 } From 4d2fb0e0817e5475234cae85b47926c9ae731160 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Thu, 24 Oct 2024 06:22:33 +0200 Subject: [PATCH 27/48] update changelog --- core/tracing/CHANGELOG.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/core/tracing/CHANGELOG.md b/core/tracing/CHANGELOG.md index 56e28b37169c..84558a2bf13d 100644 --- a/core/tracing/CHANGELOG.md +++ b/core/tracing/CHANGELOG.md @@ -6,9 +6,12 @@ All notable changes to the tracing interface will be documented in this file. 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. +### Deprecated methods + +- `OnSystemCallStart()`: This hook is deprecated in favor of `OnSystemCallStartV2(vm *VMContext)`. + ### 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. - `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. @@ -16,10 +19,7 @@ The tracing interface has been extended with backwards-compatible changes to sup - `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. +- `OnSystemCallStartV2(vm *VMContext)`. This allows access to EVM context during system calls. It is a successor to `OnSystemCallStart`. ### Modified types From a0f7cd60f18df36778f57dc2105c7a8f064020c4 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Fri, 25 Oct 2024 07:26:56 +0200 Subject: [PATCH 28/48] Add test for all underlying hooks being called --- core/tracing/journal_test.go | 95 ++++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) diff --git a/core/tracing/journal_test.go b/core/tracing/journal_test.go index 4ae26df7e6df..129209b2a3ae 100644 --- a/core/tracing/journal_test.go +++ b/core/tracing/journal_test.go @@ -19,6 +19,7 @@ package tracing import ( "errors" "math/big" + "reflect" "testing" "github.com/ethereum/go-ethereum/common" @@ -140,3 +141,97 @@ func TestJournalNestedCalls(t *testing.T) { t.Fatalf("unexpected balance: %v", tr.bal) } } + +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 + } + + // 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 + } + 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) + 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 +} From 553f02363830510003e6c23d15503f2c9f72712a Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Tue, 26 Nov 2024 16:46:41 +0100 Subject: [PATCH 29/48] handle creation nonce in journal --- core/tracing/journal.go | 25 ++++++++++++++++++++++--- core/tracing/journal_test.go | 15 +++++++++++++++ 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/core/tracing/journal.go b/core/tracing/journal.go index 88d70362977f..7fab5a301bcc 100644 --- a/core/tracing/journal.go +++ b/core/tracing/journal.go @@ -25,6 +25,11 @@ import ( "github.com/ethereum/go-ethereum/core/types" ) +const ( + CREATE = 0xf0 + CREATE2 = 0xf5 +) + type revision struct { id int journalIndex int @@ -33,8 +38,9 @@ type revision struct { // 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 { - entries []entry - hooks *Hooks + entries []entry + hooks *Hooks + lastCreator *common.Address // Account that initiated the last contract creation validRevisions []revision nextRevisionId int @@ -136,12 +142,18 @@ func (j *journal) OnTxEnd(receipt *types.Receipt, err error) { func (j *journal) OnEnter(depth int, typ byte, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { j.revIds = append(j.revIds, j.snapshot()) + if typ == CREATE || typ == CREATE2 { + j.lastCreator = &from + } if j.hooks.OnEnter != nil { j.hooks.OnEnter(depth, typ, from, to, input, gas, value) } } func (j *journal) OnExit(depth int, output []byte, gasUsed uint64, err error, reverted bool) { + if j.lastCreator != nil { + j.lastCreator = nil + } revId := j.revIds[len(j.revIds)-1] j.revIds = j.revIds[:len(j.revIds)-1] if reverted { @@ -160,7 +172,14 @@ func (j *journal) OnBalanceChange(addr common.Address, prev, new *big.Int, reaso } func (j *journal) OnNonceChange(addr common.Address, prev, new uint64) { - j.entries = append(j.entries, nonceChange{addr: addr, prev: prev, new: new}) + // When a contract is created, the nonce of the creator is incremented. + // This change is not reverted when the creation fails. + if j.lastCreator != nil && *j.lastCreator == addr { + // Skip only the first nonce change. + j.lastCreator = nil + } else { + j.entries = append(j.entries, nonceChange{addr: addr, prev: prev, new: new}) + } if j.hooks.OnNonceChange != nil { j.hooks.OnNonceChange(addr, prev, new) } diff --git a/core/tracing/journal_test.go b/core/tracing/journal_test.go index 129209b2a3ae..e9a14e585ec6 100644 --- a/core/tracing/journal_test.go +++ b/core/tracing/journal_test.go @@ -142,6 +142,21 @@ func TestJournalNestedCalls(t *testing.T) { } } +func TestNonceIncOnCreate(t *testing.T) { + tr := &testTracer{} + 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, CREATE, addr, addr, nil, 1000, big.NewInt(0)) + wr.OnNonceChange(addr, 0, 1) + wr.OnExit(0, nil, 100, errors.New("revert"), true) + if tr.nonce != 1 { + t.Fatalf("unexpected nonce: %v", tr.nonce) + } +} + func TestAllHooksCalled(t *testing.T) { tracer := newTracerAllHooks() hooks := tracer.hooks() From 4acea3bc8acf77084daea25381a3a127ef2d9fc7 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Tue, 3 Dec 2024 16:28:44 +0100 Subject: [PATCH 30/48] rm OnCodeSizeRead --- core/state/statedb_hooked.go | 6 ++++-- core/state/statedb_hooked_test.go | 5 +---- core/tracing/hooks.go | 4 ---- eth/tracers/live/noop.go | 3 --- 4 files changed, 5 insertions(+), 13 deletions(-) diff --git a/core/state/statedb_hooked.go b/core/state/statedb_hooked.go index 2314a02989bf..b647a50fbbb9 100644 --- a/core/state/statedb_hooked.go +++ b/core/state/statedb_hooked.go @@ -87,8 +87,10 @@ func (s *hookedStateDB) GetCode(addr common.Address) []byte { func (s *hookedStateDB) GetCodeSize(addr common.Address) int { size := s.inner.GetCodeSize(addr) - if s.hooks.OnCodeSizeRead != nil { - s.hooks.OnCodeSizeRead(addr, size) + if s.hooks.OnCodeRead != nil { + // GetCodeSize caches the code within the reader so this call is free. + code := s.inner.GetCode(addr) + s.hooks.OnCodeRead(addr, code) } return size } diff --git a/core/state/statedb_hooked_test.go b/core/state/statedb_hooked_test.go index 3651cc3d0a8f..864434a25fd0 100644 --- a/core/state/statedb_hooked_test.go +++ b/core/state/statedb_hooked_test.go @@ -94,7 +94,7 @@ func TestHooks(t *testing.T) { "0xaa00000000000000000000000000000000000000.nonce read: 1337", "0xaa00000000000000000000000000000000000000.code read: [19 37]", "0xaa00000000000000000000000000000000000000.storage read 0x0000000000000000000000000000000000000000000000000000000000000001: 0x0000000000000000000000000000000000000000000000000000000000000022", - "0xaa00000000000000000000000000000000000000.code size read: 2", + "0xaa00000000000000000000000000000000000000.code read: [19 37]", "0xaa00000000000000000000000000000000000000.code hash read: 0xa12ae05590de0c93a00bc7ac773c2fdb621e44f814985e72194f921c0050f728", } emitF := func(format string, a ...any) { @@ -128,9 +128,6 @@ func TestHooks(t *testing.T) { OnStorageRead: func(addr common.Address, slot common.Hash, value common.Hash) { emitF("%v.storage read %v: %v", addr, slot, value) }, - OnCodeSizeRead: func(addr common.Address, size int) { - emitF("%v.code size read: %v", addr, size) - }, OnCodeHashRead: func(addr common.Address, hash common.Hash) { emitF("%v.code hash read: %v", addr, hash) }, diff --git a/core/tracing/hooks.go b/core/tracing/hooks.go index d8816921e3c5..d31763665a90 100644 --- a/core/tracing/hooks.go +++ b/core/tracing/hooks.go @@ -186,9 +186,6 @@ type ( // CodeReadHook is called when EVM reads the code of an account. CodeReadHook = func(addr common.Address, code []byte) - // 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) @@ -228,7 +225,6 @@ type Hooks struct { OnBalanceRead BalanceReadHook OnNonceRead NonceReadHook OnCodeRead CodeReadHook - OnCodeSizeRead CodeSizeReadHook OnCodeHashRead CodeHashReadHook OnStorageRead StorageReadHook // Block hash read diff --git a/eth/tracers/live/noop.go b/eth/tracers/live/noop.go index a430b8629617..97dbbbbecba0 100644 --- a/eth/tracers/live/noop.go +++ b/eth/tracers/live/noop.go @@ -60,7 +60,6 @@ func newNoopTracer(_ json.RawMessage) (*tracing.Hooks, error) { OnBalanceRead: t.OnBalanceRead, OnNonceRead: t.OnNonceRead, OnCodeRead: t.OnCodeRead, - OnCodeSizeRead: t.OnCodeSizeRead, OnCodeHashRead: t.OnCodeHashRead, OnStorageRead: t.OnStorageRead, OnBlockHashRead: t.OnBlockHashRead, @@ -121,8 +120,6 @@ func (t *noop) OnNonceRead(addr common.Address, nonce uint64) {} func (t *noop) OnCodeRead(addr common.Address, code []byte) {} -func (t *noop) OnCodeSizeRead(addr common.Address, size int) {} - func (t *noop) OnCodeHashRead(addr common.Address, hash common.Hash) {} func (t *noop) OnStorageRead(addr common.Address, slot, val common.Hash) {} From 018df6be5b6345d2f557aa43504e5938893e04ee Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Tue, 3 Dec 2024 16:44:26 +0100 Subject: [PATCH 31/48] rm onreorg type --- core/tracing/hooks.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/core/tracing/hooks.go b/core/tracing/hooks.go index d31763665a90..d5703ffdec2b 100644 --- a/core/tracing/hooks.go +++ b/core/tracing/hooks.go @@ -135,9 +135,6 @@ 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) - // 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. From 60b22229d9f7849255af9f6dea39a1f091378640 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Tue, 3 Dec 2024 16:44:37 +0100 Subject: [PATCH 32/48] wrapper func for OnSystemCallStart --- core/state_processor.go | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/core/state_processor.go b/core/state_processor.go index 68f9a0e5c9c0..3eb83a673a96 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -23,6 +23,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus/misc" "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" @@ -212,11 +213,7 @@ func ApplyTransaction(evm *vm.EVM, gp *GasPool, statedb *state.StateDB, header * // contract. This method is exported to be used in tests. func ProcessBeaconBlockRoot(beaconRoot common.Hash, evm *vm.EVM) { if tracer := evm.Config.Tracer; tracer != nil { - if tracer.OnSystemCallStartV2 != nil { - tracer.OnSystemCallStartV2(evm.GetVMContext()) - } else if tracer.OnSystemCallStart != nil { - tracer.OnSystemCallStart() - } + onSystemCallStart(tracer, evm.GetVMContext()) if tracer.OnSystemCallEnd != nil { defer tracer.OnSystemCallEnd() } @@ -240,11 +237,7 @@ func ProcessBeaconBlockRoot(beaconRoot common.Hash, evm *vm.EVM) { // as per EIP-2935. func ProcessParentBlockHash(prevHash common.Hash, evm *vm.EVM) { if tracer := evm.Config.Tracer; tracer != nil { - if tracer.OnSystemCallStartV2 != nil { - tracer.OnSystemCallStartV2(evm.GetVMContext()) - } else if tracer.OnSystemCallStart != nil { - tracer.OnSystemCallStart() - } + onSystemCallStart(tracer, evm.GetVMContext()) if tracer.OnSystemCallEnd != nil { defer tracer.OnSystemCallEnd() } @@ -278,11 +271,7 @@ func ProcessConsolidationQueue(requests *[][]byte, evm *vm.EVM) { func processRequestsSystemCall(requests *[][]byte, evm *vm.EVM, requestType byte, addr common.Address) { if tracer := evm.Config.Tracer; tracer != nil { - if tracer.OnSystemCallStartV2 != nil { - tracer.OnSystemCallStartV2(evm.GetVMContext()) - } else if tracer.OnSystemCallStart != nil { - tracer.OnSystemCallStart() - } + onSystemCallStart(tracer, evm.GetVMContext()) if tracer.OnSystemCallEnd != nil { defer tracer.OnSystemCallEnd() } @@ -328,3 +317,11 @@ func ParseDepositLogs(requests *[][]byte, logs []*types.Log, config *params.Chai } return nil } + +func onSystemCallStart(tracer *tracing.Hooks, ctx *tracing.VMContext) { + if tracer.OnSystemCallStartV2 != nil { + tracer.OnSystemCallStartV2(ctx) + } else if tracer.OnSystemCallStart != nil { + tracer.OnSystemCallStart() + } +} From 6c56ea5669caaabfefa980cc14c434b6dfbbbd2a Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Tue, 3 Dec 2024 16:45:43 +0100 Subject: [PATCH 33/48] update changelog --- core/tracing/CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/core/tracing/CHANGELOG.md b/core/tracing/CHANGELOG.md index 84558a2bf13d..5986a7429f36 100644 --- a/core/tracing/CHANGELOG.md +++ b/core/tracing/CHANGELOG.md @@ -15,7 +15,6 @@ The tracing interface has been extended with backwards-compatible changes to sup - `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. From 7fb2688e3a8a5b7acb3c3aab7224ed98da78f9e9 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Wed, 4 Dec 2024 09:44:19 +0100 Subject: [PATCH 34/48] run go generate --- core/tracing/gen_balance_change_reason_stringer.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/core/tracing/gen_balance_change_reason_stringer.go b/core/tracing/gen_balance_change_reason_stringer.go index d3a515a12d37..4f094efb4f4c 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) { From 3228063e6324c2c22ca1148ee867f103538615e1 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Mon, 9 Dec 2024 17:13:19 +0100 Subject: [PATCH 35/48] rm read hooks --- core/state/statedb_hooked.go | 39 ++++++------------------------- core/state/statedb_hooked_test.go | 27 --------------------- core/tracing/hooks.go | 21 ----------------- eth/tracers/live/noop.go | 15 ------------ 4 files changed, 7 insertions(+), 95 deletions(-) diff --git a/core/state/statedb_hooked.go b/core/state/statedb_hooked.go index b647a50fbbb9..20f2bdc15ff5 100644 --- a/core/state/statedb_hooked.go +++ b/core/state/statedb_hooked.go @@ -54,45 +54,24 @@ func (s *hookedStateDB) CreateContract(addr common.Address) { } func (s *hookedStateDB) GetBalance(addr common.Address) *uint256.Int { - bal := s.inner.GetBalance(addr) - if s.hooks.OnBalanceRead != nil { - s.hooks.OnBalanceRead(addr, bal.ToBig()) - } - return bal + return s.inner.GetBalance(addr) } func (s *hookedStateDB) GetNonce(addr common.Address) uint64 { - nonce := s.inner.GetNonce(addr) - if s.hooks.OnNonceRead != nil { - s.hooks.OnNonceRead(addr, nonce) - } - return nonce + return s.inner.GetNonce(addr) } func (s *hookedStateDB) GetCodeHash(addr common.Address) common.Hash { - codeHash := s.inner.GetCodeHash(addr) - if s.hooks.OnCodeHashRead != nil { - s.hooks.OnCodeHashRead(addr, codeHash) - } - return codeHash + return s.inner.GetCodeHash(addr) + } func (s *hookedStateDB) GetCode(addr common.Address) []byte { - code := s.inner.GetCode(addr) - if s.hooks.OnCodeRead != nil { - s.hooks.OnCodeRead(addr, code) - } - return code + return s.inner.GetCode(addr) } func (s *hookedStateDB) GetCodeSize(addr common.Address) int { - size := s.inner.GetCodeSize(addr) - if s.hooks.OnCodeRead != nil { - // GetCodeSize caches the code within the reader so this call is free. - code := s.inner.GetCode(addr) - s.hooks.OnCodeRead(addr, code) - } - return size + return s.inner.GetCodeSize(addr) } func (s *hookedStateDB) AddRefund(u uint64) { @@ -112,11 +91,7 @@ func (s *hookedStateDB) GetCommittedState(addr common.Address, hash common.Hash) } func (s *hookedStateDB) GetState(addr common.Address, hash common.Hash) common.Hash { - val := s.inner.GetState(addr, hash) - if s.hooks.OnStorageRead != nil { - s.hooks.OnStorageRead(addr, hash, val) - } - return val + return s.inner.GetState(addr, hash) } func (s *hookedStateDB) GetStorageRoot(addr common.Address) common.Hash { diff --git a/core/state/statedb_hooked_test.go b/core/state/statedb_hooked_test.go index 864434a25fd0..5f82ed06d0f1 100644 --- a/core/state/statedb_hooked_test.go +++ b/core/state/statedb_hooked_test.go @@ -90,12 +90,6 @@ func TestHooks(t *testing.T) { "0xaa00000000000000000000000000000000000000.storage slot 0x0000000000000000000000000000000000000000000000000000000000000001: 0x0000000000000000000000000000000000000000000000000000000000000000 ->0x0000000000000000000000000000000000000000000000000000000000000011", "0xaa00000000000000000000000000000000000000.storage slot 0x0000000000000000000000000000000000000000000000000000000000000001: 0x0000000000000000000000000000000000000000000000000000000000000011 ->0x0000000000000000000000000000000000000000000000000000000000000022", "log 100", - "0xaa00000000000000000000000000000000000000.balance read: 50", - "0xaa00000000000000000000000000000000000000.nonce read: 1337", - "0xaa00000000000000000000000000000000000000.code read: [19 37]", - "0xaa00000000000000000000000000000000000000.storage read 0x0000000000000000000000000000000000000000000000000000000000000001: 0x0000000000000000000000000000000000000000000000000000000000000022", - "0xaa00000000000000000000000000000000000000.code read: [19 37]", - "0xaa00000000000000000000000000000000000000.code hash read: 0xa12ae05590de0c93a00bc7ac773c2fdb621e44f814985e72194f921c0050f728", } emitF := func(format string, a ...any) { result = append(result, fmt.Sprintf(format, a...)) @@ -116,21 +110,6 @@ func TestHooks(t *testing.T) { OnLog: func(log *types.Log) { emitF("log %v", log.TxIndex) }, - OnBalanceRead: func(addr common.Address, bal *big.Int) { - emitF("%v.balance read: %v", addr, bal) - }, - OnNonceRead: func(addr common.Address, nonce uint64) { - emitF("%v.nonce read: %v", addr, nonce) - }, - OnCodeRead: func(addr common.Address, code []byte) { - emitF("%v.code read: %v", addr, code) - }, - OnStorageRead: func(addr common.Address, slot common.Hash, value common.Hash) { - emitF("%v.storage read %v: %v", addr, slot, value) - }, - OnCodeHashRead: func(addr common.Address, hash common.Hash) { - emitF("%v.code hash read: %v", addr, hash) - }, }) sdb.AddBalance(common.Address{0xaa}, uint256.NewInt(100), tracing.BalanceChangeUnspecified) sdb.SubBalance(common.Address{0xaa}, uint256.NewInt(50), tracing.BalanceChangeTransfer) @@ -143,12 +122,6 @@ func TestHooks(t *testing.T) { sdb.AddLog(&types.Log{ Address: common.Address{0xbb}, }) - sdb.GetBalance(common.Address{0xaa}) - sdb.GetNonce(common.Address{0xaa}) - sdb.GetCode(common.Address{0xaa}) - sdb.GetState(common.Address{0xaa}, common.HexToHash("0x01")) - sdb.GetCodeSize(common.Address{0xaa}) - sdb.GetCodeHash(common.Address{0xaa}) for i, want := range wants { if have := result[i]; have != want { t.Fatalf("error event %d, have\n%v\nwant%v\n", i, have, want) diff --git a/core/tracing/hooks.go b/core/tracing/hooks.go index d5703ffdec2b..6b9b3e03e841 100644 --- a/core/tracing/hooks.go +++ b/core/tracing/hooks.go @@ -174,21 +174,6 @@ 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) - - // 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) ) @@ -218,12 +203,6 @@ type Hooks struct { OnCodeChange CodeChangeHook OnStorageChange StorageChangeHook OnLog LogHook - // State reads - OnBalanceRead BalanceReadHook - OnNonceRead NonceReadHook - OnCodeRead CodeReadHook - OnCodeHashRead CodeHashReadHook - OnStorageRead StorageReadHook // Block hash read OnBlockHashRead BlockHashReadHook } diff --git a/eth/tracers/live/noop.go b/eth/tracers/live/noop.go index 97dbbbbecba0..f3def8560646 100644 --- a/eth/tracers/live/noop.go +++ b/eth/tracers/live/noop.go @@ -57,11 +57,6 @@ func newNoopTracer(_ json.RawMessage) (*tracing.Hooks, error) { OnCodeChange: t.OnCodeChange, OnStorageChange: t.OnStorageChange, OnLog: t.OnLog, - OnBalanceRead: t.OnBalanceRead, - OnNonceRead: t.OnNonceRead, - OnCodeRead: t.OnCodeRead, - OnCodeHashRead: t.OnCodeHashRead, - OnStorageRead: t.OnStorageRead, OnBlockHashRead: t.OnBlockHashRead, }, nil } @@ -114,16 +109,6 @@ func (t *noop) OnLog(l *types.Log) { } -func (t *noop) OnBalanceRead(addr common.Address, bal *big.Int) {} - -func (t *noop) OnNonceRead(addr common.Address, nonce uint64) {} - -func (t *noop) OnCodeRead(addr common.Address, code []byte) {} - -func (t *noop) OnCodeHashRead(addr common.Address, hash common.Hash) {} - -func (t *noop) OnStorageRead(addr common.Address, slot, val common.Hash) {} - func (t *noop) OnBlockHashRead(number uint64, hash common.Hash) {} func (t *noop) OnGasChange(old, new uint64, reason tracing.GasChangeReason) { From 95b82cf0033fe6d844c9751eff1d3b3d6772e61d Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Mon, 9 Dec 2024 19:18:06 +0100 Subject: [PATCH 36/48] lint issue --- core/state/statedb_hooked.go | 1 - 1 file changed, 1 deletion(-) diff --git a/core/state/statedb_hooked.go b/core/state/statedb_hooked.go index 20f2bdc15ff5..26d021709959 100644 --- a/core/state/statedb_hooked.go +++ b/core/state/statedb_hooked.go @@ -63,7 +63,6 @@ func (s *hookedStateDB) GetNonce(addr common.Address) uint64 { func (s *hookedStateDB) GetCodeHash(addr common.Address) common.Hash { return s.inner.GetCodeHash(addr) - } func (s *hookedStateDB) GetCode(addr common.Address) []byte { From de48d559eaabfa7e9a30fca5cdb632c8e1197e40 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Tue, 10 Dec 2024 12:01:49 +0100 Subject: [PATCH 37/48] fix changelog --- core/tracing/CHANGELOG.md | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/core/tracing/CHANGELOG.md b/core/tracing/CHANGELOG.md index 5986a7429f36..b9f08bc65f30 100644 --- a/core/tracing/CHANGELOG.md +++ b/core/tracing/CHANGELOG.md @@ -4,7 +4,7 @@ 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. +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 @@ -12,11 +12,6 @@ The tracing interface has been extended with backwards-compatible changes to sup ### New methods -- `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. -- `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. - `OnSystemCallStartV2(vm *VMContext)`. This allows access to EVM context during system calls. It is a successor to `OnSystemCallStart`. From 9cae376df4610d4aa9e6b5c0d7793a3d2ba9bad6 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Tue, 10 Dec 2024 18:12:50 +0100 Subject: [PATCH 38/48] un-expose hooks copy --- core/tracing/hooks.go | 6 ++++-- core/tracing/journal.go | 2 +- core/tracing/journal_test.go | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/core/tracing/hooks.go b/core/tracing/hooks.go index 6b9b3e03e841..b70bd4cd5018 100644 --- a/core/tracing/hooks.go +++ b/core/tracing/hooks.go @@ -207,8 +207,10 @@ type Hooks struct { OnBlockHashRead BlockHashReadHook } -// Copy creates a new Hooks instance with all implemented hooks copied from the original. -func (h *Hooks) Copy() *Hooks { +// copy creates a new Hooks instance with all implemented hooks copied from the original. +// Note: it is not a deep copy. If a hook has been implemented as a closure and acts on +// a mutable state, the copied hook will still act on the same state. +func (h *Hooks) copy() *Hooks { copied := &Hooks{} srcValue := reflect.ValueOf(h).Elem() dstValue := reflect.ValueOf(copied).Elem() diff --git a/core/tracing/journal.go b/core/tracing/journal.go index 7fab5a301bcc..6f386efda5d2 100644 --- a/core/tracing/journal.go +++ b/core/tracing/journal.go @@ -62,7 +62,7 @@ func WrapWithJournal(hooks *Hooks) (*Hooks, error) { } // Create a new Hooks instance and copy all hooks - wrapped := hooks.Copy() + wrapped := hooks.copy() // Create journal j := &journal{entries: make([]entry, 0), hooks: hooks} // Scope hooks need to be re-implemented. diff --git a/core/tracing/journal_test.go b/core/tracing/journal_test.go index e9a14e585ec6..7ea28bb75e1c 100644 --- a/core/tracing/journal_test.go +++ b/core/tracing/journal_test.go @@ -179,7 +179,7 @@ func TestAllHooksCalled(t *testing.T) { continue } // Skip non-hooks, i.e. Copy - if field.Name == "Copy" { + if field.Name == "copy" { continue } From 459c50f79339873483e9b6cf5e1ddf02465bd6d7 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Tue, 4 Feb 2025 16:30:39 +0100 Subject: [PATCH 39/48] refactor copy --- core/tracing/hooks.go | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/core/tracing/hooks.go b/core/tracing/hooks.go index b70bd4cd5018..26d4578577bd 100644 --- a/core/tracing/hooks.go +++ b/core/tracing/hooks.go @@ -18,7 +18,6 @@ package tracing import ( "math/big" - "reflect" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" @@ -211,17 +210,8 @@ type Hooks struct { // Note: it is not a deep copy. If a hook has been implemented as a closure and acts on // a mutable state, the copied hook will still act on the same state. func (h *Hooks) copy() *Hooks { - 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 + c := *h + return &c } // BalanceChangeReason is used to indicate the reason for a balance change, useful From 6f5e74b71756cdf75adb3f423640a859076eab47 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Tue, 4 Feb 2025 18:19:40 +0100 Subject: [PATCH 40/48] Use nonce reason in journal --- cmd/evm/internal/t8ntool/execution.go | 2 +- core/genesis.go | 4 +-- core/state/statedb.go | 2 +- core/state/statedb_fuzz_test.go | 2 +- core/state/statedb_hooked.go | 11 ++++--- core/state/statedb_hooked_test.go | 2 +- core/state/statedb_test.go | 6 ++-- core/state_transition.go | 4 +-- core/tracing/CHANGELOG.md | 8 ++++- core/tracing/hooks.go | 30 +++++++++++++++++ core/tracing/journal.go | 39 ++++++++++++----------- core/tracing/journal_test.go | 33 +++++++++++++++++-- core/txpool/blobpool/blobpool_test.go | 8 ++--- core/txpool/legacypool/legacypool_test.go | 14 ++++---- core/verkle_witness_test.go | 3 +- core/vm/evm.go | 4 +-- core/vm/interface.go | 2 +- core/vm/runtime/runtime_test.go | 2 +- internal/ethapi/override/override.go | 2 +- tests/state_test_util.go | 2 +- 20 files changed, 124 insertions(+), 56 deletions(-) diff --git a/cmd/evm/internal/t8ntool/execution.go b/cmd/evm/internal/t8ntool/execution.go index aef497885ed9..54c9b461744c 100644 --- a/cmd/evm/internal/t8ntool/execution.go +++ b/cmd/evm/internal/t8ntool/execution.go @@ -430,7 +430,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 85ef049ba6cf..c668ed13a62d 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -137,7 +137,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) } @@ -159,7 +159,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 d279ccfdfe22..9bb749468cee 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 7cbfd9b9d7d8..77a40dc26deb 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 31bdd06b462c..e21feb1ae935 100644 --- a/core/state/statedb_hooked.go +++ b/core/state/statedb_hooked.go @@ -175,10 +175,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 5f82ed06d0f1..1580679ad3a5 100644 --- a/core/state/statedb_hooked_test.go +++ b/core/state/statedb_hooked_test.go @@ -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.NonceChangeUnspecified) 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 37141e90b021..f9cdcdbf49e9 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 58728e470e46..8600407d5b14 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -464,7 +464,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.AuthList != nil { @@ -572,7 +572,7 @@ func (st *stateTransition) applyAuthorization(msg *Message, auth *types.Authoriz } // 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 b9f08bc65f30..1e539fb1ffdb 100644 --- a/core/tracing/CHANGELOG.md +++ b/core/tracing/CHANGELOG.md @@ -9,11 +9,17 @@ The tracing interface has been extended with backwards-compatible changes to sup ### 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 @@ -39,7 +45,7 @@ func init() { The state changes that are covered by the journaling library are: - `OnBalanceChange`. Note that `OnBalanceChange` will carry the `BalanceChangeRevert` reason. -- `OnNonceChange` +- `OnNonceChange`, `OnNonceChangeV2` - `OnCodeChange` - `OnStorageChange` diff --git a/core/tracing/hooks.go b/core/tracing/hooks.go index 26d4578577bd..c39e09aca19e 100644 --- a/core/tracing/hooks.go +++ b/core/tracing/hooks.go @@ -164,6 +164,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) @@ -199,6 +202,7 @@ type Hooks struct { // State events OnBalanceChange BalanceChangeHook OnNonceChange NonceChangeHook + OnNonceChangeV2 NonceChangeHookV2 OnCodeChange CodeChangeHook OnStorageChange StorageChangeHook OnLog LogHook @@ -336,3 +340,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. + NonceChangeRevert NonceChangeReason = 6 +) diff --git a/core/tracing/journal.go b/core/tracing/journal.go index 6f386efda5d2..e32b03ce7fe0 100644 --- a/core/tracing/journal.go +++ b/core/tracing/journal.go @@ -38,9 +38,8 @@ type revision struct { // 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 { - entries []entry - hooks *Hooks - lastCreator *common.Address // Account that initiated the last contract creation + entries []entry + hooks *Hooks validRevisions []revision nextRevisionId int @@ -57,9 +56,12 @@ func WrapWithJournal(hooks *Hooks) (*Hooks, error) { 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.OnCodeChange == nil && hooks.OnStorageChange == nil { + 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.copy() @@ -73,8 +75,12 @@ func WrapWithJournal(hooks *Hooks) (*Hooks, error) { if hooks.OnBalanceChange != nil { wrapped.OnBalanceChange = j.OnBalanceChange } - if hooks.OnNonceChange != nil { - wrapped.OnNonceChange = j.OnNonceChange + 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 @@ -142,18 +148,12 @@ func (j *journal) OnTxEnd(receipt *types.Receipt, err error) { func (j *journal) OnEnter(depth int, typ byte, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { j.revIds = append(j.revIds, j.snapshot()) - if typ == CREATE || typ == CREATE2 { - j.lastCreator = &from - } if j.hooks.OnEnter != nil { j.hooks.OnEnter(depth, typ, from, to, input, gas, value) } } func (j *journal) OnExit(depth int, output []byte, gasUsed uint64, err error, reverted bool) { - if j.lastCreator != nil { - j.lastCreator = nil - } revId := j.revIds[len(j.revIds)-1] j.revIds = j.revIds[:len(j.revIds)-1] if reverted { @@ -171,16 +171,15 @@ func (j *journal) OnBalanceChange(addr common.Address, prev, new *big.Int, reaso } } -func (j *journal) OnNonceChange(addr common.Address, prev, new uint64) { +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 j.lastCreator != nil && *j.lastCreator == addr { - // Skip only the first nonce change. - j.lastCreator = nil - } else { + if reason != NonceChangeContractCreator { j.entries = append(j.entries, nonceChange{addr: addr, prev: prev, new: new}) } - if j.hooks.OnNonceChange != nil { + if j.hooks.OnNonceChangeV2 != nil { + j.hooks.OnNonceChangeV2(addr, prev, new, reason) + } else if j.hooks.OnNonceChange != nil { j.hooks.OnNonceChange(addr, prev, new) } } @@ -241,7 +240,9 @@ func (b balanceChange) revert(hooks *Hooks) { } func (n nonceChange) revert(hooks *Hooks) { - if hooks.OnNonceChange != nil { + 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) } } diff --git a/core/tracing/journal_test.go b/core/tracing/journal_test.go index 7ea28bb75e1c..64cdbe03a8cc 100644 --- a/core/tracing/journal_test.go +++ b/core/tracing/journal_test.go @@ -40,6 +40,10 @@ func (t *testTracer) OnNonceChange(addr common.Address, prev uint64, new uint64) t.nonce = new } +func (t *testTracer) OnNonceChangeV2(addr common.Address, prev uint64, new uint64, reason NonceChangeReason) { + t.nonce = new +} + func (t *testTracer) OnCodeChange(addr common.Address, prevCodeHash common.Hash, prevCode []byte, codeHash common.Hash, code []byte) { t.code = code } @@ -67,7 +71,7 @@ func TestJournalIntegration(t *testing.T) { 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.OnNonceChange(addr, 0, 1) + 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}) @@ -101,7 +105,7 @@ func TestJournalTopRevert(t *testing.T) { 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.OnNonceChange(addr, 0, 1) + 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(0, nil, 100, errors.New("revert"), true) @@ -150,13 +154,28 @@ func TestNonceIncOnCreate(t *testing.T) { } addr := common.HexToAddress("0x1234") wr.OnEnter(0, CREATE, addr, addr, nil, 1000, big.NewInt(0)) - wr.OnNonceChange(addr, 0, 1) + 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{} + 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() @@ -182,6 +201,10 @@ func TestAllHooksCalled(t *testing.T) { if field.Name == "copy" { continue } + // Skip if field is not set + if wrappedValue.Field(i).IsNil() { + continue + } // Get the method method := wrappedValue.Field(i) @@ -218,6 +241,7 @@ func newTracerAllHooks() *tracerAllHooks { for i := 0; i < hooksType.NumField(); i++ { t.hooksCalled[hooksType.Field(i).Name] = false } + delete(t.hooksCalled, "OnNonceChange") return t } @@ -242,6 +266,9 @@ func (t *tracerAllHooks) hooks() *Hooks { 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 diff --git a/core/txpool/blobpool/blobpool_test.go b/core/txpool/blobpool/blobpool_test.go index e4441bec5dad..4a96ba640a36 100644 --- a/core/txpool/blobpool/blobpool_test.go +++ b/core/txpool/blobpool/blobpool_test.go @@ -640,9 +640,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) @@ -1384,7 +1384,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 { @@ -1439,7 +1439,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 39673d176db9..f5d834101c66 100644 --- a/core/txpool/legacypool/legacypool_test.go +++ b/core/txpool/legacypool/legacypool_test.go @@ -252,7 +252,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 } @@ -313,7 +313,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() } @@ -1074,8 +1074,8 @@ func testQueueTimeLimiting(t *testing.T, nolocals bool) { } // remove current transactions and increase nonce to prepare for a reset and cleanup - statedb.SetNonce(crypto.PubkeyToAddress(remote.PublicKey), 2) - statedb.SetNonce(crypto.PubkeyToAddress(local.PublicKey), 2) + statedb.SetNonce(crypto.PubkeyToAddress(remote.PublicKey), 2, tracing.NonceChangeUnspecified) + statedb.SetNonce(crypto.PubkeyToAddress(local.PublicKey), 2, tracing.NonceChangeUnspecified) <-pool.requestReset(nil, nil) // make sure queue, pending are cleared @@ -2405,7 +2405,7 @@ func testJournaling(t *testing.T, nolocals bool) { } // Terminate the old pool, bump the local nonce, create a new pool and ensure relevant transaction survive pool.Close() - statedb.SetNonce(crypto.PubkeyToAddress(local.PublicKey), 1) + statedb.SetNonce(crypto.PubkeyToAddress(local.PublicKey), 1, tracing.NonceChangeUnspecified) blockchain = newTestBlockChain(params.TestChainConfig, 1000000, statedb, new(event.Feed)) pool = New(config, blockchain) @@ -2428,12 +2428,12 @@ func testJournaling(t *testing.T, nolocals bool) { t.Fatalf("pool internal state corrupted: %v", err) } // Bump the nonce temporarily and ensure the newly invalidated transaction is removed - statedb.SetNonce(crypto.PubkeyToAddress(local.PublicKey), 2) + statedb.SetNonce(crypto.PubkeyToAddress(local.PublicKey), 2, tracing.NonceChangeUnspecified) <-pool.requestReset(nil, nil) time.Sleep(2 * config.Rejournal) pool.Close() - statedb.SetNonce(crypto.PubkeyToAddress(local.PublicKey), 1) + statedb.SetNonce(crypto.PubkeyToAddress(local.PublicKey), 1, tracing.NonceChangeUnspecified) blockchain = newTestBlockChain(params.TestChainConfig, 1000000, statedb, new(event.Feed)) pool = New(config, blockchain) pool.Init(config.PriceLimit, blockchain.CurrentBlock(), makeAddressReserver()) diff --git a/core/verkle_witness_test.go b/core/verkle_witness_test.go index 508823120749..d20bad85bf1b 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" @@ -218,7 +219,7 @@ func TestProcessParentBlockHash(t *testing.T) { // block 2 parent hash is 0x0200.... // etc checkBlockHashes := func(statedb *state.StateDB) { - 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 1a0215459c75..3be0a3b0673c 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/interface.go b/core/vm/interface.go index 011541dde301..2596a7d9bb2e 100644 --- a/core/vm/interface.go +++ b/core/vm/interface.go @@ -38,7 +38,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 6074e9a09628..760c26c6db97 100644 --- a/core/vm/runtime/runtime_test.go +++ b/core/vm/runtime/runtime_test.go @@ -410,7 +410,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/internal/ethapi/override/override.go b/internal/ethapi/override/override.go index 70b62102751a..f6a8a94ffdaf 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 e735ce2fb86f..bb76b527c02d 100644 --- a/tests/state_test_util.go +++ b/tests/state_test_util.go @@ -505,7 +505,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) From 59a50225685343f30750540c590a130c60ca45d0 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Tue, 4 Feb 2025 18:42:05 +0100 Subject: [PATCH 41/48] fix test --- core/state/statedb_hooked_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/state/statedb_hooked_test.go b/core/state/statedb_hooked_test.go index a15b240004a5..ce42e9640977 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, tracing.NonceChangeUnspecified) + 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")) From 4ba05e9aa5dd474c0bd9f0c60177cbcc476a9223 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Tue, 4 Feb 2025 23:48:03 +0100 Subject: [PATCH 42/48] core/tracing: add logging in journal test --- core/tracing/journal_test.go | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/core/tracing/journal_test.go b/core/tracing/journal_test.go index 64cdbe03a8cc..5b5a41913610 100644 --- a/core/tracing/journal_test.go +++ b/core/tracing/journal_test.go @@ -26,6 +26,7 @@ import ( ) type testTracer struct { + t *testing.T bal *big.Int nonce uint64 code []byte @@ -33,22 +34,27 @@ type testTracer struct { } 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) 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) } @@ -60,7 +66,7 @@ func (t *testTracer) OnStorageChange(addr common.Address, slot common.Hash, prev } func TestJournalIntegration(t *testing.T) { - tr := &testTracer{} + 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) @@ -96,7 +102,7 @@ func TestJournalIntegration(t *testing.T) { } func TestJournalTopRevert(t *testing.T) { - tr := &testTracer{} + 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) @@ -119,7 +125,7 @@ func TestJournalTopRevert(t *testing.T) { } func TestJournalNestedCalls(t *testing.T) { - tr := &testTracer{} + 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) @@ -136,18 +142,19 @@ func TestJournalNestedCalls(t *testing.T) { wr.OnBalanceChange(addr, big.NewInt(100), big.NewInt(200), BalanceChangeUnspecified) 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, 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.OnExit(1, nil, 100, errors.New("revert"), true) wr.OnExit(0, nil, 150, nil, false) - if tr.bal.Cmp(big.NewInt(0)) != 0 { + if tr.bal.Sign() != 0 { t.Fatalf("unexpected balance: %v", tr.bal) } } func TestNonceIncOnCreate(t *testing.T) { - tr := &testTracer{} + tr := &testTracer{t: t} wr, err := WrapWithJournal(&Hooks{OnNonceChange: tr.OnNonceChange}) if err != nil { t.Fatalf("failed to wrap test tracer: %v", err) @@ -162,7 +169,7 @@ func TestNonceIncOnCreate(t *testing.T) { } func TestOnNonceChangeV2(t *testing.T) { - tr := &testTracer{} + tr := &testTracer{t: t} wr, err := WrapWithJournal(&Hooks{OnNonceChangeV2: tr.OnNonceChangeV2}) if err != nil { t.Fatalf("failed to wrap test tracer: %v", err) From 2795c0e23bfb4110f67ec81f6f9979a114ac2e9c Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Wed, 5 Feb 2025 00:32:55 +0100 Subject: [PATCH 43/48] core/tracing: simplify journal implementation --- core/tracing/journal.go | 84 ++++++++++++++++------------------------- 1 file changed, 33 insertions(+), 51 deletions(-) diff --git a/core/tracing/journal.go b/core/tracing/journal.go index e32b03ce7fe0..2ef0e2a649aa 100644 --- a/core/tracing/journal.go +++ b/core/tracing/journal.go @@ -19,7 +19,6 @@ package tracing import ( "fmt" "math/big" - "sort" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" @@ -30,20 +29,12 @@ const ( CREATE2 = 0xf5 ) -type revision struct { - id int - journalIndex int -} - // 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 { - entries []entry - hooks *Hooks - - validRevisions []revision - nextRevisionId int - revIds []int + hooks *Hooks + entries []entry + revisions []int } type entry interface { @@ -64,9 +55,10 @@ func WrapWithJournal(hooks *Hooks) (*Hooks, error) { } // Create a new Hooks instance and copy all hooks - wrapped := hooks.copy() + wrapped := *hooks + // Create journal - j := &journal{entries: make([]entry, 0), hooks: hooks} + j := &journal{hooks: hooks} // Scope hooks need to be re-implemented. wrapped.OnTxEnd = j.OnTxEnd wrapped.OnEnter = j.OnEnter @@ -89,7 +81,7 @@ func WrapWithJournal(hooks *Hooks) (*Hooks, error) { wrapped.OnStorageChange = j.OnStorageChange } - return wrapped, nil + return &wrapped, nil } // reset clears the journal, after this operation the journal can be used anew. @@ -97,48 +89,34 @@ func WrapWithJournal(hooks *Hooks) (*Hooks, error) { // can be reused. func (j *journal) reset() { j.entries = j.entries[:0] - j.validRevisions = j.validRevisions[:0] - j.nextRevisionId = 0 + j.revisions = j.revisions[:0] } -// snapshot returns an identifier for the current revision of the state. -func (j *journal) snapshot() int { - id := j.nextRevisionId - j.nextRevisionId++ - j.validRevisions = append(j.validRevisions, revision{id, j.length()}) - return id -} - -// revertToSnapshot reverts all state changes made since the given revision. -func (j *journal) revertToSnapshot(revid int, hooks *Hooks) { - // Find the snapshot in the stack of valid snapshots. - idx := sort.Search(len(j.validRevisions), func(i int) bool { - return j.validRevisions[i].id >= revid - }) - if idx == len(j.validRevisions) || j.validRevisions[idx].id != revid { - panic(fmt.Errorf("revision id %v cannot be reverted", revid)) - } - snapshot := j.validRevisions[idx].journalIndex - - // Replay the journal to undo changes and remove invalidated snapshots - j.revert(hooks, snapshot) - j.validRevisions = j.validRevisions[:idx] +// 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 undoes a batch of journaled modifications. -func (j *journal) revert(hooks *Hooks, snapshot int) { - for i := len(j.entries) - 1; i >= snapshot; i-- { - // Undo the changes made by the operation +// 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[:snapshot] + j.entries = j.entries[:rev] + j.popRevision() } -// length returns the current number of entries in the journal. -func (j *journal) length() int { - return len(j.entries) +// 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 { @@ -146,18 +124,22 @@ func (j *journal) OnTxEnd(receipt *types.Receipt, err error) { } } +// 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.revIds = append(j.revIds, j.snapshot()) + 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) { - revId := j.revIds[len(j.revIds)-1] - j.revIds = j.revIds[:len(j.revIds)-1] if reverted { - j.revertToSnapshot(revId, j.hooks) + j.revert(j.hooks) + } else { + j.popRevision() } if j.hooks.OnExit != nil { j.hooks.OnExit(depth, output, gasUsed, err, reverted) From 51720dcad1308c4987293527d9171e645058d247 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Wed, 5 Feb 2025 00:30:48 +0100 Subject: [PATCH 44/48] core/tracing: further improve journal tests --- core/tracing/journal.go | 5 -- core/tracing/journal_test.go | 137 ++++++++++++++++++++++++----------- 2 files changed, 93 insertions(+), 49 deletions(-) diff --git a/core/tracing/journal.go b/core/tracing/journal.go index 2ef0e2a649aa..25b124b9739a 100644 --- a/core/tracing/journal.go +++ b/core/tracing/journal.go @@ -24,11 +24,6 @@ import ( "github.com/ethereum/go-ethereum/core/types" ) -const ( - CREATE = 0xf0 - CREATE2 = 0xf5 -) - // 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 { diff --git a/core/tracing/journal_test.go b/core/tracing/journal_test.go index 5b5a41913610..c13153fc55ab 100644 --- a/core/tracing/journal_test.go +++ b/core/tracing/journal_test.go @@ -35,6 +35,9 @@ type testTracer struct { 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 } @@ -71,19 +74,25 @@ func TestJournalIntegration(t *testing.T) { 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) + { + 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) } @@ -107,15 +116,21 @@ func TestJournalTopRevert(t *testing.T) { 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(0, nil, 100, errors.New("revert"), true) - wr.OnExit(0, nil, 150, errors.New("revert"), true) + { + 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) } @@ -124,45 +139,75 @@ func TestJournalTopRevert(t *testing.T) { } } +// 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.OnEnter(1, 0, addr, addr, nil, 1000, big.NewInt(0)) - wr.OnBalanceChange(addr, big.NewInt(0), big.NewInt(100), 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.OnExit(2, nil, 100, nil, false) - wr.OnEnter(2, 0, addr, addr, nil, 1000, big.NewInt(0)) - wr.OnBalanceChange(addr, big.NewInt(100), big.NewInt(200), BalanceChangeUnspecified) - 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, 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.OnExit(1, nil, 100, errors.New("revert"), true) - wr.OnExit(0, nil, 150, nil, false) - if tr.bal.Sign() != 0 { + { + 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, CREATE, addr, addr, nil, 1000, big.NewInt(0)) - wr.OnNonceChangeV2(addr, 0, 1, NonceChangeContractCreator) - wr.OnExit(0, nil, 100, errors.New("revert"), true) + { + 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) } @@ -174,10 +219,14 @@ func TestOnNonceChangeV2(t *testing.T) { 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) + { + 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) } From 4787f31828a46a4b4aac44ae2066b5df0b7adaaa Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Wed, 5 Feb 2025 00:31:05 +0100 Subject: [PATCH 45/48] core/tracing: remove Hooks.copy --- core/tracing/hooks.go | 8 -------- 1 file changed, 8 deletions(-) diff --git a/core/tracing/hooks.go b/core/tracing/hooks.go index 46696070a118..ac5ebf1a0d50 100644 --- a/core/tracing/hooks.go +++ b/core/tracing/hooks.go @@ -209,14 +209,6 @@ type Hooks struct { OnBlockHashRead BlockHashReadHook } -// copy creates a new Hooks instance with all implemented hooks copied from the original. -// Note: it is not a deep copy. If a hook has been implemented as a closure and acts on -// a mutable state, the copied hook will still act on the same state. -func (h *Hooks) copy() *Hooks { - c := *h - return &c -} - // BalanceChangeReason is used to indicate the reason for a balance change, useful // for tracing and reporting. type BalanceChangeReason byte From 8a4402993a4eb2c3bcf0cb899be3d589d47fed3e Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Wed, 5 Feb 2025 00:39:37 +0100 Subject: [PATCH 46/48] core/tracing: add note about WrapWithJournal in comments --- core/tracing/hooks.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/tracing/hooks.go b/core/tracing/hooks.go index ac5ebf1a0d50..94b32d9b71c2 100644 --- a/core/tracing/hooks.go +++ b/core/tracing/hooks.go @@ -260,7 +260,7 @@ const ( 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. + // It is only emitted when the tracer has opted in to use the journaling wrapper (WrapWithJournal). BalanceChangeRevert BalanceChangeReason = 15 ) @@ -357,6 +357,6 @@ const ( 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. + // It is only emitted when the tracer has opted in to use the journaling wrapper (WrapWithJournal). NonceChangeRevert NonceChangeReason = 6 ) From eaacae4f4d10f407d24d600d181c0d0ea393e49b Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Wed, 5 Feb 2025 00:51:25 +0100 Subject: [PATCH 47/48] core/tracing: add a package-level doc comment --- core/tracing/hooks.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/core/tracing/hooks.go b/core/tracing/hooks.go index 94b32d9b71c2..4002b5720733 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 ( From 93432fc78623488ae7a77e704ddda7d878f13516 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Wed, 5 Feb 2025 12:31:28 +0100 Subject: [PATCH 48/48] license year --- core/tracing/journal.go | 2 +- core/tracing/journal_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/tracing/journal.go b/core/tracing/journal.go index 25b124b9739a..8937d4c5ae22 100644 --- a/core/tracing/journal.go +++ b/core/tracing/journal.go @@ -1,4 +1,4 @@ -// Copyright 2024 The go-ethereum Authors +// 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 diff --git a/core/tracing/journal_test.go b/core/tracing/journal_test.go index c13153fc55ab..d9616a2ce82b 100644 --- a/core/tracing/journal_test.go +++ b/core/tracing/journal_test.go @@ -1,4 +1,4 @@ -// Copyright 2024 The go-ethereum Authors +// 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