Skip to content

Commit 167bd29

Browse files
committed
core: improve trie updates ethereum#21047
1 parent e83b87a commit 167bd29

File tree

11 files changed

+550
-95
lines changed

11 files changed

+550
-95
lines changed

accounts/abi/bind/backends/simulated.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -218,10 +218,9 @@ func (b *SimulatedBackend) Rollback() {
218218

219219
func (b *SimulatedBackend) rollback(parent *types.Block) {
220220
blocks, _ := core.GenerateChain(b.config, parent, b.blockchain.Engine(), b.database, 1, func(int, *core.BlockGen) {})
221-
stateDB, _ := b.blockchain.State()
222221

223222
b.pendingBlock = blocks[0]
224-
b.pendingState, _ = state.New(b.pendingBlock.Root(), stateDB.Database())
223+
b.pendingState, _ = state.New(b.pendingBlock.Root(), b.blockchain.StateCache())
225224
}
226225

227226
// Fork creates a side-chain that can be used to simulate reorgs.

core/blockchain.go

Lines changed: 30 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -207,9 +207,9 @@ type BlockChain struct {
207207
procInterrupt atomic.Bool // interrupt signaler for block processing
208208

209209
engine consensus.Engine
210-
validator Validator // Block and state validator interface
211-
prefetcher Prefetcher // Block state prefetcher interface
212-
processor Processor // Block transaction processor interface
210+
validator Validator // Block and state validator interface
211+
prefetcher Prefetcher
212+
processor Processor // Block transaction processor interface
213213
vmConfig vm.Config
214214

215215
IPCEndpoint string
@@ -1723,6 +1723,9 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals bool) (int, []
17231723
if err != nil {
17241724
return it.index, events, coalescedLogs, err
17251725
}
1726+
// Enable prefetching to pull in trie node paths while processing transactions
1727+
statedb.StartPrefetcher("chain")
1728+
defer statedb.StopPrefetcher() // stopped on write anyway, defer meant to catch early error returns
17261729

17271730
// If we have a followup block, run that against the current state to pre-cache
17281731
// transactions and probabilistically some of the account/storage trie nodes.
@@ -1740,9 +1743,8 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals bool) (int, []
17401743
}(time.Now())
17411744
}
17421745
}
1743-
17441746
// Process block using the parent state as reference point.
1745-
t0 := time.Now()
1747+
substart := time.Now()
17461748
isTIPXDCXReceiver := bc.Config().IsTIPXDCXReceiver(block.Number())
17471749
tradingState, lendingState, err := bc.processTradingAndLendingStates(isTIPXDCXReceiver, block, parent, statedb)
17481750
if err != nil {
@@ -1752,47 +1754,50 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals bool) (int, []
17521754
}
17531755
feeCapacity := state.GetTRC21FeeCapacityFromStateWithCache(parent.Root, statedb)
17541756
receipts, logs, usedGas, err := bc.processor.Process(block, statedb, tradingState, bc.vmConfig, feeCapacity)
1755-
t1 := time.Now()
17561757
if err != nil {
17571758
bc.reportBlock(block, receipts, err)
17581759
followupInterrupt.Store(true)
17591760
return it.index, events, coalescedLogs, err
17601761
}
1762+
// Update the metrics touched during block processing
1763+
accountReadTimer.Update(statedb.AccountReads) // Account reads are complete, we can mark them
1764+
storageReadTimer.Update(statedb.StorageReads) // Storage reads are complete, we can mark them
1765+
accountUpdateTimer.Update(statedb.AccountUpdates) // Account updates are complete, we can mark them
1766+
storageUpdateTimer.Update(statedb.StorageUpdates) // Storage updates are complete, we can mark them
1767+
triehash := statedb.AccountHashes + statedb.StorageHashes // Save to not double count in validation
1768+
trieproc := statedb.AccountReads + statedb.AccountUpdates
1769+
trieproc += statedb.StorageReads + statedb.StorageUpdates
1770+
1771+
blockExecutionTimer.Update(time.Since(substart) - trieproc - triehash)
1772+
17611773
// Validate the state using the default validator
1774+
substart = time.Now()
17621775
err = bc.validator.ValidateState(block, statedb, receipts, usedGas)
17631776
if err != nil {
17641777
bc.reportBlock(block, receipts, err)
17651778
return it.index, events, coalescedLogs, err
17661779
}
1767-
t2 := time.Now()
17681780
proctime := time.Since(start)
17691781

