integrate trie batch update into statedb intermediate root calculation

This commit is contained in:
Jared Wasinger 2025-08-19 01:32:12 +09:00
parent cd191ed55a
commit 0dcd0e26f7
5 changed files with 70 additions and 10 deletions

View file

@ -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 {

View file

@ -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)

View file

@ -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

View file

@ -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
}

View file

@ -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 {