From a868a8e1e0e63432c36f1fca0b5cae884daf6cea Mon Sep 17 00:00:00 2001 From: Jared Wasinger Date: Wed, 22 Oct 2025 19:18:37 +0800 Subject: [PATCH 1/7] trie: make value node resolvable, not needing to be known at insertion time. --- core/state/database.go | 2 ++ core/state/statedb.go | 23 +++++++++++++++++++++-- trie/committer.go | 6 +++--- trie/hasher.go | 4 ++-- trie/iterator.go | 8 ++++---- trie/node.go | 30 +++++++++++++++++++++++------- trie/node_enc.go | 4 ++-- trie/proof.go | 18 +++++++++--------- trie/secure_trie.go | 19 +++++++++++++++++++ trie/sync.go | 4 ++-- trie/transition.go | 4 ++++ trie/trie.go | 31 +++++++++++++++++++++---------- trie/verkle.go | 4 ++++ 13 files changed, 116 insertions(+), 41 deletions(-) diff --git a/core/state/database.go b/core/state/database.go index 58d0ccfe829..d02219f28e2 100644 --- a/core/state/database.go +++ b/core/state/database.go @@ -99,6 +99,8 @@ type Trie interface { // in the trie with provided address. UpdateAccount(address common.Address, account *types.StateAccount, codeLen int) error + UpdateAccountAsync(address common.Address, accountResolver func() *types.StateAccount) error + // UpdateStorage associates key with value in the trie. If value has length zero, // any existing value is deleted from the trie. The value bytes must not be modified // by the caller while they are stored in the trie. If a node was not found in the diff --git a/core/state/statedb.go b/core/state/statedb.go index b770698255e..e51fcd2cfe4 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -577,6 +577,13 @@ func (s *StateDB) updateStateObject(obj *stateObject) { } } +// updateStateObject writes the given object to the trie. +func (s *StateDB) updateStateObjectAsync(addr common.Address, resolver func() *types.StateAccount) { + if err := s.trie.UpdateAccountAsync(addr, resolver); err != nil { + s.setError(fmt.Errorf("updateStateObject (%x) error: %v", addr, err)) + } +} + // deleteStateObject removes the given object from the state trie. func (s *StateDB) deleteStateObject(addr common.Address) { if err := s.trie.DeleteAccount(addr); err != nil { @@ -829,11 +836,14 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash { // later time. workers.SetLimit(1) } + + stateObjectsResolve := make(map[common.Address]func() *types.StateAccount) for addr, op := range s.mutations { if op.applied || op.isDelete() { continue } obj := s.stateObjects[addr] // closure for the task runner below + complete := make(chan *types.StateAccount) workers.Go(func() error { if s.db.TrieDB().IsVerkle() { obj.updateTrie() @@ -846,8 +856,13 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash { s.witness.AddState(obj.trie.Witness()) } } + complete <- &obj.data return nil }) + + stateObjectsResolve[addr] = func() *types.StateAccount { + return <-complete + } } // If witness building is enabled, gather all the read-only accesses. // Skip witness collection in Verkle mode, they will be gathered @@ -898,7 +913,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 @@ -939,7 +953,11 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash { if op.isDelete() { deletedAddrs = append(deletedAddrs, addr) } else { - s.updateStateObject(s.stateObjects[addr]) + if s.db.TrieDB().IsVerkle() { + s.updateStateObject(s.stateObjects[addr]) + } else { + s.updateStateObjectAsync(addr, stateObjectsResolve[addr]) + } s.AccountUpdated += 1 } usedAddrs = append(usedAddrs, addr) // Copy needed for closure @@ -966,6 +984,7 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash { s.witnessStats.Add(witness, common.Hash{}) } } + return hash } diff --git a/trie/committer.go b/trie/committer.go index 2a2142e0ffa..46ed7179772 100644 --- a/trie/committer.go +++ b/trie/committer.go @@ -157,8 +157,8 @@ func (c *committer) store(path []byte, n node) node { // length of leaves should be exactly same. if c.collectLeaf { if sn, ok := n.(*shortNode); ok { - if val, ok := sn.Val.(valueNode); ok { - c.nodes.AddLeaf(nhash, val) + if val, ok := sn.Val.(*valueNode); ok { + c.nodes.AddLeaf(nhash, val.resolve()) } } } @@ -182,7 +182,7 @@ func forGatherChildren(n node, onChild func(hash common.Hash)) { } case hashNode: onChild(common.BytesToHash(n)) - case valueNode, nil: + case *valueNode, nil: default: panic(fmt.Sprintf("unknown node type: %T", n)) } diff --git a/trie/hasher.go b/trie/hasher.go index a2a1f5b662c..be9ba4c4c88 100644 --- a/trie/hasher.go +++ b/trie/hasher.go @@ -110,7 +110,7 @@ func (h *hasher) encodeShortNode(n *shortNode) []byte { if hasTerm(n.Key) { var ln leafNodeEncoder ln.Key = hexToCompact(n.Key) - ln.Val = n.Val.(valueNode) + ln.Val = n.Val.(*valueNode).resolve() ln.encode(h.encbuf) return h.encodedBytes() } @@ -162,7 +162,7 @@ func (h *hasher) encodeFullNode(n *fullNode) []byte { } } if n.Children[16] != nil { - fn.Children[16] = n.Children[16].(valueNode) + fn.Children[16] = n.Children[16].(*valueNode).resolve() } fn.encode(h.encbuf) fnEncoderPool.Put(fn) diff --git a/trie/iterator.go b/trie/iterator.go index 3d3191ffba8..e45a7c07b4d 100644 --- a/trie/iterator.go +++ b/trie/iterator.go @@ -215,7 +215,7 @@ func (it *nodeIterator) Leaf() bool { func (it *nodeIterator) LeafKey() []byte { if len(it.stack) > 0 { - if _, ok := it.stack[len(it.stack)-1].node.(valueNode); ok { + if _, ok := it.stack[len(it.stack)-1].node.(*valueNode); ok { return hexToKeybytes(it.path) } } @@ -224,8 +224,8 @@ func (it *nodeIterator) LeafKey() []byte { func (it *nodeIterator) LeafBlob() []byte { if len(it.stack) > 0 { - if node, ok := it.stack[len(it.stack)-1].node.(valueNode); ok { - return node + if node, ok := it.stack[len(it.stack)-1].node.(*valueNode); ok { + return node.resolve() } } panic("not at leaf") @@ -233,7 +233,7 @@ func (it *nodeIterator) LeafBlob() []byte { func (it *nodeIterator) LeafProof() [][]byte { if len(it.stack) > 0 { - if _, ok := it.stack[len(it.stack)-1].node.(valueNode); ok { + if _, ok := it.stack[len(it.stack)-1].node.(*valueNode); ok { hasher := newHasher(false) defer returnHasherToPool(hasher) proofs := make([][]byte, 0, len(it.stack)) diff --git a/trie/node.go b/trie/node.go index 74fac4fd4ea..bb4c8facd8b 100644 --- a/trie/node.go +++ b/trie/node.go @@ -44,7 +44,10 @@ type ( flags nodeFlag } hashNode []byte - valueNode []byte + valueNode struct { + resolver func() []byte + val []byte + } // fullnodeEncoder is a type used exclusively for encoding fullNode. // Briefly instantiating a fullnodeEncoder and initializing with @@ -68,6 +71,19 @@ type ( } ) +func newValueNode(resolver func() []byte) *valueNode { + return &valueNode{ + resolver: resolver, + } +} + +func (v *valueNode) resolve() []byte { + if v.val == nil { + v.val = v.resolver() + } + return v.val +} + // EncodeRLP encodes a full node into the consensus RLP format. func (n *fullNode) EncodeRLP(w io.Writer) error { eb := rlp.NewEncoderBuffer(w) @@ -91,13 +107,13 @@ func (n nodeFlag) copy() nodeFlag { func (n *fullNode) cache() (hashNode, bool) { return n.flags.hash, n.flags.dirty } func (n *shortNode) cache() (hashNode, bool) { return n.flags.hash, n.flags.dirty } func (n hashNode) cache() (hashNode, bool) { return nil, true } -func (n valueNode) cache() (hashNode, bool) { return nil, true } +func (n *valueNode) cache() (hashNode, bool) { return nil, true } // Pretty printing. func (n *fullNode) String() string { return n.fstring("") } func (n *shortNode) String() string { return n.fstring("") } func (n hashNode) String() string { return n.fstring("") } -func (n valueNode) String() string { return n.fstring("") } +func (n *valueNode) String() string { return n.fstring("") } func (n *fullNode) fstring(ind string) string { resp := fmt.Sprintf("[\n%s ", ind) @@ -117,8 +133,8 @@ func (n *shortNode) fstring(ind string) string { func (n hashNode) fstring(ind string) string { return fmt.Sprintf("<%x> ", []byte(n)) } -func (n valueNode) fstring(ind string) string { - return fmt.Sprintf("%x ", []byte(n)) +func (n *valueNode) fstring(ind string) string { + return fmt.Sprintf("%x ", n.resolve()) } // mustDecodeNode is a wrapper of decodeNode and panic if any error is encountered. @@ -185,7 +201,7 @@ func decodeShort(hash, elems []byte) (node, error) { if err != nil { return nil, fmt.Errorf("invalid value node: %v", err) } - return &shortNode{key, valueNode(val), flag}, nil + return &shortNode{key, newValueNode(func() []byte { return val }), flag}, nil } r, _, err := decodeRef(rest) if err != nil { @@ -208,7 +224,7 @@ func decodeFull(hash, elems []byte) (*fullNode, error) { return n, err } if len(val) > 0 { - n.Children[16] = valueNode(val) + n.Children[16] = newValueNode(func() []byte { return val }) } return n, nil } diff --git a/trie/node_enc.go b/trie/node_enc.go index 02b93ee6f3e..719bd987474 100644 --- a/trie/node_enc.go +++ b/trie/node_enc.go @@ -101,6 +101,6 @@ func (n hashNode) encode(w rlp.EncoderBuffer) { w.WriteBytes(n) } -func (n valueNode) encode(w rlp.EncoderBuffer) { - w.WriteBytes(n) +func (n *valueNode) encode(w rlp.EncoderBuffer) { + w.WriteBytes(n.resolve()) } diff --git a/trie/proof.go b/trie/proof.go index 1a06ed5d5e3..1e6f1e777be 100644 --- a/trie/proof.go +++ b/trie/proof.go @@ -128,8 +128,8 @@ func VerifyProof(rootHash common.Hash, key []byte, proofDb ethdb.KeyValueReader) case hashNode: key = keyrest copy(wantHash[:], cld) - case valueNode: - return cld, nil + case *valueNode: + return cld.resolve(), nil } } } @@ -191,8 +191,8 @@ func proofToPath(rootHash common.Hash, root node, key []byte, proofDb ethdb.KeyV if err != nil { return nil, nil, err } - case valueNode: - valnode = cld + case *valueNode: + valnode = cld.resolve() } // Link the parent and child. switch pnode := parent.(type) { @@ -298,7 +298,7 @@ findFork: } // Only one proof points to non-existent key. if shortForkRight != 0 { - if _, ok := rn.Val.(valueNode); ok { + if _, ok := rn.Val.(*valueNode); ok { // The fork point is root node, unset the entire trie if parent == nil { return true, nil @@ -309,7 +309,7 @@ findFork: return false, unset(rn, rn.Val, left[pos:], len(rn.Key), false) } if shortForkLeft != 0 { - if _, ok := rn.Val.(valueNode); ok { + if _, ok := rn.Val.(*valueNode); ok { // The fork point is root node, unset the entire trie if parent == nil { return true, nil @@ -396,7 +396,7 @@ func unset(parent node, child node, key []byte, pos int, removeLeft bool) error } return nil } - if _, ok := cld.Val.(valueNode); ok { + if _, ok := cld.Val.(*valueNode); ok { fn := parent.(*fullNode) fn.Children[key[pos-1]] = nil return nil @@ -432,7 +432,7 @@ func hasRightElement(node node, key []byte) bool { return bytes.Compare(rn.Key, key[pos:]) > 0 } node, pos = rn.Val, pos+len(rn.Key) - case valueNode: + case *valueNode: return false // We have resolved the whole path default: panic(fmt.Sprintf("%T: invalid node: %v", node, node)) // hashnode @@ -612,7 +612,7 @@ func get(tn node, key []byte, skipResolved bool) ([]byte, node) { return key, n case nil: return key, nil - case valueNode: + case *valueNode: return nil, n default: panic(fmt.Sprintf("%T: invalid node: %v", tn, tn)) diff --git a/trie/secure_trie.go b/trie/secure_trie.go index 7c7bd184bf8..b48d28199bd 100644 --- a/trie/secure_trie.go +++ b/trie/secure_trie.go @@ -226,6 +226,25 @@ func (t *StateTrie) UpdateAccount(address common.Address, acc *types.StateAccoun return nil } +func (t *StateTrie) UpdateAccountAsync(address common.Address, accountResolve func() *types.StateAccount) error { + hk := crypto.Keccak256(address.Bytes()) + resolve := func() []byte { + acc := accountResolve() + data, err := rlp.EncodeToBytes(acc) + if err != nil { + panic(err) // TODO: what do do here? + } + return data + } + if err := t.trie.UpdateAsync(hk, resolve); err != nil { + return err + } + if t.preimages != nil { + t.secKeyCache[common.Hash(hk)] = address.Bytes() + } + return nil +} + func (t *StateTrie) UpdateContractCode(_ common.Address, _ common.Hash, _ []byte) error { return nil } diff --git a/trie/sync.go b/trie/sync.go index 404d67f154a..ed4f8d7bc46 100644 --- a/trie/sync.go +++ b/trie/sync.go @@ -612,7 +612,7 @@ func (s *Sync) children(req *nodeRequest, object node) ([]*nodeRequest, error) { for _, child := range children { // Notify any external watcher of a new key/value node if req.callback != nil { - if node, ok := (child.node).(valueNode); ok { + if node, ok := (child.node).(*valueNode); ok { var paths [][]byte if len(child.path) == 2*common.HashLength { paths = append(paths, hexToKeybytes(child.path)) @@ -620,7 +620,7 @@ func (s *Sync) children(req *nodeRequest, object node) ([]*nodeRequest, error) { paths = append(paths, hexToKeybytes(child.path[:2*common.HashLength])) paths = append(paths, hexToKeybytes(child.path[2*common.HashLength:])) } - if err := req.callback(paths, child.path, node, req.hash, req.path); err != nil { + if err := req.callback(paths, child.path, node.resolve(), req.hash, req.path); err != nil { return nil, err } } diff --git a/trie/transition.go b/trie/transition.go index c6eecd39376..573125b8331 100644 --- a/trie/transition.go +++ b/trie/transition.go @@ -17,6 +17,7 @@ package trie import ( + "errors" "fmt" "github.com/ethereum/go-ethereum/common" @@ -138,6 +139,9 @@ func (t *TransitionTrie) UpdateAccount(addr common.Address, account *types.State // only needs to know what the account trie does now. return t.overlay.UpdateAccount(addr, account, codeLen) } +func (t *TransitionTrie) UpdateAccountAsync(address common.Address, accountResolver func() *types.StateAccount) error { + return errors.New("not implemented") +} // DeleteStorage removes any existing value for key from the trie. If a node was not // found in the database, a trie.MissingNodeError is returned. diff --git a/trie/trie.go b/trie/trie.go index 1ef2c2f1a66..ec84a038da6 100644 --- a/trie/trie.go +++ b/trie/trie.go @@ -193,8 +193,8 @@ func (t *Trie) get(origNode node, key []byte, pos int) (value []byte, newnode no switch n := (origNode).(type) { case nil: return nil, nil, false, nil - case valueNode: - return n, n, false, nil + case *valueNode: + return n.resolve(), n, false, nil case *shortNode: if !bytes.HasPrefix(key[pos:], n.Key) { // key not found in trie @@ -322,7 +322,7 @@ func (t *Trie) getNode(origNode node, path []byte, pos int) (item []byte, newnod } // Path still needs to be traversed, descend into children switch n := (origNode).(type) { - case valueNode: + case *valueNode: // Path prematurely ended, abort return nil, nil, 0, nil @@ -382,12 +382,26 @@ func (t *Trie) Update(key, value []byte) error { return t.update(key, value) } +func (t *Trie) UpdateAsync(key []byte, valueResolver func() []byte) error { + t.unhashed++ + t.uncommitted++ + k := keybytesToHex(key) + + // NOTE: this does not support deletions (the length of the value is not known until it is resolved) + _, n, err := t.insert(t.root, nil, k, newValueNode(valueResolver)) + if err != nil { + return err + } + t.root = n + return nil +} + func (t *Trie) update(key, value []byte) error { t.unhashed++ t.uncommitted++ k := keybytesToHex(key) if len(value) != 0 { - _, n, err := t.insert(t.root, nil, k, valueNode(value)) + _, n, err := t.insert(t.root, nil, k, newValueNode(func() []byte { return value })) if err != nil { return err } @@ -404,9 +418,6 @@ func (t *Trie) update(key, value []byte) error { func (t *Trie) insert(n node, prefix, key []byte, value node) (bool, node, error) { if len(key) == 0 { - if v, ok := n.(valueNode); ok { - return !bytes.Equal(v, value.(valueNode)), value, nil - } return true, value, nil } switch n := n.(type) { @@ -616,7 +627,7 @@ func (t *Trie) delete(n node, prefix, key []byte) (bool, node, error) { // n still contains at least two values and cannot be reduced. return true, n, nil - case valueNode: + case *valueNode: return true, nil, nil case nil: @@ -646,8 +657,8 @@ func copyNode(n node) node { switch n := (n).(type) { case nil: return nil - case valueNode: - return valueNode(common.CopyBytes(n)) + case *valueNode: + return newValueNode(func() []byte { return common.CopyBytes(n.resolve()) }) case *shortNode: return &shortNode{ diff --git a/trie/verkle.go b/trie/verkle.go index 186ac1f642b..f36f436b4a0 100644 --- a/trie/verkle.go +++ b/trie/verkle.go @@ -177,6 +177,10 @@ func (t *VerkleTrie) UpdateAccount(addr common.Address, acc *types.StateAccount, return nil } +func (t *VerkleTrie) UpdateAccountAsync(address common.Address, accountResolver func() *types.StateAccount) error { + return errors.New("not implemented") +} + // UpdateStorage implements state.Trie, writing the provided storage slot into // the tree. If the tree is corrupted, an error will be returned. func (t *VerkleTrie) UpdateStorage(address common.Address, key, value []byte) error { From 5b4d7cc2e2917b88c6b4c4cd24e64569fb22415f Mon Sep 17 00:00:00 2001 From: Jared Wasinger Date: Mon, 27 Oct 2025 14:07:14 +0800 Subject: [PATCH 2/7] fixups --- core/state/statedb.go | 3 ++- tests/testdata | 2 +- trie/secure_trie.go | 3 +++ trie/trie.go | 2 ++ 4 files changed, 8 insertions(+), 2 deletions(-) diff --git a/core/state/statedb.go b/core/state/statedb.go index e51fcd2cfe4..abf0a02a873 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -577,7 +577,8 @@ func (s *StateDB) updateStateObject(obj *stateObject) { } } -// updateStateObject writes the given object to the trie. +// updateStateObject writes the given object to the trie. The actual value is +// only resolved from the provided function when it is needed during trie hashing. func (s *StateDB) updateStateObjectAsync(addr common.Address, resolver func() *types.StateAccount) { if err := s.trie.UpdateAccountAsync(addr, resolver); err != nil { s.setError(fmt.Errorf("updateStateObject (%x) error: %v", addr, err)) diff --git a/tests/testdata b/tests/testdata index 81862e48485..c67e485ff8b 160000 --- a/tests/testdata +++ b/tests/testdata @@ -1 +1 @@ -Subproject commit 81862e4848585a438d64f911a19b3825f0f4cd95 +Subproject commit c67e485ff8b5be9abc8ad15345ec21aa22e290d9 diff --git a/trie/secure_trie.go b/trie/secure_trie.go index b48d28199bd..4c8a181acfc 100644 --- a/trie/secure_trie.go +++ b/trie/secure_trie.go @@ -226,6 +226,9 @@ func (t *StateTrie) UpdateAccount(address common.Address, acc *types.StateAccoun return nil } +// UpdateAccountAsync will abstract the write of an account to the secure trie. +// The actual value of the account is not resolved from the passed function until +// it is needed when hashing the trie. func (t *StateTrie) UpdateAccountAsync(address common.Address, accountResolve func() *types.StateAccount) error { hk := crypto.Keccak256(address.Bytes()) resolve := func() []byte { diff --git a/trie/trie.go b/trie/trie.go index ec84a038da6..eb64d1f7c96 100644 --- a/trie/trie.go +++ b/trie/trie.go @@ -382,6 +382,8 @@ func (t *Trie) Update(key, value []byte) error { return t.update(key, value) } +// UpdateAsync associates a key with value in the trie. The actual value is +// not resolved until needed (by calling Get, or Hash). func (t *Trie) UpdateAsync(key []byte, valueResolver func() []byte) error { t.unhashed++ t.uncommitted++ From 263f79ee67f8b634c77f7f4bd6e7e32ca976e0a7 Mon Sep 17 00:00:00 2001 From: Jared Wasinger Date: Mon, 27 Oct 2025 14:10:40 +0800 Subject: [PATCH 3/7] missing documentation for func --- core/state/database.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/core/state/database.go b/core/state/database.go index d02219f28e2..fd30aca32fd 100644 --- a/core/state/database.go +++ b/core/state/database.go @@ -99,6 +99,9 @@ type Trie interface { // in the trie with provided address. UpdateAccount(address common.Address, account *types.StateAccount, codeLen int) error + // UpdateAccountAsync will abstract the write of an account to the secure trie. + // The actual value of the account is not resolved from the passed function until + // it is needed when hashing the trie. UpdateAccountAsync(address common.Address, accountResolver func() *types.StateAccount) error // UpdateStorage associates key with value in the trie. If value has length zero, From 27aef146f58bd1650857dea3475c6f858ce37cdc Mon Sep 17 00:00:00 2001 From: Jared Wasinger Date: Mon, 27 Oct 2025 14:22:39 +0800 Subject: [PATCH 4/7] remove unintended change to tests submodule --- tests/testdata | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/testdata b/tests/testdata index c67e485ff8b..81862e48485 160000 --- a/tests/testdata +++ b/tests/testdata @@ -1 +1 @@ -Subproject commit c67e485ff8b5be9abc8ad15345ec21aa22e290d9 +Subproject commit 81862e4848585a438d64f911a19b3825f0f4cd95 From 2fc29f61880fd3165e8d7091c2b8b118b90a9512 Mon Sep 17 00:00:00 2001 From: Jared Wasinger Date: Mon, 27 Oct 2025 14:52:40 +0800 Subject: [PATCH 5/7] fix lint --- trie/node.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/trie/node.go b/trie/node.go index bb4c8facd8b..60fe034c636 100644 --- a/trie/node.go +++ b/trie/node.go @@ -77,11 +77,11 @@ func newValueNode(resolver func() []byte) *valueNode { } } -func (v *valueNode) resolve() []byte { - if v.val == nil { - v.val = v.resolver() +func (n *valueNode) resolve() []byte { + if n.val == nil { + n.val = n.resolver() } - return v.val + return n.val } // EncodeRLP encodes a full node into the consensus RLP format. From e805c3978e59e0e5f3dea766805c4899a2ed12a8 Mon Sep 17 00:00:00 2001 From: Jared Wasinger Date: Mon, 27 Oct 2025 17:06:12 +0800 Subject: [PATCH 6/7] fix verkle --- core/state/database.go | 2 +- core/state/statedb.go | 24 +++++++++++++----------- trie/secure_trie.go | 4 ++-- trie/transition.go | 6 +++--- trie/verkle.go | 5 +++-- 5 files changed, 22 insertions(+), 19 deletions(-) diff --git a/core/state/database.go b/core/state/database.go index fd30aca32fd..82bb877f226 100644 --- a/core/state/database.go +++ b/core/state/database.go @@ -102,7 +102,7 @@ type Trie interface { // UpdateAccountAsync will abstract the write of an account to the secure trie. // The actual value of the account is not resolved from the passed function until // it is needed when hashing the trie. - UpdateAccountAsync(address common.Address, accountResolver func() *types.StateAccount) error + UpdateAccountAsync(address common.Address, accountResolver func() (*types.StateAccount, int)) error // UpdateStorage associates key with value in the trie. If value has length zero, // any existing value is deleted from the trie. The value bytes must not be modified diff --git a/core/state/statedb.go b/core/state/statedb.go index abf0a02a873..1bc6265528f 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -579,7 +579,7 @@ func (s *StateDB) updateStateObject(obj *stateObject) { // updateStateObject writes the given object to the trie. The actual value is // only resolved from the provided function when it is needed during trie hashing. -func (s *StateDB) updateStateObjectAsync(addr common.Address, resolver func() *types.StateAccount) { +func (s *StateDB) updateStateObjectAsync(addr common.Address, resolver func() (*types.StateAccount, int)) { if err := s.trie.UpdateAccountAsync(addr, resolver); err != nil { s.setError(fmt.Errorf("updateStateObject (%x) error: %v", addr, err)) } @@ -838,13 +838,17 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash { workers.SetLimit(1) } - stateObjectsResolve := make(map[common.Address]func() *types.StateAccount) + type stateAccountWithCodeLen struct { + *types.StateAccount + codeLen int + } + stateObjectsResolve := make(map[common.Address]func() (*types.StateAccount, int)) for addr, op := range s.mutations { if op.applied || op.isDelete() { continue } obj := s.stateObjects[addr] // closure for the task runner below - complete := make(chan *types.StateAccount) + complete := make(chan stateAccountWithCodeLen) workers.Go(func() error { if s.db.TrieDB().IsVerkle() { obj.updateTrie() @@ -857,12 +861,13 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash { s.witness.AddState(obj.trie.Witness()) } } - complete <- &obj.data + complete <- stateAccountWithCodeLen{&obj.data, 0} return nil }) - stateObjectsResolve[addr] = func() *types.StateAccount { - return <-complete + stateObjectsResolve[addr] = func() (*types.StateAccount, int) { + res := <-complete + return res.StateAccount, res.codeLen } } // If witness building is enabled, gather all the read-only accesses. @@ -914,6 +919,7 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash { } } } + s.StorageUpdates += time.Since(start) // Now we're about to start to write changes to the trie. The trie is so far @@ -954,11 +960,7 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash { if op.isDelete() { deletedAddrs = append(deletedAddrs, addr) } else { - if s.db.TrieDB().IsVerkle() { - s.updateStateObject(s.stateObjects[addr]) - } else { - s.updateStateObjectAsync(addr, stateObjectsResolve[addr]) - } + s.updateStateObjectAsync(addr, stateObjectsResolve[addr]) s.AccountUpdated += 1 } usedAddrs = append(usedAddrs, addr) // Copy needed for closure diff --git a/trie/secure_trie.go b/trie/secure_trie.go index 4c8a181acfc..461b163a1c4 100644 --- a/trie/secure_trie.go +++ b/trie/secure_trie.go @@ -229,10 +229,10 @@ func (t *StateTrie) UpdateAccount(address common.Address, acc *types.StateAccoun // UpdateAccountAsync will abstract the write of an account to the secure trie. // The actual value of the account is not resolved from the passed function until // it is needed when hashing the trie. -func (t *StateTrie) UpdateAccountAsync(address common.Address, accountResolve func() *types.StateAccount) error { +func (t *StateTrie) UpdateAccountAsync(address common.Address, accountResolve func() (*types.StateAccount, int)) error { hk := crypto.Keccak256(address.Bytes()) resolve := func() []byte { - acc := accountResolve() + acc, _ := accountResolve() data, err := rlp.EncodeToBytes(acc) if err != nil { panic(err) // TODO: what do do here? diff --git a/trie/transition.go b/trie/transition.go index 573125b8331..b53682add55 100644 --- a/trie/transition.go +++ b/trie/transition.go @@ -17,7 +17,6 @@ package trie import ( - "errors" "fmt" "github.com/ethereum/go-ethereum/common" @@ -139,8 +138,9 @@ func (t *TransitionTrie) UpdateAccount(addr common.Address, account *types.State // only needs to know what the account trie does now. return t.overlay.UpdateAccount(addr, account, codeLen) } -func (t *TransitionTrie) UpdateAccountAsync(address common.Address, accountResolver func() *types.StateAccount) error { - return errors.New("not implemented") +func (t *TransitionTrie) UpdateAccountAsync(address common.Address, accountResolver func() (*types.StateAccount, int)) error { + acct, codeLen := accountResolver() + return t.overlay.UpdateAccount(address, acct, codeLen) } // DeleteStorage removes any existing value for key from the trie. If a node was not diff --git a/trie/verkle.go b/trie/verkle.go index f36f436b4a0..caf206f2981 100644 --- a/trie/verkle.go +++ b/trie/verkle.go @@ -177,8 +177,9 @@ func (t *VerkleTrie) UpdateAccount(addr common.Address, acc *types.StateAccount, return nil } -func (t *VerkleTrie) UpdateAccountAsync(address common.Address, accountResolver func() *types.StateAccount) error { - return errors.New("not implemented") +func (t *VerkleTrie) UpdateAccountAsync(address common.Address, accountResolver func() (*types.StateAccount, int)) error { + acct, codeSize := accountResolver() + return t.UpdateAccount(address, acct, codeSize) } // UpdateStorage implements state.Trie, writing the provided storage slot into From 7275aa8f617f41248af0f79eefab8cf0a154d62d Mon Sep 17 00:00:00 2001 From: Jared Wasinger Date: Mon, 27 Oct 2025 17:48:30 +0800 Subject: [PATCH 7/7] fix lint --- core/state/statedb.go | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/core/state/statedb.go b/core/state/statedb.go index 1bc6265528f..086ab289c94 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -566,20 +566,9 @@ func (s *StateDB) GetTransientState(addr common.Address, key common.Hash) common // Setting, updating & deleting state object methods. // -// updateStateObject writes the given object to the trie. -func (s *StateDB) updateStateObject(obj *stateObject) { - // Encode the account and update the account trie - if err := s.trie.UpdateAccount(obj.Address(), &obj.data, len(obj.code)); err != nil { - s.setError(fmt.Errorf("updateStateObject (%x) error: %v", obj.Address(), err)) - } - if obj.dirtyCode { - s.trie.UpdateContractCode(obj.Address(), common.BytesToHash(obj.CodeHash()), obj.code) - } -} - // updateStateObject writes the given object to the trie. The actual value is // only resolved from the provided function when it is needed during trie hashing. -func (s *StateDB) updateStateObjectAsync(addr common.Address, resolver func() (*types.StateAccount, int)) { +func (s *StateDB) updateStateObject(addr common.Address, resolver func() (*types.StateAccount, int)) { if err := s.trie.UpdateAccountAsync(addr, resolver); err != nil { s.setError(fmt.Errorf("updateStateObject (%x) error: %v", addr, err)) } @@ -960,7 +949,7 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash { if op.isDelete() { deletedAddrs = append(deletedAddrs, addr) } else { - s.updateStateObjectAsync(addr, stateObjectsResolve[addr]) + s.updateStateObject(addr, stateObjectsResolve[addr]) s.AccountUpdated += 1 } usedAddrs = append(usedAddrs, addr) // Copy needed for closure