1782+
// Update the metrics touched during block validation
1783+
accountHashTimer.Update(statedb.AccountHashes) // Account hashes are complete, we can mark them
1784+
storageHashTimer.Update(statedb.StorageHashes) // Storage hashes are complete, we can mark them
1785+
1786+
blockValidationTimer.Update(time.Since(substart) - (statedb.AccountHashes + statedb.StorageHashes - triehash))
1787+
17701788
// Write the block to the chain and get the status.
1789+
substart = time.Now()
17711790
status, err := bc.writeBlockWithState(block, receipts, statedb, tradingState, lendingState)
1772-
t3 := time.Now()
17731791
followupInterrupt.Store(true)
17741792
if err != nil {
17751793
return it.index, events, coalescedLogs, err
17761794
}
1795+
// Update the metrics touched during block commit
1796+
accountCommitTimer.Update(statedb.AccountCommits) // Account commits are complete, we can mark them
1797+
storageCommitTimer.Update(statedb.StorageCommits) // Storage commits are complete, we can mark them
17771798

1778-
// Update the metrics subsystem with all the measurements
1779-
accountReadTimer.Update(statedb.AccountReads)
1780-
accountHashTimer.Update(statedb.AccountHashes)
1781-
accountUpdateTimer.Update(statedb.AccountUpdates)
1782-
accountCommitTimer.Update(statedb.AccountCommits)
1783-
1784-
storageReadTimer.Update(statedb.StorageReads)
1785-
storageHashTimer.Update(statedb.StorageHashes)
1786-
storageUpdateTimer.Update(statedb.StorageUpdates)
1787-
storageCommitTimer.Update(statedb.StorageCommits)
1788-
1789-
trieAccess := statedb.AccountReads + statedb.AccountHashes + statedb.AccountUpdates + statedb.AccountCommits
1790-
trieAccess += statedb.StorageReads + statedb.StorageHashes + statedb.StorageUpdates + statedb.StorageCommits
1791-
1799+
blockWriteTimer.Update(time.Since(substart) - statedb.AccountCommits - statedb.StorageCommits)
17921800
blockInsertTimer.UpdateSince(start)
1793-
blockExecutionTimer.Update(t1.Sub(t0) - trieAccess)
1794-
blockValidationTimer.Update(t2.Sub(t1))
1795-
blockWriteTimer.Update(t3.Sub(t2))
17961801

17971802
switch status {
17981803
case CanonStatTy:
@@ -1816,7 +1821,6 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals bool) (int, []
18161821
events = append(events, ChainSideEvent{block})
18171822
bc.UpdateBlocksHashCache(block)
18181823
}
1819-
blockInsertTimer.UpdateSince(start)
18201824
stats.processed++
18211825
stats.usedGas += usedGas
18221826

