forked from cerc-io/plugeth
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 {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
// Some nodes can be deleted from trie which can't be captured by committer
|
// Some nodes can be deleted from trie which can't be captured
|
||||||
// itself. Iterate all deleted nodes tracked by tracer and marked them as
|
// by committer itself. Iterate all deleted nodes tracked by
|
||||||
// deleted only if they are present in database previously.
|
// tracer and marked them as deleted only if they are present
|
||||||
for _, path := range c.tracer.deleteList() {
|
// in database previously.
|
||||||
// There are a few possibilities for this scenario(the node is deleted
|
c.tracer.markDeletions(c.nodes)
|
||||||
// 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)
|
|
||||||
}
|
|
||||||
return h.(hashNode), c.nodes, nil
|
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) {
|
func (t *Trie) Commit(collectLeaf bool) (common.Hash, *NodeSet, error) {
|
||||||
defer t.tracer.reset()
|
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 {
|
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
|
// Derive the hash for all dirty nodes first. We hold the assumption
|
||||||
// in the following procedure that all nodes are hashed.
|
// 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,
|
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