Skip to content

Commit 2ac83e1

Browse files
karalabejwasingerholimanrjl493456442
authored
core/state: blocking prefetcher on term signal, parallel updates (#29519)
* core/state: trie prefetcher change: calling trie() doesn't stop the associated subfetcher Co-authored-by: Martin HS <[email protected]> Co-authored-by: Péter Szilágyi <[email protected]> * core/state: improve prefetcher * core/state: restore async prefetcher stask scheduling * core/state: finish prefetching async and process storage updates async * core/state: don't use the prefetcher for missing snapshot items * core/state: remove update concurrency for Verkle tries * core/state: add some termination checks to prefetcher async shutdowns * core/state: differentiate db tries and prefetched tries * core/state: teh teh teh --------- Co-authored-by: Jared Wasinger <[email protected]> Co-authored-by: Martin HS <[email protected]> Co-authored-by: Gary Rong <[email protected]>
1 parent 44a50c9 commit 2ac83e1

File tree

5 files changed

+271
-297
lines changed

5 files changed

+271
-297
lines changed

core/blockchain.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1805,8 +1805,12 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error)
18051805
}
18061806
statedb.SetLogger(bc.logger)
18071807

1808-
// Enable prefetching to pull in trie node paths while processing transactions
1809-
statedb.StartPrefetcher("chain")
1808+
// If we are past Byzantium, enable prefetching to pull in trie node paths
1809+
// while processing transactions. Before Byzantium the prefetcher is mostly
1810+
// useless due to the intermediate root hashing after each transaction.
1811+
if bc.chainConfig.IsByzantium(block.Number()) {
1812+
statedb.StartPrefetcher("chain")
1813+
}
18101814
activeState = statedb
18111815

18121816
// If we have a followup block, run that against the current state to pre-cache

core/state/state_object.go

Lines changed: 71 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"fmt"
2222
"io"
2323
"maps"
24+
"sync"
2425
"time"
2526

2627
"github.com/ethereum/go-ethereum/common"
@@ -33,6 +34,14 @@ import (
3334
"github.com/holiman/uint256"
3435
)
3536

37+
// hasherPool holds a pool of hashers used by state objects during concurrent
38+
// trie updates.
39+
var hasherPool = sync.Pool{
40+
New: func() interface{} {
41+
return crypto.NewKeccakState()
42+
},
43+
}
44+
3645
type Storage map[common.Hash]common.Hash
3746

3847
func (s Storage) Copy() Storage {
@@ -118,27 +127,39 @@ func (s *stateObject) touch() {
118127
}
119128
}
120129

