diff --git a/trie/committer.go b/trie/committer.go index 805d4a3143..af5f5f1a17 100644 --- a/trie/committer.go +++ b/trie/committer.go @@ -20,7 +20,6 @@ import ( "fmt" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/trie/trienode" ) // leaf represents a trie leaf node @@ -34,15 +33,13 @@ type leaf struct { // insertion order. type committer struct { nodes *NodeSet - tracer *tracer collectLeaf bool } // newCommitter creates a new committer or picks one from the pool. -func newCommitter(nodeset *NodeSet, tracer *tracer, collectLeaf bool) *committer { +func newCommitter(nodeset *NodeSet, collectLeaf bool) *committer { return &committer{ nodes: nodeset, - tracer: tracer, collectLeaf: collectLeaf, } } @@ -137,22 +134,22 @@ func (c *committer) store(path []byte, n node) node { // The node is embedded in its parent, in other words, this node // will not be stored in the database independently, mark it as // deleted only if the node was existent in database before. - prev, ok := c.tracer.accessList[string(path)] - if ok { - c.nodes.addNode(path, trienode.NewWithPrev(common.Hash{}, nil, prev)) + if _, ok := c.nodes.accessList[string(path)]; ok { + c.nodes.markDeleted(path) } return n } - // Collect the dirty node to nodeset for return. + // We have the hash already, estimate the RLP encoding-size of the node. + // The size is used for mem tracking, does not need to be exact var ( nhash = common.BytesToHash(hash) - node = trienode.NewWithPrev( - nhash, - nodeToBytes(n), - c.tracer.accessList[string(path)], - ) + mnode = &memoryNode{ + hash: nhash, + node: nodeToBytes(n), + } ) - c.nodes.addNode(path, node) + // Collect the dirty node to nodeset for return. + c.nodes.markUpdated(path, mnode) // Collect the corresponding leaf node if it's required. We don't check // full node since it's impossible to store value in fullNode. The key diff --git a/trie/database.go b/trie/database.go index c105c730a2..c1f7ddd756 100644 --- a/trie/database.go +++ b/trie/database.go @@ -31,7 +31,6 @@ import ( "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/rlp" - "github.com/ethereum/go-ethereum/trie/trienode" ) var ( @@ -608,11 +607,11 @@ func (db *Database) Update(nodes *MergedNodeSet) error { } for _, owner := range order { subset := nodes.sets[owner] - subset.forEachWithOrder(func(path string, n *trienode.Node) { - if n.IsDeleted() { + subset.forEachWithOrder(func(path string, n *memoryNode) { + if n.isDeleted() { return // ignore deletion } - db.insert(n.Hash, n.Blob) + db.insert(n.hash, n.node) }) } // Link up the account trie and storage trie if the node points diff --git a/trie/nodeset.go b/trie/nodeset.go index 4ebbb3329c..fc2111375f 100644 --- a/trie/nodeset.go +++ b/trie/nodeset.go @@ -22,37 +22,89 @@ import ( "strings" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/trie/trienode" ) +// memoryNode is all the information we know about a single cached trie node +// in the memory. +type memoryNode struct { + hash common.Hash // Node hash by hashing node blob, empty for deleted nodes + node []byte // Encoded node blob, nil for deleted nodes +} + +// memorySize returns the total memory size used by this node. +// nolint:unused +func (n *memoryNode) memorySize(pathlen int) int { + return len(n.node) + common.HashLength + pathlen +} + +// rlp returns the raw rlp encoded blob of the cached trie node, either directly +// from the cache, or by regenerating it from the collapsed node. +// nolint:unused +func (n *memoryNode) rlp() []byte { + return n.node +} + +// obj returns the decoded and expanded trie node, either directly from the cache, +// or by regenerating it from the rlp encoded blob. +// nolint:unused +func (n *memoryNode) obj() node { + return mustDecodeNode(n.hash[:], n.node) +} + +// isDeleted returns the indicator if the node is marked as deleted. +func (n *memoryNode) isDeleted() bool { + return n.hash == (common.Hash{}) +} + +// nodeWithPrev wraps the memoryNode with the previous node value. +// nolint: unused +type nodeWithPrev struct { + *memoryNode + prev []byte // RLP-encoded previous value, nil means it's non-existent +} + +// unwrap returns the internal memoryNode object. +// nolint:unused +func (n *nodeWithPrev) unwrap() *memoryNode { + return n.memoryNode +} + +// memorySize returns the total memory size used by this node. It overloads +// the function in memoryNode by counting the size of previous value as well. +// nolint: unused +func (n *nodeWithPrev) memorySize(pathlen int) int { + return n.memoryNode.memorySize(pathlen) + len(n.prev) +} + // NodeSet contains all dirty nodes collected during the commit operation. // Each node is keyed by path. It's not thread-safe to use. type NodeSet struct { - owner common.Hash // the identifier of the trie - leaves []*leaf // the list of dirty leaves - updates int // the count of updated and inserted nodes - deletes int // the count of deleted nodes + owner common.Hash // the identifier of the trie + nodes map[string]*memoryNode // the set of dirty nodes(inserted, updated, deleted) + leaves []*leaf // the list of dirty leaves + updates int // the count of updated and inserted nodes + deletes int // the count of deleted nodes - // The set of all dirty nodes. Dirty nodes include newly inserted nodes, - // deleted nodes and updated nodes. The original value of the newly - // inserted node must be nil, and the original value of the other two - // types must be non-nil. - nodes map[string]*trienode.WithPrev + // The list of accessed nodes, which records the original node value. + // The origin value is expected to be nil for newly inserted node + // and is expected to be non-nil for other types(updated, deleted). + accessList map[string][]byte } // NewNodeSet initializes an empty node set to be used for tracking dirty nodes // from a specific account or storage trie. The owner is zero for the account // trie and the owning account address hash for storage tries. -func NewNodeSet(owner common.Hash) *NodeSet { +func NewNodeSet(owner common.Hash, accessList map[string][]byte) *NodeSet { return &NodeSet{ - owner: owner, - nodes: make(map[string]*trienode.WithPrev), + owner: owner, + nodes: make(map[string]*memoryNode), + accessList: accessList, } } // forEachWithOrder iterates the dirty nodes with the order from bottom to top, // right to left, nodes with the longest path will be iterated first. -func (set *NodeSet) forEachWithOrder(callback func(path string, n *trienode.Node)) { +func (set *NodeSet) forEachWithOrder(callback func(path string, n *memoryNode)) { var paths sort.StringSlice for path := range set.nodes { paths = append(paths, path) @@ -60,21 +112,23 @@ func (set *NodeSet) forEachWithOrder(callback func(path string, n *trienode.Node // Bottom-up, longest path first sort.Sort(sort.Reverse(paths)) for _, path := range paths { - callback(path, set.nodes[path].Unwrap()) + callback(path, set.nodes[path]) } } -// addNode adds the provided dirty node into set. -func (set *NodeSet) addNode(path []byte, n *trienode.WithPrev) { - if n.IsDeleted() { - set.deletes += 1 - } else { - set.updates += 1 - } - set.nodes[string(path)] = n +// markUpdated marks the node as dirty(newly-inserted or updated). +func (set *NodeSet) markUpdated(path []byte, node *memoryNode) { + set.nodes[string(path)] = node + set.updates += 1 } -// addLeaf adds the provided leaf node into set. +// markDeleted marks the node as deleted. +func (set *NodeSet) markDeleted(path []byte) { + set.nodes[string(path)] = &memoryNode{} + set.deletes += 1 +} + +// addLeaf collects the provided leaf node into set. func (set *NodeSet) addLeaf(node *leaf) { set.leaves = append(set.leaves, node) } @@ -89,7 +143,7 @@ func (set *NodeSet) Size() (int, int) { func (set *NodeSet) Hashes() []common.Hash { var ret []common.Hash for _, node := range set.nodes { - ret = append(ret, node.Hash) + ret = append(ret, node.hash) } return ret } @@ -101,17 +155,18 @@ func (set *NodeSet) Summary() string { if set.nodes != nil { for path, n := range set.nodes { // Deletion - if n.IsDeleted() { - fmt.Fprintf(out, " [-]: %x prev: %x\n", path, n.Prev) + if n.isDeleted() { + fmt.Fprintf(out, " [-]: %x prev: %x\n", path, set.accessList[path]) continue } // Insertion - if len(n.Prev) == 0 { - fmt.Fprintf(out, " [+]: %x -> %v\n", path, n.Hash) + origin, ok := set.accessList[path] + if !ok { + fmt.Fprintf(out, " [+]: %x -> %v\n", path, n.hash) continue } // Update - fmt.Fprintf(out, " [*]: %x -> %v prev: %x\n", path, n.Hash, n.Prev) + fmt.Fprintf(out, " [*]: %x -> %v prev: %x\n", path, n.hash, origin) } } for _, n := range set.leaves { diff --git a/trie/tracer.go b/trie/tracer.go index 04b2f12bf6..a27e371c7a 100644 --- a/trie/tracer.go +++ b/trie/tracer.go @@ -16,10 +16,7 @@ package trie -import ( - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/trie/trienode" -) +import "github.com/ethereum/go-ethereum/common" // tracer tracks the changes of trie nodes. During the trie operations, // some nodes can be deleted from the trie, while these deleted nodes @@ -120,10 +117,9 @@ func (t *tracer) markDeletions(set *NodeSet) { // It's possible a few deleted nodes were embedded // in their parent before, the deletions can be no // effect by deleting nothing, filter them out. - prev, ok := t.accessList[path] - if !ok { + if _, ok := set.accessList[path]; !ok { continue } - set.addNode([]byte(path), trienode.NewWithPrev(common.Hash{}, nil, prev)) + set.markDeleted([]byte(path)) } } diff --git a/trie/trie.go b/trie/trie.go index 14685c3df8..18504dc5bc 100644 --- a/trie/trie.go +++ b/trie/trie.go @@ -574,7 +574,7 @@ func (t *Trie) Hash() common.Hash { func (t *Trie) Commit(collectLeaf bool) (common.Hash, *NodeSet) { defer t.tracer.reset() - nodes := NewNodeSet(t.owner) + nodes := NewNodeSet(t.owner, t.tracer.accessList) t.tracer.markDeletions(nodes) // Trie is empty and can be classified into two types of situations: @@ -595,7 +595,7 @@ func (t *Trie) Commit(collectLeaf bool) (common.Hash, *NodeSet) { t.root = hashedNode return rootHash, nil } - t.root = newCommitter(nodes, t.tracer, collectLeaf).Commit(t.root) + t.root = newCommitter(nodes, collectLeaf).Commit(t.root) return rootHash, nodes } diff --git a/trie/trie_test.go b/trie/trie_test.go index a03a68283d..82ead8b441 100644 --- a/trie/trie_test.go +++ b/trie/trie_test.go @@ -408,36 +408,39 @@ func verifyAccessList(old *Trie, new *Trie, set *NodeSet) error { // Check insertion set for path := range inserts { n, ok := set.nodes[path] - if !ok || n.IsDeleted() { + if !ok || n.isDeleted() { return errors.New("expect new node") } - if len(n.Prev) > 0 { + _, ok = set.accessList[path] + if ok { return errors.New("unexpected origin value") } } // Check deletion set for path, blob := range deletes { n, ok := set.nodes[path] - if !ok || !n.IsDeleted() { + if !ok || !n.isDeleted() { return errors.New("expect deleted node") } - if len(n.Prev) == 0 { + v, ok := set.accessList[path] + if !ok { return errors.New("expect origin value") } - if !bytes.Equal(n.Prev, blob) { + if !bytes.Equal(v, blob) { return errors.New("invalid origin value") } } // Check update set for path, blob := range updates { n, ok := set.nodes[path] - if !ok || n.IsDeleted() { + if !ok || n.isDeleted() { return errors.New("expect updated node") } - if len(n.Prev) == 0 { + v, ok := set.accessList[path] + if !ok { return errors.New("expect origin value") } - if !bytes.Equal(n.Prev, blob) { + if !bytes.Equal(v, blob) { return errors.New("invalid origin value") } } diff --git a/trie/trienode/node.go b/trie/trienode/node.go deleted file mode 100644 index 5ebfea8896..0000000000 --- a/trie/trienode/node.go +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright 2023 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see - -package trienode - -import "github.com/ethereum/go-ethereum/common" - -// Node is a wrapper which contains the encoded blob of the trie node and its -// unique hash identifier. It is general enough that can be used to represent -// trie nodes corresponding to different trie implementations. -type Node struct { - Hash common.Hash // Node hash, empty for deleted node - Blob []byte // Encoded node blob, nil for the deleted node -} - -// Size returns the total memory size used by this node. -func (n *Node) Size() int { - return len(n.Blob) + common.HashLength -} - -// IsDeleted returns the indicator if the node is marked as deleted. -func (n *Node) IsDeleted() bool { - return n.Hash == (common.Hash{}) -} - -// WithPrev wraps the Node with the previous node value attached. -type WithPrev struct { - *Node - Prev []byte // Encoded original value, nil means it's non-existent -} - -// Unwrap returns the internal Node object. -func (n *WithPrev) Unwrap() *Node { - return n.Node -} - -// Size returns the total memory size used by this node. It overloads -// the function in Node by counting the size of previous value as well. -func (n *WithPrev) Size() int { - return n.Node.Size() + len(n.Prev) -} - -// New constructs a node with provided node information. -func New(hash common.Hash, blob []byte) *Node { - return &Node{Hash: hash, Blob: blob} -} - -// NewWithPrev constructs a node with provided node information. -func NewWithPrev(hash common.Hash, blob []byte, prev []byte) *WithPrev { - return &WithPrev{ - Node: New(hash, blob), - Prev: prev, - } -}