trie: wrap deletion in case trie.root is nil (#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).
This commit is contained in:
		
							parent
							
								
									f53ff0ff4a
								
							
						
					
					
						commit
						d3411b9f67
					
				| @ -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 | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -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.
 | ||||
|  | ||||
| @ -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") | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @ -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) | ||||
| 	} | ||||
| } | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user