Skip to content

Commit 98a772f

Browse files
rjl493456442shekhirin
authored andcommitted
trie: wrap deletion in case trie.root is nil (ethereum#26365)
This PR fixes an error in trie commit. If the trie.root is nil, it can be two possible scenarios: - The trie was empty, and no change happens - The trie was non-empty and all nodes are dropped For the latter one, we should collect the deletions and apply them into database(e.g. in PBSS).
1 parent ed54cae commit 98a772f

File tree

4 files changed

+97
-15
lines changed

4 files changed

+97
-15
lines changed

trie/committer.go

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -53,20 +53,11 @@ func (c *committer) Commit(n node) (hashNode, *NodeSet, error) {
5353
if err != nil {
5454
return nil, nil, err
5555
}
56-
// Some nodes can be deleted from trie which can't be captured by committer
57-
// itself. Iterate all deleted nodes tracked by tracer and marked them as
58-
// deleted only if they are present in database previously.
59-
for _, path := range c.tracer.deleteList() {
60-
// There are a few possibilities for this scenario(the node is deleted
61-
// but not present in database previously), for example the node was
62-
// embedded in the parent and now deleted from the trie. In this case
63-
// it's noop from database's perspective.
64-
val := c.tracer.getPrev(path)
65-
if len(val) == 0 {
66-
continue
67-
}
68-
c.nodes.markDeleted(path, val)
69-
}
56+
// Some nodes can be deleted from trie which can't be captured
57+
// by committer itself. Iterate all deleted nodes tracked by
58+
// tracer and marked them as deleted only if they are present
59+
// in database previously.
60+
c.tracer.markDeletions(c.nodes)
7061
return h.(hashNode), c.nodes, nil
7162
}
7263

trie/trie.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -569,8 +569,14 @@ func (t *Trie) Hash() common.Hash {
569569
func (t *Trie) Commit(collectLeaf bool) (common.Hash, *NodeSet, error) {
570570
defer t.tracer.reset()
571571

572+
// Trie is empty and can be classified into two types of situations:
573+
// - The trie was empty and no update happens
574+
// - The trie was non-empty and all nodes are dropped
572575
if t.root == nil {
573-
return emptyRoot, nil, nil
576+
// Wrap tracked deletions as the return
577+
set := NewNodeSet(t.owner)
578+
t.tracer.markDeletions(set)
579+
return emptyRoot, set, nil
574580
}
575581
// Derive the hash for all dirty nodes first. We hold the assumption
576582
// in the following procedure that all nodes are hashed.

trie/util_test.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,3 +242,69 @@ func TestTrieTracePrevValue(t *testing.T) {
242242
}
243243
}
244244
}
245+
246+
func TestDeleteAll(t *testing.T) {
247+
db := NewDatabase(rawdb.NewMemoryDatabase())
248+
trie := NewEmpty(db)
249+
trie.tracer = newTracer()
250+
251+
// Insert a batch of entries, all the nodes should be marked as inserted
252+
vals := []struct{ k, v string }{
253+
{"do", "verb"},
254+
{"ether", "wookiedoo"},
255+
{"horse", "stallion"},
256+
{"shaman", "horse"},
257+
{"doge", "coin"},
258+
{"dog", "puppy"},
259+
{"somethingveryoddindeedthis is", "myothernodedata"},
260+
}
261+
for _, val := range vals {
262+
trie.Update([]byte(val.k), []byte(val.v))
263+
}
264+
root, set, err := trie.Commit(false)
265+
if err != nil {
266+
t.Fatal(err)
267+
}
268+
if err := db.Update(NewWithNodeSet(set)); err != nil {
269+
t.Fatal(err)
270+
}
271+
// Delete entries from trie, ensure all values are detected
272+
trie, _ = New(TrieID(root), db)
273+
trie.tracer = newTracer()
274+
trie.resolveAndTrack(root.Bytes(), nil)
275+
276+
// Iterate all existent nodes
277+
var (
278+
it = trie.NodeIterator(nil)
279+
nodes = make(map[string][]byte)
280+
)
281+
for it.Next(true) {
282+
if it.Hash() != (common.Hash{}) {
283+
nodes[string(it.Path())] = common.CopyBytes(it.NodeBlob())
284+
}
285+
}
286+
287+
// Perform deletion to purge the entire trie
288+
for _, val := range vals {
289+
trie.Delete([]byte(val.k))
290+
}
291+
root, set, err = trie.Commit(false)
292+
if err != nil {
293+
t.Fatalf("Failed to delete trie %v", err)
294+
}
295+
if root != emptyRoot {
296+
t.Fatalf("Invalid trie root %v", root)
297+
}
298+
for path, blob := range set.deletes {
299+
prev, ok := nodes[path]
300+
if !ok {
301+
t.Fatalf("Extra node deleted %v", []byte(path))
302+
}
303+
if !bytes.Equal(prev, blob) {
304+
t.Fatalf("Unexpected previous value %v", []byte(path))
305+
}
306+
}
307+
if len(set.deletes) != len(nodes) {
308+
t.Fatalf("Unexpected deletion set")
309+
}
310+
}

trie/utils.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,3 +178,22 @@ func (t *tracer) copy() *tracer {
178178
origin: origin,
179179
}
180180
}
181+
182+
// markDeletions puts all tracked deletions into the provided nodeset.
183+
func (t *tracer) markDeletions(set *NodeSet) {
184+
// Tracer isn't used right now, remove this check later.
185+
if t == nil {
186+
return
187+
}
188+
for _, path := range t.deleteList() {
189+
// There are a few possibilities for this scenario(the node is deleted
190+
// but not present in database previously), for example the node was
191+
// embedded in the parent and now deleted from the trie. In this case
192+
// it's noop from database's perspective.
193+
val := t.getPrev(path)
194+
if len(val) == 0 {
195+
continue
196+
}
197+
set.markDeleted(path, val)
198+
}
199+
}

0 commit comments

Comments
 (0)