Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 26 additions & 1 deletion core/blockchain_reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
22 changes: 22 additions & 0 deletions core/state/statedb.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
31 changes: 30 additions & 1 deletion core/state_prefetcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

statedb should check if it is not nil beacause StateForPrefetch() may return nil

signer = types.MakeSigner(p.config, header.Number, header.Time)
)

txCh := make(chan *types.Transaction, 2*prefetchMiningThread)
for i := 0; i < prefetchMiningThread; i++ {
Expand All @@ -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 {
Expand Down
4 changes: 2 additions & 2 deletions miner/worker.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down Expand Up @@ -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
Expand Down