remove some of the perf-related components

This commit is contained in:
Jared Wasinger 2026-01-13 21:47:06 +09:00
parent 63cef8fbad
commit b86c15ea0e
6 changed files with 53 additions and 1519 deletions

View file

@ -360,7 +360,6 @@ type BlockChain struct {
validator Validator // Block and state validator interface
prefetcher Prefetcher
processor Processor // Block transaction processor interface
parallelProcessor ParallelStateProcessor
logger *tracing.Hooks
stateSizer *state.SizeTracker // State size tracking
@ -424,7 +423,6 @@ func NewBlockChain(db ethdb.Database, genesis *Genesis, engine consensus.Engine,
bc.validator = NewBlockValidator(chainConfig, bc)
bc.prefetcher = newStatePrefetcher(chainConfig, bc.hc)
bc.processor = NewStateProcessor(bc.hc)
bc.parallelProcessor = NewParallelStateProcessor(bc.hc, bc.GetVMConfig())
genesisHeader := bc.GetHeaderByNumber(0)
if genesisHeader == nil {
@ -2075,105 +2073,6 @@ func (bpr *blockProcessingResult) Stats() *ExecuteStats {
return bpr.stats
}
func (bc *BlockChain) processBlockWithAccessList(parentRoot common.Hash, block *types.Block, setHead bool) (procRes *blockProcessingResult, blockEndErr error) {
var (
startTime = time.Now()
procTime time.Duration
)
reader, err := bc.statedb.Reader(parentRoot)
if err != nil {
return nil, err
}
stateReader := state.NewBALReader(block, reader)
stateTransition, err := state.NewBALStateTransition(stateReader, bc.statedb, parentRoot)
if err != nil {
return nil, err
}
statedb, err := state.New(parentRoot, bc.statedb)
if err != nil {
return nil, err
}
statedb.SetBlockAccessList(stateReader)
if bc.logger != nil && bc.logger.OnBlockStart != nil {
bc.logger.OnBlockStart(tracing.BlockEvent{
Block: block,
Finalized: bc.CurrentFinalBlock(),
Safe: bc.CurrentSafeBlock(),
})
}
if bc.logger != nil && bc.logger.OnBlockEnd != nil {
defer func() {
bc.logger.OnBlockEnd(blockEndErr)
}()
}
res, err := bc.parallelProcessor.Process(block, stateTransition, statedb, bc.cfg.VmConfig)
if err != nil {
return nil, err
}
if err := bc.validator.ValidateState(block, stateTransition, res.ProcessResult, false); err != nil {
return nil, err
}
procTime = time.Since(startTime)
// Write the block to the chain and get the status.
var (
wstart = time.Now()
status WriteStatus
)
if !setHead {
// Don't set the head, only insert the block
err = bc.writeBlockWithState(block, res.ProcessResult.Receipts, stateTransition)
} else {
status, err = bc.writeBlockAndSetHead(block, res.ProcessResult.Receipts, res.ProcessResult.Logs, stateTransition, false)
}
if err != nil {
return nil, err
}
// Update the metrics touched during block commit
accountCommitTimer.Update(stateTransition.Metrics().AccountCommits) // Account commits are complete, we can mark them
storageCommitTimer.Update(stateTransition.Metrics().StorageCommits) // Storage commits are complete, we can mark them
snapshotCommitTimer.Update(stateTransition.Metrics().SnapshotCommits) // Snapshot commits are complete, we can mark them
triedbCommitTimer.Update(stateTransition.Metrics().TrieDBCommits) // Trie database commits are complete, we can mark them
blockWriteTimer.Update(time.Since(wstart) - max(stateTransition.Metrics().AccountCommits, stateTransition.Metrics().StorageCommits) /* concurrent */ - statedb.SnapshotCommits - statedb.TrieDBCommits)
elapsed := time.Since(startTime) + 1 // prevent zero division
blockInsertTimer.Update(elapsed)
// TODO(rjl493456442) generalize the ResettingTimer
mgasps := float64(res.ProcessResult.GasUsed) * 1000 / float64(elapsed)
chainMgaspsMeter.Update(time.Duration(mgasps))
blockPreprocessingTimer.Update(res.PreProcessTime)
txExecutionTimer.Update(res.ExecTime)
// update the metrics from the block state root update
stateTriePrefetchTimer.Update(res.StateTransitionMetrics.StatePrefetch)
accountTriesUpdateTimer.Update(res.StateTransitionMetrics.AccountUpdate)
stateTrieUpdateTimer.Update(res.StateTransitionMetrics.StateUpdate)
stateTrieHashTimer.Update(res.StateTransitionMetrics.StateHash)
stateRootComputeTimer.Update(res.StateTransitionMetrics.AccountUpdate + res.StateTransitionMetrics.StateUpdate + res.StateTransitionMetrics.StateHash)
originStorageLoadTimer.Update(res.StateTransitionMetrics.OriginStorageLoadTime)
stateCommitTimer.Update(res.StateTransitionMetrics.TotalCommitTime)
blockPostprocessingTimer.Update(res.PostProcessTime)
return &blockProcessingResult{
usedGas: res.ProcessResult.GasUsed,
procTime: procTime,
status: status,
witness: nil,
stats: &ExecuteStats{}, // TODO: actually implement this in the future
}, nil
}
// ProcessBlock executes and validates the given block. If there was no error
// it writes the block and associated state to database.
func (bc *BlockChain) ProcessBlock(parentRoot common.Hash, block *types.Block, setHead bool, makeWitness bool, constructBALForTesting bool) (result *blockProcessingResult, blockEndErr error) {
@ -2197,11 +2096,6 @@ func (bc *BlockChain) ProcessBlock(parentRoot common.Hash, block *types.Block, s
// with BALs for testing purposes).
verifyBALHeader := enableBALFork
// optimized execution path for blocks which contain BALs
if blockHasAccessList {
return bc.processBlockWithAccessList(parentRoot, block, setHead)
}
var (
err error
startTime = time.Now()
@ -2349,7 +2243,9 @@ func (bc *BlockChain) ProcessBlock(parentRoot common.Hash, block *types.Block, s
err := fmt.Errorf("block access list hash mismatch (reported=%x, computed=%x)", *block.Header().BlockAccessListHash, balTracer.AccessList().ToEncodingObj().Hash())
bc.reportBadBlock(block, res, err)
return nil, err
}
} else if block.Body().AccessList != nil {
// TODO: assert that the
} else {
// very ugly... deepcopy the block body before setting the block access
// list on it to prevent mutating the block instance passed by the caller.
existingBody := block.Body()
@ -2359,6 +2255,8 @@ func (bc *BlockChain) ProcessBlock(parentRoot common.Hash, block *types.Block, s
block = block.WithBody(*existingBody)
}
}
// If witnesses was generated and stateless self-validation requested, do
// that now. Self validation should *never* run in production, it's more of
// a tight integration to enable running *all* consensus tests through the

View file

@ -1,358 +0,0 @@
package core
import (
"cmp"
"fmt"
"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/bal"
"github.com/ethereum/go-ethereum/core/vm"
"golang.org/x/sync/errgroup"
"runtime"
"slices"
"time"
)
// ProcessResultWithMetrics wraps ProcessResult with some metrics that are
// emitted when executing blocks containing access lists.
type ProcessResultWithMetrics struct {
ProcessResult *ProcessResult
PreProcessTime time.Duration
StateTransitionMetrics *state.BALStateTransitionMetrics
// the time it took to execute all txs in the block
ExecTime time.Duration
PostProcessTime time.Duration
}
// ParallelStateProcessor is used to execute and verify blocks containing
// access lists.
type ParallelStateProcessor struct {
*StateProcessor
vmCfg *vm.Config
}
// NewParallelStateProcessor returns a new ParallelStateProcessor instance.
func NewParallelStateProcessor(chain *HeaderChain, vmConfig *vm.Config) ParallelStateProcessor {
res := NewStateProcessor(chain)
return ParallelStateProcessor{
res,
vmConfig,
}
}
// called by resultHandler when all transactions have successfully executed.
// performs post-tx state transition (system contracts and withdrawals)
// and calculates the ProcessResult, returning it to be sent on resCh
// by resultHandler
func (p *ParallelStateProcessor) prepareExecResult(block *types.Block, allStateReads *bal.StateAccesses, tExecStart time.Time, postTxState *state.StateDB, receipts types.Receipts) *ProcessResultWithMetrics {
tExec := time.Since(tExecStart)
var requests [][]byte
tPostprocessStart := time.Now()
header := block.Header()
balTracer, hooks := NewBlockAccessListTracer()
balTracer.SetPostTx()
tracingStateDB := state.NewHookedState(postTxState, hooks)
context := NewEVMBlockContext(header, p.chain, nil)
lastBALIdx := len(block.Transactions()) + 1
postTxState.SetAccessListIndex(lastBALIdx)
cfg := vm.Config{
Tracer: hooks,
NoBaseFee: p.vmCfg.NoBaseFee,
EnablePreimageRecording: p.vmCfg.EnablePreimageRecording,
ExtraEips: slices.Clone(p.vmCfg.ExtraEips),
StatelessSelfValidation: p.vmCfg.StatelessSelfValidation,
EnableWitnessStats: p.vmCfg.EnableWitnessStats,
}
cfg.Tracer = hooks
evm := vm.NewEVM(context, tracingStateDB, p.chainConfig(), cfg)
// 1. order the receipts by tx index
// 2. correctly calculate the cumulative gas used per receipt, returning bad block error if it goes over the allowed
slices.SortFunc(receipts, func(a, b *types.Receipt) int {
return cmp.Compare(a.TransactionIndex, b.TransactionIndex)
})
var cumulativeGasUsed uint64
var allLogs []*types.Log
for _, receipt := range receipts {
receipt.CumulativeGasUsed = cumulativeGasUsed + receipt.GasUsed
cumulativeGasUsed += receipt.GasUsed
if receipt.CumulativeGasUsed > header.GasLimit {
return &ProcessResultWithMetrics{
ProcessResult: &ProcessResult{Error: fmt.Errorf("gas limit exceeded")},
}
}
allLogs = append(allLogs, receipt.Logs...)
}
// Read requests if Prague is enabled.
if p.chainConfig().IsPrague(block.Number(), block.Time()) {
requests = [][]byte{}
// EIP-6110
if err := ParseDepositLogs(&requests, allLogs, p.chainConfig()); err != nil {
return &ProcessResultWithMetrics{
ProcessResult: &ProcessResult{Error: err},
}
}
// EIP-7002
err := ProcessWithdrawalQueue(&requests, evm)
if err != nil {
return &ProcessResultWithMetrics{
ProcessResult: &ProcessResult{Error: err},
}
}
// EIP-7251
err = ProcessConsolidationQueue(&requests, evm)
if err != nil {
return &ProcessResultWithMetrics{
ProcessResult: &ProcessResult{Error: err},
}
}
}
// Finalize the block, applying any consensus engine specific extras (e.g. block rewards)
p.chain.Engine().Finalize(p.chain, header, tracingStateDB, block.Body())
// invoke FinaliseIdxChanges so that withdrawals are accounted for in the state diff
postTxState.Finalise(true)
balTracer.OnBlockFinalization()
diff, stateReads := balTracer.builder.FinalizedIdxChanges()
allStateReads.Merge(stateReads)
// TODO: if there is a failure, we need to print out the detailed logs explaining why the BAL validation failed
// but logs are disabled when we are running tests to prevent a ton of output
balIdx := len(block.Transactions()) + 1
if !postTxState.BlockAccessList().ValidateStateDiff(balIdx, diff) {
return &ProcessResultWithMetrics{
ProcessResult: &ProcessResult{Error: fmt.Errorf("BAL validation failure")},
}
}
if !postTxState.BlockAccessList().ValidateStateReads(lastBALIdx, *allStateReads) {
return &ProcessResultWithMetrics{
ProcessResult: &ProcessResult{Error: fmt.Errorf("BAL validation failure")},
}
}
tPostprocess := time.Since(tPostprocessStart)
return &ProcessResultWithMetrics{
ProcessResult: &ProcessResult{
Receipts: receipts,
Requests: requests,
Logs: allLogs,
GasUsed: cumulativeGasUsed,
},
PostProcessTime: tPostprocess,
ExecTime: tExec,
}
}
type txExecResult struct {
idx int // transaction index
receipt *types.Receipt
err error // non-EVM error which would render the block invalid
stateReads bal.StateAccesses
}
// resultHandler polls until all transactions have finished executing and the
// state root calculation is complete. The result is emitted on resCh.
func (p *ParallelStateProcessor) resultHandler(block *types.Block, preTxStateReads bal.StateAccesses, postTxState *state.StateDB, tExecStart time.Time, txResCh <-chan txExecResult, stateRootCalcResCh <-chan stateRootCalculationResult, resCh chan *ProcessResultWithMetrics) {
// 1. if the block has transactions, receive the execution results from all of them and return an error on resCh if any txs err'd
// 2. once all txs are executed, compute the post-tx state transition and produce the ProcessResult sending it on resCh (or an error if the post-tx state didn't match what is reported in the BAL)
var receipts []*types.Receipt
gp := new(GasPool)
gp.SetGas(block.GasLimit())
var execErr error
var numTxComplete int
allReads := make(bal.StateAccesses)
allReads.Merge(preTxStateReads)
if len(block.Transactions()) > 0 {
loop:
for {
select {
case res := <-txResCh:
if execErr == nil {
if res.err != nil {
execErr = res.err
} else {
if err := gp.SubGas(res.receipt.GasUsed); err != nil {
execErr = err
} else {
receipts = append(receipts, res.receipt)
allReads.Merge(res.stateReads)
}
}
}
numTxComplete++
if numTxComplete == len(block.Transactions()) {
break loop
}
}
}
if execErr != nil {
resCh <- &ProcessResultWithMetrics{ProcessResult: &ProcessResult{Error: execErr}}
return
}
}
execResults := p.prepareExecResult(block, &allReads, tExecStart, postTxState, receipts)
rootCalcRes := <-stateRootCalcResCh
if execResults.ProcessResult.Error != nil {
resCh <- execResults
} else if rootCalcRes.err != nil {
resCh <- &ProcessResultWithMetrics{ProcessResult: &ProcessResult{Error: rootCalcRes.err}}
} else {
// &{20.39677ms 0s 1.149668ms 735.295µs 0s 0s 0s 0s}
execResults.StateTransitionMetrics = rootCalcRes.metrics
resCh <- execResults
}
}
type stateRootCalculationResult struct {
err error
metrics *state.BALStateTransitionMetrics
root common.Hash
}
// calcAndVerifyRoot performs the post-state root hash calculation, verifying
// it against what is reported by the block and returning a result on resCh.
func (p *ParallelStateProcessor) calcAndVerifyRoot(preState *state.StateDB, block *types.Block, stateTransition *state.BALStateTransition, resCh chan stateRootCalculationResult) {
// calculate and apply the block state modifications
//root, prestateLoadTime, rootCalcTime := preState.BlockAccessList().StateRoot(preState)
root := stateTransition.IntermediateRoot(false)
res := stateRootCalculationResult{
// TODO: I think we can remove the root from this struct
metrics: stateTransition.Metrics(),
}
// TODO: validate state root in block validator?
if root != block.Root() {
res.err = fmt.Errorf("state root mismatch. local: %x. remote: %x", root, block.Root())
}
resCh <- res
}
// execTx executes single transaction returning a result which includes state accessed/modified
func (p *ParallelStateProcessor) execTx(block *types.Block, tx *types.Transaction, txIdx int, db *state.StateDB, signer types.Signer) *txExecResult {
header := block.Header()
balTracer, hooks := NewBlockAccessListTracer()
tracingStateDB := state.NewHookedState(db, hooks)
context := NewEVMBlockContext(header, p.chain, nil)
cfg := vm.Config{
Tracer: hooks,
NoBaseFee: p.vmCfg.NoBaseFee,
EnablePreimageRecording: p.vmCfg.EnablePreimageRecording,
ExtraEips: slices.Clone(p.vmCfg.ExtraEips),
StatelessSelfValidation: p.vmCfg.StatelessSelfValidation,
EnableWitnessStats: p.vmCfg.EnableWitnessStats,
}
cfg.Tracer = hooks
evm := vm.NewEVM(context, tracingStateDB, p.chainConfig(), cfg)
msg, err := TransactionToMessage(tx, signer, header.BaseFee)
if err != nil {
err = fmt.Errorf("could not apply tx %d [%v]: %w", txIdx, tx.Hash().Hex(), err)
return &txExecResult{err: err}
}
gp := new(GasPool)
gp.SetGas(block.GasLimit())
db.SetTxContext(tx.Hash(), txIdx)
var gasUsed uint64
receipt, err := ApplyTransactionWithEVM(msg, gp, db, block.Number(), block.Hash(), context.Time, tx, &gasUsed, evm)
if err != nil {
err := fmt.Errorf("could not apply tx %d [%v]: %w", txIdx, tx.Hash().Hex(), err)
return &txExecResult{err: err}
}
diff, accesses := balTracer.builder.FinalizedIdxChanges()
if !db.BlockAccessList().ValidateStateDiff(txIdx+1, diff) {
return &txExecResult{err: fmt.Errorf("bal validation failure")}
}
return &txExecResult{
idx: txIdx,
receipt: receipt,
stateReads: accesses,
}
}
// 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, stateTransition *state.BALStateTransition, statedb *state.StateDB, cfg vm.Config) (*ProcessResultWithMetrics, error) {
var (
header = block.Header()
resCh = make(chan *ProcessResultWithMetrics)
signer = types.MakeSigner(p.chainConfig(), header.Number, header.Time)
rootCalcResultCh = make(chan stateRootCalculationResult)
context vm.BlockContext
txResCh = make(chan txExecResult)
pStart = time.Now()
tExecStart time.Time
tPreprocess time.Duration // time to create a set of prestates for parallel transaction execution
)
balTracer, hooks := NewBlockAccessListTracer()
tracingStateDB := state.NewHookedState(statedb, hooks)
// TODO: figure out exactly why we need to set the hooks on the TracingStateDB and the vm.Config
cfg.Tracer = hooks
context = NewEVMBlockContext(header, p.chain, nil)
evm := vm.NewEVM(context, tracingStateDB, p.chainConfig(), cfg)
if beaconRoot := block.BeaconRoot(); beaconRoot != nil {
ProcessBeaconBlockRoot(*beaconRoot, evm)
}
if p.chainConfig().IsPrague(block.Number(), block.Time()) || p.chainConfig().IsVerkle(block.Number(), block.Time()) {
ProcessParentBlockHash(block.ParentHash(), evm)
}
diff, stateReads := balTracer.builder.FinalizedIdxChanges()
if !statedb.BlockAccessList().ValidateStateDiff(0, diff) {
return nil, fmt.Errorf("BAL validation failure")
}
// 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()
tPreprocess = time.Since(pStart)
// execute transactions and state root calculation in parallel
tExecStart = time.Now()
go p.resultHandler(block, stateReads, postTxState, tExecStart, txResCh, rootCalcResultCh, resCh)
var workers errgroup.Group
workers.SetLimit(runtime.NumCPU())
startingState := statedb.Copy()
for i, tx := range block.Transactions() {
tx := tx
i := i
workers.Go(func() error {
res := p.execTx(block, tx, i, startingState.Copy(), signer)
txResCh <- *res
return nil
})
}
go p.calcAndVerifyRoot(statedb, block, stateTransition, rootCalcResultCh)
res := <-resCh
if res.ProcessResult.Error != nil {
return nil, res.ProcessResult.Error
}
// TODO: remove preprocess metric ?
res.PreProcessTime = tPreprocess
return res, nil
}

View file

@ -1,406 +0,0 @@
package state
import (
"context"
"fmt"
"github.com/ethereum/go-ethereum/common"
"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/log"
"github.com/holiman/uint256"
"sync"
)
// TODO: probably unnecessary to cache the resolved state object here as it will already be in the db cache?
// ^ experiment with the performance of keeping this as-is vs just using the db cache.
// prestateResolver asynchronously fetches the prestate state accounts of addresses
// which are reported as modified in EIP-7928 access lists in order to produce the full
// updated state account (including fields that weren't modified in the BAL) for the
// state root update
type prestateResolver struct {
inProgress map[common.Address]chan struct{}
resolved sync.Map
ctx context.Context
cancel func()
}
// schedule begins the retrieval of a set of state accounts running on
// a background goroutine.
func (p *prestateResolver) schedule(r Reader, addrs []common.Address) {
p.inProgress = make(map[common.Address]chan struct{})
p.ctx, p.cancel = context.WithCancel(context.Background())
for _, addr := range addrs {
p.inProgress[addr] = make(chan struct{})
}
// TODO: probably we can retrieve these on a single go-routine
// the transaction execution will also load them
for _, addr := range addrs {
resolveAddr := addr
go func() {
select {
case <-p.ctx.Done():
return
default:
}
acct, err := r.Account(resolveAddr)
if err != nil {
// TODO: what do here?
}
p.resolved.Store(resolveAddr, acct)
close(p.inProgress[resolveAddr])
}()
}
}
func (p *prestateResolver) stop() {
p.cancel()
}
// account returns the state account for the given address, blocking if it is
// still being resolved from disk.
func (p *prestateResolver) account(addr common.Address) *types.StateAccount {
if _, ok := p.inProgress[addr]; !ok {
return nil
}
select {
case <-p.inProgress[addr]:
}
res, exist := p.resolved.Load(addr)
if !exist {
return nil
}
return res.(*types.StateAccount)
}
func (r *BALReader) initObjFromDiff(db *StateDB, addr common.Address, a *types.StateAccount, diff *bal.AccountMutations) *stateObject {
var acct *types.StateAccount
if a == nil {
acct = &types.StateAccount{
Nonce: 0,
Balance: uint256.NewInt(0),
Root: types.EmptyRootHash,
CodeHash: types.EmptyCodeHash[:],
}
} else {
acct = a.Copy()
}
if diff == nil {
return newObject(db, addr, acct)
}
if diff.Nonce != nil {
acct.Nonce = *diff.Nonce
}
if diff.Balance != nil {
acct.Balance = new(uint256.Int).Set(diff.Balance)
}
obj := newObject(db, addr, acct)
if diff.Code != nil {
obj.setCode(crypto.Keccak256Hash(diff.Code), diff.Code)
}
if diff.StorageWrites != nil {
for key, val := range diff.StorageWrites {
obj.pendingStorage[common.Hash(key)] = common.Hash(val)
}
}
if obj.empty() {
return nil
}
return obj
}
// 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 {
block *types.Block
accesses map[common.Address]*bal.AccountAccess
prestateReader prestateResolver
}
// NewBALReader constructs a new reader from an access list. db is expected to have been instantiated with a reader.
func NewBALReader(block *types.Block, reader Reader) *BALReader {
r := &BALReader{accesses: make(map[common.Address]*bal.AccountAccess), block: block}
for _, acctDiff := range *block.Body().AccessList {
r.accesses[acctDiff.Address] = &acctDiff
}
r.prestateReader.schedule(reader, r.ModifiedAccounts())
return r
}
// ModifiedAccounts returns a list of all accounts with mutations in the access list
func (r *BALReader) ModifiedAccounts() (res []common.Address) {
for addr, access := range r.accesses {
if len(access.NonceChanges) != 0 || len(access.CodeChanges) != 0 || len(access.StorageChanges) != 0 || len(access.BalanceChanges) != 0 {
res = append(res, addr)
}
}
return res
}
func logReadsDiff(idx int, address common.Address, computedReads map[common.Hash]struct{}, expectedReads []*bal.EncodedStorage) {
expectedReadsMap := make(map[common.Hash]struct{})
for _, er := range expectedReads {
expectedReadsMap[er.ToHash()] = struct{}{}
}
allReads := make(map[common.Hash]struct{})
for er := range expectedReadsMap {
allReads[er] = struct{}{}
}
for cr := range computedReads {
allReads[cr] = struct{}{}
}
var missingExpected, missingComputed []common.Hash
for storage := range allReads {
_, hasComputed := computedReads[storage]
_, hasExpected := expectedReadsMap[storage]
if hasComputed && !hasExpected {
missingExpected = append(missingExpected, storage)
}
if !hasComputed && hasExpected {
missingComputed = append(missingComputed, storage)
}
}
if len(missingExpected) > 0 {
log.Error("read storage slots which were not reported in the BAL", "index", idx, "address", address, missingExpected)
}
if len(missingComputed) > 0 {
log.Error("did not read storage slots which were reported in the BAL", "index", idx, "address", address, missingComputed)
}
}
func (r *BALReader) ValidateStateReads(idx int, computedReads bal.StateAccesses) bool {
// 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 computedReads {
balAcctDiff := r.readAccountDiff(addr, len(r.block.Transactions())+2)
if balAcctDiff != nil {
for writeSlot := range balAcctDiff.StorageWrites {
delete(reads, writeSlot)
}
}
if _, ok := r.accesses[addr]; !ok {
log.Error(fmt.Sprintf("account %x was accessed during execution but is not present in the access list", addr))
return false
}
expectedReads := r.accesses[addr].StorageReads
if len(reads) != len(expectedReads) {
logReadsDiff(idx, addr, reads, expectedReads)
return false
}
for _, slot := range expectedReads {
if _, ok := reads[slot.ToHash()]; !ok {
log.Error("expected read is missing from BAL")
return false
}
}
}
return true
}
// changesAt returns all state changes occurring at the given index.
func (r *BALReader) changesAt(idx int) *bal.StateDiff {
res := &bal.StateDiff{make(map[common.Address]*bal.AccountMutations)}
for addr, _ := range r.accesses {
accountChanges := r.accountChangesAt(addr, idx)
if accountChanges != nil {
res.Mutations[addr] = accountChanges
}
}
return res
}
// accountChangesAt returns the state changes of an account at a given index,
// or nil if there are no changes.
func (r *BALReader) accountChangesAt(addr common.Address, idx int) *bal.AccountMutations {
acct, exist := r.accesses[addr]
if !exist {
return nil
}
var res bal.AccountMutations
// TODO: remove the reverse iteration here to clean the code up
for i := len(acct.BalanceChanges) - 1; i >= 0; i-- {
if acct.BalanceChanges[i].TxIdx == uint16(idx) {
res.Balance = acct.BalanceChanges[i].Balance
}
if acct.BalanceChanges[i].TxIdx < uint16(idx) {
break
}
}
for i := len(acct.CodeChanges) - 1; i >= 0; i-- {
if acct.CodeChanges[i].TxIdx == uint16(idx) {
res.Code = acct.CodeChanges[i].Code
break
}
if acct.CodeChanges[i].TxIdx < uint16(idx) {
break
}
}
for i := len(acct.NonceChanges) - 1; i >= 0; i-- {
if acct.NonceChanges[i].TxIdx == uint16(idx) {
res.Nonce = &acct.NonceChanges[i].Nonce
break
}
if acct.NonceChanges[i].TxIdx < uint16(idx) {
break
}
}
for i := len(acct.StorageChanges) - 1; i >= 0; i-- {
if res.StorageWrites == nil {
res.StorageWrites = make(map[common.Hash]common.Hash)
}
slotWrites := acct.StorageChanges[i]
for j := len(slotWrites.Accesses) - 1; j >= 0; j-- {
if slotWrites.Accesses[j].TxIdx == uint16(idx) {
res.StorageWrites[slotWrites.Slot.ToHash()] = slotWrites.Accesses[j].ValueAfter.ToHash()
break
}
if slotWrites.Accesses[j].TxIdx < uint16(idx) {
break
}
}
if len(res.StorageWrites) == 0 {
res.StorageWrites = nil
}
}
if res.Code == nil && res.Nonce == nil && len(res.StorageWrites) == 0 && res.Balance == nil {
return nil
}
return &res
}
func (r *BALReader) isModified(addr common.Address) bool {
access, ok := r.accesses[addr]
if !ok {
return false
}
return len(access.StorageChanges) > 0 || len(access.BalanceChanges) > 0 || len(access.CodeChanges) > 0 || len(access.NonceChanges) > 0
}
func (r *BALReader) readAccount(db *StateDB, addr common.Address, idx int) *stateObject {
diff := r.readAccountDiff(addr, idx)
prestate := r.prestateReader.account(addr)
return r.initObjFromDiff(db, addr, prestate, diff)
}
// readAccountDiff returns the accumulated state changes of an account up
// through, and including the given index.
func (r *BALReader) readAccountDiff(addr common.Address, idx int) *bal.AccountMutations {
diff, exist := r.accesses[addr]
if !exist {
return nil
}
var res bal.AccountMutations
for i := 0; i < len(diff.BalanceChanges) && diff.BalanceChanges[i].TxIdx <= uint16(idx); i++ {
res.Balance = diff.BalanceChanges[i].Balance
}
for i := 0; i < len(diff.CodeChanges) && diff.CodeChanges[i].TxIdx <= uint16(idx); i++ {
res.Code = diff.CodeChanges[i].Code
}
for i := 0; i < len(diff.NonceChanges) && diff.NonceChanges[i].TxIdx <= uint16(idx); i++ {
res.Nonce = &diff.NonceChanges[i].Nonce
}
if len(diff.StorageChanges) > 0 {
res.StorageWrites = make(map[common.Hash]common.Hash)
for _, slotWrites := range diff.StorageChanges {
for i := 0; i < len(slotWrites.Accesses) && slotWrites.Accesses[i].TxIdx <= uint16(idx); i++ {
res.StorageWrites[slotWrites.Slot.ToHash()] = slotWrites.Accesses[i].ValueAfter.ToHash()
}
}
}
return &res
}
func mutationsLogfmt(prefix string, mutations *bal.AccountMutations) (logs []interface{}) {
if mutations.Code != nil {
logs = append(logs, fmt.Sprintf("%s-code", prefix), fmt.Sprintf("%x", mutations.Code))
}
if mutations.Balance != nil {
logs = append(logs, fmt.Sprintf("%s-balance", prefix), mutations.Balance.String())
}
if mutations.Nonce != nil {
logs = append(logs, fmt.Sprintf("%s-nonce", prefix), mutations.Nonce)
}
if mutations.StorageWrites != nil {
for key, val := range mutations.StorageWrites {
logs = append(logs, fmt.Sprintf("%s-storage-write-key"), key, fmt.Sprintf("%s-storage-write-value"), val)
}
}
return logs
}
func logfmtMutationsDiff(local, remote map[common.Address]*bal.AccountMutations) (logs []interface{}) {
keys := make(map[common.Address]struct{})
for addr, _ := range local {
keys[addr] = struct{}{}
}
for addr, _ := range remote {
keys[addr] = struct{}{}
}
for addr := range keys {
_, hasLocal := local[addr]
_, hasRemote := remote[addr]
if hasLocal && !hasRemote {
logs = append(logs, mutationsLogfmt(fmt.Sprintf("local-%x", addr), local[addr])...)
}
if !hasLocal && hasRemote {
logs = append(logs, mutationsLogfmt(fmt.Sprintf("remote-%x", addr), remote[addr])...)
}
}
return logs
}
// ValidateStateDiff returns an error if the computed state diff is not equal to
// diff reported from the access list at the given index.
func (r *BALReader) ValidateStateDiff(idx int, computedDiff *bal.StateDiff) bool {
balChanges := r.changesAt(idx)
for addr, state := range balChanges.Mutations {
computedAccountDiff, ok := computedDiff.Mutations[addr]
if !ok {
// TODO: print out the full fields here
log.Error("BAL contained account which wasn't present in computed state diff", "address", addr)
return false
}
if !state.Eq(computedAccountDiff) {
state.LogDiff(addr, computedAccountDiff)
return false
}
}
if len(balChanges.Mutations) != len(computedDiff.Mutations) {
log.Error("computed state diff contained accounts that weren't reported in BAL", logfmtMutationsDiff(computedDiff.Mutations, balChanges.Mutations))
return false
}
return true
}

View file

@ -1,570 +0,0 @@
package state
import (
"github.com/ethereum/go-ethereum/common"
"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/rlp"
"github.com/ethereum/go-ethereum/trie/trienode"
"github.com/holiman/uint256"
"golang.org/x/sync/errgroup"
"maps"
"sync"
"sync/atomic"
"time"
)
// BALStateTransition is responsible for performing the state root update
// and commit for EIP 7928 access-list-containing blocks. An instance of
// this object is only used for a single block.
type BALStateTransition struct {
accessList *BALReader
db Database
reader Reader
stateTrie Trie
parentRoot common.Hash
// the computed state root of the block
rootHash common.Hash
// the state modifications performed by the block
diffs map[common.Address]*bal.AccountMutations
// a map of common.Address -> *types.StateAccount containing the block
// prestate of all accounts that will be modified
prestates sync.Map
postStates map[common.Address]*types.StateAccount
// a map of common.Address -> Trie containing the account tries for all
// accounts with mutated storage
tries sync.Map //map[common.Address]Trie
deletions map[common.Address]struct{}
originStorages map[common.Address]map[common.Hash]common.Hash
originStoragesWG sync.WaitGroup
accountDeleted int64
accountUpdated int64
storageDeleted atomic.Int64
storageUpdated atomic.Int64
stateUpdate *stateUpdate
metrics BALStateTransitionMetrics
err error
}
func (s *BALStateTransition) Metrics() *BALStateTransitionMetrics {
return &s.metrics
}
type BALStateTransitionMetrics struct {
// trie hashing metrics
AccountUpdate time.Duration
StatePrefetch time.Duration
StateUpdate time.Duration
StateHash time.Duration
OriginStorageLoadTime time.Duration
// commit metrics
AccountCommits time.Duration
StorageCommits time.Duration
SnapshotCommits time.Duration
TrieDBCommits time.Duration
TotalCommitTime time.Duration
}
func NewBALStateTransition(accessList *BALReader, db Database, parentRoot common.Hash) (*BALStateTransition, error) {
reader, err := db.Reader(parentRoot)
if err != nil {
return nil, err
}
stateTrie, err := db.OpenTrie(parentRoot)
if err != nil {
return nil, err
}
return &BALStateTransition{
accessList: accessList,
db: db,
reader: reader,
stateTrie: stateTrie,
parentRoot: parentRoot,
rootHash: common.Hash{},
diffs: make(map[common.Address]*bal.AccountMutations),
prestates: sync.Map{},
postStates: make(map[common.Address]*types.StateAccount),
tries: sync.Map{},
deletions: make(map[common.Address]struct{}),
originStorages: make(map[common.Address]map[common.Hash]common.Hash),
originStoragesWG: sync.WaitGroup{},
stateUpdate: nil,
}, nil
}
func (s *BALStateTransition) Error() error {
return s.err
}
func (s *BALStateTransition) setError(err error) {
if s.err != nil {
s.err = err
}
}
// TODO: refresh my knowledge of the storage-clearing EIP and ensure that my assumptions around
// an empty account which contains storage are valid here.
//
// isAccountDeleted checks whether the state account was deleted in this block. Post selfdestruct-removal,
// deletions can only occur if an account which has a balance becomes the target of a CREATE2 initcode
// which calls SENDALL, clearing the account and marking it for deletion.
func isAccountDeleted(prestate *types.StateAccount, mutations *bal.AccountMutations) bool {
// TODO: figure out how to simplify this method
if mutations.Code != nil && len(mutations.Code) != 0 {
return false
}
if mutations.Nonce != nil && *mutations.Nonce != 0 {
return false
}
if mutations.StorageWrites != nil && len(mutations.StorageWrites) > 0 {
return false
}
if mutations.Balance != nil {
if mutations.Balance.IsZero() {
if prestate.Nonce != 0 || prestate.Balance.IsZero() || common.BytesToHash(prestate.CodeHash) != types.EmptyCodeHash {
return false
}
// consider an empty account with storage to be deleted, so we don't check root here
return true
}
}
return false
}
// updateAccount applies the block state mutations to a given account returning
// the updated state account and new code (if the account code changed)
func (s *BALStateTransition) updateAccount(addr common.Address) (*types.StateAccount, []byte) {
a, _ := s.prestates.Load(addr)
acct := a.(*types.StateAccount)
acct, diff := acct.Copy(), s.diffs[addr]
code := diff.Code
if diff.Nonce != nil {
acct.Nonce = *diff.Nonce
}
if diff.Balance != nil {
acct.Balance = new(uint256.Int).Set(diff.Balance)
}
if tr, ok := s.tries.Load(addr); ok {
acct.Root = tr.(Trie).Hash()
}
return acct, code
}
func (s *BALStateTransition) commitAccount(addr common.Address) (*accountUpdate, *trienode.NodeSet, error) {
var (
encode = func(val common.Hash) []byte {
if val == (common.Hash{}) {
return nil
}
blob, _ := rlp.EncodeToBytes(common.TrimLeftZeroes(val[:]))
return blob
}
)
op := &accountUpdate{
address: addr,
data: types.SlimAccountRLP(*s.postStates[addr]), // TODO: cache the updated state acocunt somewhere
}
if prestate, exist := s.prestates.Load(addr); exist {
prestate := prestate.(*types.StateAccount)
op.origin = types.SlimAccountRLP(*prestate)
}
if s.diffs[addr].Code != nil {
code := contractCode{
hash: crypto.Keccak256Hash(s.diffs[addr].Code),
blob: s.diffs[addr].Code,
}
if op.origin == nil {
code.originHash = types.EmptyCodeHash
} else {
code.originHash = crypto.Keccak256Hash(op.origin)
}
op.code = &code
}
if len(s.diffs[addr].StorageWrites) == 0 {
return op, nil, nil
}
op.storages = make(map[common.Hash][]byte)
op.storagesOriginByHash = make(map[common.Hash][]byte)
op.storagesOriginByKey = make(map[common.Hash][]byte)
for key, value := range s.diffs[addr].StorageWrites {
hash := crypto.Keccak256Hash(key[:])
op.storages[hash] = encode(common.Hash(value))
origin := encode(s.originStorages[addr][common.Hash(key)])
op.storagesOriginByHash[hash] = origin
op.storagesOriginByKey[common.Hash(key)] = origin
}
tr, _ := s.tries.Load(addr)
root, nodes := tr.(Trie).Commit(false)
s.postStates[addr].Root = root
return op, nodes, nil
}
// CommitWithUpdate flushes mutated trie nodes and state accounts to disk.
func (s *BALStateTransition) CommitWithUpdate(block uint64, deleteEmptyObjects bool, noStorageWiping bool) (common.Hash, *stateUpdate, error) {
// 1) create a stateUpdate object
// Commit objects to the trie, measuring the elapsed time
var (
commitStart = time.Now()
accountTrieNodesUpdated int
accountTrieNodesDeleted int
storageTrieNodesUpdated int
storageTrieNodesDeleted int
lock sync.Mutex // protect two maps below
nodes = trienode.NewMergedNodeSet() // aggregated trie nodes
updates = make(map[common.Hash]*accountUpdate, len(s.diffs)) // aggregated account updates
// merge aggregates the dirty trie nodes into the global set.
//
// Given that some accounts may be destroyed and then recreated within
// the same block, it's possible that a node set with the same owner
// may already exist. In such cases, these two sets are combined, with
// the later one overwriting the previous one if any nodes are modified
// or deleted in both sets.
//
// merge run concurrently across all the state objects and account trie.
merge = func(set *trienode.NodeSet) error {
if set == nil {
return nil
}
lock.Lock()
defer lock.Unlock()
updates, deletes := set.Size()
if set.Owner == (common.Hash{}) {
accountTrieNodesUpdated += updates
accountTrieNodesDeleted += deletes
} else {
storageTrieNodesUpdated += updates
storageTrieNodesDeleted += deletes
}
return nodes.Merge(set)
}
)
destructedPrestates := make(map[common.Address]*types.StateAccount)
s.prestates.Range(func(key, value any) bool {
addr := key.(common.Address)
acct := value.(*types.StateAccount)
destructedPrestates[addr] = acct
return true
})
deletes, delNodes, err := handleDestruction(s.db, s.stateTrie, noStorageWiping, maps.Keys(s.deletions), destructedPrestates)
if err != nil {
return common.Hash{}, nil, err
}
for _, set := range delNodes {
if err := merge(set); err != nil {
return common.Hash{}, nil, err
}
}
// Handle all state updates afterwards, concurrently to one another to shave
// off some milliseconds from the commit operation. Also accumulate the code
// writes to run in parallel with the computations.
var (
start = time.Now()
root common.Hash
workers errgroup.Group
)
// Schedule the account trie first since that will be the biggest, so give
// it the most time to crunch.
//
// TODO(karalabe): This account trie commit is *very* heavy. 5-6ms at chain
// heads, which seems excessive given that it doesn't do hashing, it just
// shuffles some data. For comparison, the *hashing* at chain head is 2-3ms.
// We need to investigate what's happening as it seems something's wonky.
// Obviously it's not an end of the world issue, just something the original
// code didn't anticipate for.
workers.Go(func() error {
// Write the account trie changes, measuring the amount of wasted time
newroot, set := s.stateTrie.Commit(true)
root = newroot
if err := merge(set); err != nil {
return err
}
s.metrics.AccountCommits = time.Since(start)
return nil
})
s.originStoragesWG.Wait()
// Schedule each of the storage tries that need to be updated, so they can
// run concurrently to one another.
//
// TODO(karalabe): Experimentally, the account commit takes approximately the
// same time as all the storage commits combined, so we could maybe only have
// 2 threads in total. But that kind of depends on the account commit being
// more expensive than it should be, so let's fix that and revisit this todo.
for addr, _ := range s.diffs {
if _, isDeleted := s.deletions[addr]; isDeleted {
continue
}
address := addr
// Run the storage updates concurrently to one another
workers.Go(func() error {
// Write any storage changes in the state object to its storage trie
update, set, err := s.commitAccount(address)
if err != nil {
return err
}
if err := merge(set); err != nil {
return err
}
lock.Lock()
updates[crypto.Keccak256Hash(address[:])] = update
s.metrics.StorageCommits = time.Since(start) // overwrite with the longest storage commit runtime
lock.Unlock()
return nil
})
}
// Wait for everything to finish and update the metrics
if err := workers.Wait(); err != nil {
return common.Hash{}, nil, err
}
accountUpdatedMeter.Mark(s.accountUpdated)
storageUpdatedMeter.Mark(s.storageUpdated.Load())
accountDeletedMeter.Mark(s.accountDeleted)
storageDeletedMeter.Mark(s.storageDeleted.Load())
accountTrieUpdatedMeter.Mark(int64(accountTrieNodesUpdated))
accountTrieDeletedMeter.Mark(int64(accountTrieNodesDeleted))
storageTriesUpdatedMeter.Mark(int64(storageTrieNodesUpdated))
storageTriesDeletedMeter.Mark(int64(storageTrieNodesDeleted))
ret := newStateUpdate(noStorageWiping, s.parentRoot, root, block, deletes, updates, nodes)
snapshotCommits, trieDBCommits, err := flushStateUpdate(s.db, block, ret)
if err != nil {
return common.Hash{}, nil, err
}
s.metrics.SnapshotCommits, s.metrics.TrieDBCommits = snapshotCommits, trieDBCommits
s.metrics.TotalCommitTime = time.Since(commitStart)
return root, ret, nil
}
func (s *BALStateTransition) Commit(block uint64, deleteEmptyObjects bool, noStorageWiping bool) (common.Hash, error) {
root, _, err := s.CommitWithUpdate(block, deleteEmptyObjects, noStorageWiping)
return root, err
}
func (s *BALStateTransition) loadOriginStorages() {
lastIdx := len(s.accessList.block.Transactions()) + 1
type originStorage struct {
address common.Address
key common.Hash
value common.Hash
}
originStoragesCh := make(chan *originStorage)
var pendingStorageCount int
for _, addr := range s.accessList.ModifiedAccounts() {
diff := s.accessList.readAccountDiff(addr, lastIdx)
pendingStorageCount += len(diff.StorageWrites)
s.originStorages[addr] = make(map[common.Hash]common.Hash)
for key := range diff.StorageWrites {
storageKey := key
go func() {
val, err := s.reader.Storage(addr, common.Hash(storageKey))
if err != nil {
s.setError(err)
return
}
originStoragesCh <- &originStorage{
addr,
common.Hash(storageKey),
val,
}
}()
}
}
if pendingStorageCount == 0 {
return
}
for {
select {
case acctStorage := <-originStoragesCh:
s.originStorages[acctStorage.address][acctStorage.key] = acctStorage.value
pendingStorageCount--
if pendingStorageCount == 0 {
return
}
}
}
}
// IntermediateRoot applies block state mutations and computes the updated state
// trie root.
func (s *BALStateTransition) IntermediateRoot(_ bool) common.Hash {
if s.rootHash != (common.Hash{}) {
return s.rootHash
}
// State root calculation proceeds as follows:
// 1 (a): load the prestate state accounts for addresses which were modified in the block
// 1 (b): load the origin storage values for all slots which were modified during the block (this is needed for computing the stateUpdate)
// 1 (c): update each mutated account, producing the post-block state object by applying the state mutations to the prestate (retrieved in 1a).
// 1 (d): prefetch the intermediate trie nodes of the mutated state set from the account trie.
//
// 2: compute the post-state root of the account trie
//
// Steps 1/2 are performed sequentially, with steps 1a-d performed in parallel
start := time.Now()
lastIdx := len(s.accessList.block.Transactions()) + 1
//1 (b): load the origin storage values for all slots which were modified during the block
s.originStoragesWG.Add(1)
go func() {
defer s.originStoragesWG.Done()
s.loadOriginStorages()
s.metrics.OriginStorageLoadTime = time.Since(start)
}()
var wg sync.WaitGroup
for _, addr := range s.accessList.ModifiedAccounts() {
diff := s.accessList.readAccountDiff(addr, lastIdx)
s.diffs[addr] = diff
}
for _, addr := range s.accessList.ModifiedAccounts() {
wg.Add(1)
address := addr
go func() {
defer wg.Done()
// 1 (c): update each mutated account, producing the post-block state object by applying the state mutations to the prestate (retrieved in 1a).
acct := s.accessList.prestateReader.account(address)
diff := s.diffs[address]
if acct == nil {
acct = types.NewEmptyStateAccount()
}
s.prestates.Store(address, acct)
if len(diff.StorageWrites) > 0 {
tr, err := s.db.OpenStorageTrie(s.parentRoot, address, acct.Root, s.stateTrie)
if err != nil {
s.setError(err)
return
}
s.tries.Store(address, tr)
var (
updateKeys, updateValues [][]byte
deleteKeys [][]byte
)
for key, val := range diff.StorageWrites {
if val != (common.Hash{}) {
updateKeys = append(updateKeys, key[:])
updateValues = append(updateValues, common.TrimLeftZeroes(val[:]))
s.storageUpdated.Add(1)
} else {
deleteKeys = append(deleteKeys, key[:])
s.storageDeleted.Add(1)
}
}
if err := tr.UpdateStorageBatch(address, updateKeys, updateValues); err != nil {
s.setError(err)
return
}
for _, key := range deleteKeys {
if err := tr.DeleteStorage(address, key); err != nil {
s.setError(err)
return
}
}
hashStart := time.Now()
tr.Hash()
s.metrics.StateHash = time.Since(hashStart)
}
}()
}
wg.Add(1)
// 1 (d): prefetch the intermediate trie nodes of the mutated state set from the account trie.
go func() {
defer wg.Done()
prefetchStart := time.Now()
if err := s.stateTrie.PrefetchAccount(s.accessList.ModifiedAccounts()); err != nil {
s.setError(err)
return
}
s.metrics.StatePrefetch = time.Since(prefetchStart)
}()
wg.Wait()
s.metrics.AccountUpdate = time.Since(start)
// 2: compute the post-state root of the account trie
stateUpdateStart := time.Now()
for mutatedAddr, _ := range s.diffs {
p, _ := s.prestates.Load(mutatedAddr)
prestate := p.(*types.StateAccount)
isDeleted := isAccountDeleted(prestate, s.diffs[mutatedAddr])
if isDeleted {
if err := s.stateTrie.DeleteAccount(mutatedAddr); err != nil {
s.setError(err)
return common.Hash{}
}
s.deletions[mutatedAddr] = struct{}{}
} else {
acct, code := s.updateAccount(mutatedAddr)
if code != nil {
codeHash := crypto.Keccak256Hash(code)
acct.CodeHash = codeHash.Bytes()
if err := s.stateTrie.UpdateContractCode(mutatedAddr, codeHash, code); err != nil {
s.setError(err)
return common.Hash{}
}
}
if err := s.stateTrie.UpdateAccount(mutatedAddr, acct, len(code)); err != nil {
s.setError(err)
return common.Hash{}
}
s.postStates[mutatedAddr] = acct
}
}
s.metrics.StateUpdate = time.Since(stateUpdateStart)
stateTrieHashStart := time.Now()
s.rootHash = s.stateTrie.Hash()
s.metrics.StateHash = time.Since(stateTrieHashStart)
return s.rootHash
}
func (s *BALStateTransition) Preimages() map[common.Hash][]byte {
// TODO: implement this
return make(map[common.Hash][]byte)
}

View file

@ -155,8 +155,6 @@ type StateDB struct {
witness *stateless.Witness
witnessStats *stateless.WitnessStats
blockAccessList *BALReader
// Measurements gathered during execution for debugging purposes
AccountReads time.Duration
AccountHashes time.Duration
@ -179,10 +177,6 @@ type StateDB struct {
CodeLoaded int // Number of contract code loaded during the state transition
}
func (s *StateDB) BlockAccessList() *BALReader {
return s.blockAccessList
}
// New creates a new state from a given trie.
func New(root common.Hash, db Database) (*StateDB, error) {
reader, err := db.Reader(root)
@ -313,10 +307,6 @@ func (s *StateDB) AddRefund(gas uint64) {
s.refund += gas
}
func (s *StateDB) SetBlockAccessList(al *BALReader) {
s.blockAccessList = al
}
// LoadModifiedPrestate instantiates the live object based on accounts
// which appeared in the total state diff of a block, and were also preexisting.
func (s *StateDB) LoadModifiedPrestate(addrs []common.Address) (res map[common.Address]*types.StateAccount) {
@ -680,23 +670,6 @@ func (s *StateDB) getStateObject(addr common.Address) *stateObject {
return nil
}
// if we are executing against a block access list, construct the account
// state at the current tx index by applying the access-list diff on top
// of the prestate value for the account.
if s.blockAccessList != nil && s.balIndex != 0 && s.blockAccessList.isModified(addr) {
acct := s.blockAccessList.readAccount(s, addr, s.balIndex-1)
if acct != nil {
s.setStateObject(acct)
return acct
}
return nil
// if the acct was nil, it might be non-existent or was not explicitly requested for loading from the blockAcccessList object.
// try to load it from the snapshot.
// TODO: if the acct was non-existent because it was deleted, we should just return nil herre.
}
s.AccountLoaded++
start := time.Now()
@ -787,9 +760,6 @@ func (s *StateDB) Copy() *StateDB {
logSize: s.logSize,
preimages: maps.Clone(s.preimages),
// don't deep-copy these
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.
// In practice, we only ever copy state _between_ transactions/blocks, never