core/state/database.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,12 +127,20 @@ type cachingDB struct {
127127

128128
// OpenTrie opens the main account trie at a specific root hash.
129129
func (db *cachingDB) OpenTrie(root common.Hash) (Trie, error) {
130-
return trie.NewSecure(root, db.db)
130+
tr, err := trie.NewSecure(root, db.db)
131+
if err != nil {
132+
return nil, err
133+
}
134+
return tr, nil
131135
}
132136

133137
// OpenStorageTrie opens the storage trie of an account.
134138
func (db *cachingDB) OpenStorageTrie(addrHash, root common.Hash) (Trie, error) {
135-
return trie.NewSecure(root, db.db)
139+
tr, err := trie.NewSecure(root, db.db)
140+
if err != nil {
141+
return nil, err
142+
}
143+
return tr, nil
136144
}
137145

138146
// CopyTrie returns an independent copy of the given trie.

core/state/state_object.go

Lines changed: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -174,11 +174,20 @@ func (s *stateObject) touch() {
174174

175175
func (s *stateObject) getTrie(db Database) Trie {
176176
if s.trie == nil {
177-
var err error
178-
s.trie, err = db.OpenStorageTrie(s.addrHash, s.data.Root)
179-
if err != nil {
180-
s.trie, _ = db.OpenStorageTrie(s.addrHash, types.EmptyRootHash)
181-
s.setError(fmt.Errorf("can't create storage trie: %v", err))
177+
// Try fetching from prefetcher first
178+
// We don't prefetch empty tries
179+
if s.data.Root != types.EmptyRootHash && s.db.prefetcher != nil {
180+
// When the miner is creating the pending state, there is no
181+
// prefetcher
182+
s.trie = s.db.prefetcher.trie(s.data.Root)
183+
}
184+
if s.trie == nil {
185+
var err error
186+
s.trie, err = db.OpenStorageTrie(s.addrHash, s.data.Root)
187+
if err != nil {
188+
s.trie, _ = db.OpenStorageTrie(s.addrHash, common.Hash{})
189+
s.setError(fmt.Errorf("can't create storage trie: %v", err))
190+
}
182191
}
183192
}
184193
return s.trie
@@ -281,9 +290,16 @@ func (s *stateObject) setState(key, value common.Hash) {
281290

282291
// finalise moves all dirty storage slots into the pending area to be hashed or
283292
// committed later. It is invoked at the end of every transaction.
284-
func (s *stateObject) finalise() {
293+
func (s *stateObject) finalise(prefetch bool) {
294+
slotsToPrefetch := make([][]byte, 0, len(s.dirtyStorage))
285295
for key, value := range s.dirtyStorage {
286296
s.pendingStorage[key] = value
297+
if value != s.originStorage[key] {
298+
slotsToPrefetch = append(slotsToPrefetch, common.CopyBytes(key[:])) // Copy needed for closure
299+
}
300+
}
301+
if s.db.prefetcher != nil && prefetch && len(slotsToPrefetch) > 0 && s.data.Root != types.EmptyRootHash {
302+
s.db.prefetcher.prefetch(s.data.Root, slotsToPrefetch)
287303
}
288304
if len(s.dirtyStorage) > 0 {
289305
s.dirtyStorage = make(Storage)
@@ -294,28 +310,35 @@ func (s *stateObject) finalise() {
294310
// It will return nil if the trie has not been loaded and no changes have been made
295311
func (s *stateObject) updateTrie(db Database) Trie {
296312
// Make sure all dirty slots are finalized into the pending storage area
297-
s.finalise()
313+
s.finalise(false) // Don't prefetch any more, pull directly if need be
298314
if len(s.pendingStorage) == 0 {
299315
return s.trie
300316
}
301317
// Track the amount of time wasted on updating the storage trie
302318
defer func(start time.Time) { s.db.StorageUpdates += time.Since(start) }(time.Now())
303319
// Insert all the pending updates into the trie
304320
tr := s.getTrie(db)
321+
322+
usedStorage := make([][]byte, 0, len(s.pendingStorage))
305323
for key, value := range s.pendingStorage {
306324
// Skip noop changes, persist actual changes
307325
if value == s.originStorage[key] {
308326
continue
309327
}
310328
s.originStorage[key] = value
311329

330+
var v []byte
312331
if (value == common.Hash{}) {
313332
s.setError(tr.TryDelete(key[:]))
314-
continue
333+
} else {
334+
// Encoding []byte cannot fail, ok to ignore the error.
335+
v, _ = rlp.EncodeToBytes(common.TrimLeftZeroes(value[:]))
336+
s.setError(tr.TryUpdate(key[:], v))
315337
}
316-
// Encoding []byte cannot fail, ok to ignore the error.
317-
v, _ := rlp.EncodeToBytes(common.TrimLeftZeroes(value[:]))
318-
s.setError(tr.TryUpdate(key[:], v))
338+
usedStorage = append(usedStorage, common.CopyBytes(key[:])) // Copy needed for closure
339+
}
340+
if s.db.prefetcher != nil {
341+
s.db.prefetcher.used(s.data.Root, usedStorage)
319342
}
320343
if len(s.pendingStorage) > 0 {
321344
s.pendingStorage = make(Storage)

core/state/state_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ func TestSnapshot2(t *testing.T) {
164164
state.setStateObject(so0)
165165

166166
root, _ := state.Commit(false)
167-
state.Reset(root)
167+
state, _ = New(root, state.db)
168168

169169
// and one with deleted == true
170170
so1 := state.getStateObject(stateobjaddr1)

0 commit comments

Comments
 (0)