Skip to content

Commit

Permalink
feat: Support Empty Hashes and Add constructor (#11)
Browse files Browse the repository at this point in the history
* Export siblings

* Add deepsubtree constructor

* Support empty root hashes

* Use working hash instead of root.hash
  • Loading branch information
Manav-Aggarwal authored Nov 1, 2022
1 parent e5b57bc commit bdca532
Show file tree
Hide file tree
Showing 2 changed files with 118 additions and 39 deletions.
67 changes: 47 additions & 20 deletions deepsubtree.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
}
Expand All @@ -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
}
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
}
Expand Down
90 changes: 71 additions & 19 deletions deepsubtree_test.go
Original file line number Diff line number Diff line change
@@ -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) {
Expand All @@ -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{
Expand All @@ -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
Expand Down Expand Up @@ -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}
Expand All @@ -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 {
Expand All @@ -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)
}
}

Expand All @@ -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}
Expand Down Expand Up @@ -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
Expand All @@ -174,26 +220,32 @@ 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
for i := range keysToAdd {
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)
}
}

0 comments on commit bdca532

Please sign in to comment.