diff --git a/core/blockchain_reader.go b/core/blockchain_reader.go index 29f620f3d1..e6e488c468 100644 --- a/core/blockchain_reader.go +++ b/core/blockchain_reader.go @@ -496,7 +496,32 @@ func (bc *BlockChain) StateAt(root common.Hash) (*state.StateDB, error) { return nil, errors.New("state is not available") } - return stateDb, err + return stateDb, nil +} + +// StateWithCacheAt returns a new mutable state with cache based on a particular point in time. +func (bc *BlockChain) StateWithCacheAt(root common.Hash) (*state.StateDB, error) { + _, process, err := bc.statedb.ReadersWithCacheStats(root) + if err != nil { + return nil, err + } + stateDb, err := state.NewWithReader(root, bc.statedb, process) + if bc.cfg.EnableBAL { + stateDb.InitBlockAccessList() + } + if err != nil { + return nil, err + } + + // If there's no trie and the specified snapshot is not available, getting + // any state will by default return nil. + // Instead of that, it will be more useful to return an error to indicate + // the state is not available. + if stateDb.NoTries() && stateDb.GetSnap() == nil { + return nil, errors.New("state is not available") + } + + return stateDb, nil } // HistoricState returns a historic state specified by the given root. diff --git a/core/state/statedb.go b/core/state/statedb.go index 66847236fa..11744ef91e 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -765,6 +765,28 @@ func (s *StateDB) CreateContract(addr common.Address) { } } +// StateForPrefetch creates a mirrored StateDB instance that shares the same +// underlying state reader and cache as the current one. It is typically used +// for state prefetching. +// +// Note: If the reader implements readerWithCacheStats, a new wrapper is created +// to maintain independent cache statistics while reusing the same cache source. +func (s *StateDB) StateForPrefetch() *StateDB { + reader := s.reader + if readerWithCacheStats, ok := s.reader.(*readerWithCacheStats); ok { + reader = newReaderWithCacheStats(readerWithCacheStats.readerWithCache) + } + state, err := NewWithReader(s.originalRoot, s.db, reader) + if err != nil { + log.Error("Failed to create StateDB for prefetch", "err", err) + return nil + } + + state.prefetcher = s.prefetcher + + return state +} + // Copy creates a deep, independent copy of the state. // Snapshots of the copied state cannot be applied to the copy. func (s *StateDB) Copy() *StateDB { diff --git a/core/state_prefetcher.go b/core/state_prefetcher.go index 36a0a38c88..4f71e33cb1 100644 --- a/core/state_prefetcher.go +++ b/core/state_prefetcher.go @@ -277,7 +277,13 @@ func (p *statePrefetcher) PrefetchBAL(block *types.Block, statedb *state.StateDB // the transaction messages using the statedb, but any changes are discarded. The // only goal is to warm the state caches. Only used for mining stage. func (p *statePrefetcher) PrefetchMining(txs TransactionsByPriceAndNonce, header *types.Header, gasLimit uint64, statedb *state.StateDB, cfg vm.Config, interruptCh <-chan struct{}, txCurr **types.Transaction) { - var signer = types.MakeSigner(p.config, header.Number, header.Time) + if statedb == nil { + return + } + var ( + reader = statedb.Reader() + signer = types.MakeSigner(p.config, header.Number, header.Time) + ) txCh := make(chan *types.Transaction, 2*prefetchMiningThread) for i := 0; i < prefetchMiningThread; i++ { @@ -289,6 +295,29 @@ func (p *statePrefetcher) PrefetchMining(txs TransactionsByPriceAndNonce, header for { select { case tx := <-startCh: + // Preload the touched accounts and storage slots in advance + sender, err := types.Sender(signer, tx) + if err == nil { + reader.Account(sender) + } + + if tx.To() != nil { + account, _ := reader.Account(*tx.To()) + + // Preload the contract code if the destination has non-empty code + if account != nil && !bytes.Equal(account.CodeHash, types.EmptyCodeHash.Bytes()) { + reader.Code(*tx.To(), common.BytesToHash(account.CodeHash)) + } + } + for _, list := range tx.AccessList() { + reader.Account(list.Address) + if len(list.StorageKeys) > 0 { + for _, slot := range list.StorageKeys { + reader.Storage(list.Address, slot) + } + } + } + // Convert the transaction into an executable message and pre-cache its sender msg, err := TransactionToMessage(tx, signer, header.BaseFee) if err != nil { diff --git a/miner/worker.go b/miner/worker.go index cdb349b741..dbabc947dd 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -647,7 +647,7 @@ func (w *worker) makeEnv(parent *types.Header, header *types.Header, coinbase co prevEnv *environment, witness bool) (*environment, error) { // Retrieve the parent state to execute on top and start a prefetcher for // the miner to speed block sealing up a bit - state, err := w.chain.StateAt(parent.Root) + state, err := w.chain.StateWithCacheAt(parent.Root) if err != nil { return nil, err } @@ -762,7 +762,7 @@ func (w *worker) commitTransactions(env *environment, plainTxs, blobTxs *transac tx := txsPrefetch.PeekWithUnwrap() if tx != nil { txCurr := &tx - w.prefetcher.PrefetchMining(txsPrefetch, env.header, env.gasPool.Gas(), env.state.CopyDoPrefetch(), *w.chain.GetVMConfig(), stopPrefetchCh, txCurr) + w.prefetcher.PrefetchMining(txsPrefetch, env.header, env.gasPool.Gas(), env.state.StateForPrefetch(), *w.chain.GetVMConfig(), stopPrefetchCh, txCurr) } signal := commitInterruptNone