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 ( import (
"errors" "errors"
"fmt" "fmt"
state2 "github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/vm"
"math/big" "math/big"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/consensus/misc/eip1559" "github.com/ethereum/go-ethereum/consensus/misc/eip1559"
"github.com/ethereum/go-ethereum/consensus/misc/eip4844" "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/tracing"
"github.com/ethereum/go-ethereum/core/types" "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/params"
"github.com/ethereum/go-ethereum/trie" "github.com/ethereum/go-ethereum/trie"
"github.com/holiman/uint256" "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 // FinalizeAndAssemble implements consensus.Engine, setting the final state and
// assembling the block. // 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) { if !beacon.IsPoSHeader(header) {
return beacon.ethone.FinalizeAndAssemble(chain, header, state, body, receipts) 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. // Assign the final state root to header.
header.Root = state.IntermediateRoot(true) 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. // Assemble the final block.
block := types.NewBlock(header, body, receipts, trie.NewStackTrie(nil)) block := types.NewBlock(header, body, receipts, trie.NewStackTrie(nil))

View file

@ -18,12 +18,12 @@
package consensus package consensus
import ( import (
state2 "github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/vm"
"math/big" "math/big"
"github.com/ethereum/go-ethereum/common" "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/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/params" "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 // Note: The block header and state database might be updated to reflect any
// consensus rules that happen at finalization (e.g. block rewards). // 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 // Seal generates a new sealing request for the given input block and pushes
// the result into the given channel. // 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 // FinalizeAndAssemble implements consensus.Engine, accumulating the block and
// uncle rewards, setting the final state and assembling the block. // 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 { if len(body.Withdrawals) > 0 {
return nil, errors.New("ethash does not support withdrawals") return nil, errors.New("ethash does not support withdrawals")
} }

View file

@ -1,6 +1,7 @@
package core package core
import ( import (
"fmt"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
@ -51,6 +52,11 @@ func (a *BlockAccessListTracer) AccessList() *bal.ConstructionBlockAccessList {
return a.callAccessLists[0] 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) { func (a *BlockAccessListTracer) TxEndHook(receipt *types.Receipt, err error) {
a.txIdx++ a.txIdx++
} }
@ -71,9 +77,14 @@ func (a *BlockAccessListTracer) OnExit(depth int, output []byte, gasUsed uint64,
if a.selfdestructedAccount != nil { if a.selfdestructedAccount != nil {
delete(a.callAccessLists[len(a.callAccessLists)-1].Accounts, *a.selfdestructedAccount) delete(a.callAccessLists[len(a.callAccessLists)-1].Accounts, *a.selfdestructedAccount)
} }
if !reverted {
parentAccessList := a.callAccessLists[len(a.callAccessLists)-2] parentAccessList := a.callAccessLists[len(a.callAccessLists)-2]
scopeAccessList := a.callAccessLists[len(a.callAccessLists)-1] 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) parentAccessList.Merge(scopeAccessList)
} }
@ -101,6 +112,7 @@ func (a *BlockAccessListTracer) OnColdStorageRead(addr common.Address, key commo
} }
func (a *BlockAccessListTracer) OnColdAccountRead(addr common.Address) { func (a *BlockAccessListTracer) OnColdAccountRead(addr common.Address) {
fmt.Printf("cold account read %x\n", addr)
a.callAccessLists[len(a.callAccessLists)-1].AccountRead(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, // 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. // 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 { if res == nil {
return errors.New("nil ProcessResult value") 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) ptime = time.Since(pstart)
// unset the BAL-creation tracer (dirty)
bc.cfg.VmConfig.Tracer = nil
vstart := time.Now() vstart := time.Now()
if err := bc.validator.ValidateState(block, statedb, res, true, false); err != nil { if err := bc.validator.ValidateState(block, statedb, res, true, false); err != nil {
bc.reportBlock(block, res, err) 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. // The system contracts clear themselves on a system-initiated read.
// When reading the requests mid-block, we don't want this behavior, so fork // When reading the requests mid-block, we don't want this behavior, so fork
// off the statedb before executing the system calls. // 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) { 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) // Finalize the block, applying any consensus engine specific extras (e.g. block rewards)
p.chain.engine.Finalize(p.chain, header, tracingStateDB, block.Body()) p.chain.engine.Finalize(p.chain, header, tracingStateDB, block.Body())
// invoke Finalise so that withdrawals are accounted for in the state diff // invoke Finalise so that withdrawals are accounted for in the state diff
finalDiff, finalAccesses := postTxState.Finalise(true) postTxState.Finalise(true)
computedDiff.Merge(finalDiff)
computedAccesses.Merge(*finalAccesses)
if err := postTxState.BlockAccessList().ValidateStateDiff(len(block.Transactions())+1, computedDiff); err != nil { if err := postTxState.BlockAccessList().ValidateStateDiff(len(block.Transactions())+1, computedDiff); err != nil {
return &ProcessResultWithMetrics{ 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 // 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 { func (p *ParallelStateProcessor) execTx(block *types.Block, tx *types.Transaction, idx int, db *state.StateDB, signer types.Signer) *txExecResult {
header := block.Header() header := block.Header()
var tracingStateDB = vm.StateDB(db) balTracer, hooks := NewBlockAccessListTracer()
if hooks := p.vmCfg.Tracer; hooks != nil { tracingStateDB := state.NewHookedState(db, hooks)
tracingStateDB = state.NewHookedState(db, hooks)
}
context := NewEVMBlockContext(header, p.chain, nil) context := NewEVMBlockContext(header, p.chain, nil)
evm := vm.NewEVM(context, tracingStateDB, p.config, *p.vmCfg) 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} 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} 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 // Process performs EVM execution and state root computation for a block which is known
// to contain an access list. // to contain an access list.
func (p *ParallelStateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg vm.Config) (*ProcessResultWithMetrics, error) { func (p *ParallelStateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg vm.Config) (*ProcessResultWithMetrics, error) {
fmt.Println("start ParallelProcess")
var ( var (
header = block.Header() header = block.Header()
resCh = make(chan *ProcessResultWithMetrics) 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) // 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 // the post-tx state transition is verified by resultHandler
postTxState := statedb.Copy().(*state.StateDB) postTxState := statedb.Copy()
tPreprocess = time.Since(pStart) tPreprocess = time.Since(pStart)
@ -356,7 +353,7 @@ func (p *ParallelStateProcessor) Process(block *types.Block, statedb *state.Stat
tx := tx tx := tx
i := i i := i
workers.Go(func() error { 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 txResCh <- *res
return nil return nil
}) })

View file

@ -7,7 +7,6 @@ import (
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/types/bal" "github.com/ethereum/go-ethereum/core/types/bal"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/params"
"github.com/holiman/uint256" "github.com/holiman/uint256"
"sync" "sync"
"time" "time"
@ -135,10 +134,6 @@ func (s *BALReader) initMutatedObjFromDiff(db *StateDB, addr common.Address, a *
return obj 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 // BALReader provides methods for reading account state from a block access
// list. State values returned from the Reader methods must not be modified. // list. State values returned from the Reader methods must not be modified.
type BALReader struct { type BALReader struct {
@ -168,7 +163,6 @@ func (r *BALReader) ModifiedAccounts() (res []common.Address) {
} }
func (r *BALReader) ValidateStateReads(allReads bal.StateAccesses) error { 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 // 1. remove any slots from 'allReads' which were written
// 2. validate that the read set in the BAL matches 'allReads' exactly // 2. validate that the read set in the BAL matches 'allReads' exactly
for addr, reads := range allReads { for addr, reads := range allReads {
@ -178,8 +172,10 @@ func (r *BALReader) ValidateStateReads(allReads bal.StateAccesses) error {
delete(reads, writeSlot) 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 expectedReads := r.accesses[addr].StorageReads
if len(reads) != len(expectedReads) { if len(reads) != len(expectedReads) {
return fmt.Errorf("mismatch between the number of computed reads and number of expected reads") return fmt.Errorf("mismatch between the number of computed reads and number of expected reads")

View file

@ -23,8 +23,6 @@ import (
"slices" "slices"
"time" "time"
"github.com/ethereum/go-ethereum/core/types/bal"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto" "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 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 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. // Write caches.
trie Trie // storage trie, which becomes non-nil on first access trie Trie // storage trie, which becomes non-nil on first access
code []byte // contract bytecode, which gets set when code is loaded code []byte // contract bytecode, which gets set when code is loaded
@ -78,8 +73,6 @@ type stateObject struct {
// Cache flags. // Cache flags.
dirtyCode bool // true if the code was updated 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 // Flag whether the account was marked as self-destructed. The self-destructed
// account is still accessible in the scope of same transaction. // account is still accessible in the scope of same transaction.
selfDestructed bool selfDestructed bool
@ -111,8 +104,6 @@ func newObject(db *StateDB, address common.Address, acct *types.StateAccount) *s
addrHash: crypto.Keccak256Hash(address[:]), addrHash: crypto.Keccak256Hash(address[:]),
origin: origin, origin: origin,
data: *acct, data: *acct,
txPreBalance: acct.Balance.Clone(),
txPreNonce: acct.Nonce,
originStorage: make(Storage), originStorage: make(Storage),
dirtyStorage: make(Storage), dirtyStorage: make(Storage),
pendingStorage: 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 // 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. // committed later. It is invoked at the end of every transaction.
func (s *stateObject) finalise() *bal.AccountState { func (s *stateObject) finalise() {
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
}
}
}
slotsToPrefetch := make([]common.Hash, 0, len(s.dirtyStorage)) slotsToPrefetch := make([]common.Hash, 0, len(s.dirtyStorage))
for key, value := range s.dirtyStorage { for key, value := range s.dirtyStorage {
if origin, exist := s.uncommittedStorage[key]; exist && origin == value { 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. // to avoid thrashing the data structures.
delete(s.uncommittedStorage, key) 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 { } else if exist {
// The slot is modified to another value and the slot has been // The slot is modified to another value and the slot has been
// tracked for commit in uncommittedStorage. // 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 { } else {
// The slot is different from its original value and hasn't been // The slot is different from its original value and hasn't been
// tracked for commit yet. // tracked for commit yet.
s.uncommittedStorage[key] = s.GetCommittedState(key) s.uncommittedStorage[key] = s.GetCommittedState(key)
slotsToPrefetch = append(slotsToPrefetch, key) // Copy needed for closure 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 // Aggregate the dirty storage slots into the pending area. It might
// be possible that the value of tracked slot here is same with the // 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 // 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. // by EIP-6780. For non-newly-created objects, it's a no-op.
s.newContract = false 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 // updateTrie is responsible for persisting cached storage changes into the
@ -558,8 +503,6 @@ func (s *stateObject) deepCopy(db *StateDB) *stateObject {
dirtyCode: s.dirtyCode, dirtyCode: s.dirtyCode,
selfDestructed: s.selfDestructed, selfDestructed: s.selfDestructed,
newContract: s.newContract, newContract: s.newContract,
txPreNonce: s.txPreNonce,
txPreBalance: s.txPreBalance.Clone(),
} }
if s.trie != nil { if s.trie != nil {
obj.trie = mustCopyTrie(s.trie) 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) { func (s *stateObject) setCodeModified(codeHash common.Hash, code []byte) {
s.setCode(codeHash, code) s.setCode(codeHash, code)
s.dirtyCode = true s.dirtyCode = true
s.nonFinalizedCode = true
} }
func (s *stateObject) SetNonce(nonce uint64) { func (s *stateObject) SetNonce(nonce uint64) {

View file

@ -26,8 +26,6 @@ import (
"sync/atomic" "sync/atomic"
"time" "time"
"github.com/ethereum/go-ethereum/core/types/bal"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state/snapshot" "github.com/ethereum/go-ethereum/core/state/snapshot"
@ -149,9 +147,6 @@ type StateDB struct {
witness *stateless.Witness witness *stateless.Witness
witnessStats *stateless.WitnessStats 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 blockAccessList *BALReader
// Measurements gathered during execution for debugging purposes // Measurements gathered during execution for debugging purposes
@ -201,7 +196,6 @@ func NewWithReader(root common.Hash, db Database, reader Reader) (*StateDB, erro
journal: newJournal(), journal: newJournal(),
accessList: newAccessList(), accessList: newAccessList(),
transientStorage: newTransientStorage(), transientStorage: newTransientStorage(),
stateAccesses: make(bal.StateAccesses),
} }
if db.TrieDB().IsVerkle() { if db.TrieDB().IsVerkle() {
sdb.accessEvents = NewAccessEvents(db.PointCache()) sdb.accessEvents = NewAccessEvents(db.PointCache())
@ -209,13 +203,6 @@ func NewWithReader(root common.Hash, db Database, reader Reader) (*StateDB, erro
return sdb, nil 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 // 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 // state trie concurrently while the state is mutated so that when we reach the
// commit phase, most of the needed data is already hot. // 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. // GetState retrieves the value associated with the specific key.
func (s *StateDB) GetState(addr common.Address, hash common.Hash) common.Hash { func (s *StateDB) GetState(addr common.Address, hash common.Hash) common.Hash {
stateObject := s.getStateObject(addr) 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 { if stateObject != nil {
return stateObject.GetState(hash) 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 // getStateObject retrieves a state object given by the address, returning nil if
// the object is not found or was deleted in this execution context. // the object is not found or was deleted in this execution context.
func (s *StateDB) getStateObject(addr common.Address) *stateObject { 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 // Prefer live objects if any is available
if obj := s.stateObjects[addr]; obj != nil { if obj := s.stateObjects[addr]; obj != nil {
return obj return obj
@ -797,8 +769,6 @@ func (s *StateDB) Copy() *StateDB {
preimages: maps.Clone(s.preimages), preimages: maps.Clone(s.preimages),
// don't deep-copy these // 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? // Do we need to copy the access list and transient storage?
@ -865,8 +835,7 @@ func (s *StateDB) GetRefund() uint64 {
// //
// If EnableStateDiffRecording has been called, it returns a state diff containing // If EnableStateDiffRecording has been called, it returns a state diff containing
// the state which was mutated since the previous invocation of Finalise. Otherwise, nil. // 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) { func (s *StateDB) Finalise(deleteEmptyObjects bool) {
mutations = &bal.StateDiff{Mutations: make(map[common.Address]*bal.AccountState)}
addressesToPrefetch := make([]common.Address, 0, len(s.journal.dirties)) addressesToPrefetch := make([]common.Address, 0, len(s.journal.dirties))
for addr := range s.journal.dirties { for addr := range s.journal.dirties {
obj, exist := s.stateObjects[addr] obj, exist := s.stateObjects[addr]
@ -880,24 +849,6 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) (mutations *bal.StateDiff, a
continue continue
} }
if obj.selfDestructed || (deleteEmptyObjects && obj.empty()) { 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) delete(s.stateObjects, obj.address)
s.markDelete(addr) s.markDelete(addr)
// We need to maintain account deletions explicitly (will remain // 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 s.stateObjectsDestruct[obj.address] = obj
} }
} else { } else {
accountPost := obj.finalise() 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)
}
}
s.markUpdate(addr) s.markUpdate(addr)
} // At this point, also ship the address off to the precacher. The precacher } // At this point, also ship the address off to the precacher. The precacher
// will start loading tries, and when the change is eventually committed, // 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. // Invalidate journal because reverting across transactions is not allowed.
s.clearJournalAndRefund() 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. // IntermediateRoot computes the current root hash of the state trie.

View file

@ -19,8 +19,6 @@ package state
import ( import (
"math/big" "math/big"
"github.com/ethereum/go-ethereum/core/types/bal"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/stateless" "github.com/ethereum/go-ethereum/core/stateless"
"github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/tracing"
@ -34,12 +32,12 @@ import (
// hookedStateDB represents a statedb which emits calls to tracing-hooks // hookedStateDB represents a statedb which emits calls to tracing-hooks
// on state operations. // on state operations.
type hookedStateDB struct { type hookedStateDB struct {
inner BlockProcessingDB inner *StateDB
hooks *tracing.Hooks hooks *tracing.Hooks
} }
// NewHookedState wraps the given stateDb with the given 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} s := &hookedStateDB{stateDb, hooks}
if s.hooks == nil { if s.hooks == nil {
s.hooks = new(tracing.Hooks) s.hooks = new(tracing.Hooks)
@ -277,13 +275,8 @@ func (s *hookedStateDB) AddLog(log *types.Log) {
} }
} }
func (s *hookedStateDB) TxIndex() int { func (s *hookedStateDB) Finalise(deleteEmptyObjects bool) {
return s.inner.TxIndex() defer s.inner.Finalise(deleteEmptyObjects)
}
func (s *hookedStateDB) Finalise(deleteEmptyObjects bool) (*bal.StateDiff, *bal.StateAccesses) {
/*
// TODO: implement this code without peering into statedb internals!!!
if s.hooks.OnBalanceChange != nil { if s.hooks.OnBalanceChange != nil {
for addr := range s.inner.journal.dirties { for addr := range s.inner.journal.dirties {
obj := s.inner.stateObjects[addr] obj := s.inner.stateObjects[addr]
@ -295,24 +288,8 @@ func (s *hookedStateDB) Finalise(deleteEmptyObjects bool) (*bal.StateDiff, *bal.
} }
} }
} }
*/
return s.inner.Finalise(deleteEmptyObjects)
} }
func (s *hookedStateDB) GetLogs(hash common.Hash, blockNumber uint64, blockHash common.Hash, blockTime uint64) []*types.Log { func (s *hookedStateDB) TxIndex() int {
return s.inner.GetLogs(hash, blockNumber, blockHash, blockTime) return s.inner.TxIndex()
}
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 // 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 // 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. // 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 ( var (
receipts types.Receipts receipts types.Receipts
usedGas = new(uint64) usedGas = new(uint64)
@ -98,10 +98,6 @@ func (p *StateProcessor) Process(block *types.Block, statedb state.BlockProcessi
} }
statedb.SetTxContext(tx.Hash(), i) 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) _, _, receipt, err := ApplyTransactionWithEVM(msg, gp, statedb, blockNumber, blockHash, context.Time, tx, usedGas, evm)
if err != nil { if err != nil {
return nil, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err) 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...) allLogs = append(allLogs, receipt.Logs...)
} }
if alDB, ok := statedb.(*state.AccessListCreationDB); ok {
alDB.SetAccessListIndex(len(block.Transactions()) + 1)
}
// Read requests if Prague is enabled. // Read requests if Prague is enabled.
var requests [][]byte var requests [][]byte
if p.config.IsPrague(block.Number(), block.Time()) { 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) // Finalize the block, applying any consensus engine specific extras (e.g. block rewards)
p.chain.engine.Finalize(p.chain, header, tracingStateDB, block.Body()) 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{ return &ProcessResult{
Receipts: receipts, Receipts: receipts,
Requests: requests, 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 // ApplyTransactionWithEVM attempts to apply a transaction to the given state database
// and uses the input parameters for its environment similar to ApplyTransaction. However, // and uses the input parameters for its environment similar to ApplyTransaction. However,
// this method takes an already created EVM instance as input. // 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 := evm.Config.Tracer; hooks != nil {
if hooks.OnTxStart != nil { if hooks.OnTxStart != nil {
hooks.OnTxStart(evm.GetVMContext(), tx, msg.From) 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. // 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 // Create a new receipt for the transaction, storing the intermediate root and gas used
// by the tx. // by the tx.
receipt := &types.Receipt{Type: tx.Type(), PostState: root, CumulativeGasUsed: usedGas} 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 // and uses the input parameters for its environment. It returns the receipt
// for the transaction, gas used and an error if the transaction failed, // for the transaction, gas used and an error if the transaction failed,
// indicating the block was invalid. // 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) msg, err := TransactionToMessage(tx, types.MakeSigner(evm.ChainConfig(), header.Number, header.Time), header.BaseFee)
if err != nil { if err != nil {
return nil, err return nil, err

View file

@ -32,7 +32,7 @@ type Validator interface {
ValidateBody(block *types.Block) error ValidateBody(block *types.Block) error
// ValidateState validates the given statedb and optionally the process result. // 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. // 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 // 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 transaction messages using the statedb and applying any rewards to both
// the processor (coinbase) and any included uncles. // 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. // ProcessResult contains the values computed by Process.

View file

@ -19,6 +19,7 @@ package bal
import ( import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"github.com/ethereum/go-ethereum/params"
"maps" "maps"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
@ -32,6 +33,27 @@ type CodeChange struct {
Code []byte `json:"code,omitempty"` 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 // 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 // all storage keys that were read during execution. It is used when building block
// access list during execution. // 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. // AccountRead records the address of an account that has been read during execution.
func (c *ConstructionBlockAccessList) AccountRead(addr common.Address) { func (c *ConstructionBlockAccessList) AccountRead(addr common.Address) {
if _, ok := c.Accounts[addr]; !ok { 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 return cur
} }
func (c *ConstructionBlockAccessList) Merge(next *ConstructionBlockAccessList) { // MergeReads combines the account/storage reads from a completed EVM execution scope
for addr, accountAccess := range next.Accounts { // 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 { if _, ok := c.Accounts[addr]; !ok {
c.Accounts[addr] = accountAccess c.Accounts[addr] = accountAccess
continue continue
@ -172,18 +244,18 @@ func (c *ConstructionBlockAccessList) Merge(next *ConstructionBlockAccessList) {
// copy the entries from 'next' into 'c' overwriting 'c' entries with // copy the entries from 'next' into 'c' overwriting 'c' entries with
// 'next entries when the bal index matches. // '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 { for storageRead, _ := range childScope.Accounts[addr].StorageReads {
next.Accounts[addr].StorageReads[storageRead] = struct{}{} 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 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 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 c.Accounts[addr].BalanceChanges[idx] = balance
} }
} }
@ -221,76 +293,6 @@ func (c *ConstructionBlockAccessList) Copy() *ConstructionBlockAccessList {
return res 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 { type StateDiff struct {
Mutations map[common.Address]*AccountState `json:"Mutations,omitempty"` 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/stateless"
"github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types" "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/params"
"github.com/ethereum/go-ethereum/trie/utils" "github.com/ethereum/go-ethereum/trie/utils"
"github.com/holiman/uint256" "github.com/holiman/uint256"
@ -103,5 +102,5 @@ type StateDB interface {
TxIndex() int TxIndex() int
// Finalise must be invoked at the end of a transaction // 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 ( import (
"errors" "errors"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/core/tracing" "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 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 // If he does afford it, we can skip checking the same thing later on, during execution
evm.StateDB.AddSlotToAccessList(contract.Address(), slot) 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.ColdSloadCostEIP2929, nil
} }
return params.WarmStorageReadCostEIP2929, 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 // TODO: same issue as OnColdSStorageRead. See the TODO above near OnColdStorageRead
if evm.Config.Tracer != nil && evm.Config.Tracer.OnColdAccountRead != nil { if evm.Config.Tracer != nil && evm.Config.Tracer.OnColdAccountRead != nil {
evm.Config.Tracer.OnColdAccountRead(contract.Address()) evm.Config.Tracer.OnColdAccountRead(addr)
} }
var overflow bool var overflow bool
@ -156,6 +161,9 @@ func gasEip2929AccountCheck(evm *EVM, contract *Contract, stack *Stack, mem *Mem
addr := common.Address(stack.peek().Bytes20()) addr := common.Address(stack.peek().Bytes20())
// Check slot presence in the access list // Check slot presence in the access list
if !evm.StateDB.AddressInAccessList(addr) { 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 // If the caller cannot afford the cost, this change will be rolled back
evm.StateDB.AddAddressToAccessList(addr) evm.StateDB.AddAddressToAccessList(addr)
// The warm storage read cost is already charged as constantGas // 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 // the cost to charge for cold access, if any, is Cold - Warm
coldCost := params.ColdAccountAccessCostEIP2929 - params.WarmStorageReadCostEIP2929 coldCost := params.ColdAccountAccessCostEIP2929 - params.WarmStorageReadCostEIP2929
if !warmAccess { if !warmAccess {
if evm.Config.Tracer != nil && evm.Config.Tracer.OnColdAccountRead != nil {
evm.Config.Tracer.OnColdAccountRead(addr)
}
evm.StateDB.AddAddressToAccessList(addr) evm.StateDB.AddAddressToAccessList(addr)
// Charge the remaining difference here already, to correctly calculate available // Charge the remaining difference here already, to correctly calculate available
// gas for call // gas for call
@ -239,6 +250,9 @@ func makeSelfdestructGasFn(refundsEnabled bool) gasFunc {
address = common.Address(stack.peek().Bytes20()) address = common.Address(stack.peek().Bytes20())
) )
if !evm.StateDB.AddressInAccessList(address) { 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 // If the caller cannot afford the cost, this change will be rolled back
evm.StateDB.AddAddressToAccessList(address) evm.StateDB.AddAddressToAccessList(address)
gas = params.ColdAccountAccessCostEIP2929 gas = params.ColdAccountAccessCostEIP2929
@ -271,6 +285,9 @@ func makeCallVariantGasCallEIP7702(oldCalculator gasFunc) gasFunc {
// Check slot presence in the access list // Check slot presence in the access list
if !evm.StateDB.AddressInAccessList(addr) { if !evm.StateDB.AddressInAccessList(addr) {
if evm.Config.Tracer != nil && evm.Config.Tracer.OnColdAccountRead != nil {
evm.Config.Tracer.OnColdAccountRead(addr)
}
evm.StateDB.AddAddressToAccessList(addr) evm.StateDB.AddAddressToAccessList(addr)
// The WarmStorageReadCostEIP2929 (100) is already deducted in the form of a constant cost, so // 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 // 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) { if evm.StateDB.AddressInAccessList(target) {
cost = params.WarmStorageReadCostEIP2929 cost = params.WarmStorageReadCostEIP2929
} else { } else {
if evm.Config.Tracer != nil && evm.Config.Tracer.OnColdAccountRead != nil {
evm.Config.Tracer.OnColdAccountRead(target)
}
evm.StateDB.AddAddressToAccessList(target) evm.StateDB.AddAddressToAccessList(target)
cost = params.ColdAccountAccessCostEIP2929 cost = params.ColdAccountAccessCostEIP2929
} }