From b2c4d19ca25051b323515bb204c5f1f6545d658c Mon Sep 17 00:00:00 2001 From: Jared Wasinger Date: Tue, 30 Sep 2025 15:05:34 +0800 Subject: [PATCH] fix some stuff. replace the state diff/read calculation in statedb with computation of a block access list --- consensus/beacon/consensus.go | 11 +- consensus/consensus.go | 6 +- consensus/ethash/consensus.go | 2 +- core/block_access_list_creation.go | 18 +++- core/block_validator.go | 2 +- core/blockchain.go | 3 + core/chain_makers.go | 2 +- core/parallel_state_processor.go | 17 ++-- core/state/bal_reader.go | 10 +- core/state/state_object.go | 60 +---------- core/state/statedb.go | 79 +-------------- core/state/statedb_hooked.go | 57 ++++------- core/state_processor.go | 22 +--- core/types.go | 4 +- core/types/bal/bal.go | 158 +++++++++++++++-------------- core/vm/interface.go | 3 +- core/vm/operations_acl.go | 24 ++++- 17 files changed, 167 insertions(+), 311 deletions(-) diff --git a/consensus/beacon/consensus.go b/consensus/beacon/consensus.go index d5d9ff3975..84926c3d0b 100644 --- a/consensus/beacon/consensus.go +++ b/consensus/beacon/consensus.go @@ -19,16 +19,16 @@ package beacon import ( "errors" "fmt" - state2 "github.com/ethereum/go-ethereum/core/state" - "github.com/ethereum/go-ethereum/core/vm" "math/big" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/consensus/misc/eip1559" "github.com/ethereum/go-ethereum/consensus/misc/eip4844" + "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/params" "github.com/ethereum/go-ethereum/trie" "github.com/holiman/uint256" @@ -343,7 +343,7 @@ func (beacon *Beacon) Finalize(chain consensus.ChainHeaderReader, header *types. // FinalizeAndAssemble implements consensus.Engine, setting the final state and // assembling the block. -func (beacon *Beacon) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header *types.Header, state state2.BlockProcessingDB, body *types.Body, receipts []*types.Receipt) (*types.Block, error) { +func (beacon *Beacon) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, body *types.Body, receipts []*types.Receipt) (*types.Block, error) { if !beacon.IsPoSHeader(header) { return beacon.ethone.FinalizeAndAssemble(chain, header, state, body, receipts) } @@ -364,11 +364,6 @@ func (beacon *Beacon) FinalizeAndAssemble(chain consensus.ChainHeaderReader, hea // Assign the final state root to header. header.Root = state.IntermediateRoot(true) - // embed the block access list in the body - if chain.Config().IsAmsterdam(header.Number, header.Time) { - body.AccessList = state.(*state2.AccessListCreationDB).ConstructedBlockAccessList().ToEncodingObj() - } - // Assemble the final block. block := types.NewBlock(header, body, receipts, trie.NewStackTrie(nil)) diff --git a/consensus/consensus.go b/consensus/consensus.go index f6e94050b7..a68351f7ff 100644 --- a/consensus/consensus.go +++ b/consensus/consensus.go @@ -18,12 +18,12 @@ package consensus import ( - state2 "github.com/ethereum/go-ethereum/core/state" - "github.com/ethereum/go-ethereum/core/vm" "math/big" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/params" ) @@ -92,7 +92,7 @@ type Engine interface { // // Note: The block header and state database might be updated to reflect any // consensus rules that happen at finalization (e.g. block rewards). - FinalizeAndAssemble(chain ChainHeaderReader, header *types.Header, state state2.BlockProcessingDB, body *types.Body, receipts []*types.Receipt) (*types.Block, error) + FinalizeAndAssemble(chain ChainHeaderReader, header *types.Header, state *state.StateDB, body *types.Body, receipts []*types.Receipt) (*types.Block, error) // Seal generates a new sealing request for the given input block and pushes // the result into the given channel. diff --git a/consensus/ethash/consensus.go b/consensus/ethash/consensus.go index 220bb05db8..4f92f1282b 100644 --- a/consensus/ethash/consensus.go +++ b/consensus/ethash/consensus.go @@ -511,7 +511,7 @@ func (ethash *Ethash) Finalize(chain consensus.ChainHeaderReader, header *types. // FinalizeAndAssemble implements consensus.Engine, accumulating the block and // uncle rewards, setting the final state and assembling the block. -func (ethash *Ethash) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header *types.Header, state state.BlockProcessingDB, body *types.Body, receipts []*types.Receipt) (*types.Block, error) { +func (ethash *Ethash) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, body *types.Body, receipts []*types.Receipt) (*types.Block, error) { if len(body.Withdrawals) > 0 { return nil, errors.New("ethash does not support withdrawals") } diff --git a/core/block_access_list_creation.go b/core/block_access_list_creation.go index a0367bd6d5..e6f5bac856 100644 --- a/core/block_access_list_creation.go +++ b/core/block_access_list_creation.go @@ -1,6 +1,7 @@ package core import ( + "fmt" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/types" @@ -51,6 +52,11 @@ func (a *BlockAccessListTracer) AccessList() *bal.ConstructionBlockAccessList { return a.callAccessLists[0] } +// StateDiff returns a state diff at the current BAL index +func (a *BlockAccessListTracer) StateDiff() *bal.StateDiff { + return a.callAccessLists[0]. +} + func (a *BlockAccessListTracer) TxEndHook(receipt *types.Receipt, err error) { a.txIdx++ } @@ -71,9 +77,14 @@ func (a *BlockAccessListTracer) OnExit(depth int, output []byte, gasUsed uint64, if a.selfdestructedAccount != nil { delete(a.callAccessLists[len(a.callAccessLists)-1].Accounts, *a.selfdestructedAccount) } - if !reverted { - parentAccessList := a.callAccessLists[len(a.callAccessLists)-2] - scopeAccessList := a.callAccessLists[len(a.callAccessLists)-1] + parentAccessList := a.callAccessLists[len(a.callAccessLists)-2] + scopeAccessList := a.callAccessLists[len(a.callAccessLists)-1] + if reverted { + fmt.Println("exit reverted") + parentAccessList.MergeReads(scopeAccessList) + } else { + fmt.Println("exit normal") + fmt.Printf("scope access list is\n%s\n", scopeAccessList.ToEncodingObj().String()) parentAccessList.Merge(scopeAccessList) } @@ -101,6 +112,7 @@ func (a *BlockAccessListTracer) OnColdStorageRead(addr common.Address, key commo } func (a *BlockAccessListTracer) OnColdAccountRead(addr common.Address) { + fmt.Printf("cold account read %x\n", addr) a.callAccessLists[len(a.callAccessLists)-1].AccountRead(addr) } diff --git a/core/block_validator.go b/core/block_validator.go index 53be47ec56..d16a2402fa 100644 --- a/core/block_validator.go +++ b/core/block_validator.go @@ -147,7 +147,7 @@ func (v *BlockValidator) ValidateBody(block *types.Block) error { // ValidateState validates the various changes that happen after a state transition, // such as amount of used gas, the receipt roots and the state root itself. -func (v *BlockValidator) ValidateState(block *types.Block, statedb state.BlockProcessingDB, res *ProcessResult, validateStateRoot, stateless bool) error { +func (v *BlockValidator) ValidateState(block *types.Block, statedb *state.StateDB, res *ProcessResult, validateStateRoot, stateless bool) error { if res == nil { return errors.New("nil ProcessResult value") } diff --git a/core/blockchain.go b/core/blockchain.go index d35c78ae4a..898be9f6cc 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -2161,6 +2161,9 @@ func (bc *BlockChain) ProcessBlock(parentRoot common.Hash, block *types.Block, s } ptime = time.Since(pstart) + // unset the BAL-creation tracer (dirty) + bc.cfg.VmConfig.Tracer = nil + vstart := time.Now() if err := bc.validator.ValidateState(block, statedb, res, true, false); err != nil { bc.reportBlock(block, res, err) diff --git a/core/chain_makers.go b/core/chain_makers.go index 0cfb5cada4..32c444dee7 100644 --- a/core/chain_makers.go +++ b/core/chain_makers.go @@ -311,7 +311,7 @@ func (b *BlockGen) collectRequests(readonly bool) (requests [][]byte) { // The system contracts clear themselves on a system-initiated read. // When reading the requests mid-block, we don't want this behavior, so fork // off the statedb before executing the system calls. - statedb = statedb.Copy().(*state.StateDB) + statedb = statedb.Copy() } if b.cm.config.IsPrague(b.header.Number, b.header.Time) { diff --git a/core/parallel_state_processor.go b/core/parallel_state_processor.go index af957c73e0..e39d708bdb 100644 --- a/core/parallel_state_processor.go +++ b/core/parallel_state_processor.go @@ -122,9 +122,7 @@ func (p *ParallelStateProcessor) prepareExecResult(block *types.Block, allStateR // Finalize the block, applying any consensus engine specific extras (e.g. block rewards) p.chain.engine.Finalize(p.chain, header, tracingStateDB, block.Body()) // invoke Finalise so that withdrawals are accounted for in the state diff - finalDiff, finalAccesses := postTxState.Finalise(true) - computedDiff.Merge(finalDiff) - computedAccesses.Merge(*finalAccesses) + postTxState.Finalise(true) if err := postTxState.BlockAccessList().ValidateStateDiff(len(block.Transactions())+1, computedDiff); err != nil { return &ProcessResultWithMetrics{ @@ -247,10 +245,8 @@ func (p *ParallelStateProcessor) calcAndVerifyRoot(preState *state.StateDB, bloc // execTx executes single transaction returning a result which includes state accessed/modified func (p *ParallelStateProcessor) execTx(block *types.Block, tx *types.Transaction, idx int, db *state.StateDB, signer types.Signer) *txExecResult { header := block.Header() - var tracingStateDB = vm.StateDB(db) - if hooks := p.vmCfg.Tracer; hooks != nil { - tracingStateDB = state.NewHookedState(db, hooks) - } + balTracer, hooks := NewBlockAccessListTracer() + tracingStateDB := state.NewHookedState(db, hooks) context := NewEVMBlockContext(header, p.chain, nil) evm := vm.NewEVM(context, tracingStateDB, p.config, *p.vmCfg) @@ -274,7 +270,7 @@ func (p *ParallelStateProcessor) execTx(block *types.Block, tx *types.Transactio return &txExecResult{err: err} } - if err := db.BlockAccessList().ValidateStateDiff(idx+1, mutatedState); err != nil { + if err := db.BlockAccessList().ValidateStateDiff(idx+1, balTracer.AccessList().DiffAt(uint16(idx)+1)); err != nil { return &txExecResult{err: err} } @@ -289,6 +285,7 @@ func (p *ParallelStateProcessor) execTx(block *types.Block, tx *types.Transactio // Process performs EVM execution and state root computation for a block which is known // to contain an access list. func (p *ParallelStateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg vm.Config) (*ProcessResultWithMetrics, error) { + fmt.Println("start ParallelProcess") var ( header = block.Header() resCh = make(chan *ProcessResultWithMetrics) @@ -342,7 +339,7 @@ func (p *ParallelStateProcessor) Process(block *types.Block, statedb *state.Stat // compute the post-tx state prestate (before applying final block system calls and eip-4895 withdrawals) // the post-tx state transition is verified by resultHandler - postTxState := statedb.Copy().(*state.StateDB) + postTxState := statedb.Copy() tPreprocess = time.Since(pStart) @@ -356,7 +353,7 @@ func (p *ParallelStateProcessor) Process(block *types.Block, statedb *state.Stat tx := tx i := i workers.Go(func() error { - res := p.execTx(block, tx, i, startingState.Copy().(*state.StateDB), signer) + res := p.execTx(block, tx, i, startingState.Copy(), signer) txResCh <- *res return nil }) diff --git a/core/state/bal_reader.go b/core/state/bal_reader.go index 1c6820ad9c..016b9eb4d6 100644 --- a/core/state/bal_reader.go +++ b/core/state/bal_reader.go @@ -7,7 +7,6 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types/bal" "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/params" "github.com/holiman/uint256" "sync" "time" @@ -135,10 +134,6 @@ func (s *BALReader) initMutatedObjFromDiff(db *StateDB, addr common.Address, a * return obj } -var IgnoredBALAddresses map[common.Address]struct{} = map[common.Address]struct{}{ - params.SystemAddress: {}, -} - // BALReader provides methods for reading account state from a block access // list. State values returned from the Reader methods must not be modified. type BALReader struct { @@ -168,7 +163,6 @@ func (r *BALReader) ModifiedAccounts() (res []common.Address) { } func (r *BALReader) ValidateStateReads(allReads bal.StateAccesses) error { - fmt.Printf("bal is %v\n", r.block.Body().AccessList.String()) // 1. remove any slots from 'allReads' which were written // 2. validate that the read set in the BAL matches 'allReads' exactly for addr, reads := range allReads { @@ -178,8 +172,10 @@ func (r *BALReader) ValidateStateReads(allReads bal.StateAccesses) error { delete(reads, writeSlot) } } + if _, ok := r.accesses[addr]; !ok { + panic(fmt.Sprintf("%x wasn't in BAL", addr)) + } - fmt.Printf("addr is %x\n", addr) expectedReads := r.accesses[addr].StorageReads if len(reads) != len(expectedReads) { return fmt.Errorf("mismatch between the number of computed reads and number of expected reads") diff --git a/core/state/state_object.go b/core/state/state_object.go index 27f41a045d..4a29764b52 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -23,8 +23,6 @@ import ( "slices" "time" - "github.com/ethereum/go-ethereum/core/types/bal" - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" @@ -53,9 +51,6 @@ type stateObject struct { origin *types.StateAccount // Account original data without any change applied, nil means it was not existent data types.StateAccount // Account data with all mutations applied in the scope of block - txPreBalance *uint256.Int // the account balance after the last call to finalise - txPreNonce uint64 // the account nonce after the last call to finalise - // Write caches. trie Trie // storage trie, which becomes non-nil on first access code []byte // contract bytecode, which gets set when code is loaded @@ -78,8 +73,6 @@ type stateObject struct { // Cache flags. dirtyCode bool // true if the code was updated - nonFinalizedCode bool // true if the code was updated since the last call to finalise - // Flag whether the account was marked as self-destructed. The self-destructed // account is still accessible in the scope of same transaction. selfDestructed bool @@ -111,8 +104,6 @@ func newObject(db *StateDB, address common.Address, acct *types.StateAccount) *s addrHash: crypto.Keccak256Hash(address[:]), origin: origin, data: *acct, - txPreBalance: acct.Balance.Clone(), - txPreNonce: acct.Nonce, originStorage: make(Storage), dirtyStorage: make(Storage), pendingStorage: make(Storage), @@ -248,29 +239,7 @@ func (s *stateObject) setState(key common.Hash, value common.Hash, origin common // finalise moves all dirty storage slots into the pending area to be hashed or // committed later. It is invoked at the end of every transaction. -func (s *stateObject) finalise() *bal.AccountState { - var accountPost bal.AccountState - if s.db.enableStateDiffRecording { - if s.Balance().Cmp(s.txPreBalance) != 0 { - accountPost.Balance = s.Balance() - } - - if s.Nonce() != s.txPreNonce { - accountPost.Nonce = new(uint64) - *accountPost.Nonce = s.Nonce() - } - - // include account code changes: created contracts and 7702 delegation authority code changes - if s.nonFinalizedCode { - if s.code == nil { - // code cleared (7702). code must be non-nil in the post to signal that it's part of the diff vs being unchanged. - accountPost.Code = []byte{} - } else { - accountPost.Code = s.code - } - } - } - +func (s *stateObject) finalise() { slotsToPrefetch := make([]common.Hash, 0, len(s.dirtyStorage)) for key, value := range s.dirtyStorage { if origin, exist := s.uncommittedStorage[key]; exist && origin == value { @@ -278,32 +247,14 @@ func (s *stateObject) finalise() *bal.AccountState { // to avoid thrashing the data structures. delete(s.uncommittedStorage, key) - if s.db.enableStateDiffRecording { - if accountPost.StorageWrites == nil { - accountPost.StorageWrites = make(map[common.Hash]common.Hash) - } - accountPost.StorageWrites[key] = value - } } else if exist { // The slot is modified to another value and the slot has been // tracked for commit in uncommittedStorage. - if s.db.enableStateDiffRecording { - if accountPost.StorageWrites == nil { - accountPost.StorageWrites = make(map[common.Hash]common.Hash) - } - accountPost.StorageWrites[key] = value - } } else { // The slot is different from its original value and hasn't been // tracked for commit yet. s.uncommittedStorage[key] = s.GetCommittedState(key) slotsToPrefetch = append(slotsToPrefetch, key) // Copy needed for closure - if s.db.enableStateDiffRecording { - if accountPost.StorageWrites == nil { - accountPost.StorageWrites = make(map[common.Hash]common.Hash) - } - accountPost.StorageWrites[key] = value - } } // Aggregate the dirty storage slots into the pending area. It might // be possible that the value of tracked slot here is same with the @@ -325,12 +276,6 @@ func (s *stateObject) finalise() *bal.AccountState { // of the newly-created object as it's no longer eligible for self-destruct // by EIP-6780. For non-newly-created objects, it's a no-op. s.newContract = false - - s.nonFinalizedCode = false - - s.txPreBalance = s.data.Balance.Clone() - s.txPreNonce = s.data.Nonce - return &accountPost } // updateTrie is responsible for persisting cached storage changes into the @@ -558,8 +503,6 @@ func (s *stateObject) deepCopy(db *StateDB) *stateObject { dirtyCode: s.dirtyCode, selfDestructed: s.selfDestructed, newContract: s.newContract, - txPreNonce: s.txPreNonce, - txPreBalance: s.txPreBalance.Clone(), } if s.trie != nil { obj.trie = mustCopyTrie(s.trie) @@ -631,7 +574,6 @@ func (s *stateObject) setCode(codeHash common.Hash, code []byte) { func (s *stateObject) setCodeModified(codeHash common.Hash, code []byte) { s.setCode(codeHash, code) s.dirtyCode = true - s.nonFinalizedCode = true } func (s *stateObject) SetNonce(nonce uint64) { diff --git a/core/state/statedb.go b/core/state/statedb.go index 606102a53c..90d9e343e2 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -26,8 +26,6 @@ import ( "sync/atomic" "time" - "github.com/ethereum/go-ethereum/core/types/bal" - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/state/snapshot" @@ -149,9 +147,6 @@ type StateDB struct { witness *stateless.Witness witnessStats *stateless.WitnessStats - enableStateDiffRecording bool // if true, calls to Finalise will return the mutated state - stateAccesses bal.StateAccesses // accounts/storage accessed during transaction execution - blockAccessList *BALReader // Measurements gathered during execution for debugging purposes @@ -201,7 +196,6 @@ func NewWithReader(root common.Hash, db Database, reader Reader) (*StateDB, erro journal: newJournal(), accessList: newAccessList(), transientStorage: newTransientStorage(), - stateAccesses: make(bal.StateAccesses), } if db.TrieDB().IsVerkle() { sdb.accessEvents = NewAccessEvents(db.PointCache()) @@ -209,13 +203,6 @@ func NewWithReader(root common.Hash, db Database, reader Reader) (*StateDB, erro return sdb, nil } -// EnableStateDiffRecording enables the recording of state modifications -// which are accumulated at each call to Finalise and can be retrieved -// using GetStateDiff. -func (s *StateDB) EnableStateDiffRecording() { - s.enableStateDiffRecording = true -} - // StartPrefetcher initializes a new trie prefetcher to pull in nodes from the // state trie concurrently while the state is mutated so that when we reach the // commit phase, most of the needed data is already hot. @@ -434,14 +421,6 @@ func (s *StateDB) GetCodeHash(addr common.Address) common.Hash { // GetState retrieves the value associated with the specific key. func (s *StateDB) GetState(addr common.Address, hash common.Hash) common.Hash { stateObject := s.getStateObject(addr) - if s.enableStateDiffRecording { - if _, shouldIgnore := IgnoredBALAddresses[addr]; !shouldIgnore { - if _, ok := s.stateAccesses[addr]; !ok { - s.stateAccesses[addr] = make(map[common.Hash]struct{}) - } - s.stateAccesses[addr][hash] = struct{}{} - } - } if stateObject != nil { return stateObject.GetState(hash) } @@ -673,13 +652,6 @@ func (s *StateDB) deleteStateObject(addr common.Address) { // getStateObject retrieves a state object given by the address, returning nil if // the object is not found or was deleted in this execution context. func (s *StateDB) getStateObject(addr common.Address) *stateObject { - if s.enableStateDiffRecording { - if _, shouldIgnore := IgnoredBALAddresses[addr]; !shouldIgnore { - if _, ok := s.stateAccesses[addr]; !ok { - s.stateAccesses[addr] = make(map[common.Hash]struct{}) - } - } - } // Prefer live objects if any is available if obj := s.stateObjects[addr]; obj != nil { return obj @@ -797,9 +769,7 @@ func (s *StateDB) Copy() *StateDB { preimages: maps.Clone(s.preimages), // don't deep-copy these - enableStateDiffRecording: s.enableStateDiffRecording, - stateAccesses: make(bal.StateAccesses), // Don't deep copy state accesses - blockAccessList: s.blockAccessList, + blockAccessList: s.blockAccessList, // Do we need to copy the access list and transient storage? // In practice: No. At the start of a transaction, these two lists are empty. @@ -865,8 +835,7 @@ func (s *StateDB) GetRefund() uint64 { // // If EnableStateDiffRecording has been called, it returns a state diff containing // the state which was mutated since the previous invocation of Finalise. Otherwise, nil. -func (s *StateDB) Finalise(deleteEmptyObjects bool) (mutations *bal.StateDiff, accesses *bal.StateAccesses) { - mutations = &bal.StateDiff{Mutations: make(map[common.Address]*bal.AccountState)} +func (s *StateDB) Finalise(deleteEmptyObjects bool) { addressesToPrefetch := make([]common.Address, 0, len(s.journal.dirties)) for addr := range s.journal.dirties { obj, exist := s.stateObjects[addr] @@ -880,24 +849,6 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) (mutations *bal.StateDiff, a continue } if obj.selfDestructed || (deleteEmptyObjects && obj.empty()) { - // record state diffs for any preexisting accounts which became empty - if s.enableStateDiffRecording && obj.txPreBalance != nil && !obj.txPreBalance.IsZero() { - if _, shouldIgnore := IgnoredBALAddresses[obj.address]; !shouldIgnore { - // TODO: IsZero check is somehow needed for coinbase. figure out why. - // TODO: the above check should be as easy as checking that the account was not selfdestructed in the - // current transaction, but that causes spec tests to fail. need to figure out why. - postState := bal.NewEmptyAccountState() - postState.Balance = new(uint256.Int) - mutations.Mutations[obj.address] = postState - - // note that this account cannot have any storage accesses in the same tx: - // * it cannot have code if it was previously-existing and became empty in this tx - // * perhaps it could have had code deployed before selfdestruct removal, that set some storage - // and the code was removed (pre-selfdestruct removal) leaving the storage. But the storage - // won't be cleared from the state here right - } - } - delete(s.stateObjects, obj.address) s.markDelete(addr) // We need to maintain account deletions explicitly (will remain @@ -907,26 +858,7 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) (mutations *bal.StateDiff, a s.stateObjectsDestruct[obj.address] = obj } } else { - accountPost := obj.finalise() - - if s.enableStateDiffRecording { - if accountPost.Nonce != nil || accountPost.Code != nil || accountPost.StorageWrites != nil || accountPost.Balance != nil { - // the account executed SENDALL but did not send a balance, don't include it in the diff - // TODO: probably shouldn't include the account in the dirty set in this case (unrelated to the BAL changes) - mutations.Mutations[obj.address] = accountPost - } - if len(accountPost.StorageWrites) > 0 { - // remove all the written slots from the accessedState - if _, ok := s.stateAccesses[obj.address]; ok { - for slot, _ := range accountPost.StorageWrites { - delete(s.stateAccesses[obj.address], slot) - } - } - } - if _, ok := s.stateAccesses[obj.address]; ok && len(s.stateAccesses[obj.address]) == 0 { - delete(s.stateAccesses, obj.address) - } - } + obj.finalise() s.markUpdate(addr) } // At this point, also ship the address off to the precacher. The precacher // will start loading tries, and when the change is eventually committed, @@ -941,11 +873,6 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) (mutations *bal.StateDiff, a // Invalidate journal because reverting across transactions is not allowed. s.clearJournalAndRefund() - - accesses = new(bal.StateAccesses) - *accesses = s.stateAccesses - s.stateAccesses = make(bal.StateAccesses) - return mutations, accesses } // IntermediateRoot computes the current root hash of the state trie. diff --git a/core/state/statedb_hooked.go b/core/state/statedb_hooked.go index 7919601a73..5e53feb9c9 100644 --- a/core/state/statedb_hooked.go +++ b/core/state/statedb_hooked.go @@ -19,8 +19,6 @@ package state import ( "math/big" - "github.com/ethereum/go-ethereum/core/types/bal" - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/stateless" "github.com/ethereum/go-ethereum/core/tracing" @@ -34,12 +32,12 @@ import ( // hookedStateDB represents a statedb which emits calls to tracing-hooks // on state operations. type hookedStateDB struct { - inner BlockProcessingDB + inner *StateDB hooks *tracing.Hooks } // NewHookedState wraps the given stateDb with the given hooks -func NewHookedState(stateDb BlockProcessingDB, hooks *tracing.Hooks) *hookedStateDB { +func NewHookedState(stateDb *StateDB, hooks *tracing.Hooks) *hookedStateDB { s := &hookedStateDB{stateDb, hooks} if s.hooks == nil { s.hooks = new(tracing.Hooks) @@ -277,42 +275,21 @@ func (s *hookedStateDB) AddLog(log *types.Log) { } } +func (s *hookedStateDB) Finalise(deleteEmptyObjects bool) { + defer s.inner.Finalise(deleteEmptyObjects) + if s.hooks.OnBalanceChange != nil { + for addr := range s.inner.journal.dirties { + obj := s.inner.stateObjects[addr] + if obj != nil && obj.selfDestructed { + // If ether was sent to account post-selfdestruct it is burnt. + if bal := obj.Balance(); bal.Sign() != 0 { + s.hooks.OnBalanceChange(addr, bal.ToBig(), new(big.Int), tracing.BalanceDecreaseSelfdestructBurn) + } + } + } + } +} + func (s *hookedStateDB) TxIndex() int { return s.inner.TxIndex() } - -func (s *hookedStateDB) Finalise(deleteEmptyObjects bool) (*bal.StateDiff, *bal.StateAccesses) { - /* - // TODO: implement this code without peering into statedb internals!!! - if s.hooks.OnBalanceChange != nil { - for addr := range s.inner.journal.dirties { - obj := s.inner.stateObjects[addr] - if obj != nil && obj.selfDestructed { - // If ether was sent to account post-selfdestruct it is burnt. - if bal := obj.Balance(); bal.Sign() != 0 { - s.hooks.OnBalanceChange(addr, bal.ToBig(), new(big.Int), tracing.BalanceDecreaseSelfdestructBurn) - } - } - } - } - */ - return s.inner.Finalise(deleteEmptyObjects) -} - -func (s *hookedStateDB) GetLogs(hash common.Hash, blockNumber uint64, blockHash common.Hash, blockTime uint64) []*types.Log { - return s.inner.GetLogs(hash, blockNumber, blockHash, blockTime) -} - -func (s *hookedStateDB) IntermediateRoot(deleteEmpty bool) common.Hash { - return s.inner.IntermediateRoot(deleteEmpty) -} -func (a *hookedStateDB) Database() Database { - return a.inner.Database() -} -func (a *hookedStateDB) GetTrie() Trie { - return a.inner.GetTrie() -} - -func (a *hookedStateDB) SetTxContext(thash common.Hash, ti int) { - a.inner.SetTxContext(thash, ti) -} diff --git a/core/state_processor.go b/core/state_processor.go index a6ea4aa798..7f7352222a 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -55,7 +55,7 @@ func NewStateProcessor(config *params.ChainConfig, chain *HeaderChain) *StatePro // Process returns the receipts and logs accumulated during the process and // returns the amount of gas that was used in the process. If any of the // transactions failed to execute due to insufficient gas it will return an error. -func (p *StateProcessor) Process(block *types.Block, statedb state.BlockProcessingDB, cfg vm.Config) (*ProcessResult, error) { +func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg vm.Config) (*ProcessResult, error) { var ( receipts types.Receipts usedGas = new(uint64) @@ -98,10 +98,6 @@ func (p *StateProcessor) Process(block *types.Block, statedb state.BlockProcessi } statedb.SetTxContext(tx.Hash(), i) - if alDB, ok := statedb.(*state.AccessListCreationDB); ok { - alDB.SetAccessListIndex(i + 1) - } - _, _, receipt, err := ApplyTransactionWithEVM(msg, gp, statedb, blockNumber, blockHash, context.Time, tx, usedGas, evm) if err != nil { return nil, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err) @@ -110,9 +106,6 @@ func (p *StateProcessor) Process(block *types.Block, statedb state.BlockProcessi allLogs = append(allLogs, receipt.Logs...) } - if alDB, ok := statedb.(*state.AccessListCreationDB); ok { - alDB.SetAccessListIndex(len(block.Transactions()) + 1) - } // Read requests if Prague is enabled. var requests [][]byte if p.config.IsPrague(block.Number(), block.Time()) { @@ -134,13 +127,6 @@ func (p *StateProcessor) Process(block *types.Block, statedb state.BlockProcessi // Finalize the block, applying any consensus engine specific extras (e.g. block rewards) p.chain.engine.Finalize(p.chain, header, tracingStateDB, block.Body()) - // if we are building access list, manually call finalise here to ensure - // that state diffs due to eip-4895 withdrawals are picked up on the access - // list. - if alDB, ok := statedb.(*state.AccessListCreationDB); ok { - alDB.Finalise(true) - } - return &ProcessResult{ Receipts: receipts, Requests: requests, @@ -152,7 +138,7 @@ func (p *StateProcessor) Process(block *types.Block, statedb state.BlockProcessi // ApplyTransactionWithEVM attempts to apply a transaction to the given state database // and uses the input parameters for its environment similar to ApplyTransaction. However, // this method takes an already created EVM instance as input. -func ApplyTransactionWithEVM(msg *Message, gp *GasPool, statedb state.BlockProcessingDB, blockNumber *big.Int, blockHash common.Hash, blockTime uint64, tx *types.Transaction, usedGas *uint64, evm *vm.EVM) (mutatedState *bal.StateDiff, accessedState *bal.StateAccesses, receipt *types.Receipt, err error) { +func ApplyTransactionWithEVM(msg *Message, gp *GasPool, statedb *state.StateDB, blockNumber *big.Int, blockHash common.Hash, blockTime uint64, tx *types.Transaction, usedGas *uint64, evm *vm.EVM) (mutatedState *bal.StateDiff, accessedState *bal.StateAccesses, receipt *types.Receipt, err error) { if hooks := evm.Config.Tracer; hooks != nil { if hooks.OnTxStart != nil { hooks.OnTxStart(evm.GetVMContext(), tx, msg.From) @@ -185,7 +171,7 @@ func ApplyTransactionWithEVM(msg *Message, gp *GasPool, statedb state.BlockProce } // MakeReceipt generates the receipt object for a transaction given its execution result. -func MakeReceipt(evm *vm.EVM, result *ExecutionResult, statedb state.BlockProcessingDB, blockNumber *big.Int, blockHash common.Hash, blockTime uint64, tx *types.Transaction, usedGas uint64, root []byte) *types.Receipt { +func MakeReceipt(evm *vm.EVM, result *ExecutionResult, statedb *state.StateDB, blockNumber *big.Int, blockHash common.Hash, blockTime uint64, tx *types.Transaction, usedGas uint64, root []byte) *types.Receipt { // Create a new receipt for the transaction, storing the intermediate root and gas used // by the tx. receipt := &types.Receipt{Type: tx.Type(), PostState: root, CumulativeGasUsed: usedGas} @@ -220,7 +206,7 @@ func MakeReceipt(evm *vm.EVM, result *ExecutionResult, statedb state.BlockProces // and uses the input parameters for its environment. It returns the receipt // for the transaction, gas used and an error if the transaction failed, // indicating the block was invalid. -func ApplyTransaction(evm *vm.EVM, gp *GasPool, statedb state.BlockProcessingDB, header *types.Header, tx *types.Transaction, usedGas *uint64) (*types.Receipt, error) { +func ApplyTransaction(evm *vm.EVM, gp *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction, usedGas *uint64) (*types.Receipt, error) { msg, err := TransactionToMessage(tx, types.MakeSigner(evm.ChainConfig(), header.Number, header.Time), header.BaseFee) if err != nil { return nil, err diff --git a/core/types.go b/core/types.go index cff67cad0b..0f422a14e0 100644 --- a/core/types.go +++ b/core/types.go @@ -32,7 +32,7 @@ type Validator interface { ValidateBody(block *types.Block) error // ValidateState validates the given statedb and optionally the process result. - ValidateState(block *types.Block, state state.BlockProcessingDB, res *ProcessResult, validateState, stateless bool) error + ValidateState(block *types.Block, state *state.StateDB, res *ProcessResult, validateStateRoot, stateless bool) error } // Prefetcher is an interface for pre-caching transaction signatures and state. @@ -48,7 +48,7 @@ type Processor interface { // Process processes the state changes according to the Ethereum rules by running // the transaction messages using the statedb and applying any rewards to both // the processor (coinbase) and any included uncles. - Process(block *types.Block, statedb state.BlockProcessingDB, cfg vm.Config) (*ProcessResult, error) + Process(block *types.Block, statedb *state.StateDB, cfg vm.Config) (*ProcessResult, error) } // ProcessResult contains the values computed by Process. diff --git a/core/types/bal/bal.go b/core/types/bal/bal.go index 0d82454090..29139f2164 100644 --- a/core/types/bal/bal.go +++ b/core/types/bal/bal.go @@ -19,6 +19,7 @@ package bal import ( "bytes" "encoding/json" + "github.com/ethereum/go-ethereum/params" "maps" "github.com/ethereum/go-ethereum/common" @@ -32,6 +33,27 @@ type CodeChange struct { Code []byte `json:"code,omitempty"` } +var IgnoredBALAddresses map[common.Address]struct{} = map[common.Address]struct{}{ + params.SystemAddress: {}, + common.BytesToAddress([]byte{0x01}): {}, + common.BytesToAddress([]byte{0x02}): {}, + common.BytesToAddress([]byte{0x03}): {}, + common.BytesToAddress([]byte{0x04}): {}, + common.BytesToAddress([]byte{0x05}): {}, + common.BytesToAddress([]byte{0x06}): {}, + common.BytesToAddress([]byte{0x07}): {}, + common.BytesToAddress([]byte{0x08}): {}, + common.BytesToAddress([]byte{0x09}): {}, + common.BytesToAddress([]byte{0x0a}): {}, + common.BytesToAddress([]byte{0x0b}): {}, + common.BytesToAddress([]byte{0x0c}): {}, + common.BytesToAddress([]byte{0x0d}): {}, + common.BytesToAddress([]byte{0x0e}): {}, + common.BytesToAddress([]byte{0x0f}): {}, + common.BytesToAddress([]byte{0x10}): {}, + common.BytesToAddress([]byte{0x11}): {}, +} + // ConstructionAccountAccess contains post-block account state for mutations as well as // all storage keys that were read during execution. It is used when building block // access list during execution. @@ -83,6 +105,36 @@ func NewConstructionBlockAccessList() *ConstructionBlockAccessList { } } +func (c *ConstructionBlockAccessList) DiffAt(idx uint16) *StateDiff { + res := &StateDiff{make(map[common.Address]*AccountState)} + + for addr, account := range c.Accounts { + accountState := &AccountState{} + if balance, ok := account.BalanceChanges[idx]; ok { + accountState.Balance = balance + } + if nonce, ok := account.NonceChanges[idx]; ok { + accountState.Nonce = &nonce + } + if code, ok := account.CodeChanges[idx]; ok { + accountState.Code = code.Code + } + + storageWrites := make(map[common.Hash]common.Hash) + for slot, writes := range account.StorageWrites { + if val, ok := writes[idx]; ok { + storageWrites[slot] = val + } + } + if len(storageWrites) > 0 { + accountState.StorageWrites = make(map[common.Hash]common.Hash) + } + + res.Mutations[addr] = accountState + } + return res +} + // AccountRead records the address of an account that has been read during execution. func (c *ConstructionBlockAccessList) AccountRead(addr common.Address) { if _, ok := c.Accounts[addr]; !ok { @@ -163,8 +215,28 @@ func mergeStorageWrites(cur, next map[common.Hash]map[uint16]common.Hash) map[co return cur } -func (c *ConstructionBlockAccessList) Merge(next *ConstructionBlockAccessList) { - for addr, accountAccess := range next.Accounts { +// MergeReads combines the account/storage reads from a completed EVM execution scope +// into the parent calling scope's access list. +// It is intended to be called when the child execution scope terminates in a revert +// which means that only the state reads performed by that execution should be reported +// in the BAL. +func (c *ConstructionBlockAccessList) MergeReads(childScope *ConstructionBlockAccessList) { + for addr, accountAccess := range childScope.Accounts { + if _, ok := c.Accounts[addr]; !ok { + c.Accounts[addr] = &ConstructionAccountAccess{StorageReads: accountAccess.StorageReads} + continue + } + + for storageRead, _ := range childScope.Accounts[addr].StorageReads { + c.Accounts[addr].StorageReads[storageRead] = struct{}{} + } + } +} + +// Merge combines the state changes from a nested execution with the parent context +// It is meant to be invoked after an EVM call completes (without reverting). +func (c *ConstructionBlockAccessList) Merge(childScope *ConstructionBlockAccessList) { + for addr, accountAccess := range childScope.Accounts { if _, ok := c.Accounts[addr]; !ok { c.Accounts[addr] = accountAccess continue @@ -172,18 +244,18 @@ func (c *ConstructionBlockAccessList) Merge(next *ConstructionBlockAccessList) { // copy the entries from 'next' into 'c' overwriting 'c' entries with // 'next entries when the bal index matches. - next.Accounts[addr].StorageWrites = mergeStorageWrites(next.Accounts[addr].StorageWrites, c.Accounts[addr].StorageWrites) + c.Accounts[addr].StorageWrites = mergeStorageWrites(c.Accounts[addr].StorageWrites, childScope.Accounts[addr].StorageWrites) - for storageRead, _ := range c.Accounts[addr].StorageReads { - next.Accounts[addr].StorageReads[storageRead] = struct{}{} + for storageRead, _ := range childScope.Accounts[addr].StorageReads { + c.Accounts[addr].StorageReads[storageRead] = struct{}{} } - for idx, nonce := range next.Accounts[addr].NonceChanges { + for idx, nonce := range childScope.Accounts[addr].NonceChanges { c.Accounts[addr].NonceChanges[idx] = nonce } - for idx, code := range next.Accounts[addr].CodeChanges { + for idx, code := range childScope.Accounts[addr].CodeChanges { c.Accounts[addr].CodeChanges[idx] = code } - for idx, balance := range next.Accounts[addr].BalanceChanges { + for idx, balance := range childScope.Accounts[addr].BalanceChanges { c.Accounts[addr].BalanceChanges[idx] = balance } } @@ -221,76 +293,6 @@ func (c *ConstructionBlockAccessList) Copy() *ConstructionBlockAccessList { return res } -// ApplyDiff includes the given changes in the block access list at the given index. -func (c *ConstructionBlockAccessList) ApplyDiff(i uint, diff *StateDiff) { - idx := uint16(i) - for addr, acctDiff := range diff.Mutations { - if _, ok := c.Accounts[addr]; !ok { - c.Accounts[addr] = &ConstructionAccountAccess{} - } - if acctDiff.Balance != nil { - if c.Accounts[addr].BalanceChanges == nil { - c.Accounts[addr].BalanceChanges = make(map[uint16]*uint256.Int) - } - c.Accounts[addr].BalanceChanges[idx] = acctDiff.Balance - } - if acctDiff.Nonce != nil { - if c.Accounts[addr].NonceChanges == nil { - c.Accounts[addr].NonceChanges = make(map[uint16]uint64) - } - c.Accounts[addr].NonceChanges[idx] = *acctDiff.Nonce - } - if acctDiff.Code != nil { - if c.Accounts[addr].CodeChanges == nil { - c.Accounts[addr].CodeChanges = make(map[uint16]CodeChange) - } - // TODO: make the CodeChanges value just be []byte - c.Accounts[addr].CodeChanges[idx] = CodeChange{idx, acctDiff.Code} - } - if acctDiff.StorageWrites != nil { - if c.Accounts[addr].StorageWrites == nil { - // TODO: can we instantiate all these maps in the constructor? - c.Accounts[addr].StorageWrites = make(map[common.Hash]map[uint16]common.Hash) - } - - for slot, val := range acctDiff.StorageWrites { - if c.Accounts[addr].StorageWrites[slot] == nil { - c.Accounts[addr].StorageWrites[slot] = make(map[uint16]common.Hash) - } - - c.Accounts[addr].StorageWrites[slot][idx] = val - - delete(c.Accounts[addr].StorageReads, slot) - } - } - } -} - -// ApplyAccesses records the given account/storage accesses in the BAL. -func (c *ConstructionBlockAccessList) ApplyAccesses(accesses StateAccesses) { - for addr, acctAccesses := range accesses { - if c.Accounts[addr] == nil { - c.Accounts[addr] = &ConstructionAccountAccess{} - } - if len(acctAccesses) > 0 { - - if c.Accounts[addr].StorageReads == nil { - c.Accounts[addr].StorageReads = make(map[common.Hash]struct{}) - } - for key, _ := range acctAccesses { - // if any of the accessed keys were previously written, they - // appear in the written set only and not also in accesses. - if len(c.Accounts[addr].StorageWrites) > 0 { - if _, ok := c.Accounts[addr].StorageWrites[key]; ok { - continue - } - } - c.Accounts[addr].StorageReads[key] = struct{}{} - } - } - } -} - type StateDiff struct { Mutations map[common.Address]*AccountState `json:"Mutations,omitempty"` } diff --git a/core/vm/interface.go b/core/vm/interface.go index c6c01534d2..475f285486 100644 --- a/core/vm/interface.go +++ b/core/vm/interface.go @@ -22,7 +22,6 @@ import ( "github.com/ethereum/go-ethereum/core/stateless" "github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/core/types/bal" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/trie/utils" "github.com/holiman/uint256" @@ -103,5 +102,5 @@ type StateDB interface { TxIndex() int // Finalise must be invoked at the end of a transaction - Finalise(bool) (*bal.StateDiff, *bal.StateAccesses) + Finalise(bool) } diff --git a/core/vm/operations_acl.go b/core/vm/operations_acl.go index 43f01ba5cd..df1b5c9b8f 100644 --- a/core/vm/operations_acl.go +++ b/core/vm/operations_acl.go @@ -18,7 +18,6 @@ package vm import ( "errors" - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/core/tracing" @@ -109,6 +108,12 @@ func gasSLoadEIP2929(evm *EVM, contract *Contract, stack *Stack, mem *Memory, me // If the caller cannot afford the cost, this change will be rolled back // If he does afford it, we can skip checking the same thing later on, during execution evm.StateDB.AddSlotToAccessList(contract.Address(), slot) + if evm.Config.Tracer != nil && evm.Config.Tracer.OnColdStorageRead != nil { + // TODO: should these only be called if the cold storage read didn't go OOG? + // it's harder to implement, but I lean towards "yes". + // need to clarify this in the spec. + evm.Config.Tracer.OnColdStorageRead(contract.Address(), slot) + } return params.ColdSloadCostEIP2929, nil } return params.WarmStorageReadCostEIP2929, nil @@ -132,7 +137,7 @@ func gasExtCodeCopyEIP2929(evm *EVM, contract *Contract, stack *Stack, mem *Memo // TODO: same issue as OnColdSStorageRead. See the TODO above near OnColdStorageRead if evm.Config.Tracer != nil && evm.Config.Tracer.OnColdAccountRead != nil { - evm.Config.Tracer.OnColdAccountRead(contract.Address()) + evm.Config.Tracer.OnColdAccountRead(addr) } var overflow bool @@ -156,6 +161,9 @@ func gasEip2929AccountCheck(evm *EVM, contract *Contract, stack *Stack, mem *Mem addr := common.Address(stack.peek().Bytes20()) // Check slot presence in the access list if !evm.StateDB.AddressInAccessList(addr) { + if evm.Config.Tracer != nil && evm.Config.Tracer.OnColdAccountRead != nil { + evm.Config.Tracer.OnColdAccountRead(addr) + } // If the caller cannot afford the cost, this change will be rolled back evm.StateDB.AddAddressToAccessList(addr) // The warm storage read cost is already charged as constantGas @@ -173,6 +181,9 @@ func makeCallVariantGasCallEIP2929(oldCalculator gasFunc, addressPosition int) g // the cost to charge for cold access, if any, is Cold - Warm coldCost := params.ColdAccountAccessCostEIP2929 - params.WarmStorageReadCostEIP2929 if !warmAccess { + if evm.Config.Tracer != nil && evm.Config.Tracer.OnColdAccountRead != nil { + evm.Config.Tracer.OnColdAccountRead(addr) + } evm.StateDB.AddAddressToAccessList(addr) // Charge the remaining difference here already, to correctly calculate available // gas for call @@ -239,6 +250,9 @@ func makeSelfdestructGasFn(refundsEnabled bool) gasFunc { address = common.Address(stack.peek().Bytes20()) ) if !evm.StateDB.AddressInAccessList(address) { + if evm.Config.Tracer != nil && evm.Config.Tracer.OnColdAccountRead != nil { + evm.Config.Tracer.OnColdAccountRead(address) + } // If the caller cannot afford the cost, this change will be rolled back evm.StateDB.AddAddressToAccessList(address) gas = params.ColdAccountAccessCostEIP2929 @@ -271,6 +285,9 @@ func makeCallVariantGasCallEIP7702(oldCalculator gasFunc) gasFunc { // Check slot presence in the access list if !evm.StateDB.AddressInAccessList(addr) { + if evm.Config.Tracer != nil && evm.Config.Tracer.OnColdAccountRead != nil { + evm.Config.Tracer.OnColdAccountRead(addr) + } evm.StateDB.AddAddressToAccessList(addr) // The WarmStorageReadCostEIP2929 (100) is already deducted in the form of a constant cost, so // the cost to charge for cold access, if any, is Cold - Warm @@ -289,6 +306,9 @@ func makeCallVariantGasCallEIP7702(oldCalculator gasFunc) gasFunc { if evm.StateDB.AddressInAccessList(target) { cost = params.WarmStorageReadCostEIP2929 } else { + if evm.Config.Tracer != nil && evm.Config.Tracer.OnColdAccountRead != nil { + evm.Config.Tracer.OnColdAccountRead(target) + } evm.StateDB.AddAddressToAccessList(target) cost = params.ColdAccountAccessCostEIP2929 }