diff --git a/core/state/database.go b/core/state/database.go index 58d0ccfe829..82bb877f226 100644 --- a/core/state/database.go +++ b/core/state/database.go @@ -99,6 +99,11 @@ 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, 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 // 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..086ab289c94 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -566,14 +566,11 @@ 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) 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)) } } @@ -829,11 +826,18 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash { // later time. workers.SetLimit(1) } + + 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 stateAccountWithCodeLen) workers.Go(func() error { if s.db.TrieDB().IsVerkle() { obj.updateTrie() @@ -846,8 +850,14 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash { s.witness.AddState(obj.trie.Witness()) } } + complete <- stateAccountWithCodeLen{&obj.data, 0} return nil }) + + stateObjectsResolve[addr] = func() (*types.StateAccount, int) { + res := <-complete + return res.StateAccount, res.codeLen + } } // If witness building is enabled, gather all the read-only accesses. // Skip witness collection in Verkle mode, they will be gathered @@ -898,7 +908,7 @@ 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 +949,7 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash { if op.isDelete() { deletedAddrs = append(deletedAddrs, addr) } else { - s.updateStateObject(s.stateObjects[addr]) + s.updateStateObject(addr, stateObjectsResolve[addr]) s.AccountUpdated += 1 } usedAddrs = append(usedAddrs, addr) // Copy needed for closure @@ -966,6 +976,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..60fe034c636 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 (n *valueNode) resolve() []byte { + if n.val == nil { + n.val = n.resolver() + } + return n.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..461b163a1c4 100644 --- a/trie/secure_trie.go +++ b/trie/secure_trie.go @@ -226,6 +226,28 @@ 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, int)) 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..b53682add55 100644 --- a/trie/transition.go +++ b/trie/transition.go @@ -138,6 +138,10 @@ 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, 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 // found in the database, a trie.MissingNodeError is returned. diff --git a/trie/trie.go b/trie/trie.go index 1ef2c2f1a66..eb64d1f7c96 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,28 @@ 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++ + 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 +420,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 +629,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 +659,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..caf206f2981 100644 --- a/trie/verkle.go +++ b/trie/verkle.go @@ -177,6 +177,11 @@ func (t *VerkleTrie) UpdateAccount(addr common.Address, acc *types.StateAccount, return nil } +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 // the tree. If the tree is corrupted, an error will be returned. func (t *VerkleTrie) UpdateStorage(address common.Address, key, value []byte) error {