diff --git a/core/state/statedb.go b/core/state/statedb.go
index 3d8fd15bb..247aef8b2 100644
--- a/core/state/statedb.go
+++ b/core/state/statedb.go
@@ -970,8 +970,8 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, error) {
storageTrieNodesUpdated int
storageTrieNodesDeleted int
nodes = trie.NewMergedNodeSet()
+ codeWriter = s.db.DiskDB().NewBatch()
)
- codeWriter := s.db.DiskDB().NewBatch()
for addr := range s.stateObjectsDirty {
if obj := s.stateObjects[addr]; !obj.deleted {
// Write any contract code associated with the state object
diff --git a/trie/committer.go b/trie/committer.go
index c4957f349..9f978873a 100644
--- a/trie/committer.go
+++ b/trie/committer.go
@@ -33,29 +33,20 @@ 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(owner common.Hash, tracer *tracer, collectLeaf bool) *committer {
+func newCommitter(nodeset *NodeSet, collectLeaf bool) *committer {
return &committer{
- nodes: NewNodeSet(owner),
- tracer: tracer,
+ nodes: nodeset,
collectLeaf: collectLeaf,
}
}
-// Commit collapses a node down into a hash node and returns it along with
-// the modified nodeset.
-func (c *committer) Commit(n node) (hashNode, *NodeSet) {
- h := c.commit(nil, n)
- // Some nodes can be deleted from trie which can't be captured
- // by committer itself. Iterate all deleted nodes tracked by
- // tracer and marked them as deleted only if they are present
- // in database previously.
- c.tracer.markDeletions(c.nodes)
- return h.(hashNode), c.nodes
+// Commit collapses a node down into a hash node.
+func (c *committer) Commit(n node) hashNode {
+ return c.commit(nil, n).(hashNode)
}
// commit collapses a node down into a hash node and returns it.
@@ -74,9 +65,7 @@ func (c *committer) commit(path []byte, n node) node {
// If the child is fullNode, recursively commit,
// otherwise it can only be hashNode or valueNode.
if _, ok := cn.Val.(*fullNode); ok {
- childV := c.commit(append(path, cn.Key...), cn.Val)
-
- collapsed.Val = childV
+ collapsed.Val = c.commit(append(path, cn.Key...), cn.Val)
}
// The key needs to be copied, since we're adding it to the
// modified nodeset.
@@ -85,12 +74,6 @@ func (c *committer) commit(path []byte, n node) node {
if hn, ok := hashedNode.(hashNode); ok {
return hn
}
- // The short node now is embedded in its parent. Mark the node as
- // deleted if it's present in database previously. It's equivalent
- // as deletion from database's perspective.
- if prev := c.tracer.getPrev(path); len(prev) != 0 {
- c.nodes.markDeleted(path, prev)
- }
return collapsed
case *fullNode:
hashedKids := c.commitChildren(path, cn)
@@ -101,12 +84,6 @@ func (c *committer) commit(path []byte, n node) node {
if hn, ok := hashedNode.(hashNode); ok {
return hn
}
- // The full node now is embedded in its parent. Mark the node as
- // deleted if it's present in database previously. It's equivalent
- // as deletion from database's perspective.
- if prev := c.tracer.getPrev(path); len(prev) != 0 {
- c.nodes.markDeleted(path, prev)
- }
return collapsed
case hashNode:
return cn
@@ -134,8 +111,7 @@ func (c *committer) commitChildren(path []byte, n *fullNode) [17]node {
// Commit the child recursively and store the "hashed" value.
// Note the returned node can be some embedded nodes, so it's
// possible the type is not hashNode.
- hashed := c.commit(append(path, byte(i)), child)
- children[i] = hashed
+ children[i] = c.commit(append(path, byte(i)), child)
}
// For the 17th child, it's possible the type is valuenode.
if n.Children[16] != nil {
@@ -155,6 +131,12 @@ func (c *committer) store(path []byte, n node) node {
// usually is leaf node). But small value (less than 32bytes) is not
// our target (leaves in account trie only).
if hash == nil {
+ // 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.
+ if _, ok := c.nodes.accessList[string(path)]; ok {
+ c.nodes.markDeleted(path)
+ }
return n
}
// We have the hash already, estimate the RLP encoding-size of the node.
@@ -169,7 +151,7 @@ func (c *committer) store(path []byte, n node) node {
}
)
// Collect the dirty node to nodeset for return.
- c.nodes.markUpdated(path, mnode, c.tracer.getPrev(path))
+ 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 895ffdf89..200ed3674 100644
--- a/trie/database.go
+++ b/trie/database.go
@@ -792,13 +792,12 @@ func (db *Database) Update(nodes *MergedNodeSet) error {
}
for _, owner := range order {
subset := nodes.sets[owner]
- for _, path := range subset.updates.order {
- n, ok := subset.updates.nodes[path]
- if !ok {
- return fmt.Errorf("missing node %x %v", owner, path)
+ subset.forEachWithOrder(func(path string, n *memoryNode) {
+ if n.isDeleted() {
+ return // ignore deletion
}
db.insert(n.hash, int(n.size), n.node)
- }
+ })
}
// Link up the account trie and storage trie if the node points
// to an account trie leaf.
diff --git a/trie/nodeset.go b/trie/nodeset.go
index 928172350..99e4a80fa 100644
--- a/trie/nodeset.go
+++ b/trie/nodeset.go
@@ -19,6 +19,7 @@ package trie
import (
"fmt"
"reflect"
+ "sort"
"strings"
"github.com/ethereum/go-ethereum/common"
@@ -40,8 +41,8 @@ var memoryNodeSize = int(reflect.TypeOf(memoryNode{}).Size())
// memorySize returns the total memory size used by this node.
// nolint:unused
-func (n *memoryNode) memorySize(key int) int {
- return int(n.size) + memoryNodeSize + key
+func (n *memoryNode) memorySize(pathlen int) int {
+ return int(n.size) + memoryNodeSize + pathlen
}
// rlp returns the raw rlp encoded blob of the cached trie node, either directly
@@ -64,7 +65,13 @@ func (n *memoryNode) obj() node {
return expandNode(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
@@ -79,64 +86,60 @@ func (n *nodeWithPrev) unwrap() *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)
-}
-
-// nodesWithOrder represents a collection of dirty nodes which includes
-// newly-inserted and updated nodes. The modification order of all nodes
-// is represented by order list.
-type nodesWithOrder struct {
- order []string // the path list of dirty nodes, sort by insertion order
- nodes map[string]*nodeWithPrev // the map of dirty nodes, keyed by node path
+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
- updates *nodesWithOrder // the set of updated nodes(newly inserted, updated)
- deletes map[string][]byte // the map of deleted nodes, keyed by node
- leaves []*leaf // the list of dirty leaves
+ 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 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,
- updates: &nodesWithOrder{
- nodes: make(map[string]*nodeWithPrev),
- },
- deletes: make(map[string][]byte),
+ owner: owner,
+ nodes: make(map[string]*memoryNode),
+ accessList: accessList,
}
}
-/*
-// NewNodeSetWithDeletion initializes the nodeset with provided deletion set.
-func NewNodeSetWithDeletion(owner common.Hash, paths [][]byte, prev [][]byte) *NodeSet {
- set := NewNodeSet(owner)
- for i, path := range paths {
- set.markDeleted(path, prev[i])
+// 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)
}
- return set
-}
-*/
-
-// markUpdated marks the node as dirty(newly-inserted or updated) with provided
-// node path, node object along with its previous value.
-func (set *NodeSet) markUpdated(path []byte, node *memoryNode, prev []byte) {
- set.updates.order = append(set.updates.order, string(path))
- set.updates.nodes[string(path)] = &nodeWithPrev{
- memoryNode: node,
- prev: prev,
+ // Bottom-up, longest path first
+ sort.Sort(sort.Reverse(paths))
+ for _, path := range paths {
+ callback(path, set.nodes[path])
}
}
-// markDeleted marks the node as deleted with provided path and previous value.
-func (set *NodeSet) markDeleted(path []byte, prev []byte) {
- set.deletes[string(path)] = prev
+// 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
+}
+
+// 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.
@@ -144,16 +147,16 @@ func (set *NodeSet) addLeaf(node *leaf) {
set.leaves = append(set.leaves, node)
}
-// Size returns the number of updated and deleted nodes contained in the set.
+// Size returns the number of dirty nodes in set.
func (set *NodeSet) Size() (int, int) {
- return len(set.updates.order), len(set.deletes)
+ 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.updates.nodes {
+ for _, node := range set.nodes {
ret = append(ret, node.hash)
}
return ret
@@ -163,19 +166,23 @@ func (set *NodeSet) Hashes() []common.Hash {
func (set *NodeSet) Summary() string {
var out = new(strings.Builder)
fmt.Fprintf(out, "nodeset owner: %v\n", set.owner)
- if set.updates != nil {
- for _, key := range set.updates.order {
- updated := set.updates.nodes[key]
- if updated.prev != nil {
- fmt.Fprintf(out, " [*]: %x -> %v prev: %x\n", key, updated.hash, updated.prev)
- } else {
- fmt.Fprintf(out, " [+]: %x -> %v\n", key, updated.hash)
+ if set.nodes != nil {
+ for path, n := range set.nodes {
+ // Deletion
+ if n.isDeleted() {
+ fmt.Fprintf(out, " [-]: %x prev: %x\n", path, set.accessList[path])
+ continue
}
+ // Insertion
+ 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, origin)
}
}
- for k, n := range set.deletes {
- fmt.Fprintf(out, " [-]: %x -> %x\n", k, n)
- }
for _, n := range set.leaves {
fmt.Fprintf(out, "[leaf]: %v\n", n)
}
diff --git a/trie/proof.go b/trie/proof.go
index af49ce36b..f11dfc47a 100644
--- a/trie/proof.go
+++ b/trie/proof.go
@@ -563,7 +563,7 @@ func VerifyRangeProof(rootHash common.Hash, firstKey []byte, lastKey []byte, key
}
// Rebuild the trie with the leaf stream, the shape of trie
// should be same with the original one.
- tr := &Trie{root: root, reader: newEmptyReader()}
+ tr := &Trie{root: root, reader: newEmptyReader(), tracer: newTracer()}
if empty {
tr.root = nil
}
diff --git a/trie/tracer.go b/trie/tracer.go
new file mode 100644
index 000000000..a27e371c7
--- /dev/null
+++ b/trie/tracer.go
@@ -0,0 +1,125 @@
+// 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 "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
+// won't be captured by trie.Hasher or trie.Committer. Thus, these deleted
+// nodes won't be removed from the disk at all. Tracer is an auxiliary tool
+// used to track all insert and delete operations of trie and capture all
+// deleted nodes eventually.
+//
+// The changed nodes can be mainly divided into two categories: the leaf
+// node and intermediate node. The former is inserted/deleted by callers
+// while the latter is inserted/deleted in order to follow the rule of trie.
+// This tool can track all of them no matter the node is embedded in its
+// parent or not, but valueNode is never tracked.
+//
+// Besides, it's also used for recording the original value of the nodes
+// when they are resolved from the disk. The pre-value of the nodes will
+// be used to construct trie history in the future.
+//
+// Note tracer is not thread-safe, callers should be responsible for handling
+// the concurrency issues by themselves.
+type tracer struct {
+ inserts map[string]struct{}
+ deletes map[string]struct{}
+ accessList map[string][]byte
+}
+
+// newTracer initializes the tracer for capturing trie changes.
+func newTracer() *tracer {
+ return &tracer{
+ inserts: make(map[string]struct{}),
+ deletes: make(map[string]struct{}),
+ accessList: make(map[string][]byte),
+ }
+}
+
+// onRead tracks the newly loaded trie node and caches the rlp-encoded
+// blob internally. Don't change the value outside of function since
+// it's not deep-copied.
+func (t *tracer) onRead(path []byte, val []byte) {
+ t.accessList[string(path)] = val
+}
+
+// onInsert tracks the newly inserted trie node. If it's already
+// in the deletion set (resurrected node), then just wipe it from
+// the deletion set as it's "untouched".
+func (t *tracer) onInsert(path []byte) {
+ if _, present := t.deletes[string(path)]; present {
+ delete(t.deletes, string(path))
+ return
+ }
+ t.inserts[string(path)] = struct{}{}
+}
+
+// onDelete tracks the newly deleted trie node. If it's already
+// in the addition set, then just wipe it from the addition set
+// as it's untouched.
+func (t *tracer) onDelete(path []byte) {
+ if _, present := t.inserts[string(path)]; present {
+ delete(t.inserts, string(path))
+ return
+ }
+ t.deletes[string(path)] = struct{}{}
+}
+
+// reset clears the content tracked by tracer.
+func (t *tracer) reset() {
+ t.inserts = make(map[string]struct{})
+ t.deletes = make(map[string]struct{})
+ t.accessList = make(map[string][]byte)
+}
+
+// copy returns a deep copied tracer instance.
+func (t *tracer) copy() *tracer {
+ var (
+ inserts = make(map[string]struct{})
+ deletes = make(map[string]struct{})
+ accessList = make(map[string][]byte)
+ )
+ for path := range t.inserts {
+ inserts[path] = struct{}{}
+ }
+ for path := range t.deletes {
+ deletes[path] = struct{}{}
+ }
+ for path, blob := range t.accessList {
+ accessList[path] = common.CopyBytes(blob)
+ }
+ return &tracer{
+ inserts: inserts,
+ deletes: deletes,
+ accessList: accessList,
+ }
+}
+
+// markDeletions puts all tracked deletions into the provided nodeset.
+func (t *tracer) markDeletions(set *NodeSet) {
+ for path := range t.deletes {
+ // 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.
+ if _, ok := set.accessList[path]; !ok {
+ continue
+ }
+ set.markDeleted([]byte(path))
+ }
+}
diff --git a/trie/tracer_test.go b/trie/tracer_test.go
new file mode 100644
index 000000000..5e627c89c
--- /dev/null
+++ b/trie/tracer_test.go
@@ -0,0 +1,368 @@
+// 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 (
+ "bytes"
+ "testing"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/core/rawdb"
+)
+
+var (
+ tiny = []struct{ k, v string }{
+ {"k1", "v1"},
+ {"k2", "v2"},
+ {"k3", "v3"},
+ }
+ nonAligned = []struct{ k, v string }{
+ {"do", "verb"},
+ {"ether", "wookiedoo"},
+ {"horse", "stallion"},
+ {"shaman", "horse"},
+ {"doge", "coin"},
+ {"dog", "puppy"},
+ {"somethingveryoddindeedthis is", "myothernodedata"},
+ }
+ standard = []struct{ k, v string }{
+ {string(randBytes(32)), "verb"},
+ {string(randBytes(32)), "wookiedoo"},
+ {string(randBytes(32)), "stallion"},
+ {string(randBytes(32)), "horse"},
+ {string(randBytes(32)), "coin"},
+ {string(randBytes(32)), "puppy"},
+ {string(randBytes(32)), "myothernodedata"},
+ }
+)
+
+func TestTrieTracer(t *testing.T) {
+ testTrieTracer(t, tiny)
+ testTrieTracer(t, nonAligned)
+ testTrieTracer(t, standard)
+}
+
+// Tests if the trie diffs are tracked correctly. Tracer should capture
+// all non-leaf dirty nodes, no matter the node is embedded or not.
+func testTrieTracer(t *testing.T, vals []struct{ k, v string }) {
+ db := NewDatabase(rawdb.NewMemoryDatabase())
+ trie := NewEmpty(db)
+
+ // Determine all new nodes are tracked
+ for _, val := range vals {
+ trie.Update([]byte(val.k), []byte(val.v))
+ }
+ insertSet := copySet(trie.tracer.inserts) // copy before commit
+ deleteSet := copySet(trie.tracer.deletes) // copy before commit
+ root, nodes := trie.Commit(false)
+ db.Update(NewWithNodeSet(nodes))
+
+ seen := setKeys(iterNodes(db, root))
+ if !compareSet(insertSet, seen) {
+ t.Fatal("Unexpected insertion set")
+ }
+ if !compareSet(deleteSet, nil) {
+ t.Fatal("Unexpected deletion set")
+ }
+
+ // Determine all deletions are tracked
+ trie, _ = New(TrieID(root), db)
+ for _, val := range vals {
+ trie.Delete([]byte(val.k))
+ }
+ insertSet, deleteSet = copySet(trie.tracer.inserts), copySet(trie.tracer.deletes)
+ if !compareSet(insertSet, nil) {
+ t.Fatal("Unexpected insertion set")
+ }
+ if !compareSet(deleteSet, seen) {
+ t.Fatal("Unexpected deletion set")
+ }
+}
+
+// Test that after inserting a new batch of nodes and deleting them immediately,
+// the trie tracer should be cleared normally as no operation happened.
+func TestTrieTracerNoop(t *testing.T) {
+ testTrieTracerNoop(t, tiny)
+ testTrieTracerNoop(t, nonAligned)
+ testTrieTracerNoop(t, standard)
+}
+
+func testTrieTracerNoop(t *testing.T, vals []struct{ k, v string }) {
+ trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase()))
+ for _, val := range vals {
+ trie.Update([]byte(val.k), []byte(val.v))
+ }
+ for _, val := range vals {
+ trie.Delete([]byte(val.k))
+ }
+ if len(trie.tracer.inserts) != 0 {
+ t.Fatal("Unexpected insertion set")
+ }
+ if len(trie.tracer.deletes) != 0 {
+ t.Fatal("Unexpected deletion set")
+ }
+}
+
+// Tests if the accessList is correctly tracked.
+func TestAccessList(t *testing.T) {
+ testAccessList(t, tiny)
+ testAccessList(t, nonAligned)
+ testAccessList(t, standard)
+}
+
+func testAccessList(t *testing.T, vals []struct{ k, v string }) {
+ var (
+ db = NewDatabase(rawdb.NewMemoryDatabase())
+ trie = NewEmpty(db)
+ orig = trie.Copy()
+ )
+ // Create trie from scratch
+ for _, val := range vals {
+ trie.Update([]byte(val.k), []byte(val.v))
+ }
+ root, nodes := trie.Commit(false)
+ db.Update(NewWithNodeSet(nodes))
+
+ trie, _ = New(TrieID(root), db)
+ if err := verifyAccessList(orig, trie, nodes); err != nil {
+ t.Fatalf("Invalid accessList %v", err)
+ }
+
+ // Update trie
+ trie, _ = New(TrieID(root), db)
+ orig = trie.Copy()
+ for _, val := range vals {
+ trie.Update([]byte(val.k), randBytes(32))
+ }
+ root, nodes = trie.Commit(false)
+ db.Update(NewWithNodeSet(nodes))
+
+ trie, _ = New(TrieID(root), db)
+ if err := verifyAccessList(orig, trie, nodes); err != nil {
+ t.Fatalf("Invalid accessList %v", err)
+ }
+
+ // Add more new nodes
+ trie, _ = New(TrieID(root), db)
+ orig = trie.Copy()
+ var keys []string
+ for i := 0; i < 30; i++ {
+ key := randBytes(32)
+ keys = append(keys, string(key))
+ trie.Update(key, randBytes(32))
+ }
+ root, nodes = trie.Commit(false)
+ db.Update(NewWithNodeSet(nodes))
+
+ trie, _ = New(TrieID(root), db)
+ if err := verifyAccessList(orig, trie, nodes); err != nil {
+ t.Fatalf("Invalid accessList %v", err)
+ }
+
+ // Partial deletions
+ trie, _ = New(TrieID(root), db)
+ orig = trie.Copy()
+ for _, key := range keys {
+ trie.Update([]byte(key), nil)
+ }
+ root, nodes = trie.Commit(false)
+ db.Update(NewWithNodeSet(nodes))
+
+ trie, _ = New(TrieID(root), db)
+ if err := verifyAccessList(orig, trie, nodes); err != nil {
+ t.Fatalf("Invalid accessList %v", err)
+ }
+
+ // Delete all
+ trie, _ = New(TrieID(root), db)
+ orig = trie.Copy()
+ for _, val := range vals {
+ trie.Update([]byte(val.k), nil)
+ }
+ root, nodes = trie.Commit(false)
+ db.Update(NewWithNodeSet(nodes))
+
+ trie, _ = New(TrieID(root), db)
+ if err := verifyAccessList(orig, trie, nodes); err != nil {
+ t.Fatalf("Invalid accessList %v", err)
+ }
+}
+
+// Tests origin values won't be tracked in Iterator or Prover
+func TestAccessListLeak(t *testing.T) {
+ var (
+ db = NewDatabase(rawdb.NewMemoryDatabase())
+ trie = NewEmpty(db)
+ )
+ // Create trie from scratch
+ for _, val := range standard {
+ trie.Update([]byte(val.k), []byte(val.v))
+ }
+ root, nodes := trie.Commit(false)
+ db.Update(NewWithNodeSet(nodes))
+
+ var cases = []struct {
+ op func(tr *Trie)
+ }{
+ {
+ func(tr *Trie) {
+ it := tr.NodeIterator(nil)
+ for it.Next(true) {
+ }
+ },
+ },
+ {
+ func(tr *Trie) {
+ it := NewIterator(tr.NodeIterator(nil))
+ for it.Next() {
+ }
+ },
+ },
+ {
+ func(tr *Trie) {
+ for _, val := range standard {
+ tr.Prove([]byte(val.k), 0, rawdb.NewMemoryDatabase())
+ }
+ },
+ },
+ }
+ for _, c := range cases {
+ trie, _ = New(TrieID(root), db)
+ n1 := len(trie.tracer.accessList)
+ c.op(trie)
+ n2 := len(trie.tracer.accessList)
+
+ if n1 != n2 {
+ t.Fatalf("AccessList is leaked, prev %d after %d", n1, n2)
+ }
+ }
+}
+
+// Tests whether the original tree node is correctly deleted after being embedded
+// in its parent due to the smaller size of the original tree node.
+func TestTinyTree(t *testing.T) {
+ var (
+ db = NewDatabase(rawdb.NewMemoryDatabase())
+ trie = NewEmpty(db)
+ )
+ for _, val := range tiny {
+ trie.Update([]byte(val.k), randBytes(32))
+ }
+ root, set := trie.Commit(false)
+ db.Update(NewWithNodeSet(set))
+
+ trie, _ = New(TrieID(root), db)
+ orig := trie.Copy()
+ for _, val := range tiny {
+ trie.Update([]byte(val.k), []byte(val.v))
+ }
+ root, set = trie.Commit(false)
+ db.Update(NewWithNodeSet(set))
+
+ trie, _ = New(TrieID(root), db)
+ if err := verifyAccessList(orig, trie, set); err != nil {
+ t.Fatalf("Invalid accessList %v", err)
+ }
+}
+
+func compareSet(setA, setB map[string]struct{}) bool {
+ if len(setA) != len(setB) {
+ return false
+ }
+ for key := range setA {
+ if _, ok := setB[key]; !ok {
+ return false
+ }
+ }
+ return true
+}
+
+func forNodes(tr *Trie) map[string][]byte {
+ var (
+ it = tr.NodeIterator(nil)
+ nodes = make(map[string][]byte)
+ )
+ for it.Next(true) {
+ if it.Leaf() {
+ continue
+ }
+ nodes[string(it.Path())] = common.CopyBytes(it.NodeBlob())
+ }
+ return nodes
+}
+
+func iterNodes(db *Database, root common.Hash) map[string][]byte {
+ tr, _ := New(TrieID(root), db)
+ return forNodes(tr)
+}
+
+func forHashedNodes(tr *Trie) map[string][]byte {
+ var (
+ it = tr.NodeIterator(nil)
+ nodes = make(map[string][]byte)
+ )
+ for it.Next(true) {
+ if it.Hash() == (common.Hash{}) {
+ continue
+ }
+ nodes[string(it.Path())] = common.CopyBytes(it.NodeBlob())
+ }
+ return nodes
+}
+
+func diffTries(trieA, trieB *Trie) (map[string][]byte, map[string][]byte, map[string][]byte) {
+ var (
+ nodesA = forHashedNodes(trieA)
+ nodesB = forHashedNodes(trieB)
+ inA = make(map[string][]byte) // hashed nodes in trie a but not b
+ inB = make(map[string][]byte) // hashed nodes in trie b but not a
+ both = make(map[string][]byte) // hashed nodes in both tries but different value
+ )
+ for path, blobA := range nodesA {
+ if blobB, ok := nodesB[path]; ok {
+ if bytes.Equal(blobA, blobB) {
+ continue
+ }
+ both[path] = blobA
+ continue
+ }
+ inA[path] = blobA
+ }
+ for path, blobB := range nodesB {
+ if _, ok := nodesA[path]; ok {
+ continue
+ }
+ inB[path] = blobB
+ }
+ return inA, inB, both
+}
+
+func setKeys(set map[string][]byte) map[string]struct{} {
+ keys := make(map[string]struct{})
+ for k := range set {
+ keys[k] = struct{}{}
+ }
+ return keys
+}
+
+func copySet(set map[string]struct{}) map[string]struct{} {
+ copied := make(map[string]struct{})
+ for k := range set {
+ copied[k] = struct{}{}
+ }
+ return copied
+}
diff --git a/trie/trie.go b/trie/trie.go
index cf9108f10..17bacba00 100644
--- a/trie/trie.go
+++ b/trie/trie.go
@@ -81,7 +81,7 @@ func New(id *ID, db NodeReader) (*Trie, error) {
trie := &Trie{
owner: id.Owner,
reader: reader,
- //tracer: newTracer(),
+ tracer: newTracer(),
}
if id.Root != (common.Hash{}) && id.Root != types.EmptyRootHash {
rootnode, err := trie.resolveAndTrack(id.Root[:], nil)
@@ -547,7 +547,7 @@ func (t *Trie) resolveAndTrack(n hashNode, prefix []byte) (node, error) {
// Hash returns the root hash of the trie. It does not write to the
// database and can be used even if the trie doesn't have one.
func (t *Trie) Hash() common.Hash {
- hash, cached, _ := t.hashRoot()
+ hash, cached := t.hashRoot()
t.root = cached
return common.BytesToHash(hash.(hashNode))
}
@@ -561,14 +561,14 @@ func (t *Trie) Hash() common.Hash {
func (t *Trie) Commit(collectLeaf bool) (common.Hash, *NodeSet) {
defer t.tracer.reset()
+ nodes := NewNodeSet(t.owner, t.tracer.accessList)
+ t.tracer.markDeletions(nodes)
+
// Trie is empty and can be classified into two types of situations:
// - The trie was empty and no update happens
// - The trie was non-empty and all nodes are dropped
if t.root == nil {
- // Wrap tracked deletions as the return
- set := NewNodeSet(t.owner)
- t.tracer.markDeletions(set)
- return types.EmptyRootHash, set
+ return types.EmptyRootHash, nodes
}
// Derive the hash for all dirty nodes first. We hold the assumption
// in the following procedure that all nodes are hashed.
@@ -582,23 +582,23 @@ func (t *Trie) Commit(collectLeaf bool) (common.Hash, *NodeSet) {
t.root = hashedNode
return rootHash, nil
}
- h := newCommitter(t.owner, t.tracer, collectLeaf)
- newRoot, nodes := h.Commit(t.root)
- t.root = newRoot
+ t.root = newCommitter(nodes, collectLeaf).Commit(t.root)
return rootHash, nodes
}
// hashRoot calculates the root hash of the given trie
-func (t *Trie) hashRoot() (node, node, error) {
+func (t *Trie) hashRoot() (node, node) {
if t.root == nil {
- return hashNode(types.EmptyRootHash.Bytes()), nil, nil
+ return hashNode(types.EmptyRootHash.Bytes()), nil
}
// If the number of changes is below 100, we let one thread handle it
h := newHasher(t.unhashed >= 100)
- defer returnHasherToPool(h)
+ defer func() {
+ returnHasherToPool(h)
+ t.unhashed = 0
+ }()
hashed, cached := h.hash(t.root, true)
- t.unhashed = 0
- return hashed, cached, nil
+ return hashed, cached
}
// Reset drops the referenced root node and cleans all internal state.
diff --git a/trie/trie_test.go b/trie/trie_test.go
index 2f56c89cd..e41593707 100644
--- a/trie/trie_test.go
+++ b/trie/trie_test.go
@@ -403,6 +403,51 @@ func (randTest) Generate(r *rand.Rand, size int) reflect.Value {
return reflect.ValueOf(steps)
}
+func verifyAccessList(old *Trie, new *Trie, set *NodeSet) error {
+ deletes, inserts, updates := diffTries(old, new)
+
+ // Check insertion set
+ for path := range inserts {
+ n, ok := set.nodes[path]
+ if !ok || n.isDeleted() {
+ return errors.New("expect new node")
+ }
+ _, 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() {
+ return errors.New("expect deleted node")
+ }
+ v, ok := set.accessList[path]
+ if !ok {
+ return errors.New("expect origin value")
+ }
+ 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() {
+ return errors.New("expect updated node")
+ }
+ v, ok := set.accessList[path]
+ if !ok {
+ return errors.New("expect origin value")
+ }
+ if !bytes.Equal(v, blob) {
+ return errors.New("invalid origin value")
+ }
+ }
+ return nil
+}
+
func runRandTest(rt randTest) bool {
var (
triedb = NewDatabase(rawdb.NewMemoryDatabase())
@@ -410,8 +455,6 @@ func runRandTest(rt randTest) bool {
values = make(map[string]string) // tracks content of the trie
origTrie = NewEmpty(triedb)
)
- tr.tracer = newTracer()
-
for i, step := range rt {
// fmt.Printf("{op: %d, key: common.Hex2Bytes(\"%x\"), value: common.Hex2Bytes(\"%x\")}, // step %d\n",
// step.op, step.key, step.value, i)
@@ -447,24 +490,6 @@ func runRandTest(rt randTest) bool {
tr.Hash()
case opCommit:
root, nodes := tr.Commit(true)
- // Validity the returned nodeset
- if nodes != nil {
- for path, node := range nodes.updates.nodes {
- blob, _, _ := origTrie.TryGetNode(hexToCompact([]byte(path)))
- got := node.prev
- if !bytes.Equal(blob, got) {
- rt[i].err = fmt.Errorf("prevalue mismatch for 0x%x, got 0x%x want 0x%x", path, got, blob)
- panic(rt[i].err)
- }
- }
- for path, prev := range nodes.deletes {
- blob, _, _ := origTrie.TryGetNode(hexToCompact([]byte(path)))
- if !bytes.Equal(blob, prev) {
- rt[i].err = fmt.Errorf("prevalue mismatch for 0x%x, got 0x%x want 0x%x", path, prev, blob)
- return false
- }
- }
- }
if nodes != nil {
triedb.Update(NewWithNodeSet(nodes))
}
@@ -473,13 +498,13 @@ func runRandTest(rt randTest) bool {
rt[i].err = err
return false
}
+ if nodes != nil {
+ if err := verifyAccessList(origTrie, newtr, nodes); err != nil {
+ rt[i].err = err
+ return false
+ }
+ }
tr = newtr
-
- // Enable node tracing. Resolve the root node again explicitly
- // since it's not captured at the beginning.
- tr.tracer = newTracer()
- tr.resolveAndTrack(root.Bytes(), nil)
-
origTrie = tr.Copy()
case opItercheckhash:
checktr := NewEmpty(triedb)
@@ -492,8 +517,6 @@ func runRandTest(rt randTest) bool {
}
case opNodeDiff:
var (
- inserted = tr.tracer.insertList()
- deleted = tr.tracer.deleteList()
origIter = origTrie.NodeIterator(nil)
curIter = tr.NodeIterator(nil)
origSeen = make(map[string]struct{})
@@ -527,19 +550,19 @@ func runRandTest(rt randTest) bool {
deleteExp[path] = struct{}{}
}
}
- if len(insertExp) != len(inserted) {
+ if len(insertExp) != len(tr.tracer.inserts) {
rt[i].err = fmt.Errorf("insert set mismatch")
}
- if len(deleteExp) != len(deleted) {
+ if len(deleteExp) != len(tr.tracer.deletes) {
rt[i].err = fmt.Errorf("delete set mismatch")
}
- for _, insert := range inserted {
- if _, present := insertExp[string(insert)]; !present {
+ for insert := range tr.tracer.inserts {
+ if _, present := insertExp[insert]; !present {
rt[i].err = fmt.Errorf("missing inserted node")
}
}
- for _, del := range deleted {
- if _, present := deleteExp[string(del)]; !present {
+ for del := range tr.tracer.deletes {
+ if _, present := deleteExp[del]; !present {
rt[i].err = fmt.Errorf("missing deleted node")
}
}
diff --git a/trie/util_test.go b/trie/util_test.go
deleted file mode 100644
index 8d925a16a..000000000
--- a/trie/util_test.go
+++ /dev/null
@@ -1,305 +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 (
- "bytes"
- "testing"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/core/rawdb"
- "github.com/ethereum/go-ethereum/core/types"
-)
-
-// Tests if the trie diffs are tracked correctly.
-func TestTrieTracer(t *testing.T) {
- db := NewDatabase(rawdb.NewMemoryDatabase())
- trie := NewEmpty(db)
- trie.tracer = newTracer()
-
- // Insert a batch of entries, all the nodes should be marked as inserted
- vals := []struct{ k, v string }{
- {"do", "verb"},
- {"ether", "wookiedoo"},
- {"horse", "stallion"},
- {"shaman", "horse"},
- {"doge", "coin"},
- {"dog", "puppy"},
- {"somethingveryoddindeedthis is", "myothernodedata"},
- }
- for _, val := range vals {
- trie.Update([]byte(val.k), []byte(val.v))
- }
- trie.Hash()
-
- seen := make(map[string]struct{})
- it := trie.NodeIterator(nil)
- for it.Next(true) {
- if it.Leaf() {
- continue
- }
- seen[string(it.Path())] = struct{}{}
- }
- inserted := trie.tracer.insertList()
- if len(inserted) != len(seen) {
- t.Fatalf("Unexpected inserted node tracked want %d got %d", len(seen), len(inserted))
- }
- for _, k := range inserted {
- _, ok := seen[string(k)]
- if !ok {
- t.Fatalf("Unexpected inserted node")
- }
- }
- deleted := trie.tracer.deleteList()
- if len(deleted) != 0 {
- t.Fatalf("Unexpected deleted node tracked %d", len(deleted))
- }
-
- // Commit the changes and re-create with new root
- root, nodes := trie.Commit(false)
- if err := db.Update(NewWithNodeSet(nodes)); err != nil {
- t.Fatal(err)
- }
- trie, _ = New(TrieID(root), db)
- trie.tracer = newTracer()
-
- // Delete all the elements, check deletion set
- for _, val := range vals {
- trie.Delete([]byte(val.k))
- }
- trie.Hash()
-
- inserted = trie.tracer.insertList()
- if len(inserted) != 0 {
- t.Fatalf("Unexpected inserted node tracked %d", len(inserted))
- }
- deleted = trie.tracer.deleteList()
- if len(deleted) != len(seen) {
- t.Fatalf("Unexpected deleted node tracked want %d got %d", len(seen), len(deleted))
- }
- for _, k := range deleted {
- _, ok := seen[string(k)]
- if !ok {
- t.Fatalf("Unexpected inserted node")
- }
- }
-}
-
-func TestTrieTracerNoop(t *testing.T) {
- trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase()))
- trie.tracer = newTracer()
-
- // Insert a batch of entries, all the nodes should be marked as inserted
- vals := []struct{ k, v string }{
- {"do", "verb"},
- {"ether", "wookiedoo"},
- {"horse", "stallion"},
- {"shaman", "horse"},
- {"doge", "coin"},
- {"dog", "puppy"},
- {"somethingveryoddindeedthis is", "myothernodedata"},
- }
- for _, val := range vals {
- trie.Update([]byte(val.k), []byte(val.v))
- }
- for _, val := range vals {
- trie.Delete([]byte(val.k))
- }
- if len(trie.tracer.insertList()) != 0 {
- t.Fatalf("Unexpected inserted node tracked %d", len(trie.tracer.insertList()))
- }
- if len(trie.tracer.deleteList()) != 0 {
- t.Fatalf("Unexpected deleted node tracked %d", len(trie.tracer.deleteList()))
- }
-}
-
-func TestTrieTracePrevValue(t *testing.T) {
- db := NewDatabase(rawdb.NewMemoryDatabase())
- trie := NewEmpty(db)
- trie.tracer = newTracer()
-
- paths, blobs := trie.tracer.prevList()
- if len(paths) != 0 || len(blobs) != 0 {
- t.Fatalf("Nothing should be tracked")
- }
- // Insert a batch of entries, all the nodes should be marked as inserted
- vals := []struct{ k, v string }{
- {"do", "verb"},
- {"ether", "wookiedoo"},
- {"horse", "stallion"},
- {"shaman", "horse"},
- {"doge", "coin"},
- {"dog", "puppy"},
- {"somethingveryoddindeedthis is", "myothernodedata"},
- }
- for _, val := range vals {
- trie.Update([]byte(val.k), []byte(val.v))
- }
- paths, blobs = trie.tracer.prevList()
- if len(paths) != 0 || len(blobs) != 0 {
- t.Fatalf("Nothing should be tracked")
- }
-
- // Commit the changes and re-create with new root
- root, nodes := trie.Commit(false)
- if err := db.Update(NewWithNodeSet(nodes)); err != nil {
- t.Fatal(err)
- }
- trie, _ = New(TrieID(root), db)
- trie.tracer = newTracer()
- trie.resolveAndTrack(root.Bytes(), nil)
-
- // Load all nodes in trie
- for _, val := range vals {
- trie.TryGet([]byte(val.k))
- }
-
- // Ensure all nodes are tracked by tracer with correct prev-values
- iter := trie.NodeIterator(nil)
- seen := make(map[string][]byte)
- for iter.Next(true) {
- // Embedded nodes are ignored since they are not present in
- // database.
- if iter.Hash() == (common.Hash{}) {
- continue
- }
- seen[string(iter.Path())] = common.CopyBytes(iter.NodeBlob())
- }
-
- paths, blobs = trie.tracer.prevList()
- if len(paths) != len(seen) || len(blobs) != len(seen) {
- t.Fatalf("Unexpected tracked values")
- }
- for i, path := range paths {
- blob := blobs[i]
- prev, ok := seen[string(path)]
- if !ok {
- t.Fatalf("Missing node %v", path)
- }
- if !bytes.Equal(blob, prev) {
- t.Fatalf("Unexpected value path: %v, want: %v, got: %v", path, prev, blob)
- }
- }
-
- // Re-open the trie and iterate the trie, ensure nothing will be tracked.
- // Iterator will not link any loaded nodes to trie.
- trie, _ = New(TrieID(root), db)
- trie.tracer = newTracer()
-
- iter = trie.NodeIterator(nil)
- for iter.Next(true) {
- }
- paths, blobs = trie.tracer.prevList()
- if len(paths) != 0 || len(blobs) != 0 {
- t.Fatalf("Nothing should be tracked")
- }
-
- // Re-open the trie and generate proof for entries, ensure nothing will
- // be tracked. Prover will not link any loaded nodes to trie.
- trie, _ = New(TrieID(root), db)
- trie.tracer = newTracer()
- for _, val := range vals {
- trie.Prove([]byte(val.k), 0, rawdb.NewMemoryDatabase())
- }
- paths, blobs = trie.tracer.prevList()
- if len(paths) != 0 || len(blobs) != 0 {
- t.Fatalf("Nothing should be tracked")
- }
-
- // Delete entries from trie, ensure all previous values are correct.
- trie, _ = New(TrieID(root), db)
- trie.tracer = newTracer()
- trie.resolveAndTrack(root.Bytes(), nil)
-
- for _, val := range vals {
- trie.TryDelete([]byte(val.k))
- }
- paths, blobs = trie.tracer.prevList()
- if len(paths) != len(seen) || len(blobs) != len(seen) {
- t.Fatalf("Unexpected tracked values")
- }
- for i, path := range paths {
- blob := blobs[i]
- prev, ok := seen[string(path)]
- if !ok {
- t.Fatalf("Missing node %v", path)
- }
- if !bytes.Equal(blob, prev) {
- t.Fatalf("Unexpected value path: %v, want: %v, got: %v", path, prev, blob)
- }
- }
-}
-
-func TestDeleteAll(t *testing.T) {
- db := NewDatabase(rawdb.NewMemoryDatabase())
- trie := NewEmpty(db)
- trie.tracer = newTracer()
-
- // Insert a batch of entries, all the nodes should be marked as inserted
- vals := []struct{ k, v string }{
- {"do", "verb"},
- {"ether", "wookiedoo"},
- {"horse", "stallion"},
- {"shaman", "horse"},
- {"doge", "coin"},
- {"dog", "puppy"},
- {"somethingveryoddindeedthis is", "myothernodedata"},
- }
- for _, val := range vals {
- trie.Update([]byte(val.k), []byte(val.v))
- }
- root, set := trie.Commit(false)
- if err := db.Update(NewWithNodeSet(set)); err != nil {
- t.Fatal(err)
- }
- // Delete entries from trie, ensure all values are detected
- trie, _ = New(TrieID(root), db)
- trie.tracer = newTracer()
- trie.resolveAndTrack(root.Bytes(), nil)
-
- // Iterate all existent nodes
- var (
- it = trie.NodeIterator(nil)
- nodes = make(map[string][]byte)
- )
- for it.Next(true) {
- if it.Hash() != (common.Hash{}) {
- nodes[string(it.Path())] = common.CopyBytes(it.NodeBlob())
- }
- }
-
- // Perform deletion to purge the entire trie
- for _, val := range vals {
- trie.Delete([]byte(val.k))
- }
- root, set = trie.Commit(false)
- if root != types.EmptyRootHash {
- t.Fatalf("Invalid trie root %v", root)
- }
- for path, blob := range set.deletes {
- prev, ok := nodes[path]
- if !ok {
- t.Fatalf("Extra node deleted %v", []byte(path))
- }
- if !bytes.Equal(prev, blob) {
- t.Fatalf("Unexpected previous value %v", []byte(path))
- }
- }
- if len(set.deletes) != len(nodes) {
- t.Fatalf("Unexpected deletion set")
- }
-}
diff --git a/trie/utils.go b/trie/utils.go
deleted file mode 100644
index 5dce65cd2..000000000
--- a/trie/utils.go
+++ /dev/null
@@ -1,199 +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
-
-// 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.Committer. Thus, these deleted
-// nodes won't be removed from the disk at all. Tracer is an auxiliary tool
-// used to track all insert and delete operations of trie and capture all
-// deleted nodes eventually.
-//
-// The changed nodes can be mainly divided into two categories: the leaf
-// node and intermediate node. The former is inserted/deleted by callers
-// while the latter is inserted/deleted in order to follow the rule of trie.
-// This tool can track all of them no matter the node is embedded in its
-// parent or not, but valueNode is never tracked.
-//
-// Besides, it's also used for recording the original value of the nodes
-// when they are resolved from the disk. The pre-value of the nodes will
-// be used to construct reverse-diffs in the future.
-//
-// Note tracer is not thread-safe, callers should be responsible for handling
-// the concurrency issues by themselves.
-type tracer struct {
- insert map[string]struct{}
- delete map[string]struct{}
- origin map[string][]byte
-}
-
-// newTracer initializes the tracer for capturing trie changes.
-func newTracer() *tracer {
- return &tracer{
- insert: make(map[string]struct{}),
- delete: make(map[string]struct{}),
- origin: make(map[string][]byte),
- }
-}
-
-// onRead tracks the newly loaded trie node and caches the rlp-encoded blob internally.
-// Don't change the value outside of function since it's not deep-copied.
-func (t *tracer) onRead(path []byte, val []byte) {
- // Tracer isn't used right now, remove this check later.
- if t == nil {
- return
- }
- t.origin[string(path)] = val
-}
-
-// onInsert tracks the newly inserted trie node. If it's already in the deletion set
-// (resurrected node), then just wipe it from the deletion set as the "untouched".
-func (t *tracer) onInsert(path []byte) {
- // Tracer isn't used right now, remove this check later.
- if t == nil {
- return
- }
- if _, present := t.delete[string(path)]; present {
- delete(t.delete, string(path))
- return
- }
- t.insert[string(path)] = struct{}{}
-}
-
-// onDelete tracks the newly deleted trie node. If it's already
-// in the addition set, then just wipe it from the addition set
-// as it's untouched.
-func (t *tracer) onDelete(path []byte) {
- // Tracer isn't used right now, remove this check later.
- if t == nil {
- return
- }
- if _, present := t.insert[string(path)]; present {
- delete(t.insert, string(path))
- return
- }
- t.delete[string(path)] = struct{}{}
-}
-
-// insertList returns the tracked inserted trie nodes in list format.
-func (t *tracer) insertList() [][]byte {
- // Tracer isn't used right now, remove this check later.
- if t == nil {
- return nil
- }
- var ret [][]byte
- for path := range t.insert {
- ret = append(ret, []byte(path))
- }
- return ret
-}
-
-// deleteList returns the tracked deleted trie nodes in list format.
-func (t *tracer) deleteList() [][]byte {
- // Tracer isn't used right now, remove this check later.
- if t == nil {
- return nil
- }
- var ret [][]byte
- for path := range t.delete {
- ret = append(ret, []byte(path))
- }
- return ret
-}
-
-// prevList returns the tracked node blobs in list format.
-func (t *tracer) prevList() ([][]byte, [][]byte) {
- // Tracer isn't used right now, remove this check later.
- if t == nil {
- return nil, nil
- }
- var (
- paths [][]byte
- blobs [][]byte
- )
- for path, blob := range t.origin {
- paths = append(paths, []byte(path))
- blobs = append(blobs, blob)
- }
- return paths, blobs
-}
-
-// getPrev returns the cached original value of the specified node.
-func (t *tracer) getPrev(path []byte) []byte {
- // Tracer isn't used right now, remove this check later.
- if t == nil {
- return nil
- }
- return t.origin[string(path)]
-}
-
-// reset clears the content tracked by tracer.
-func (t *tracer) reset() {
- // Tracer isn't used right now, remove this check later.
- if t == nil {
- return
- }
- t.insert = make(map[string]struct{})
- t.delete = make(map[string]struct{})
- t.origin = make(map[string][]byte)
-}
-
-// copy returns a deep copied tracer instance.
-func (t *tracer) copy() *tracer {
- // Tracer isn't used right now, remove this check later.
- if t == nil {
- return nil
- }
- var (
- insert = make(map[string]struct{})
- delete = make(map[string]struct{})
- origin = make(map[string][]byte)
- )
- for key := range t.insert {
- insert[key] = struct{}{}
- }
- for key := range t.delete {
- delete[key] = struct{}{}
- }
- for key, val := range t.origin {
- origin[key] = val
- }
- return &tracer{
- insert: insert,
- delete: delete,
- origin: origin,
- }
-}
-
-// markDeletions puts all tracked deletions into the provided nodeset.
-func (t *tracer) markDeletions(set *NodeSet) {
- // Tracer isn't used right now, remove this check later.
- if t == nil {
- return
- }
- for _, path := range t.deleteList() {
- // There are a few possibilities for this scenario(the node is deleted
- // but not present in database previously), for example the node was
- // embedded in the parent and now deleted from the trie. In this case
- // it's noop from database's perspective.
- val := t.getPrev(path)
- if len(val) == 0 {
- continue
- }
- set.markDeleted(path, val)
- }
-}