From 01ec38ab87c244a0affb2a5b142f92e35925f6bf Mon Sep 17 00:00:00 2001 From: Francesco4203 Date: Fri, 27 Sep 2024 15:47:53 +0700 Subject: [PATCH] trie: refactor trie node --- core/rawdb/schema.go | 48 +++++++- core/state/database.go | 3 +- core/state/snapshot/generate.go | 6 +- core/state/state_object.go | 4 +- core/state/statedb.go | 5 +- trie/committer.go | 32 ++---- trie/nodeset.go | 198 -------------------------------- trie/secure_trie.go | 3 +- trie/tracer.go | 9 +- trie/trie.go | 5 +- trie/trienode/node.go | 197 +++++++++++++++++++++++++++++++ 11 files changed, 279 insertions(+), 231 deletions(-) delete mode 100644 trie/nodeset.go create mode 100644 trie/trienode/node.go diff --git a/core/rawdb/schema.go b/core/rawdb/schema.go index ae9cbcff96..439e47df9f 100644 --- a/core/rawdb/schema.go +++ b/core/rawdb/schema.go @@ -22,6 +22,7 @@ import ( "encoding/binary" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/metrics" ) @@ -103,7 +104,7 @@ var ( internalTxsPrefix = []byte("itxs") // internalTxsPrefix + block hash -> internal transactions dirtyAccountsKey = []byte("dacc") // dirtyAccountsPrefix + block hash -> dirty accounts - // Path-based trie node scheme. + // Path-based storage scheme of merkle patricia trie. trieNodeAccountPrefix = []byte("A") // trieNodeAccountPrefix + hexPath -> trie node trieNodeStoragePrefix = []byte("O") // trieNodeStoragePrefix + accountHash + hexPath -> trie node @@ -250,3 +251,48 @@ func storageTrieNodeKey(accountHash common.Hash, path []byte) []byte { func snapshotConsortiumKey(hash common.Hash) []byte { return append(snapshotConsortiumPrefix, hash.Bytes()...) } + +// IsLegacyTrieNode reports whether a provided database entry is a legacy trie +// node. The characteristics of legacy trie node are: +// - the key length is 32 bytes +// - the key is the hash of val +func IsLegacyTrieNode(key []byte, val []byte) bool { + if len(key) != common.HashLength { + return false + } + return bytes.Equal(key, crypto.Keccak256(val)) +} + +// IsAccountTrieNode reports whether a provided database entry is an account +// trie node in path-based state scheme. +func IsAccountTrieNode(key []byte) (bool, []byte) { + if !bytes.HasPrefix(key, trieNodeAccountPrefix) { + return false, nil + } + // The remaining key should only consist a hex node path + // whose length is in the range 0 to 64 (64 is excluded + // since leaves are always wrapped with shortNode). + if len(key) >= len(trieNodeAccountPrefix)+common.HashLength*2 { + return false, nil + } + return true, key[len(trieNodeAccountPrefix):] +} + +// IsStorageTrieNode reports whether a provided database entry is a storage +// trie node in path-based state scheme. +func IsStorageTrieNode(key []byte) (bool, common.Hash, []byte) { + if !bytes.HasPrefix(key, trieNodeStoragePrefix) { + return false, common.Hash{}, nil + } + // The remaining key consists of 2 parts: + // - 32 bytes account hash + // - hex node path whose length is in the range 0 to 64 + if len(key) < len(trieNodeStoragePrefix)+common.HashLength { + return false, common.Hash{}, nil + } + if len(key) >= len(trieNodeStoragePrefix)+common.HashLength+common.HashLength*2 { + return false, common.Hash{}, nil + } + accountHash := common.BytesToHash(key[len(trieNodeStoragePrefix) : len(trieNodeStoragePrefix)+common.HashLength]) + return true, accountHash, key[len(trieNodeStoragePrefix)+common.HashLength:] +} diff --git a/core/state/database.go b/core/state/database.go index d2837c83f9..6991e85843 100644 --- a/core/state/database.go +++ b/core/state/database.go @@ -26,6 +26,7 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/trie" + "github.com/ethereum/go-ethereum/trie/trienode" lru "github.com/hashicorp/golang-lru/v2" ) @@ -92,7 +93,7 @@ type Trie interface { // corresponding node hash. All collected nodes(including dirty leaves if // collectLeaf is true) will be encapsulated into a nodeset for return. // The returned nodeset can be nil if the trie is clean(nothing to commit). - Commit(collectLeaf bool) (common.Hash, *trie.NodeSet, error) + Commit(collectLeaf bool) (common.Hash, *trienode.NodeSet, error) // NodeIterator returns an iterator that returns nodes of the trie. Iteration // starts at the key after the given start key. diff --git a/core/state/snapshot/generate.go b/core/state/snapshot/generate.go index af4279da6d..e824a91bc5 100644 --- a/core/state/snapshot/generate.go +++ b/core/state/snapshot/generate.go @@ -29,12 +29,14 @@ import ( "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/trie" + "github.com/ethereum/go-ethereum/trie/trienode" ) var ( @@ -438,9 +440,9 @@ func (dl *diskLayer) generateRange(trieID *trie.ID, prefix []byte, kind string, } root, nodes, _ := snapTrie.Commit(false) if nodes != nil { - snapTrieDb.Update(trie.NewWithNodeSet(nodes)) + snapTrieDb.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes)) } - snapTrieDb.Commit(root, false, nil) + snapTrieDb.Commit(root, false) } tr := result.tr if tr == nil { diff --git a/core/state/state_object.go b/core/state/state_object.go index bb9e3d6781..3adcff01e9 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -28,7 +28,7 @@ import ( "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/rlp" - "github.com/ethereum/go-ethereum/trie" + "github.com/ethereum/go-ethereum/trie/trienode" ) var emptyCodeHash = crypto.Keccak256(nil) @@ -394,7 +394,7 @@ func (s *stateObject) updateRoot(db Database) { // commitTrie submits the storage changes into the storage trie and re-computes // the root. Besides, all trie changes will be collected in a nodeset and returned. -func (s *stateObject) commitTrie(db Database) (*trie.NodeSet, error) { +func (s *stateObject) commitTrie(db Database) (*trienode.NodeSet, error) { // If nothing changed, don't bother with hashing anything if s.updateTrie(db) == nil { return nil, nil diff --git a/core/state/statedb.go b/core/state/statedb.go index 898e65597c..c15613de40 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -34,6 +34,7 @@ import ( "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/trie" + "github.com/ethereum/go-ethereum/trie/trienode" ) type revision struct { @@ -978,7 +979,7 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, error) { accountTrieNodesDeleted int storageTrieNodesUpdated int storageTrieNodesDeleted int - nodes = trie.NewMergedNodeSet() + nodes = trienode.NewMergedNodeSet() ) codeWriter := s.db.TrieDB().DiskDB().NewBatch() for addr := range s.stateObjectsDirty { @@ -1082,7 +1083,7 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, error) { } if root != origin { start := time.Now() - if err := s.db.TrieDB().Update(nodes); err != nil { + if err := s.db.TrieDB().Update(root, origin, nodes); err != nil { return common.Hash{}, err } s.originalRoot = root diff --git a/trie/committer.go b/trie/committer.go index b19316631f..83d08f4dcd 100644 --- a/trie/committer.go +++ b/trie/committer.go @@ -23,6 +23,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/trie/trienode" "golang.org/x/crypto/sha3" ) @@ -30,13 +31,6 @@ import ( // some parallelism but not incur too much memory overhead. const leafChanSize = 200 -// leaf represents a trie leaf value -type leaf struct { - blob []byte // raw blob of leaf - parent common.Hash // the hash of parent node - path []byte // the path from the root node -} - // committer is a type used for the trie Commit operation. The committer will // capture all dirty nodes during the commit process and keep them cached in // insertion order. @@ -45,7 +39,7 @@ type committer struct { sha crypto.KeccakState owner common.Hash // TODO: same as nodes.owner, consider removing - nodes *NodeSet + nodes *trienode.NodeSet tracer *tracer collectLeaf bool } @@ -61,7 +55,7 @@ var committerPool = sync.Pool{ } // newCommitter creates a new committer or picks one from the pool. -func newCommitter(nodes *NodeSet, tracer *tracer, collectLeaf bool) *committer { +func newCommitter(nodes *trienode.NodeSet, tracer *tracer, collectLeaf bool) *committer { return &committer{ nodes: nodes, tracer: tracer, @@ -70,7 +64,7 @@ func newCommitter(nodes *NodeSet, tracer *tracer, collectLeaf bool) *committer { } // Commit collapses a node down into a hash node and inserts it into the database -func (c *committer) Commit(n node) (hashNode, *NodeSet, error) { +func (c *committer) Commit(n node) (hashNode, *trienode.NodeSet, error) { h, err := c.commit(nil, n) if err != nil { return nil, nil, err @@ -176,7 +170,7 @@ func (c *committer) store(path []byte, n node) node { // deleted only if the node was existent in database before. prev, ok := c.tracer.accessList[string(path)] if ok { - c.nodes.addNode(path, &nodeWithPrev{&memoryNode{}, prev}) + c.nodes.AddNode(path, trienode.NewWithPrev(common.Hash{}, nil, prev)) } return n } @@ -185,24 +179,22 @@ func (c *committer) store(path []byte, n node) node { var ( nhash = common.BytesToHash(hash) blob, _ = rlp.EncodeToBytes(n) - node = &nodeWithPrev{ - &memoryNode{ - nhash, - blob, - }, + node = trienode.NewWithPrev( + nhash, + blob, c.tracer.accessList[string(path)], - } + ) ) // Collect the dirty node to nodeset for return. - c.nodes.addNode(path, node) + c.nodes.AddNode(path, node) // 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 // 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(&leaf{blob: val, parent: nhash}) + c.nodes.AddLeaf(nhash, val) } } } @@ -214,7 +206,7 @@ type mptResolver struct{} // ForEach implements childResolver, decodes the provided node and // traverses the children inside. -func (resolver mptResolver) forEach(node []byte, onChild func(common.Hash)) { +func (resolver mptResolver) ForEach(node []byte, onChild func(common.Hash)) { forGatherChildren(mustDecodeNode(nil, node), onChild) } diff --git a/trie/nodeset.go b/trie/nodeset.go deleted file mode 100644 index 9288033548..0000000000 --- a/trie/nodeset.go +++ /dev/null @@ -1,198 +0,0 @@ -// Copyright 2022 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 trie - -import ( - "fmt" - "sort" - "strings" - - "github.com/ethereum/go-ethereum/common" -) - -// memoryNode is all the information we know about a single cached trie node -// in the memory. -type memoryNode struct { - hash common.Hash // Node hash, computed by hashing rlp value, 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 -} - -// isDeleted returns the indicator if the node is marked as deleted. -func (n *memoryNode) isDeleted() bool { - return n.hash == (common.Hash{}) -} - -// 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) -} - -// nodeWithPrev wraps the memoryNode with the previous node value. -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(key int) int { - return n.memoryNode.memorySize(key) + len(n.prev) -} - -// NodeSet contains all dirty nodes collected during the commit operation -// Each node is keyed by path. It's not the 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 - - // 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]*nodeWithPrev -} - -// 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 { - return &NodeSet{ - owner: owner, - nodes: make(map[string]*nodeWithPrev), - } -} - -// 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 *memoryNode)) { - var paths sort.StringSlice - for path := range set.nodes { - paths = append(paths, path) - } - // Bottom-up, longest path first - sort.Sort(sort.Reverse(paths)) - for _, path := range paths { - callback(path, set.nodes[path].unwrap()) - } -} - -// addNode adds the provided dirty node into set. -func (set *NodeSet) addNode(path []byte, n *nodeWithPrev) { - if n.isDeleted() { - set.deletes += 1 - } else { - set.updates += 1 - } - set.nodes[string(path)] = n -} - -// addLeaf collects the provided leaf node into set. -func (set *NodeSet) addLeaf(leaf *leaf) { - set.leaves = append(set.leaves, leaf) -} - -// Size returns the number of updated and deleted nodes contained in the set. -func (set *NodeSet) Size() (int, int) { - return set.updates, set.deletes -} - -// Hashes returns the hashes of all updated nodes. -func (set *NodeSet) Hashes() []common.Hash { - var ret []common.Hash - for _, node := range set.nodes { - ret = append(ret, node.hash) - } - return ret -} - -// Summary returns a string-representation of the NodeSet. -func (set *NodeSet) Summary() string { - var out = new(strings.Builder) - fmt.Fprintf(out, "nodeset owner: %v\n", set.owner) - if set.nodes != nil { - for path, n := range set.nodes { - // Deletion - if n.isDeleted() { - fmt.Fprintf(out, " [-]: %x prev: %x\n", path, n.prev) - continue - } - // Insertion - if len(n.prev) == 0 { - fmt.Fprintf(out, " [+]: %x -> %v\n", path, n.hash) - continue - } - // Update - fmt.Fprintf(out, " [*]: %x -> %v prev: %x\n", path, n.hash, n.prev) - } - } - for _, n := range set.leaves { - fmt.Fprintf(out, "[leaf]: %v\n", n) - } - return out.String() -} - -// MergedNodeSet represents a merged dirty node set for a group of tries. -type MergedNodeSet struct { - sets map[common.Hash]*NodeSet -} - -// NewMergedNodeSet initializes an empty merged set. -func NewMergedNodeSet() *MergedNodeSet { - return &MergedNodeSet{sets: make(map[common.Hash]*NodeSet)} -} - -// NewWithNodeSet constructs a merged nodeset with the provided single set. -func NewWithNodeSet(set *NodeSet) *MergedNodeSet { - merged := NewMergedNodeSet() - merged.Merge(set) - return merged -} - -// Merge merges the provided dirty nodes of a trie into the set. The assumption -// is held that no duplicated set belonging to the same trie will be merged twice. -func (set *MergedNodeSet) Merge(other *NodeSet) error { - _, present := set.sets[other.owner] - if present { - return fmt.Errorf("duplicate trie for owner %#x", other.owner) - } - set.sets[other.owner] = other - return nil -} diff --git a/trie/secure_trie.go b/trie/secure_trie.go index ce69b839bb..973596a58f 100644 --- a/trie/secure_trie.go +++ b/trie/secure_trie.go @@ -23,6 +23,7 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/trie/trienode" ) // SecureTrie wraps a trie with key hashing. In a secure trie, all @@ -166,7 +167,7 @@ func (t *SecureTrie) GetKey(shaKey []byte) []byte { // collectLeaf is true) will be encapsulated into a nodeset for return. // The returned nodeset can be nil if the trie is clean(nothing to commit). // All cached preimages will be also flushed if preimages recording is enabled. -func (t *SecureTrie) Commit(collectLeaf bool) (common.Hash, *NodeSet, error) { +func (t *SecureTrie) Commit(collectLeaf bool) (common.Hash, *trienode.NodeSet, error) { // Write all the pre-images to the actual disk database if len(t.getSecKeyCache()) > 0 { if t.preimages != nil { // Ugly direct check but avoids the below write lock diff --git a/trie/tracer.go b/trie/tracer.go index cd5ebb85a2..56238713f7 100644 --- a/trie/tracer.go +++ b/trie/tracer.go @@ -16,6 +16,11 @@ package trie +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/trie/trienode" +) + // tracer tracks the changes of trie nodes. During the trie operations, // some nodes can be deleted from the trie, while these deleted nodes // won't be captured by trie.Hasher or trie.Commiter. Thus, these deleted @@ -110,7 +115,7 @@ func (t *tracer) copy() *tracer { } // markDeletions puts all tracked deletions into the provided nodeset. -func (t *tracer) markDeletions(set *NodeSet) { +func (t *tracer) markDeletions(set *trienode.NodeSet) { for path := range t.deletes { // It's possible a few deleted nodes were embedded // in their parent before, the deletions can be no @@ -119,6 +124,6 @@ func (t *tracer) markDeletions(set *NodeSet) { if !ok { continue } - set.addNode([]byte(path), &nodeWithPrev{&memoryNode{}, prev}) + set.AddNode([]byte(path), trienode.NewWithPrev(common.Hash{}, nil, prev)) } } diff --git a/trie/trie.go b/trie/trie.go index bbfb0b662f..ae00e542e5 100644 --- a/trie/trie.go +++ b/trie/trie.go @@ -27,6 +27,7 @@ import ( "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/trie/trienode" ) var ( @@ -582,9 +583,9 @@ func (t *Trie) Hash() common.Hash { // The returned nodeset can be nil if the trie is clean(nothing to commit). // Once the trie is committed, it's not usable anymore. A new trie must // be created with new root and updated trie database for following usage -func (t *Trie) Commit(collectLeaf bool) (common.Hash, *NodeSet, error) { +func (t *Trie) Commit(collectLeaf bool) (common.Hash, *trienode.NodeSet, error) { defer t.tracer.reset() - nodes := NewNodeSet(t.owner) + nodes := trienode.NewNodeSet(t.owner) t.tracer.markDeletions(nodes) if t.root == nil { diff --git a/trie/trienode/node.go b/trie/trienode/node.go new file mode 100644 index 0000000000..8152eab6c0 --- /dev/null +++ b/trie/trienode/node.go @@ -0,0 +1,197 @@ +// 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 ( + "fmt" + "sort" + "strings" + + "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, + } +} + +// leaf represents a trie leaf node +type leaf struct { + Blob []byte // raw blob of leaf + Parent common.Hash // the hash of parent node +} + +// NodeSet contains a set of 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 + Leaves []*leaf + Nodes map[string]*WithPrev + updates int // the count of updated and inserted nodes + deletes int // the count of deleted nodes +} + +// NewNodeSet initializes a node set. The owner is zero for the account trie and +// the owning account address hash for storage tries. +func NewNodeSet(owner common.Hash) *NodeSet { + return &NodeSet{ + Owner: owner, + Nodes: make(map[string]*WithPrev), + } +} + +// ForEachWithOrder iterates the 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 *Node)) { + var paths sort.StringSlice + for path := range set.Nodes { + paths = append(paths, path) + } + // Bottom-up, longest path first + sort.Sort(sort.Reverse(paths)) + for _, path := range paths { + callback(path, set.Nodes[path].Unwrap()) + } +} + +// AddNode adds the provided node into set. +func (set *NodeSet) AddNode(path []byte, n *WithPrev) { + if n.IsDeleted() { + set.deletes += 1 + } else { + set.updates += 1 + } + set.Nodes[string(path)] = n +} + +// AddLeaf adds the provided leaf node into set. TODO(rjl493456442) how can +// we get rid of it? +func (set *NodeSet) AddLeaf(parent common.Hash, blob []byte) { + set.Leaves = append(set.Leaves, &leaf{Blob: blob, Parent: parent}) +} + +// Size returns the number of dirty nodes in set. +func (set *NodeSet) Size() (int, int) { + return set.updates, set.deletes +} + +// Hashes returns the hashes of all updated nodes. TODO(rjl493456442) how can +// we get rid of it? +func (set *NodeSet) Hashes() []common.Hash { + var ret []common.Hash + for _, node := range set.Nodes { + ret = append(ret, node.Hash) + } + return ret +} + +// Summary returns a string-representation of the NodeSet. +func (set *NodeSet) Summary() string { + var out = new(strings.Builder) + fmt.Fprintf(out, "nodeset owner: %v\n", set.Owner) + if set.Nodes != nil { + for path, n := range set.Nodes { + // Deletion + if n.IsDeleted() { + fmt.Fprintf(out, " [-]: %x prev: %x\n", path, n.Prev) + continue + } + // Insertion + if len(n.Prev) == 0 { + fmt.Fprintf(out, " [+]: %x -> %v\n", path, n.Hash) + continue + } + // Update + fmt.Fprintf(out, " [*]: %x -> %v prev: %x\n", path, n.Hash, n.Prev) + } + } + for _, n := range set.Leaves { + fmt.Fprintf(out, "[leaf]: %v\n", n) + } + return out.String() +} + +// MergedNodeSet represents a merged node set for a group of tries. +type MergedNodeSet struct { + Sets map[common.Hash]*NodeSet +} + +// NewMergedNodeSet initializes an empty merged set. +func NewMergedNodeSet() *MergedNodeSet { + return &MergedNodeSet{Sets: make(map[common.Hash]*NodeSet)} +} + +// NewWithNodeSet constructs a merged nodeset with the provided single set. +func NewWithNodeSet(set *NodeSet) *MergedNodeSet { + merged := NewMergedNodeSet() + merged.Merge(set) + return merged +} + +// Merge merges the provided dirty nodes of a trie into the set. The assumption +// is held that no duplicated set belonging to the same trie will be merged twice. +func (set *MergedNodeSet) Merge(other *NodeSet) error { + _, present := set.Sets[other.Owner] + if present { + return fmt.Errorf("duplicate trie for owner %#x", other.Owner) + } + set.Sets[other.Owner] = other + return nil +}