Skip to content

trie: wrap deletion in case trie.root is nil #26365

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Dec 19, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 5 additions & 14 deletions trie/committer.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,20 +53,11 @@ func (c *committer) Commit(n node) (hashNode, *NodeSet, error) {
if err != nil {
return nil, nil, err
}
// 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.
for _, path := range c.tracer.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 := c.tracer.getPrev(path)
if len(val) == 0 {
continue
}
c.nodes.markDeleted(path, val)
}
// 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, nil
}

Expand Down
8 changes: 7 additions & 1 deletion trie/trie.go
Original file line number Diff line number Diff line change
Expand Up @@ -569,8 +569,14 @@ func (t *Trie) Hash() common.Hash {
func (t *Trie) Commit(collectLeaf bool) (common.Hash, *NodeSet, error) {
defer t.tracer.reset()

// 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 {
return emptyRoot, nil, nil
// Wrap tracked deletions as the return
set := NewNodeSet(t.owner)
t.tracer.markDeletions(set)
return emptyRoot, set, nil
}
// Derive the hash for all dirty nodes first. We hold the assumption
// in the following procedure that all nodes are hashed.
Expand Down
66 changes: 66 additions & 0 deletions trie/util_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -242,3 +242,69 @@ func TestTrieTracePrevValue(t *testing.T) {
}
}
}

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, err := trie.Commit(false)
if err != nil {
t.Fatal(err)
}
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, err = trie.Commit(false)
if err != nil {
t.Fatalf("Failed to delete trie %v", err)
}
if root != emptyRoot {
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")
}
}
19 changes: 19 additions & 0 deletions trie/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -178,3 +178,22 @@ func (t *tracer) copy() *tracer {
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)
}
}