Skip to content

Commit a4fbda2

Browse files
trie: wrap deletion in case trie.root is nil ethereum#26365 (XinFinOrg#1124)
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). Co-authored-by: rjl493456442 <garyrong0905@gmail.com>
1 parent 39eb155 commit a4fbda2

File tree

4 files changed

+98
-15
lines changed

4 files changed

+98
-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
@@ -772,8 +772,14 @@ func (t *Trie) Hash() common.Hash {
772772
func (t *Trie) Commit(collectLeaf bool) (common.Hash, *NodeSet, error) {
773773
defer t.tracer.reset()
774774

775+
// Trie is empty and can be classified into two types of situations:
776+
// - The trie was empty and no update happens
777+
// - The trie was non-empty and all nodes are dropped
775778
if t.root == nil {
776-
return types.EmptyRootHash, nil, nil
779+
// Wrap tracked deletions as the return
780+
set := NewNodeSet(t.owner)
781+
t.tracer.markDeletions(set)
782+
return types.EmptyRootHash, set, nil
777783
}
778784
// Derive the hash for all dirty nodes first. We hold the assumption
779785
// in the following procedure that all nodes are hashed.

trie/util_test.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222

2323
"github.com/XinFinOrg/XDPoSChain/common"
2424
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
25+
"github.com/XinFinOrg/XDPoSChain/core/types"
2526
)
2627

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

trie/utils.go

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

0 commit comments

Comments
 (0)