From 03a4b24aca11710d2d6fdbb168aa79850a523d0d Mon Sep 17 00:00:00 2001 From: Gary Rong Date: Tue, 28 Oct 2025 14:59:27 +0800 Subject: [PATCH] core/state: paralleize the account trie hash along with storages --- core/state/statedb.go | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/core/state/statedb.go b/core/state/statedb.go index b770698255..3f7dfcab8d 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -818,8 +818,11 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash { // method will internally call a blocking trie fetch from the prefetcher, // so there's no need to explicitly wait for the prefetchers to finish. var ( - start = time.Now() workers errgroup.Group + start = time.Now() + + storageUpdates atomic.Int64 + pendingAccounts = make(chan *stateObject, len(s.mutations)) ) if s.db.TrieDB().IsVerkle() { // Whilst MPT storage tries are independent, Verkle has one single trie @@ -846,6 +849,8 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash { s.witness.AddState(obj.trie.Witness()) } } + pendingAccounts <- obj + storageUpdates.Store(time.Since(start).Nanoseconds()) // overwrite with the longest storage update runtime return nil }) } @@ -898,9 +903,6 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash { } } } - workers.Wait() - s.StorageUpdates += time.Since(start) - // Now we're about to start to write changes to the trie. The trie is so far // _untouched_. We can check with the prefetcher, if it can give us a trie // which has the same root, but also has some content loaded into it. @@ -908,7 +910,6 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash { // Don't check prefetcher if verkle trie has been used. In the context of verkle, // only a single trie is used for state hashing. Replacing a non-nil verkle tree // here could result in losing uncommitted changes from storage. - start = time.Now() if s.prefetcher != nil { if trie := s.prefetcher.trie(common.Hash{}, s.originalRoot); trie == nil { log.Error("Failed to retrieve account pre-fetcher trie") @@ -927,8 +928,9 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash { // into a shortnode. This requires `B` to be resolved from disk. // Whereas if the created node is handled first, then the collapse is avoided, and `B` is not resolved. var ( - usedAddrs []common.Address - deletedAddrs []common.Address + usedAddrs []common.Address + deletedAddrs []common.Address + pendingUpdates int ) for addr, op := range s.mutations { if op.applied { @@ -939,16 +941,24 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash { if op.isDelete() { deletedAddrs = append(deletedAddrs, addr) } else { - s.updateStateObject(s.stateObjects[addr]) + pendingUpdates += 1 s.AccountUpdated += 1 } usedAddrs = append(usedAddrs, addr) // Copy needed for closure } + for pendingUpdates > 0 { + select { + case obj := <-pendingAccounts: + s.updateStateObject(obj) + pendingUpdates-- + } + } for _, deletedAddr := range deletedAddrs { s.deleteStateObject(deletedAddr) s.AccountDeleted += 1 } s.AccountUpdates += time.Since(start) + s.StorageUpdates += time.Duration(storageUpdates.Load()) if s.prefetcher != nil { s.prefetcher.used(common.Hash{}, s.originalRoot, usedAddrs, nil)