From 4d1f76a1878d3fa58e10c7ef82f7388382656ea0 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Wed, 24 Jul 2024 17:34:51 +0200 Subject: [PATCH 1/3] add DeleteAtStem command for state rollbacks --- doc.go | 2 ++ tree.go | 69 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+) diff --git a/doc.go b/doc.go index 8e8d1f04..258ec2c1 100644 --- a/doc.go +++ b/doc.go @@ -30,6 +30,8 @@ import "errors" var ( errInsertIntoHash = errors.New("trying to insert into hashed node") errDeleteHash = errors.New("trying to delete from a hashed subtree") + errDeleteMissing = errors.New("trying to delete a missing group") + errDeleteUnknown = errors.New("trying to delete an out-of-view node") errReadFromInvalid = errors.New("trying to read from an invalid child") errSerializeHashedNode = errors.New("trying to serialize a hashed internal node") errInsertIntoOtherStem = errors.New("insert splits a stem where it should not happen") diff --git a/tree.go b/tree.go index f0788164..1e7ad133 100644 --- a/tree.go +++ b/tree.go @@ -631,6 +631,75 @@ func (n *InternalNode) Delete(key []byte, resolver NodeResolverFn) (bool, error) } } +func (n *InternalNode) DeleteAtStem(key []byte, resolver NodeResolverFn) (bool, error) { + nChild := offset2key(key, n.depth) + switch child := n.children[nChild].(type) { + case Empty: + return false, nil + case HashedNode: + if resolver == nil { + return false, errDeleteHash + } + payload, err := resolver(key[:n.depth+1]) + if err != nil { + return false, err + } + // deserialize the payload and set it as the child + c, err := ParseNode(payload, n.depth+1) + if err != nil { + return false, err + } + n.children[nChild] = c + return n.DeleteAtStem(key, resolver) + case *LeafNode: + if !bytes.Equal(child.stem, key[:31]) { + return false, errDeleteMissing + } + + n.cowChild(nChild) + n.children[nChild] = Empty{} + + // Check if all children are gone, if so + // signal that this node should be deleted + // as well. + for _, c := range n.children { + if _, ok := c.(Empty); !ok { + return false, nil + } + } + + return true, nil + case *InternalNode: + n.cowChild(nChild) + del, err := child.DeleteAtStem(key, resolver) + if err != nil { + return false, err + } + + // delete the entire child if instructed to by + // the recursive algorigthm. + if del { + n.children[nChild] = Empty{} + + // Check if all children are gone, if so + // signal that this node should be deleted + // as well. + for _, c := range n.children { + if _, ok := c.(Empty); !ok { + return false, nil + } + } + + return true, nil + } + + return false, nil + default: + // only unknown nodes are left + return false, errDeleteUnknown + } +} + // Flush hashes the children of an internal node and replaces them // with HashedNode. It also sends the current node on the flush channel. func (n *InternalNode) Flush(flush NodeFlushFn) { From b9f52b9482f94f00e84bf3fe37642f93b0cb5a66 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Fri, 26 Jul 2024 16:32:02 +0200 Subject: [PATCH 2/3] review feedback --- tree.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tree.go b/tree.go index 1e7ad133..e3a20f22 100644 --- a/tree.go +++ b/tree.go @@ -631,11 +631,14 @@ func (n *InternalNode) Delete(key []byte, resolver NodeResolverFn) (bool, error) } } +// DeleteAtStem delete a full stem. Unlike Delete, it will error out if the stem that is to +// be deleted does not exist in the tree, because it's meant to be used by rollback code, +// that should only delete things that exist. func (n *InternalNode) DeleteAtStem(key []byte, resolver NodeResolverFn) (bool, error) { nChild := offset2key(key, n.depth) switch child := n.children[nChild].(type) { case Empty: - return false, nil + return false, errDeleteMissing case HashedNode: if resolver == nil { return false, errDeleteHash From 7dc5142667fa25ab8b30fd51ca542c410cf173f7 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Fri, 26 Jul 2024 16:39:12 +0200 Subject: [PATCH 3/3] add a test --- tree_test.go | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/tree_test.go b/tree_test.go index 594d6a75..7ee3ceff 100644 --- a/tree_test.go +++ b/tree_test.go @@ -357,6 +357,53 @@ func TestDelLeaf(t *testing.T) { // skipcq: GO-R1005 } } +func TestDeleteAtStem(t *testing.T) { + t.Parallel() + + key1, _ := hex.DecodeString("0105000000000000000000000000000000000000000000000000000000000000") + key1p, _ := hex.DecodeString("0105000000000000000000000000000000000000000000000000000000000001") // same Cn group as key1 + key1pp, _ := hex.DecodeString("0105000000000000000000000000000000000000000000000000000000000081") // Other Cn group as key1 + key2, _ := hex.DecodeString("0107000000000000000000000000000000000000000000000000000000000000") + tree := New() + if err := tree.Insert(key1, fourtyKeyTest, nil); err != nil { + t.Fatalf("inserting into the original failed: %v", err) + } + if err := tree.Insert(key1p, fourtyKeyTest, nil); err != nil { + t.Fatalf("inserting into the original failed: %v", err) + } + if err := tree.Insert(key1pp, fourtyKeyTest, nil); err != nil { + t.Fatalf("inserting into the original failed: %v", err) + } + if err := tree.Insert(key2, fourtyKeyTest, nil); err != nil { + t.Fatalf("inserting into the original failed: %v", err) + } + var init Point + init.Set(tree.Commit()) + + if _, err := tree.(*InternalNode).DeleteAtStem(key1[:31], nil); err != err { + t.Error(err) + } + + res, err := tree.Get(key1, nil) + if err != nil { + t.Error(err) + } + if len(res) > 0 { + t.Error("leaf hasnt been deleted") + } + res, err = tree.Get(key1pp, nil) + if err != nil { + t.Error(err) + } + if len(res) > 0 { + t.Error("leaf hasnt been deleted") + } + + if _, err := tree.(*InternalNode).DeleteAtStem(zeroKeyTest[:31], nil); err != errDeleteMissing { + t.Fatal(err) + } +} + func TestDeleteNonExistent(t *testing.T) { t.Parallel()