121-
// getTrie returns the associated storage trie. The trie will be opened
122-
// if it's not loaded previously. An error will be returned if trie can't
123-
// be loaded.
130+
// getTrie returns the associated storage trie. The trie will be opened if it'
131+
// not loaded previously. An error will be returned if trie can't be loaded.
132+
//
133+
// If a new trie is opened, it will be cached within the state object to allow
134+
// subsequent reads to expand the same trie instead of reloading from disk.
124135
func (s *stateObject) getTrie() (Trie, error) {
125136
if s.trie == nil {
126-
// Try fetching from prefetcher first
127-
if s.data.Root != types.EmptyRootHash && s.db.prefetcher != nil {
128-
// When the miner is creating the pending state, there is no prefetcher
129-
s.trie = s.db.prefetcher.trie(s.addrHash, s.data.Root)
130-
}
131-
if s.trie == nil {
132-
tr, err := s.db.db.OpenStorageTrie(s.db.originalRoot, s.address, s.data.Root, s.db.trie)
133-
if err != nil {
134-
return nil, err
135-
}
136-
s.trie = tr
137+
tr, err := s.db.db.OpenStorageTrie(s.db.originalRoot, s.address, s.data.Root, s.db.trie)
138+
if err != nil {
139+
return nil, err
137140
}
141+
s.trie = tr
138142
}
139143
return s.trie, nil
140144
}
141145

146+
// getPrefetchedTrie returns the associated trie, as populated by the prefetcher
147+
// if it's available.
148+
//
149+
// Note, opposed to getTrie, this method will *NOT* blindly cache the resulting
150+
// trie in the state object. The caller might want to do that, but it's cleaner
151+
// to break the hidden interdependency between retrieving tries from the db or
152+
// from the prefetcher.
153+
func (s *stateObject) getPrefetchedTrie() (Trie, error) {
154+
// If there's nothing to meaningfully return, let the user figure it out by
155+
// pulling the trie from disk.
156+
if s.data.Root == types.EmptyRootHash || s.db.prefetcher == nil {
157+
return nil, nil
158+
}
159+
// Attempt to retrieve the trie from the pretecher
160+
return s.db.prefetcher.trie(s.addrHash, s.data.Root)
161+
}
162+
142163
// GetState retrieves a value from the account storage trie.
143164
func (s *stateObject) GetState(key common.Hash) common.Hash {
144165
value, _ := s.getState(key)
@@ -248,7 +269,7 @@ func (s *stateObject) setState(key common.Hash, value common.Hash, origin common
248269

249270
// finalise moves all dirty storage slots into the pending area to be hashed or
250271
// committed later. It is invoked at the end of every transaction.
251-
func (s *stateObject) finalise(prefetch bool) {
272+
func (s *stateObject) finalise() {
252273
slotsToPrefetch := make([][]byte, 0, len(s.dirtyStorage))
253274
for key, value := range s.dirtyStorage {
254275
// If the slot is different from its original value, move it into the
@@ -263,8 +284,10 @@ func (s *stateObject) finalise(prefetch bool) {
263284
delete(s.pendingStorage, key)
264285
}
265286
}
266-
if s.db.prefetcher != nil && prefetch && len(slotsToPrefetch) > 0 && s.data.Root != types.EmptyRootHash {
267-
s.db.prefetcher.prefetch(s.addrHash, s.data.Root, s.address, slotsToPrefetch)
287+
if s.db.prefetcher != nil && len(slotsToPrefetch) > 0 && s.data.Root != types.EmptyRootHash {
288+
if err := s.db.prefetcher.prefetch(s.addrHash, s.data.Root, s.address, slotsToPrefetch); err != nil {
289+
log.Error("Failed to prefetch slots", "addr", s.address, "slots", len(slotsToPrefetch), "err", err)
290+
}
268291
}
269292
if len(s.dirtyStorage) > 0 {
270293
s.dirtyStorage = make(Storage)
@@ -283,25 +306,43 @@ func (s *stateObject) finalise(prefetch bool) {
283306
// storage change at all.
284307
func (s *stateObject) updateTrie() (Trie, error) {
285308
// Make sure all dirty slots are finalized into the pending storage area
286-
s.finalise(false)
309+
s.finalise()
287310

288311
// Short circuit if nothing changed, don't bother with hashing anything
289312
if len(s.pendingStorage) == 0 {
290313
return s.trie, nil
291314
}
315+
// Retrieve a pretecher populated trie, or fall back to the database
316+
tr, err := s.getPrefetchedTrie()
317+
switch {
318+
case err != nil:
319+
// Fetcher retrieval failed, something's very wrong, abort
320+
s.db.setError(err)
321+
return nil, err
322+
323+
case tr == nil:
324+
// Fetcher not running or empty trie, fallback to the database trie
325+
tr, err = s.getTrie()
326+
if err != nil {
327+
s.db.setError(err)
328+
return nil, err
329+
}
330+
331+
default:
332+
// Prefetcher returned a live trie, swap it out for the current one
333+
s.trie = tr
334+
}
292335
// The snapshot storage map for the object
293336
var (
294337
storage map[common.Hash][]byte
295338
origin map[common.Hash][]byte
296339
)
297-
tr, err := s.getTrie()
298-
if err != nil {
299-
s.db.setError(err)
300-
return nil, err
301-
}
302340
// Insert all the pending storage updates into the trie
303341
usedStorage := make([][]byte, 0, len(s.pendingStorage))
304342

343+
hasher := hasherPool.Get().(crypto.KeccakState)
344+
defer hasherPool.Put(hasher)
345+
305346
// Perform trie updates before deletions. This prevents resolution of unnecessary trie nodes
306347
// in circumstances similar to the following:
307348
//
@@ -330,26 +371,30 @@ func (s *stateObject) updateTrie() (Trie, error) {
330371
s.db.setError(err)
331372
return nil, err
332373
}
333-
s.db.StorageUpdated += 1
374+
s.db.StorageUpdated.Add(1)
334375
} else {
335376
deletions = append(deletions, key)
336377
}
337378
// Cache the mutated storage slots until commit
338379
if storage == nil {
380+
s.db.storagesLock.Lock()
339381
if storage = s.db.storages[s.addrHash]; storage == nil {
340382
storage = make(map[common.Hash][]byte)
341383
s.db.storages[s.addrHash] = storage
342384
}
385+
s.db.storagesLock.Unlock()
343386
}
344-
khash := crypto.HashData(s.db.hasher, key[:])
387+
khash := crypto.HashData(hasher, key[:])
345388
storage[khash] = encoded // encoded will be nil if it's deleted
346389

347390
// Cache the original value of mutated storage slots
348391
if origin == nil {
392+
s.db.storagesLock.Lock()
349393
if origin = s.db.storagesOrigin[s.address]; origin == nil {
350394
origin = make(map[common.Hash][]byte)
351395
s.db.storagesOrigin[s.address] = origin
352396
}
397+
s.db.storagesLock.Unlock()
353398
}
354399
// Track the original value of slot only if it's mutated first time
355400
if _, ok := origin[khash]; !ok {
@@ -369,7 +414,7 @@ func (s *stateObject) updateTrie() (Trie, error) {
369414
s.db.setError(err)
370415
return nil, err
371416
}
372-
s.db.StorageDeleted += 1
417+
s.db.StorageDeleted.Add(1)
373418
}
374419
// If no slots were touched, issue a warning as we shouldn't have done all
375420
// the above work in the first place

0 commit comments

Comments
 (0)