From bdca53299580f040d4e434a4e89071b86a3aab34 Mon Sep 17 00:00:00 2001 From: Manav Aggarwal Date: Tue, 1 Nov 2022 16:22:06 +0100 Subject: [PATCH] feat: Support Empty Hashes and Add constructor (#11) * Export siblings * Add deepsubtree constructor * Support empty root hashes * Use working hash instead of root.hash --- deepsubtree.go | 67 +++++++++++++++++++++++---------- deepsubtree_test.go | 90 +++++++++++++++++++++++++++++++++++---------- 2 files changed, 118 insertions(+), 39 deletions(-) diff --git a/deepsubtree.go b/deepsubtree.go index fb68b09da..206e037bf 100644 --- a/deepsubtree.go +++ b/deepsubtree.go @@ -8,6 +8,8 @@ import ( "strings" ics23 "github.com/confio/ics23/go" + dbm "github.com/cosmos/cosmos-db" + "github.com/cosmos/iavl/fastnode" ) // Represents a IAVL Deep Subtree that can contain @@ -18,8 +20,26 @@ type DeepSubTree struct { type DSTNonExistenceProof struct { *ics23.NonExistenceProof - leftSiblingProof *ics23.ExistenceProof - rightSiblingProof *ics23.ExistenceProof + LeftSiblingProof *ics23.ExistenceProof + RightSiblingProof *ics23.ExistenceProof +} + +// NewDeepSubTree returns a new deep subtree with the specified cache size, datastore, and version. +func NewDeepSubTree(db dbm.DB, cacheSize int, skipFastStorageUpgrade bool, version int64) (*DeepSubTree, error) { + ndb := newNodeDB(db, cacheSize, nil) + head := &ImmutableTree{ndb: ndb, version: version} + mutableTree := &MutableTree{ + ImmutableTree: head, + lastSaved: head.clone(), + orphans: map[string]int64{}, + versions: map[int64]bool{}, + allRootLoaded: false, + unsavedFastNodeAdditions: make(map[string]*fastnode.Node), + unsavedFastNodeRemovals: make(map[string]interface{}), + ndb: ndb, + skipFastStorageUpgrade: skipFastStorageUpgrade, + } + return &DeepSubTree{MutableTree: mutableTree}, nil } // Constructs a DSTNonExistenceProof using an ICS23 Non-Existence proof @@ -36,7 +56,7 @@ func ConvertToDSTNonExistenceProof( if err != nil { return nil, err } - dstNonExistenceProof.leftSiblingProof, err = tree.createExistenceProof(leftSibling.key) + dstNonExistenceProof.LeftSiblingProof, err = tree.createExistenceProof(leftSibling.key) if err != nil { return nil, err } @@ -46,7 +66,7 @@ func ConvertToDSTNonExistenceProof( if err != nil { return nil, err } - dstNonExistenceProof.rightSiblingProof, err = tree.createExistenceProof(rightSibling.key) + dstNonExistenceProof.RightSiblingProof, err = tree.createExistenceProof(rightSibling.key) if err != nil { return nil, err } @@ -106,19 +126,26 @@ func (node *Node) updateInnerNodeKey() { // and links them together using the populated left and right // hashes and sets the root to be the node with the given rootHash func (dst *DeepSubTree) BuildTree(rootHash []byte) error { - if dst.root == nil { - rootNode, rootErr := dst.ndb.GetNode(rootHash) - if rootErr != nil { - return fmt.Errorf("could not set root of deep subtree: %w", rootErr) - } - dst.root = rootNode - } else if !bytes.Equal(dst.root.hash, rootHash) { - return fmt.Errorf( - "deep Subtree rootHash: %s does not match expected rootHash: %s", - dst.root.hash, - rootHash, - ) + workingHash, err := dst.WorkingHash() + if err != nil { + return err } + if !bytes.Equal(workingHash, rootHash) { + if dst.root == nil { + rootNode, rootErr := dst.ndb.GetNode(rootHash) + if rootErr != nil { + return fmt.Errorf("could not set root of deep subtree: %w", rootErr) + } + dst.root = rootNode + } else { + return fmt.Errorf( + "deep Subtree rootHash: %s does not match expected rootHash: %s", + workingHash, + rootHash, + ) + } + } + nodes, traverseErr := dst.ndb.nodes() if traverseErr != nil { return fmt.Errorf("could not traverse nodedb: %w", traverseErr) @@ -481,14 +508,14 @@ func (dst *DeepSubTree) AddNonExistenceProof(nonExistProof *DSTNonExistenceProof return err } } - if nonExistProof.leftSiblingProof != nil { - err := dst.AddExistenceProof(nonExistProof.leftSiblingProof) + if nonExistProof.LeftSiblingProof != nil { + err := dst.AddExistenceProof(nonExistProof.LeftSiblingProof) if err != nil { return err } } - if nonExistProof.rightSiblingProof != nil { - err := dst.AddExistenceProof(nonExistProof.rightSiblingProof) + if nonExistProof.RightSiblingProof != nil { + err := dst.AddExistenceProof(nonExistProof.RightSiblingProof) if err != nil { return err } diff --git a/deepsubtree_test.go b/deepsubtree_test.go index 5c7b84a33..5b4d41dd2 100644 --- a/deepsubtree_test.go +++ b/deepsubtree_test.go @@ -1,12 +1,52 @@ package iavl import ( + "bytes" "testing" db "github.com/cosmos/cosmos-db" "github.com/stretchr/testify/require" ) +// Returns whether given trees have equal hashes +func haveEqualRoots(tree1 *MutableTree, tree2 *MutableTree) (bool, error) { + rootHash, err := tree1.WorkingHash() + if err != nil { + return false, err + } + + treeWorkingHash, err := tree2.WorkingHash() + if err != nil { + return false, err + } + + // Check root hashes are equal + return bytes.Equal(rootHash, treeWorkingHash), nil +} + +// Tests creating an empty Deep Subtree +func TestEmptyDeepSubtree(t *testing.T) { + require := require.New(t) + getTree := func() *MutableTree { + tree, err := getTestTree(0) + require.NoError(err) + return tree + } + + tree := getTree() + rootHash, err := tree.WorkingHash() + require.NoError(err) + + dst, err := NewDeepSubTree(db.NewMemDB(), 100, false, 0) + require.NoError(err) + err = dst.BuildTree(rootHash) + require.NoError(err) + + areEqual, err := haveEqualRoots(dst.MutableTree, tree) + require.NoError(err) + require.True(areEqual) +} + // Tests creating a Deep Subtree step by step // as a full IAVL tree and checks if roots are equal func TestDeepSubtreeStepByStep(t *testing.T) { @@ -27,11 +67,11 @@ func TestDeepSubtreeStepByStep(t *testing.T) { } tree := getTree() - rootHash := tree.root.hash + rootHash, err := tree.WorkingHash() + require.NoError(err) - mutableTree, err := NewMutableTree(db.NewMemDB(), 100, false) + dst, err := NewDeepSubTree(db.NewMemDB(), 100, false, 0) require.NoError(err) - dst := DeepSubTree{mutableTree} // insert key/value pairs in tree allkeys := [][]byte{ @@ -49,8 +89,9 @@ func TestDeepSubtreeStepByStep(t *testing.T) { require.NoError(err) } - // Check root hashes are equal - require.Equal(dst.root.hash, tree.root.hash) + areEqual, err := haveEqualRoots(dst.MutableTree, tree) + require.NoError(err) + require.True(areEqual) } // Tests updating the deepsubtree returns the @@ -84,7 +125,8 @@ func TestDeepSubtreeWithUpdates(t *testing.T) { for _, subsetKeys := range testCases { tree := getTree() - rootHash := tree.root.hash + rootHash, err := tree.WorkingHash() + require.NoError(err) mutableTree, err := NewMutableTree(db.NewMemDB(), 100, false) require.NoError(err) dst := DeepSubTree{mutableTree} @@ -98,8 +140,9 @@ func TestDeepSubtreeWithUpdates(t *testing.T) { require.NoError(err) dst.SaveVersion() - // Check root hashes are equal - require.Equal(dst.root.hash, tree.root.hash) + areEqual, err := haveEqualRoots(dst.MutableTree, tree) + require.NoError(err) + require.True(areEqual) values := [][]byte{{10}, {20}} for i, subsetKey := range subsetKeys { @@ -109,8 +152,9 @@ func TestDeepSubtreeWithUpdates(t *testing.T) { tree.SaveVersion() } - // Check root hashes are equal - require.Equal(dst.root.hash, tree.root.hash) + areEqual, err = haveEqualRoots(dst.MutableTree, tree) + require.NoError(err) + require.True(areEqual) } } @@ -134,7 +178,8 @@ func TestDeepSubtreeWWithAddsAndDeletes(t *testing.T) { subsetKeys := [][]byte{ []byte("b"), } - rootHash := tree.root.hash + rootHash, err := tree.WorkingHash() + require.NoError(err) mutableTree, err := NewMutableTree(db.NewMemDB(), 100, false) require.NoError(err) dst := DeepSubTree{mutableTree} @@ -164,8 +209,9 @@ func TestDeepSubtreeWWithAddsAndDeletes(t *testing.T) { } dst.SaveVersion() - // Check root hashes are equal - require.Equal(dst.root.hash, tree.root.hash) + areEqual, err := haveEqualRoots(dst.MutableTree, tree) + require.NoError(err) + require.True(areEqual) require.Equal(len(keysToAdd), len(valuesToAdd)) // Add all the keys we intend to add and check root hashes stay equal @@ -174,13 +220,16 @@ func TestDeepSubtreeWWithAddsAndDeletes(t *testing.T) { valueToAdd := valuesToAdd[i] dst.Set(keyToAdd, valueToAdd) dst.SaveVersion() - err = dst.BuildTree(dst.root.hash) + rootHash, err := dst.WorkingHash() + require.NoError(err) + err = dst.BuildTree(rootHash) require.NoError(err) tree.Set(keyToAdd, valueToAdd) tree.SaveVersion() - // Check root hashes are equal - require.Equal(dst.root.hash, tree.root.hash) + areEqual, err := haveEqualRoots(dst.MutableTree, tree) + require.NoError(err) + require.True(areEqual) } // Delete all the keys we added and check root hashes stay equal @@ -188,12 +237,15 @@ func TestDeepSubtreeWWithAddsAndDeletes(t *testing.T) { keyToAdd := keysToAdd[i] dst.Remove(keyToAdd) dst.SaveVersion() - err = dst.BuildTree(dst.root.hash) + rootHash, err := dst.WorkingHash() + require.NoError(err) + err = dst.BuildTree(rootHash) require.NoError(err) tree.Remove(keyToAdd) tree.SaveVersion() - // Check root hashes are equal - require.Equal(dst.root.hash, tree.root.hash) + areEqual, err := haveEqualRoots(dst.MutableTree, tree) + require.NoError(err) + require.True(areEqual) } }