mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-21 20:26:41 +00:00
remove some of the perf-related components
This commit is contained in:
parent
63cef8fbad
commit
b86c15ea0e
6 changed files with 53 additions and 1519 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue