diff --git a/beacon/engine/gen_ed.go b/beacon/engine/gen_ed.go index 9706c001b1..94ef5c7f19 100644 --- a/beacon/engine/gen_ed.go +++ b/beacon/engine/gen_ed.go @@ -18,24 +18,24 @@ var _ = (*executableDataMarshaling)(nil) // MarshalJSON marshals as JSON. func (e ExecutableData) MarshalJSON() ([]byte, error) { type ExecutableData struct { - ParentHash common.Hash `json:"parentHash" gencodec:"required"` - FeeRecipient common.Address `json:"feeRecipient" gencodec:"required"` - StateRoot common.Hash `json:"stateRoot" gencodec:"required"` - ReceiptsRoot common.Hash `json:"receiptsRoot" gencodec:"required"` - LogsBloom hexutil.Bytes `json:"logsBloom" gencodec:"required"` - Random common.Hash `json:"prevRandao" gencodec:"required"` - Number hexutil.Uint64 `json:"blockNumber" gencodec:"required"` - GasLimit hexutil.Uint64 `json:"gasLimit" gencodec:"required"` - GasUsed hexutil.Uint64 `json:"gasUsed" gencodec:"required"` - Timestamp hexutil.Uint64 `json:"timestamp" gencodec:"required"` - ExtraData hexutil.Bytes `json:"extraData" gencodec:"required"` - BaseFeePerGas *hexutil.Big `json:"baseFeePerGas" gencodec:"required"` - BlockHash common.Hash `json:"blockHash" gencodec:"required"` - Transactions []hexutil.Bytes `json:"transactions" gencodec:"required"` - Withdrawals []*types.Withdrawal `json:"withdrawals"` - BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed"` - ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas"` - BlockAccessList *bal.BlockAccessList `json:"blockAccessList"` + ParentHash common.Hash `json:"parentHash" gencodec:"required"` + FeeRecipient common.Address `json:"feeRecipient" gencodec:"required"` + StateRoot common.Hash `json:"stateRoot" gencodec:"required"` + ReceiptsRoot common.Hash `json:"receiptsRoot" gencodec:"required"` + LogsBloom hexutil.Bytes `json:"logsBloom" gencodec:"required"` + Random common.Hash `json:"prevRandao" gencodec:"required"` + Number hexutil.Uint64 `json:"blockNumber" gencodec:"required"` + GasLimit hexutil.Uint64 `json:"gasLimit" gencodec:"required"` + GasUsed hexutil.Uint64 `json:"gasUsed" gencodec:"required"` + Timestamp hexutil.Uint64 `json:"timestamp" gencodec:"required"` + ExtraData hexutil.Bytes `json:"extraData" gencodec:"required"` + BaseFeePerGas *hexutil.Big `json:"baseFeePerGas" gencodec:"required"` + BlockHash common.Hash `json:"blockHash" gencodec:"required"` + Transactions []hexutil.Bytes `json:"transactions" gencodec:"required"` + Withdrawals []*types.Withdrawal `json:"withdrawals"` + BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed"` + ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas"` + BlockAccessList *bal.BlockAccessList `json:"blockAccessList"` } var enc ExecutableData enc.ParentHash = e.ParentHash @@ -67,24 +67,24 @@ func (e ExecutableData) MarshalJSON() ([]byte, error) { // UnmarshalJSON unmarshals from JSON. func (e *ExecutableData) UnmarshalJSON(input []byte) error { type ExecutableData struct { - ParentHash *common.Hash `json:"parentHash" gencodec:"required"` - FeeRecipient *common.Address `json:"feeRecipient" gencodec:"required"` - StateRoot *common.Hash `json:"stateRoot" gencodec:"required"` - ReceiptsRoot *common.Hash `json:"receiptsRoot" gencodec:"required"` - LogsBloom *hexutil.Bytes `json:"logsBloom" gencodec:"required"` - Random *common.Hash `json:"prevRandao" gencodec:"required"` - Number *hexutil.Uint64 `json:"blockNumber" gencodec:"required"` - GasLimit *hexutil.Uint64 `json:"gasLimit" gencodec:"required"` - GasUsed *hexutil.Uint64 `json:"gasUsed" gencodec:"required"` - Timestamp *hexutil.Uint64 `json:"timestamp" gencodec:"required"` - ExtraData *hexutil.Bytes `json:"extraData" gencodec:"required"` - BaseFeePerGas *hexutil.Big `json:"baseFeePerGas" gencodec:"required"` - BlockHash *common.Hash `json:"blockHash" gencodec:"required"` - Transactions []hexutil.Bytes `json:"transactions" gencodec:"required"` - Withdrawals []*types.Withdrawal `json:"withdrawals"` - BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed"` - ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas"` - BlockAccessList *bal.BlockAccessList `json:"blockAccessList"` + ParentHash *common.Hash `json:"parentHash" gencodec:"required"` + FeeRecipient *common.Address `json:"feeRecipient" gencodec:"required"` + StateRoot *common.Hash `json:"stateRoot" gencodec:"required"` + ReceiptsRoot *common.Hash `json:"receiptsRoot" gencodec:"required"` + LogsBloom *hexutil.Bytes `json:"logsBloom" gencodec:"required"` + Random *common.Hash `json:"prevRandao" gencodec:"required"` + Number *hexutil.Uint64 `json:"blockNumber" gencodec:"required"` + GasLimit *hexutil.Uint64 `json:"gasLimit" gencodec:"required"` + GasUsed *hexutil.Uint64 `json:"gasUsed" gencodec:"required"` + Timestamp *hexutil.Uint64 `json:"timestamp" gencodec:"required"` + ExtraData *hexutil.Bytes `json:"extraData" gencodec:"required"` + BaseFeePerGas *hexutil.Big `json:"baseFeePerGas" gencodec:"required"` + BlockHash *common.Hash `json:"blockHash" gencodec:"required"` + Transactions []hexutil.Bytes `json:"transactions" gencodec:"required"` + Withdrawals []*types.Withdrawal `json:"withdrawals"` + BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed"` + ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas"` + BlockAccessList *bal.BlockAccessList `json:"blockAccessList"` } var dec ExecutableData if err := json.Unmarshal(input, &dec); err != nil { diff --git a/core/blockchain.go b/core/blockchain.go index 68c6e7b383..c9da244970 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -356,13 +356,12 @@ type BlockChain struct { stopping atomic.Bool // false if chain is running, true when stopped procInterrupt atomic.Bool // interrupt signaler for block processing - engine consensus.Engine - 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 + engine consensus.Engine + validator Validator // Block and state validator interface + prefetcher Prefetcher + processor Processor // Block transaction processor interface + logger *tracing.Hooks + stateSizer *state.SizeTracker // State size tracking lastForkReadyAlert time.Time // Last time there was a fork readiness print out slowBlockThreshold time.Duration // Block execution time threshold beyond which detailed statistics will be logged @@ -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,14 +2243,18 @@ 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() + block = block.WithBody(*existingBody) + existingBody = block.Body() + existingBody.AccessList = balTracer.AccessList().ToEncodingObj() + block = block.WithBody(*existingBody) } - // 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() - block = block.WithBody(*existingBody) - existingBody = block.Body() - existingBody.AccessList = balTracer.AccessList().ToEncodingObj() - block = block.WithBody(*existingBody) + } // If witnesses was generated and stateless self-validation requested, do diff --git a/core/parallel_state_processor.go b/core/parallel_state_processor.go deleted file mode 100644 index 6a9c91750f..0000000000 --- a/core/parallel_state_processor.go +++ /dev/null @@ -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 -} diff --git a/core/state/bal_reader.go b/core/state/bal_reader.go deleted file mode 100644 index 3d76c290d6..0000000000 --- a/core/state/bal_reader.go +++ /dev/null @@ -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 -} diff --git a/core/state/bal_state_transition.go b/core/state/bal_state_transition.go deleted file mode 100644 index 4ebe710557..0000000000 --- a/core/state/bal_state_transition.go +++ /dev/null @@ -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) -} diff --git a/core/state/statedb.go b/core/state/statedb.go index 9fc84d20d8..33bb946a78 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -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