fix some stuff. replace the state diff/read calculation in statedb with computation of a block access list

This commit is contained in:
Jared Wasinger 2025-09-30 15:05:34 +08:00
parent 4282087fb0
commit b2c4d19ca2
17 changed files with 167 additions and 311 deletions

View file

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

View file

@ -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.

View file

@ -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")
}

View file

@ -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)
}

View file

@ -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")
}

View file

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

View file

@ -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) {

View file

@ -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
})

View file

@ -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")

View file

@ -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) {

View file

@ -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.

View file

@ -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)
}

View file

@ -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

View file

@ -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.

View file

@ -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"`
}

View file

@ -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)
}

View file

@ -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
}