From 0dcd0e26f7fed337d1c02d9fb292110b9beb76fc Mon Sep 17 00:00:00 2001 From: Jared Wasinger Date: Tue, 19 Aug 2025 01:32:12 +0900 Subject: [PATCH] integrate trie batch update into statedb intermediate root calculation --- core/block_validator.go | 1 + core/state/state_object.go | 18 +++++++++----- core/state/statedb.go | 51 ++++++++++++++++++++++++++++++++++++-- tests/block_test.go | 2 +- trie/trie.go | 8 +++++- 5 files changed, 70 insertions(+), 10 deletions(-) diff --git a/core/block_validator.go b/core/block_validator.go index 008444fbbc..afc89012f1 100644 --- a/core/block_validator.go +++ b/core/block_validator.go @@ -160,6 +160,7 @@ func (v *BlockValidator) ValidateState(block *types.Block, statedb *state.StateD } else if res.Requests != nil { return errors.New("block has requests before prague fork") } + // Validate the state root against the received state root and throw // an error if they don't match. if root := statedb.IntermediateRoot(v.config.IsEIP158(header.Number)); header.Root != root { diff --git a/core/state/state_object.go b/core/state/state_object.go index 767f469bfd..0269a62179 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -322,8 +322,10 @@ func (s *stateObject) updateTrie() (Trie, error) { // 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 ( - deletions []common.Hash - used = make([]common.Hash, 0, len(s.uncommittedStorage)) + deletions []common.Hash + used = make([]common.Hash, 0, len(s.uncommittedStorage)) + updateKeys [][]byte + updateValues [][]byte ) for key, origin := range s.uncommittedStorage { // Skip noop changes, persist actual changes @@ -337,10 +339,8 @@ func (s *stateObject) updateTrie() (Trie, error) { continue } if (value != common.Hash{}) { - if err := tr.UpdateStorage(s.address, key[:], common.TrimLeftZeroes(value[:])); err != nil { - s.db.setError(err) - return nil, err - } + updateKeys = append(updateKeys, key[:]) + updateValues = append(updateValues, common.TrimLeftZeroes(value[:])) s.db.StorageUpdated.Add(1) } else { deletions = append(deletions, key) @@ -348,6 +348,12 @@ func (s *stateObject) updateTrie() (Trie, error) { // Cache the items for preloading used = append(used, key) // Copy needed for closure } + if len(updateKeys) > 0 { + if err := tr.UpdateStorageBatch(common.Address{}, updateKeys, updateValues); err != nil { + s.db.setError(err) + return nil, err + } + } for _, key := range deletions { if err := tr.DeleteStorage(s.address, key[:]); err != nil { s.db.setError(err) diff --git a/core/state/statedb.go b/core/state/statedb.go index efb09a08a0..e889e27d8f 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -574,6 +574,25 @@ func (s *StateDB) updateStateObject(obj *stateObject) { s.trie.UpdateContractCode(obj.Address(), common.BytesToHash(obj.CodeHash()), obj.code) } } +func (s *StateDB) updateStateObjects(objs []*stateObject) { + var addrs []common.Address + var accts []*types.StateAccount + + for _, obj := range objs { + addrs = append(addrs, obj.Address()) + accts = append(accts, &obj.data) + } + + if err := s.trie.UpdateAccountBatch(addrs, accts, nil); err != nil { + s.setError(fmt.Errorf("updateStateObjects error: %v", err)) + } + + for _, obj := range objs { + if obj.dirtyCode { + s.trie.UpdateContractCode(obj.Address(), common.BytesToHash(obj.CodeHash()), obj.code) + } + } +} // deleteStateObject removes the given object from the state trie. func (s *StateDB) deleteStateObject(addr common.Address) { @@ -883,6 +902,31 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash { workers.Wait() s.StorageUpdates += time.Since(start) + /* + fmt.Println("begin print mutations") + fmt.Println(s.txIndex) + for addr, _ := range s.mutations { + if _, ok := s.stateObjects[addr]; !ok { + continue + } + if s.stateObjects[addr].trie == nil { + continue + } + fmt.Printf("mut %x/%x:\n", addr, s.stateObjects[addr].addrHash) + it, err := s.stateObjects[addr].trie.NodeIterator([]byte{}) + if err != nil { + panic(err) + } + for it.Next(true) { + if it.Leaf() { + fmt.Printf("%x: %x\n", it.Path(), it.LeafBlob()) + } else { + fmt.Printf("%x: %x\n", it.Path(), it.Hash()) + } + } + } + */ + // 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. @@ -911,6 +955,7 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash { var ( usedAddrs []common.Address deletedAddrs []common.Address + updatedObjs []*stateObject ) for addr, op := range s.mutations { if op.applied { @@ -921,11 +966,14 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash { if op.isDelete() { deletedAddrs = append(deletedAddrs, addr) } else { - s.updateStateObject(s.stateObjects[addr]) + updatedObjs = append(updatedObjs, s.stateObjects[addr]) s.AccountUpdated += 1 } usedAddrs = append(usedAddrs, addr) // Copy needed for closure } + if len(updatedObjs) > 0 { + s.updateStateObjects(updatedObjs) + } for _, deletedAddr := range deletedAddrs { s.deleteStateObject(deletedAddr) s.AccountDeleted += 1 @@ -937,7 +985,6 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash { } // Track the amount of time wasted on hashing the account trie defer func(start time.Time) { s.AccountHashes += time.Since(start) }(time.Now()) - hash := s.trie.Hash() // If witness building is enabled, gather the account trie witness diff --git a/tests/block_test.go b/tests/block_test.go index 91d9f2e653..82eeb534fc 100644 --- a/tests/block_test.go +++ b/tests/block_test.go @@ -105,7 +105,7 @@ func execBlockTest(t *testing.T, bt *testMatcher, test *BlockTest) { } for _, snapshot := range snapshotConf { for _, dbscheme := range dbschemeConf { - if err := bt.checkFailure(t, test.Run(snapshot, dbscheme, true, nil, nil)); err != nil { + if err := bt.checkFailure(t, test.Run(snapshot, dbscheme, false, nil, nil)); err != nil { t.Errorf("test with config {snapshotter:%v, scheme:%v} failed: %v", snapshot, dbscheme, err) return } diff --git a/trie/trie.go b/trie/trie.go index 34a428f671..ade5c06e49 100644 --- a/trie/trie.go +++ b/trie/trie.go @@ -419,6 +419,7 @@ func (t *Trie) UpdateBatch(keys [][]byte, values [][]byte) error { // Insert the entries sequentially if there are not too many // trie nodes in the trie. fn, ok := t.root.(*fullNode) + if !ok || len(keys) < 4 { // TODO(rjl493456442) the parallelism threshold should be twisted for i, key := range keys { err := t.Update(key, values[i]) @@ -438,7 +439,12 @@ func (t *Trie) UpdateBatch(keys [][]byte, values [][]byte) error { ikeys[hkey[0]] = append(ikeys[hkey[0]], hkey) ivals[hkey[0]] = append(ivals[hkey[0]], values[i]) } - for pos, ks := range ikeys { + if len(keys) > 0 { + fn.flags = t.newFlag() + } + for p, k := range ikeys { + pos := p + ks := k eg.Go(func() error { vs := ivals[pos] for i, k := range ks {