diff --git a/basic_test.go b/basic_test.go index 44dbabd5a..94d4baf73 100644 --- a/basic_test.go +++ b/basic_test.go @@ -2,7 +2,6 @@ package iavl import ( - "bytes" "encoding/hex" mrand "math/rand" "sort" @@ -171,26 +170,6 @@ func TestBasic(t *testing.T) { } func TestUnit(t *testing.T) { - expectHash := func(tree *ImmutableTree, hashCount int64) { - // ensure number of new hash calculations is as expected. - hash, count, err := tree.root.hashWithCount() - require.NoError(t, err) - if count != hashCount { - t.Fatalf("Expected %v new hashes, got %v", hashCount, count) - } - // nuke hashes and reconstruct hash, ensure it's the same. - tree.root.traverse(tree, true, func(node *Node) bool { - node.hash = nil - return false - }) - // ensure that the new hash after nuking is the same as the old. - newHash, _, err := tree.root.hashWithCount() - require.NoError(t, err) - if !bytes.Equal(hash, newHash) { - t.Fatalf("Expected hash %v but got %v after nuking", hash, newHash) - } - } - expectSet := func(tree *MutableTree, i int, repr string, hashCount int64) { tree.SaveVersion() updated, err := tree.Set(i2b(i), []byte{}) @@ -200,8 +179,6 @@ func TestUnit(t *testing.T) { t.Fatalf("Adding %v to %v:\nExpected %v\nUnexpectedly got %v updated:%v", i, P(tree.lastSaved.root, tree.lastSaved), repr, P(tree.root, tree.ImmutableTree), updated) } - // ensure hash calculation requirements - expectHash(tree.ImmutableTree, hashCount) tree.ImmutableTree = tree.lastSaved.clone() } @@ -214,8 +191,6 @@ func TestUnit(t *testing.T) { t.Fatalf("Removing %v from %v:\nExpected %v\nUnexpectedly got %v value:%v removed:%v", i, P(tree.lastSaved.root, tree.lastSaved), repr, P(tree.root, tree.ImmutableTree), value, removed) } - // ensure hash calculation requirements - expectHash(tree.ImmutableTree, hashCount) tree.ImmutableTree = tree.lastSaved.clone() } diff --git a/cache/cache.go b/cache/cache.go index 5f7fcba20..650480ef3 100644 --- a/cache/cache.go +++ b/cache/cache.go @@ -6,29 +6,29 @@ import ( ibytes "github.com/cosmos/iavl/internal/bytes" ) -// Node[T] represents a node eligible for caching. -type Node[T any] interface { - GetKey() T +// Node represents a node eligible for caching. +type Node interface { + GetKey() []byte } // Cache is an in-memory structure to persist nodes for quick access. // Please see lruCache for more details about why we need a custom // cache implementation. -type Cache[T any] interface { +type Cache interface { // Adds node to cache. If full and had to remove the oldest element, // returns the oldest, otherwise nil. // CONTRACT: node can never be nil. Otherwise, cache panics. - Add(node Node[T]) Node[T] + Add(node Node) Node - // Returns Node[T] for the key, if exists. nil otherwise. - Get(key T) Node[T] + // Returns Node for the key, if exists. nil otherwise. + Get(key []byte) Node // Has returns true if node with key exists in cache, false otherwise. - Has(key T) bool + Has(key []byte) bool // Remove removes node with key from cache. The removed node is returned. // if not in cache, return nil. - Remove(key T) Node[T] + Remove(key []byte) Node // Len returns the cache length. Len() int @@ -44,33 +44,33 @@ type Cache[T any] interface { // The alternative implementations do not allow for // customization and the ability to estimate the byte // size of the cache. -type lruCache[K comparable, T any] struct { - dict map[K]*list.Element // FastNode cache. - maxElementCount int // FastNode the maximum number of nodes in the cache. - ll *list.List // LRU queue of cache elements. Used for deletion. +type lruCache struct { + dict map[string]*list.Element // FastNode cache. + maxElementCount int // FastNode the maximum number of nodes in the cache. + ll *list.List // LRU queue of cache elements. Used for deletion. } -var _ Cache[int] = (*lruCache[string, int])(nil) +var _ Cache = (*lruCache)(nil) -func New[K comparable, T any](maxElementCount int) Cache[T] { - return &lruCache[K, T]{ - dict: make(map[K]*list.Element), +func New(maxElementCount int) Cache { + return &lruCache{ + dict: make(map[string]*list.Element), maxElementCount: maxElementCount, ll: list.New(), } } -func (c *lruCache[K, T]) Add(node Node[T]) Node[T] { - key := c.getKey(node.GetKey()) - if e, exists := c.dict[key]; exists { +func (c *lruCache) Add(node Node) Node { + keyStr := ibytes.UnsafeBytesToStr(node.GetKey()) + if e, exists := c.dict[keyStr]; exists { c.ll.MoveToFront(e) old := e.Value e.Value = node - return old.(Node[T]) + return old.(Node) } elem := c.ll.PushFront(node) - c.dict[key] = elem + c.dict[keyStr] = elem if c.ll.Len() > c.maxElementCount { oldest := c.ll.Back() @@ -79,41 +79,32 @@ func (c *lruCache[K, T]) Add(node Node[T]) Node[T] { return nil } -func (c *lruCache[K, T]) Get(key T) Node[T] { - if ele, hit := c.dict[c.getKey(key)]; hit { +func (c *lruCache) Get(key []byte) Node { + if ele, hit := c.dict[ibytes.UnsafeBytesToStr(key)]; hit { c.ll.MoveToFront(ele) - return ele.Value.(Node[T]) + return ele.Value.(Node) } return nil } -func (c *lruCache[K, T]) Has(key T) bool { - _, exists := c.dict[c.getKey(key)] +func (c *lruCache) Has(key []byte) bool { + _, exists := c.dict[ibytes.UnsafeBytesToStr(key)] return exists } -func (c *lruCache[K, T]) Len() int { +func (c *lruCache) Len() int { return c.ll.Len() } -func (c *lruCache[K, T]) Remove(key T) Node[T] { - if elem, exists := c.dict[c.getKey(key)]; exists { +func (c *lruCache) Remove(key []byte) Node { + if elem, exists := c.dict[ibytes.UnsafeBytesToStr(key)]; exists { return c.remove(elem) } return nil } -func (c *lruCache[K, T]) remove(e *list.Element) Node[T] { - removed := c.ll.Remove(e).(Node[T]) - delete(c.dict, c.getKey(removed.GetKey())) +func (c *lruCache) remove(e *list.Element) Node { + removed := c.ll.Remove(e).(Node) + delete(c.dict, ibytes.UnsafeBytesToStr(removed.GetKey())) return removed } - -func (c *lruCache[K, T]) getKey(key T) K { - switch any(key).(type) { - case []byte: - return any(ibytes.UnsafeBytesToStr(any(key).([]byte))).(K) - default: - return any(key).(K) - } -} diff --git a/cache/cache_bench_test.go b/cache/cache_bench_test.go index e552f4a81..7332b6869 100644 --- a/cache/cache_bench_test.go +++ b/cache/cache_bench_test.go @@ -28,7 +28,7 @@ func BenchmarkAdd(b *testing.B) { } for name, tc := range testcases { - cache := cache.New[string, []byte](tc.cacheMax) + cache := cache.New(tc.cacheMax) b.Run(name, func(b *testing.B) { for i := 0; i < b.N; i++ { b.StopTimer() @@ -46,7 +46,7 @@ func BenchmarkAdd(b *testing.B) { func BenchmarkRemove(b *testing.B) { b.ReportAllocs() - cache := cache.New[string, []byte](1000) + cache := cache.New(1000) existentKeyMirror := [][]byte{} // Populate cache for i := 0; i < 50; i++ { diff --git a/cache/cache_test.go b/cache/cache_test.go index 558ce2df3..a4f6f6a28 100644 --- a/cache/cache_test.go +++ b/cache/cache_test.go @@ -29,7 +29,7 @@ type cacheOp struct { } type testcase struct { - setup func(cache.Cache[[]byte]) + setup func(cache.Cache) cacheMax int cacheOps []cacheOp expectedNodeIndexes []int // contents of the cache once test case completes represent by indexes in testNodes @@ -43,9 +43,9 @@ const ( testKey = "key" ) -var _ cache.Node[[]byte] = (*testNode)(nil) +var _ cache.Node = (*testNode)(nil) -var testNodes = []cache.Node[[]byte]{ +var testNodes = []cache.Node{ &testNode{ key: []byte(fmt.Sprintf("%s%d", testKey, 1)), }, @@ -150,7 +150,7 @@ func Test_Cache_Add(t *testing.T) { for name, tc := range testcases { t.Run(name, func(t *testing.T) { - cache := cache.New[string, []byte](tc.cacheMax) + cache := cache.New(tc.cacheMax) expectedCurSize := 0 @@ -189,7 +189,7 @@ func Test_Cache_Remove(t *testing.T) { }, }, "remove non-existent key, cache max 1 - nil returned": { - setup: func(c cache.Cache[[]byte]) { + setup: func(c cache.Cache) { require.Nil(t, c.Add(testNodes[1])) require.Equal(t, 1, c.Len()) }, @@ -203,7 +203,7 @@ func Test_Cache_Remove(t *testing.T) { expectedNodeIndexes: []int{1}, }, "remove existent key, cache max 1 - removed": { - setup: func(c cache.Cache[[]byte]) { + setup: func(c cache.Cache) { require.Nil(t, c.Add(testNodes[0])) require.Equal(t, 1, c.Len()) }, @@ -216,7 +216,7 @@ func Test_Cache_Remove(t *testing.T) { }, }, "remove twice, cache max 1 - removed first time, then nil": { - setup: func(c cache.Cache[[]byte]) { + setup: func(c cache.Cache) { require.Nil(t, c.Add(testNodes[0])) require.Equal(t, 1, c.Len()) }, @@ -233,7 +233,7 @@ func Test_Cache_Remove(t *testing.T) { }, }, "remove all, cache max 3": { - setup: func(c cache.Cache[[]byte]) { + setup: func(c cache.Cache) { require.Nil(t, c.Add(testNodes[0])) require.Nil(t, c.Add(testNodes[1])) require.Nil(t, c.Add(testNodes[2])) @@ -259,7 +259,7 @@ func Test_Cache_Remove(t *testing.T) { for name, tc := range testcases { t.Run(name, func(t *testing.T) { - cache := cache.New[string, []byte](tc.cacheMax) + cache := cache.New(tc.cacheMax) if tc.setup != nil { tc.setup(cache) @@ -290,7 +290,7 @@ func Test_Cache_Remove(t *testing.T) { } } -func validateCacheContentsAfterTest(t *testing.T, tc testcase, cache cache.Cache[[]byte]) { +func validateCacheContentsAfterTest(t *testing.T, tc testcase, cache cache.Cache) { require.Equal(t, len(tc.expectedNodeIndexes), cache.Len()) for _, idx := range tc.expectedNodeIndexes { expectedNode := testNodes[idx] diff --git a/export.go b/export.go index ae9f294f2..88ba22550 100644 --- a/export.go +++ b/export.go @@ -17,7 +17,7 @@ var ErrorExportDone = errors.New("export is complete") type ExportNode struct { Key []byte Value []byte - Version int64 + NodeKey *NodeKey Height int8 } @@ -53,7 +53,7 @@ func (e *Exporter) export(ctx context.Context) { exportNode := &ExportNode{ Key: node.key, Value: node.value, - Version: node.version, + NodeKey: node.nodeKey, Height: node.subtreeHeight, } diff --git a/export_test.go b/export_test.go index 4f380f787..c80e53364 100644 --- a/export_test.go +++ b/export_test.go @@ -148,15 +148,15 @@ func TestExporter(t *testing.T) { tree := setupExportTreeBasic(t) expect := []*ExportNode{ - {Key: []byte("a"), Value: []byte{1}, Version: 1, Height: 0}, - {Key: []byte("b"), Value: []byte{2}, Version: 3, Height: 0}, - {Key: []byte("b"), Value: nil, Version: 3, Height: 1}, - {Key: []byte("c"), Value: []byte{3}, Version: 3, Height: 0}, - {Key: []byte("c"), Value: nil, Version: 3, Height: 2}, - {Key: []byte("d"), Value: []byte{4}, Version: 2, Height: 0}, - {Key: []byte("e"), Value: []byte{5}, Version: 3, Height: 0}, - {Key: []byte("e"), Value: nil, Version: 3, Height: 1}, - {Key: []byte("d"), Value: nil, Version: 3, Height: 3}, + {Key: []byte("a"), Value: []byte{1}, NodeKey: &NodeKey{version: 1, nonce: 3}, Height: 0}, + {Key: []byte("b"), Value: []byte{2}, NodeKey: &NodeKey{version: 3, nonce: 4}, Height: 0}, + {Key: []byte("b"), Value: nil, NodeKey: &NodeKey{version: 3, nonce: 3}, Height: 1}, + {Key: []byte("c"), Value: []byte{3}, NodeKey: &NodeKey{version: 3, nonce: 5}, Height: 0}, + {Key: []byte("c"), Value: nil, NodeKey: &NodeKey{version: 3, nonce: 2}, Height: 2}, + {Key: []byte("d"), Value: []byte{4}, NodeKey: &NodeKey{version: 2, nonce: 5}, Height: 0}, + {Key: []byte("e"), Value: []byte{5}, NodeKey: &NodeKey{version: 3, nonce: 7}, Height: 0}, + {Key: []byte("e"), Value: nil, NodeKey: &NodeKey{version: 3, nonce: 6}, Height: 1}, + {Key: []byte("d"), Value: nil, NodeKey: &NodeKey{version: 3, nonce: 1}, Height: 3}, } actual := make([]*ExportNode, 0, len(expect)) diff --git a/fastnode/fast_node.go b/fastnode/fast_node.go index dbe4dcb53..d458ee4d9 100644 --- a/fastnode/fast_node.go +++ b/fastnode/fast_node.go @@ -18,7 +18,7 @@ type Node struct { value []byte } -var _ cache.Node[[]byte] = (*Node)(nil) +var _ cache.Node = (*Node)(nil) // NewNode returns a new fast node from a value and version. func NewNode(key []byte, value []byte, version int64) *Node { diff --git a/immutable_tree.go b/immutable_tree.go index 81539c1af..cf0876045 100644 --- a/immutable_tree.go +++ b/immutable_tree.go @@ -17,7 +17,6 @@ type ImmutableTree struct { root *Node ndb *nodeDB version int64 - nonce int64 skipFastStorageUpgrade bool } @@ -132,12 +131,6 @@ func (t *ImmutableTree) Version() int64 { return t.version } -// IncreaseNonce increases the nonce by 1 and returns. -func (t *ImmutableTree) IncreaseNonce() int64 { - t.nonce++ - return t.nonce -} - // Height returns the height of the tree. func (t *ImmutableTree) Height() int8 { if t.root == nil { @@ -156,8 +149,7 @@ func (t *ImmutableTree) Has(key []byte) (bool, error) { // Hash returns the root hash. func (t *ImmutableTree) Hash() ([]byte, error) { - hash, _, err := t.root.hashWithCount() - return hash, err + return t.root.hashWithCount(t.version + 1) } // Export returns an iterator that exports tree nodes as ExportNodes. These nodes can be @@ -290,7 +282,7 @@ func (t *ImmutableTree) IterateRangeInclusive(start, end []byte, ascending bool, } return t.root.traverseInRange(t, start, end, ascending, true, false, func(node *Node) bool { if node.subtreeHeight == 0 { - return fn(node.key, node.value, node.version) + return fn(node.key, node.value, node.nodeKey.version) } return false }) @@ -323,7 +315,6 @@ func (t *ImmutableTree) clone() *ImmutableTree { root: t.root, ndb: t.ndb, version: t.version, - nonce: t.nonce, } } @@ -331,10 +322,5 @@ func (t *ImmutableTree) clone() *ImmutableTree { // //nolint:unused func (t *ImmutableTree) nodeSize() int { - size := 0 - t.root.traverse(t, true, func(n *Node) bool { - size++ - return false - }) - return size + return int(t.root.size*2 - 1) } diff --git a/import.go b/import.go index c0b123ec1..2d9fe1632 100644 --- a/import.go +++ b/import.go @@ -73,15 +73,15 @@ func (i *Importer) Add(exportNode *ExportNode) error { if exportNode == nil { return errors.New("node cannot be nil") } - if exportNode.Version > i.version { + if exportNode.NodeKey.version > i.version { return fmt.Errorf("node version %v can't be greater than import version %v", - exportNode.Version, i.version) + exportNode.NodeKey.version, i.version) } node := &Node{ key: exportNode.Key, value: exportNode.Value, - version: exportNode.Version, + nodeKey: exportNode.NodeKey, subtreeHeight: exportNode.Height, } @@ -93,16 +93,12 @@ func (i *Importer) Add(exportNode *ExportNode) error { // We don't modify the stack until we've verified the built node, to avoid leaving the // importer in an inconsistent state when we return an error. stackSize := len(i.stack) - node.nodeKey = i.tree.IncreaseNonce() switch { case stackSize >= 2 && i.stack[stackSize-1].subtreeHeight < node.subtreeHeight && i.stack[stackSize-2].subtreeHeight < node.subtreeHeight: node.leftNode = i.stack[stackSize-2] - node.leftHash = node.leftNode.hash node.rightNode = i.stack[stackSize-1] - node.rightHash = node.rightNode.hash case stackSize >= 1 && i.stack[stackSize-1].subtreeHeight < node.subtreeHeight: node.leftNode = i.stack[stackSize-1] - node.leftHash = node.leftNode.hash } if node.subtreeHeight == 0 { @@ -117,7 +113,7 @@ func (i *Importer) Add(exportNode *ExportNode) error { node.rightNodeKey = node.rightNode.nodeKey } - _, err := node._hash() + _, err := node._hash(exportNode.NodeKey.version) if err != nil { return err } @@ -175,20 +171,15 @@ func (i *Importer) Commit() error { switch len(i.stack) { case 0: - if err := i.batch.Set(i.tree.ndb.rootKey(i.version), []byte{0, 0}); err != nil { + if err := i.batch.Set(i.tree.ndb.versionKey(i.version), []byte{0}); err != nil { return err } case 1: buf := new(bytes.Buffer) - err := encoding.EncodeVarint(buf, i.stack[0].nodeKey) - if err != nil { - return err - } - err = encoding.EncodeVarint(buf, i.tree.nonce) - if err != nil { + if err := encoding.EncodeVarint(buf, i.stack[0].nodeKey.version); err != nil { return err } - if err := i.batch.Set(i.tree.ndb.rootKey(i.version), buf.Bytes()); err != nil { + if err := i.batch.Set(i.tree.ndb.versionKey(i.version), buf.Bytes()); err != nil { return err } default: diff --git a/import_test.go b/import_test.go index 39c9863a0..0b5eae33e 100644 --- a/import_test.go +++ b/import_test.go @@ -116,11 +116,11 @@ func TestImporter_Add(t *testing.T) { valid bool }{ "nil node": {nil, false}, - "valid": {&ExportNode{Key: k, Value: v, Version: 1, Height: 0}, true}, - "no key": {&ExportNode{Key: nil, Value: v, Version: 1, Height: 0}, false}, - "no value": {&ExportNode{Key: k, Value: nil, Version: 1, Height: 0}, false}, - "version too large": {&ExportNode{Key: k, Value: v, Version: 2, Height: 0}, false}, - "no version": {&ExportNode{Key: k, Value: v, Version: 0, Height: 0}, false}, + "valid": {&ExportNode{Key: k, Value: v, NodeKey: &NodeKey{version: 1, nonce: 1}, Height: 0}, true}, + "no key": {&ExportNode{Key: nil, Value: v, NodeKey: &NodeKey{version: 1, nonce: 2}, Height: 0}, false}, + "no value": {&ExportNode{Key: k, Value: nil, NodeKey: &NodeKey{version: 1, nonce: 3}, Height: 0}, false}, + "version too large": {&ExportNode{Key: k, Value: v, NodeKey: &NodeKey{version: 2, nonce: 1}, Height: 0}, false}, + "no version": {&ExportNode{Key: k, Value: v, NodeKey: &NodeKey{version: 0, nonce: 1}, Height: 0}, false}, // further cases will be handled by Node.validate() } for desc, tc := range testcases { @@ -149,7 +149,7 @@ func TestImporter_Add_Closed(t *testing.T) { require.NoError(t, err) importer.Close() - err = importer.Add(&ExportNode{Key: []byte("key"), Value: []byte("value"), Version: 1, Height: 0}) + err = importer.Add(&ExportNode{Key: []byte("key"), Value: []byte("value"), NodeKey: &NodeKey{version: 1, nonce: 1}, Height: 0}) require.Error(t, err) require.Equal(t, ErrNoImport, err) } @@ -160,7 +160,7 @@ func TestImporter_Close(t *testing.T) { importer, err := tree.Import(1) require.NoError(t, err) - err = importer.Add(&ExportNode{Key: []byte("key"), Value: []byte("value"), Version: 1, Height: 0}) + err = importer.Add(&ExportNode{Key: []byte("key"), Value: []byte("value"), NodeKey: &NodeKey{version: 1, nonce: 1}, Height: 0}) require.NoError(t, err) importer.Close() @@ -177,7 +177,7 @@ func TestImporter_Commit(t *testing.T) { importer, err := tree.Import(1) require.NoError(t, err) - err = importer.Add(&ExportNode{Key: []byte("key"), Value: []byte("value"), Version: 1, Height: 0}) + err = importer.Add(&ExportNode{Key: []byte("key"), Value: []byte("value"), NodeKey: &NodeKey{version: 1, nonce: 1}, Height: 0}) require.NoError(t, err) err = importer.Commit() @@ -193,7 +193,7 @@ func TestImporter_Commit_Closed(t *testing.T) { importer, err := tree.Import(1) require.NoError(t, err) - err = importer.Add(&ExportNode{Key: []byte("key"), Value: []byte("value"), Version: 1, Height: 0}) + err = importer.Add(&ExportNode{Key: []byte("key"), Value: []byte("value"), NodeKey: &NodeKey{version: 1, nonce: 1}, Height: 0}) require.NoError(t, err) importer.Close() diff --git a/keyformat/key_format.go b/keyformat/key_format.go index 00d9ecf2f..3e22db7b9 100644 --- a/keyformat/key_format.go +++ b/keyformat/key_format.go @@ -158,6 +158,10 @@ func scan(a interface{}, value []byte) { *v = int64(binary.BigEndian.Uint64(value)) case *uint64: *v = binary.BigEndian.Uint64(value) + case *uint32: + *v = binary.BigEndian.Uint32(value) + case *int32: + *v = int32(binary.BigEndian.Uint32(value)) case *[]byte: *v = value default: @@ -176,6 +180,10 @@ func format(a interface{}) []byte { return formatUint64(uint64(v)) case int: return formatUint64(uint64(v)) + case uint32: + return formatUint32(v) + case int32: + return formatUint32(uint32(v)) case []byte: return v default: @@ -188,3 +196,9 @@ func formatUint64(v uint64) []byte { binary.BigEndian.PutUint64(bs, v) return bs } + +func formatUint32(v uint32) []byte { + bs := make([]byte, 4) + binary.BigEndian.PutUint32(bs, v) + return bs +} diff --git a/mutable_tree.go b/mutable_tree.go index ead6f0252..7ef70b29e 100644 --- a/mutable_tree.go +++ b/mutable_tree.go @@ -30,7 +30,8 @@ var ErrVersionDoesNotExist = errors.New("version does not exist") type MutableTree struct { *ImmutableTree // The current, working tree. lastSaved *ImmutableTree // The most recently saved tree. - orphans map[int64]int64 // Nodes removed by changes to working tree. + nonce int32 // To track the nonce in the SaveVersion. + orphanedLeaves []*NodeKey // Leaves removed by updates of working tree. versions map[int64]bool // The previous, saved versions of the tree. allRootLoaded bool // Whether all roots are loaded or not(by LazyLoadVersion) unsavedFastNodeAdditions map[string]*fastnode.Node // FastNodes that have not yet been saved to disk @@ -54,7 +55,7 @@ func NewMutableTreeWithOpts(db dbm.DB, cacheSize int, opts *Options, skipFastSto return &MutableTree{ ImmutableTree: head, lastSaved: head.clone(), - orphans: map[int64]int64{}, + orphanedLeaves: []*NodeKey{}, versions: map[int64]bool{}, allRootLoaded: false, unsavedFastNodeAdditions: make(map[string]*fastnode.Node), @@ -83,7 +84,7 @@ func (tree *MutableTree) VersionExists(version int64) bool { if ok { return has } - has, _ = tree.ndb.HasRoot(version) + has, _ = tree.ndb.HasVersion(version) tree.versions[version] = has return has } @@ -119,26 +120,15 @@ func (tree *MutableTree) String() (string, error) { return tree.ndb.String() } -// Set/Remove will orphan at most tree.Height nodes, -// balancing the tree after a Set/Remove will orphan at most 3 nodes. -func (tree *MutableTree) prepareOrphansSlice() []*Node { - return make([]*Node, 0, tree.Height()+3) -} - // Set sets a key in the working tree. Nil values are invalid. The given // key/value byte slices must not be modified after this call, since they point // to slices stored within IAVL. It returns true when an existing value was // updated, while false means it was a new key. func (tree *MutableTree) Set(key, value []byte) (updated bool, err error) { - var orphaned []*Node - orphaned, updated, err = tree.set(key, value) + updated, err = tree.set(key, value) if err != nil { return false, err } - err = tree.addOrphans(orphaned) - if err != nil { - return updated, err - } return updated, nil } @@ -220,25 +210,24 @@ func (tree *MutableTree) Iterator(start, end []byte, ascending bool) (dbm.Iterat return tree.ImmutableTree.Iterator(start, end, ascending) } -func (tree *MutableTree) set(key []byte, value []byte) (orphans []*Node, updated bool, err error) { +func (tree *MutableTree) set(key []byte, value []byte) (updated bool, err error) { if value == nil { - return nil, updated, fmt.Errorf("attempt to store nil value at key '%s'", key) + return updated, fmt.Errorf("attempt to store nil value at key '%s'", key) } if tree.ImmutableTree.root == nil { if !tree.skipFastStorageUpgrade { tree.addUnsavedAddition(key, fastnode.NewNode(key, value, tree.version+1)) } - tree.ImmutableTree.root = NewNode(key, value, tree.version+1, tree.IncreaseNonce()) - return nil, updated, nil + tree.ImmutableTree.root = NewNode(key, value) + return updated, nil } - orphans = tree.prepareOrphansSlice() - tree.ImmutableTree.root, updated, err = tree.recursiveSet(tree.ImmutableTree.root, key, value, &orphans) - return orphans, updated, err + tree.ImmutableTree.root, updated, err = tree.recursiveSet(tree.ImmutableTree.root, key, value) + return updated, err } -func (tree *MutableTree) recursiveSet(node *Node, key []byte, value []byte, orphans *[]*Node) ( +func (tree *MutableTree) recursiveSet(node *Node, key []byte, value []byte) ( newSelf *Node, updated bool, err error, ) { version := tree.version + 1 @@ -247,72 +236,47 @@ func (tree *MutableTree) recursiveSet(node *Node, key []byte, value []byte, orph if !tree.skipFastStorageUpgrade { tree.addUnsavedAddition(key, fastnode.NewNode(key, value, version)) } - nonce := node.nodeKey - if node.persisted { - nonce = tree.IncreaseNonce() - } else { - node.nodeKey = tree.IncreaseNonce() - } switch bytes.Compare(key, node.key) { case -1: return &Node{ key: node.key, subtreeHeight: 1, size: 2, - nodeKey: nonce, - leftNode: NewNode(key, value, version, tree.IncreaseNonce()), + nodeKey: nil, + leftNode: NewNode(key, value), rightNode: node, - version: version, }, false, nil case 1: return &Node{ key: key, subtreeHeight: 1, size: 2, - nodeKey: nonce, + nodeKey: nil, leftNode: node, - rightNode: NewNode(key, value, version, tree.IncreaseNonce()), - version: version, + rightNode: NewNode(key, value), }, false, nil default: - if node.persisted { - *orphans = append(*orphans, node) + if node.nodeKey != nil { + tree.orphanedLeaves = append(tree.orphanedLeaves, node.nodeKey) } - return NewNode(key, value, version, nonce), true, nil + return NewNode(key, value), true, nil } } else { - if node.persisted { - *orphans = append(*orphans, node) - node, err = node.clone(version) - if err != nil { - return nil, false, err - } - node.nodeKey = tree.IncreaseNonce() + node, err = node.clone(tree.ImmutableTree) + if err != nil { + return nil, false, err } - node.hash = nil if bytes.Compare(key, node.key) < 0 { - leftNode, err := node.getLeftNode(tree.ImmutableTree) - if err != nil { - return nil, false, err - } - node.leftNode, updated, err = tree.recursiveSet(leftNode, key, value, orphans) + node.leftNode, updated, err = tree.recursiveSet(node.leftNode, key, value) if err != nil { return nil, updated, err } - node.leftHash = nil // leftHash is yet unknown - node.leftNodeKey = 0 // leftNodeKey is yet unknown } else { - rightNode, err := node.getRightNode(tree.ImmutableTree) - if err != nil { - return nil, false, err - } - node.rightNode, updated, err = tree.recursiveSet(rightNode, key, value, orphans) + node.rightNode, updated, err = tree.recursiveSet(node.rightNode, key, value) if err != nil { return nil, updated, err } - node.rightHash = nil // rightHash is yet unknown - node.rightNodeKey = 0 // rightNodeKey is yet unknown } if updated { @@ -322,7 +286,7 @@ func (tree *MutableTree) recursiveSet(node *Node, key []byte, value []byte, orph if err != nil { return nil, false, err } - newNode, err := tree.balance(node, orphans) + newNode, err := tree.balance(node) if err != nil { return nil, false, err } @@ -333,46 +297,23 @@ func (tree *MutableTree) recursiveSet(node *Node, key []byte, value []byte, orph // Remove removes a key from the working tree. The given key byte slice should not be modified // after this call, since it may point to data stored inside IAVL. func (tree *MutableTree) Remove(key []byte) ([]byte, bool, error) { - val, orphaned, removed, err := tree.remove(key) - if err != nil { - return nil, false, err - } - - err = tree.addOrphans(orphaned) - if err != nil { - return val, removed, err - } - return val, removed, nil -} - -// remove tries to remove a key from the tree and if removed, returns its -// value, nodes orphaned and 'true'. -func (tree *MutableTree) remove(key []byte) (value []byte, orphaned []*Node, removed bool, err error) { if tree.root == nil { - return nil, nil, false, nil + return nil, false, nil } - orphaned = tree.prepareOrphansSlice() - _, newRootNodeKey, newRoot, _, value, err := tree.recursiveRemove(tree.root, key, &orphaned) + newRoot, _, value, err := tree.recursiveRemove(tree.root, key) if err != nil { - return nil, nil, false, err + return nil, false, err } if value == nil { - return nil, nil, false, nil + return nil, false, nil } if !tree.skipFastStorageUpgrade { tree.addUnsavedRemoval(key) } - if newRoot == nil && newRootNodeKey != 0 { - tree.root, err = tree.ndb.GetNode(newRootNodeKey) - if err != nil { - return nil, nil, false, err - } - } else { - tree.root = newRoot - } - return value, orphaned, true, nil + tree.root = newRoot + return value, true, nil } // removes the node corresponding to the passed key and balances the tree. @@ -382,104 +323,78 @@ func (tree *MutableTree) remove(key []byte) (value []byte, orphaned []*Node, rem // - new leftmost leaf key for tree after successfully removing 'key' if changed. // - the removed value // - the orphaned nodes. -func (tree *MutableTree) recursiveRemove(node *Node, key []byte, orphans *[]*Node) (newHash []byte, newNodeKey int64, newSelf *Node, newKey []byte, newValue []byte, err error) { - version := tree.version + 1 - +func (tree *MutableTree) recursiveRemove(node *Node, key []byte) (newSelf *Node, newKey []byte, newValue []byte, err error) { + logger.Debug("recursiveRemove node: %v, key: %x\n", node, key) if node.isLeaf() { if bytes.Equal(key, node.key) { - if node.persisted { - *orphans = append(*orphans, node) + if node.nodeKey != nil { + tree.orphanedLeaves = append(tree.orphanedLeaves, node.nodeKey) } - return nil, 0, nil, nil, node.value, nil + return nil, nil, node.value, nil } - return node.hash, node.nodeKey, node, nil, nil, nil + return node, nil, nil, nil + } + + node, err = node.clone(tree.ImmutableTree) + if err != nil { + return nil, nil, nil, err } // node.key < key; we go to the left to find the key: if bytes.Compare(key, node.key) < 0 { - leftNode, err := node.getLeftNode(tree.ImmutableTree) + newLeftNode, newKey, value, err := tree.recursiveRemove(node.leftNode, key) if err != nil { - return nil, 0, nil, nil, nil, err - } - newLeftHash, newLeftNodeKey, newLeftNode, newKey, value, err := tree.recursiveRemove(leftNode, key, orphans) - if err != nil { - return nil, 0, nil, nil, nil, err + return nil, nil, nil, err } if value == nil { - return node.hash, node.nodeKey, node, nil, value, nil - } - if node.persisted { - *orphans = append(*orphans, node) - } - if newLeftNodeKey == 0 && newLeftNode == nil { // left node held value, was removed - return node.rightHash, node.rightNodeKey, node.rightNode, node.key, value, nil + return node, nil, value, nil } - if node.persisted { - node, err = node.clone(version) - if err != nil { - return nil, 0, nil, nil, nil, err - } - node.nodeKey = tree.IncreaseNonce() + if newLeftNode == nil { // left node held value, was removed + return node.rightNode, node.key, value, nil } - node.hash = nil - node.leftHash, node.leftNodeKey, node.leftNode = newLeftHash, newLeftNodeKey, newLeftNode + node.leftNode = newLeftNode err = node.calcHeightAndSize(tree.ImmutableTree) if err != nil { - return nil, 0, nil, nil, nil, err + return nil, nil, nil, err } - node, err = tree.balance(node, orphans) + node, err = tree.balance(node) if err != nil { - return nil, 0, nil, nil, nil, err + return nil, nil, nil, err } - return node.hash, node.nodeKey, node, newKey, value, nil + return node, newKey, value, nil } // node.key >= key; either found or look to the right: - rightNode, err := node.getRightNode(tree.ImmutableTree) - if err != nil { - return nil, 0, nil, nil, nil, err - } - newRightHash, newRightNodeKey, newRightNode, newKey, value, err := tree.recursiveRemove(rightNode, key, orphans) + newRightNode, newKey, value, err := tree.recursiveRemove(node.rightNode, key) if err != nil { - return nil, 0, nil, nil, nil, err + return nil, nil, nil, err } if value == nil { - return node.hash, node.nodeKey, node, nil, value, nil - } - if node.persisted { - *orphans = append(*orphans, node) - } - if newRightNodeKey == 0 && newRightNode == nil { // right node held value, was removed - return node.leftHash, node.leftNodeKey, node.leftNode, nil, value, nil + return node, nil, value, nil } - if node.persisted { - node, err = node.clone(version) - if err != nil { - return nil, 0, nil, nil, nil, err - } - node.nodeKey = tree.IncreaseNonce() + if newRightNode == nil { // right node held value, was removed + return node.leftNode, nil, value, nil } - node.hash = nil - node.rightHash, node.rightNodeKey, node.rightNode = newRightHash, newRightNodeKey, newRightNode + node.rightNode = newRightNode if newKey != nil { node.key = newKey } err = node.calcHeightAndSize(tree.ImmutableTree) if err != nil { - return nil, 0, nil, nil, nil, err + return nil, nil, nil, err } - node, err = tree.balance(node, orphans) + node, err = tree.balance(node) if err != nil { - return nil, 0, nil, nil, nil, err + return nil, nil, nil, err } - return node.hash, node.nodeKey, node, nil, value, nil + return node, nil, value, nil } // Load the latest versioned tree from disk. @@ -522,10 +437,13 @@ func (tree *MutableTree) LazyLoadVersion(targetVersion int64) (int64, error) { targetVersion = latestVersion } - rootNodeKey, _, err := tree.ndb.getRoot(targetVersion) + rootNodeKey, err := tree.ndb.GetRoot(targetVersion) if err != nil { return 0, err } + if rootNodeKey == nil { + return latestVersion, ErrVersionDoesNotExist + } tree.mtx.Lock() defer tree.mtx.Unlock() @@ -537,16 +455,17 @@ func (tree *MutableTree) LazyLoadVersion(targetVersion int64) (int64, error) { version: targetVersion, skipFastStorageUpgrade: tree.skipFastStorageUpgrade, } - if rootNodeKey > 0 { - // If rootNodeKey is zero then root of tree should be nil - // This makes `LazyLoadVersion` to do the same thing as `LoadVersion` + + // This makes `LazyLoadVersion` to do the same thing as `LoadVersion` + // rootNodeKey.version = 0 means the root is a nil + if rootNodeKey.version != 0 { iTree.root, err = tree.ndb.GetNode(rootNodeKey) if err != nil { return 0, err } } - tree.orphans = map[int64]int64{} + tree.orphanedLeaves = []*NodeKey{} tree.ImmutableTree = iTree tree.lastSaved = iTree.clone() @@ -562,12 +481,12 @@ func (tree *MutableTree) LazyLoadVersion(targetVersion int64) (int64, error) { // Returns the version number of the latest version found func (tree *MutableTree) LoadVersion(targetVersion int64) (int64, error) { - roots, err := tree.ndb.getRoots() + versions, err := tree.ndb.getVersions() if err != nil { return 0, err } - if len(roots) == 0 { + if len(versions) == 0 { if targetVersion <= 0 { if !tree.skipFastStorageUpgrade { tree.mtx.Lock() @@ -586,19 +505,14 @@ func (tree *MutableTree) LoadVersion(targetVersion int64) (int64, error) { tree.mtx.Lock() defer tree.mtx.Unlock() - var latestRoot, latestNonce int64 - for version, r := range roots { + for _, version := range versions { tree.versions[version] = true if version > latestVersion && (targetVersion == 0 || version <= targetVersion) { latestVersion = version - latestRoot = r.rootKey } if firstVersion == 0 || version < firstVersion { firstVersion = version } - if r.nonce > latestNonce { - latestNonce = r.nonce - } } if !(targetVersion == 0 || latestVersion == targetVersion) { @@ -614,18 +528,26 @@ func (tree *MutableTree) LoadVersion(targetVersion int64) (int64, error) { t := &ImmutableTree{ ndb: tree.ndb, version: latestVersion, - nonce: latestNonce, skipFastStorageUpgrade: tree.skipFastStorageUpgrade, } - if latestRoot != 0 { - t.root, err = tree.ndb.GetNode(latestRoot) + rootNodeKey, err := tree.ndb.GetRoot(latestVersion) + if err != nil { + return 0, err + } + if rootNodeKey == nil { + return 0, ErrVersionDoesNotExist + } + + // rootNodeKey.version = 0 means root is a nil + if rootNodeKey.version != 0 { + t.root, err = tree.ndb.GetNode(rootNodeKey) if err != nil { return 0, err } } - tree.orphans = map[int64]int64{} + tree.orphanedLeaves = []*NodeKey{} tree.ImmutableTree = t tree.lastSaved = t.clone() tree.allRootLoaded = true @@ -643,18 +565,15 @@ func (tree *MutableTree) LoadVersion(targetVersion int64) (int64, error) { // LoadVersionForOverwriting attempts to load a tree at a previously committed // version, or the latest version below it. Any versions greater than targetVersion will be deleted. func (tree *MutableTree) LoadVersionForOverwriting(targetVersion int64) (int64, error) { - if err := tree.ndb.DeleteVersionsFrom(targetVersion + 1); err != nil { - return targetVersion, err - } - if err := tree.ndb.Commit(); err != nil { - return targetVersion, err - } - latestVersion, err := tree.LoadVersion(targetVersion) if err != nil { return latestVersion, err } + if err = tree.ndb.DeleteVersionsFrom(targetVersion + 1); err != nil { + return latestVersion, err + } + if !tree.skipFastStorageUpgrade { if err := tree.enableFastStorageAndCommitLocked(); err != nil { return latestVersion, err @@ -770,33 +689,31 @@ func (tree *MutableTree) enableFastStorageAndCommit() error { // GetImmutable loads an ImmutableTree at a given version for querying. The returned tree is // safe for concurrent access, provided the version is not deleted, e.g. via `DeleteVersion()`. func (tree *MutableTree) GetImmutable(version int64) (*ImmutableTree, error) { - rootNodeKey, nonce, err := tree.ndb.getRoot(version) + rootNodeKey, err := tree.ndb.GetRoot(version) if err != nil { return nil, err } + if rootNodeKey == nil { + return nil, ErrVersionDoesNotExist + } tree.mtx.Lock() defer tree.mtx.Unlock() - if rootNodeKey == 0 { - tree.versions[version] = true - return &ImmutableTree{ - ndb: tree.ndb, - version: version, - nonce: nonce, - skipFastStorageUpgrade: tree.skipFastStorageUpgrade, - }, nil - } tree.versions[version] = true - root, err := tree.ndb.GetNode(rootNodeKey) - if err != nil { - return nil, err + var root *Node + // rootNodeKey.version = 0 means root is a nil + if rootNodeKey.version != 0 { + root, err = tree.ndb.GetNode(rootNodeKey) + if err != nil { + return nil, err + } } + return &ImmutableTree{ root: root, ndb: tree.ndb, version: version, - nonce: nonce, skipFastStorageUpgrade: tree.skipFastStorageUpgrade, }, nil } @@ -813,7 +730,7 @@ func (tree *MutableTree) Rollback() { skipFastStorageUpgrade: tree.skipFastStorageUpgrade, } } - tree.orphans = map[int64]int64{} + tree.orphanedLeaves = []*NodeKey{} if !tree.skipFastStorageUpgrade { tree.unsavedFastNodeAdditions = map[string]*fastnode.Node{} tree.unsavedFastNodeRemovals = map[string]interface{}{} @@ -865,14 +782,16 @@ func (tree *MutableTree) SaveVersion() ([]byte, int64, error) { if tree.VersionExists(version) { // If the version already exists, return an error as we're attempting to overwrite. // However, the same hash means idempotent (i.e. no-op). - existingNodeKey, _, err := tree.ndb.getRoot(version) + existingNodeKey, err := tree.ndb.GetRoot(version) if err != nil { return nil, version, err } - - existingRoot, err := tree.ndb.GetNode(existingNodeKey) - if err != nil { - return nil, version, err + var existingRoot *Node + if existingNodeKey.version != 0 { + existingRoot, err = tree.ndb.GetNode(existingNodeKey) + if err != nil { + return nil, version, err + } } newHash, err := tree.WorkingHash() @@ -885,35 +804,47 @@ func (tree *MutableTree) SaveVersion() ([]byte, int64, error) { tree.root = existingRoot tree.ImmutableTree = tree.ImmutableTree.clone() tree.lastSaved = tree.ImmutableTree.clone() - tree.orphans = map[int64]int64{} + tree.orphanedLeaves = []*NodeKey{} return newHash, version, nil } - return nil, version, fmt.Errorf("version %d was already saved to different hash %X (existing nodeKey %d)", version, newHash, existingNodeKey) + return nil, version, fmt.Errorf("version %d was already saved to different hash from %X (existing nodeKey %d)", version, newHash, existingNodeKey) } - if tree.root == nil { - // There can still be orphans, for example if the root is the node being - // removed. - logger.Debug("SAVE EMPTY TREE %v\n", version) - if err := tree.ndb.SaveOrphans(version, tree.orphans); err != nil { - return nil, 0, err - } - if err := tree.ndb.SaveEmptyRoot(version); err != nil { - return nil, 0, err - } - } else { - logger.Debug("SAVE TREE %v\n", version) - if _, _, err := tree.ndb.SaveBranch(tree.root); err != nil { + logger.Debug("SAVE TREE %v\n", version) + // save orphans + orphans := tree.getOrphans() + orphans = append(orphans, tree.orphanedLeaves...) // should add updated leaves + if err := tree.ndb.SaveOrphans(version, orphans); err != nil { + return nil, 0, err + } + // save new nodes + if tree.root != nil { + // ensure init the nonce + tree.nonce = 0 + newNodes, err := tree.getNewNodes() + if err != nil { return nil, 0, err } - if err := tree.ndb.SaveOrphans(version, tree.orphans); err != nil { - return nil, 0, err + for i := int32(1); i <= tree.nonce; i++ { + node := newNodes[i] + if err := tree.ndb.SaveNode(node); err != nil { + return nil, 0, err + } } - if err := tree.ndb.SaveRoot(tree.root.GetKey(), tree.nonce, version); err != nil { - return nil, 0, err + } + // save root + rootVersion := int64(0) + if tree.root != nil { + if tree.root.nodeKey != nil { + rootVersion = tree.root.nodeKey.version + } else { + rootVersion = version } } + if err := tree.ndb.SaveRoot(version, rootVersion); err != nil { + return nil, 0, err + } if !tree.skipFastStorageUpgrade { if err := tree.saveFastNodeVersion(); err != nil { @@ -933,7 +864,7 @@ func (tree *MutableTree) SaveVersion() ([]byte, int64, error) { // set new working tree tree.ImmutableTree = tree.ImmutableTree.clone() tree.lastSaved = tree.ImmutableTree.clone() - tree.orphans = map[int64]int64{} + tree.orphanedLeaves = []*NodeKey{} if !tree.skipFastStorageUpgrade { tree.unsavedFastNodeAdditions = make(map[string]*fastnode.Node) tree.unsavedFastNodeRemovals = make(map[string]interface{}) @@ -1107,99 +1038,69 @@ func (tree *MutableTree) DeleteVersion(version int64) error { } // Rotate right and return the new node and orphan. -func (tree *MutableTree) rotateRight(node *Node) (*Node, *Node, error) { - version := tree.version + 1 - +func (tree *MutableTree) rotateRight(node *Node) (*Node, error) { var err error // TODO: optimize balance & rotate. - if node.persisted { - node, err = node.clone(version) - if err != nil { - return nil, nil, err - } - node.nodeKey = tree.IncreaseNonce() - } - - orphaned, err := node.getLeftNode(tree.ImmutableTree) + node, err = node.clone(tree.ImmutableTree) if err != nil { - return nil, nil, err + return nil, err } - newNode, err := orphaned.clone(version) + + newNode, err := node.leftNode.clone(tree.ImmutableTree) if err != nil { - return nil, nil, err - } - if orphaned.persisted { - newNode.nodeKey = tree.IncreaseNonce() - } else { - orphaned = nil + return nil, err } - node.nodeKey, newNode.nodeKey = newNode.nodeKey, node.nodeKey - node.leftHash, node.leftNodeKey, node.leftNode = newNode.rightHash, newNode.rightNodeKey, newNode.rightNode + node.leftNode = newNode.rightNode newNode.rightNode = node err = node.calcHeightAndSize(tree.ImmutableTree) if err != nil { - return nil, nil, err + return nil, err } err = newNode.calcHeightAndSize(tree.ImmutableTree) if err != nil { - return nil, nil, err + return nil, err } - return newNode, orphaned, nil + return newNode, nil } // Rotate left and return the new node and orphan. -func (tree *MutableTree) rotateLeft(node *Node) (*Node, *Node, error) { - version := tree.version + 1 - +func (tree *MutableTree) rotateLeft(node *Node) (*Node, error) { var err error // TODO: optimize balance & rotate. - if node.persisted { - node, err = node.clone(version) - if err != nil { - return nil, nil, err - } - node.nodeKey = tree.IncreaseNonce() - } - - orphaned, err := node.getRightNode(tree.ImmutableTree) + node, err = node.clone(tree.ImmutableTree) if err != nil { - return nil, nil, err + return nil, err } - newNode, err := orphaned.clone(version) + + newNode, err := node.rightNode.clone(tree.ImmutableTree) if err != nil { - return nil, nil, err - } - if orphaned.persisted { - newNode.nodeKey = tree.IncreaseNonce() - } else { - orphaned = nil + return nil, err } - node.nodeKey, newNode.nodeKey = newNode.nodeKey, node.nodeKey - node.rightHash, node.rightNodeKey, node.rightNode = newNode.leftHash, newNode.leftNodeKey, newNode.leftNode + node.rightNode = newNode.leftNode newNode.leftNode = node err = node.calcHeightAndSize(tree.ImmutableTree) if err != nil { - return nil, nil, err + return nil, err } err = newNode.calcHeightAndSize(tree.ImmutableTree) if err != nil { - return nil, nil, err + return nil, err } - return newNode, orphaned, nil + return newNode, nil } // NOTE: assumes that node can be modified // TODO: optimize balance & rotate -func (tree *MutableTree) balance(node *Node, orphans *[]*Node) (newSelf *Node, err error) { - if node.persisted { +func (tree *MutableTree) balance(node *Node) (newSelf *Node, err error) { + if node.nodeKey != nil { return nil, fmt.Errorf("unexpected balance() call on persisted node") } balance, err := node.calcBalance(tree.ImmutableTree) @@ -1208,49 +1109,31 @@ func (tree *MutableTree) balance(node *Node, orphans *[]*Node) (newSelf *Node, e } if balance > 1 { - leftNode, err := node.getLeftNode(tree.ImmutableTree) - if err != nil { - return nil, err - } - - lftBalance, err := leftNode.calcBalance(tree.ImmutableTree) + lftBalance, err := node.leftNode.calcBalance(tree.ImmutableTree) if err != nil { return nil, err } if lftBalance >= 0 { // Left Left Case - newNode, orphaned, err := tree.rotateRight(node) + newNode, err := tree.rotateRight(node) if err != nil { return nil, err } - if orphaned != nil { - *orphans = append(*orphans, orphaned) - } return newNode, nil } // Left Right Case - var leftOrphaned *Node - node.leftHash = nil - node.leftNodeKey = 0 - node.leftNode, leftOrphaned, err = tree.rotateLeft(leftNode) + node.leftNodeKey = nil + node.leftNode, err = tree.rotateLeft(node.leftNode) if err != nil { return nil, err } - newNode, rightOrphaned, err := tree.rotateRight(node) + newNode, err := tree.rotateRight(node) if err != nil { return nil, err } - if leftNode.persisted { - *orphans = append(*orphans, leftNode) - } - if leftOrphaned != nil { - *orphans = append(*orphans, leftOrphaned) - } - if rightOrphaned != nil { - *orphans = append(*orphans, rightOrphaned) - } + return newNode, nil } if balance < -1 { @@ -1265,50 +1148,100 @@ func (tree *MutableTree) balance(node *Node, orphans *[]*Node) (newSelf *Node, e } if rightBalance <= 0 { // Right Right Case - newNode, orphaned, err := tree.rotateLeft(node) + newNode, err := tree.rotateLeft(node) if err != nil { return nil, err } - if orphaned != nil { - *orphans = append(*orphans, orphaned) - } return newNode, nil } // Right Left Case - var rightOrphaned *Node - - node.rightHash = nil - node.rightNodeKey = 0 - node.rightNode, rightOrphaned, err = tree.rotateRight(rightNode) + node.rightNodeKey = nil + node.rightNode, err = tree.rotateRight(rightNode) if err != nil { return nil, err } - newNode, leftOrphaned, err := tree.rotateLeft(node) + newNode, err := tree.rotateLeft(node) if err != nil { return nil, err } + return newNode, nil + } + // Nothing changed + return node, nil +} - if rightNode.persisted { - *orphans = append(*orphans, rightNode) +// getOrphans gets orphaned nodes by the changes of the working tree. +func (tree *MutableTree) getOrphans() []*NodeKey { + prevRoot := tree.lastSaved.root + orphans := make([]*NodeKey, 0) + + var recursiveSpread func(*Node) + recursiveSpread = func(node *Node) { + isParent := false + if node.leftNode != nil { + isParent = true + recursiveSpread(node.leftNode) } - if leftOrphaned != nil { - *orphans = append(*orphans, leftOrphaned) + if node.rightNode != nil { + isParent = true + recursiveSpread(node.rightNode) } - if rightOrphaned != nil { - *orphans = append(*orphans, rightOrphaned) + if isParent { + orphans = append(orphans, node.nodeKey) } - return newNode, nil } - // Nothing changed - return node, nil + + if prevRoot != nil { + recursiveSpread(prevRoot) + } + + return orphans } -func (tree *MutableTree) addOrphans(orphans []*Node) error { - for _, node := range orphans { - if node.nodeKey == 0 { - return fmt.Errorf("expected to find nodeKey, but was zero") +// getNewNodes gets new created nodes by the changes of the working tree. +// NOTE: This function clears leftNode/rigthNode recursively and +// calls _hash() on the given node. +func (tree *MutableTree) getNewNodes() (map[int32]*Node, error) { + newNodes := make(map[int32]*Node) + version := tree.version + 1 + + var recursiveSpread func(*Node) (*NodeKey, error) + recursiveSpread = func(node *Node) (*NodeKey, error) { + if node.nodeKey != nil { + return node.nodeKey, nil + } + tree.nonce++ + node.nodeKey = &NodeKey{ + version: version, + nonce: tree.nonce, + } + + var err error + if node.leftNode != nil { + node.leftNodeKey, err = recursiveSpread(node.leftNode) + if err != nil { + return nil, err + } + } + + if node.rightNode != nil { + node.rightNodeKey, err = recursiveSpread(node.rightNode) + if err != nil { + return nil, err + } + } + + _, err = node._hash(version) + if err != nil { + return nil, err } - tree.orphans[node.nodeKey] = node.version + + node.leftNode = nil + node.rightNode = nil + newNodes[node.nodeKey.nonce] = node + return node.nodeKey, nil } - return nil + + _, err := recursiveSpread(tree.root) + return newNodes, err } diff --git a/mutable_tree_test.go b/mutable_tree_test.go index 85bb9c97b..84b4ac18e 100644 --- a/mutable_tree_test.go +++ b/mutable_tree_test.go @@ -50,7 +50,7 @@ func TestIterateConcurrency(t *testing.T) { for i := 0; i < 100; i++ { for j := 0; j < maxIterator; j++ { wg.Add(1) - func(i, j int) { + go func(i, j int) { defer wg.Done() tree.Set([]byte(fmt.Sprintf("%d%d", i, j)), rand.Bytes(1)) }(i, j) @@ -75,7 +75,7 @@ func TestIteratorConcurrency(t *testing.T) { for i := 0; i < 100; i++ { for j := 0; j < maxIterator; j++ { wg.Add(1) - func(i, j int) { + go func(i, j int) { defer wg.Done() tree.Set([]byte(fmt.Sprintf("%d%d", i, j)), rand.Bytes(1)) }(i, j) @@ -98,7 +98,7 @@ func TestNewIteratorConcurrency(t *testing.T) { it := NewIterator(nil, nil, true, tree.ImmutableTree) for j := 0; j < maxIterator; j++ { wg.Add(1) - func(i, j int) { + go func(i, j int) { defer wg.Done() tree.Set([]byte(fmt.Sprintf("%d%d", i, j)), rand.Bytes(1)) }(i, j) @@ -114,7 +114,6 @@ func TestDelete(t *testing.T) { tree.set([]byte("k1"), []byte("Fred")) _, version, err := tree.SaveVersion() - rootNodeKey := tree.root.GetKey() require.NoError(t, err) _, _, err = tree.SaveVersion() require.NoError(t, err) @@ -125,14 +124,9 @@ func TestDelete(t *testing.T) { require.EqualError(t, err, ErrVersionDoesNotExist.Error()) require.Nil(t, proof) - buf := new(bytes.Buffer) - err = encoding.EncodeVarint(buf, rootNodeKey) - require.NoError(t, err) - err = encoding.EncodeVarint(buf, tree.nonce) - require.NoError(t, err) - key := tree.ndb.rootKey(version) - err = tree.ndb.db.Set(key, buf.Bytes()) + err = tree.ndb.SaveRoot(version, version) require.NoError(t, err) + require.NoError(t, tree.ndb.Commit()) tree.versions[version] = true proof, err = tree.GetVersionedProof([]byte("k1"), version) @@ -901,7 +895,7 @@ func TestUpgradeStorageToFast_DbErrorConstructor_Failure(t *testing.T) { // rIterMock is used to get the latest version from disk. We are mocking that rIterMock returns latestTreeVersion from disk rIterMock.EXPECT().Valid().Return(true).Times(1) - rIterMock.EXPECT().Key().Return(rootKeyFormat.Key([]byte(defaultStorageVersionValue))) + rIterMock.EXPECT().Key().Return(versionKeyFormat.Key([]byte(defaultStorageVersionValue))) rIterMock.EXPECT().Close().Return(nil).Times(1) expectedError := errors.New("some db error") @@ -926,7 +920,7 @@ func TestUpgradeStorageToFast_DbErrorEnableFastStorage_Failure(t *testing.T) { // rIterMock is used to get the latest version from disk. We are mocking that rIterMock returns latestTreeVersion from disk rIterMock.EXPECT().Valid().Return(true).Times(1) - rIterMock.EXPECT().Key().Return(rootKeyFormat.Key([]byte(defaultStorageVersionValue))) + rIterMock.EXPECT().Key().Return(versionKeyFormat.Key([]byte(defaultStorageVersionValue))) rIterMock.EXPECT().Close().Return(nil).Times(1) expectedError := errors.New("some db error") @@ -977,7 +971,7 @@ func TestFastStorageReUpgradeProtection_NoForceUpgrade_Success(t *testing.T) { // rIterMock is used to get the latest version from disk. We are mocking that rIterMock returns latestTreeVersion from disk rIterMock.EXPECT().Valid().Return(true).Times(1) - rIterMock.EXPECT().Key().Return(rootKeyFormat.Key(latestTreeVersion)) + rIterMock.EXPECT().Key().Return(versionKeyFormat.Key(latestTreeVersion)) rIterMock.EXPECT().Close().Return(nil).Times(1) batchMock := mock.NewMockBatch(ctrl) @@ -1040,7 +1034,7 @@ func TestFastStorageReUpgradeProtection_ForceUpgradeFirstTime_NoForceSecondTime_ // rIterMock is used to get the latest version from disk. We are mocking that rIterMock returns latestTreeVersion from disk rIterMock.EXPECT().Valid().Return(true).Times(1) - rIterMock.EXPECT().Key().Return(rootKeyFormat.Key(latestTreeVersion)) + rIterMock.EXPECT().Key().Return(versionKeyFormat.Key(latestTreeVersion)) rIterMock.EXPECT().Close().Return(nil).Times(1) fastNodeKeyToDelete := []byte("some_key") diff --git a/node.go b/node.go index 3c8ab961f..0299089f7 100644 --- a/node.go +++ b/node.go @@ -6,6 +6,7 @@ package iavl import ( "bytes" "crypto/sha256" + "encoding/binary" "errors" "fmt" "io" @@ -16,35 +17,42 @@ import ( "github.com/cosmos/iavl/internal/encoding" ) +// NodeKey represents a key of node in the DB. +type NodeKey struct { + version int64 + nonce int32 +} + +func (nk *NodeKey) GetKey() []byte { + b := make([]byte, 12) + binary.BigEndian.PutUint64(b, uint64(nk.version)) + binary.BigEndian.PutUint32(b[8:], uint32(nk.nonce)) + return b +} + // Node represents a node in a Tree. type Node struct { key []byte value []byte hash []byte - leftHash []byte - rightHash []byte - nodeKey int64 - leftNodeKey int64 - rightNodeKey int64 - version int64 + nodeKey *NodeKey + leftNodeKey *NodeKey + rightNodeKey *NodeKey size int64 leftNode *Node rightNode *Node subtreeHeight int8 - persisted bool } -var _ cache.Node[int64] = (*Node)(nil) +var _ cache.Node = (*Node)(nil) // NewNode returns a new node from a key, value and version. -func NewNode(key []byte, value []byte, version, nodeKey int64) *Node { +func NewNode(key []byte, value []byte) *Node { return &Node{ key: key, value: value, subtreeHeight: 0, size: 1, - version: version, - nodeKey: nodeKey, } } @@ -52,8 +60,8 @@ func NewNode(key []byte, value []byte, version, nodeKey int64) *Node { // // The new node doesn't have its hash saved or set. The caller must set it // afterwards. -func MakeNode(nodeKey int64, buf []byte) (*Node, error) { - // Read node header (height, size, version, nodeKey, key). +func MakeNode(nodeKey *NodeKey, buf []byte) (*Node, error) { + // Read node header (height, size, key). height, n, cause := encoding.DecodeVarint(buf) if cause != nil { return nil, fmt.Errorf("decoding node.height, %w", cause) @@ -69,12 +77,6 @@ func MakeNode(nodeKey int64, buf []byte) (*Node, error) { } buf = buf[n:] - ver, n, cause := encoding.DecodeVarint(buf) - if cause != nil { - return nil, fmt.Errorf("decoding node.version, %w", cause) - } - buf = buf[n:] - key, n, cause := encoding.DecodeBytes(buf) if cause != nil { return nil, fmt.Errorf("decoding node.key, %w", cause) @@ -84,7 +86,6 @@ func MakeNode(nodeKey int64, buf []byte) (*Node, error) { node := &Node{ subtreeHeight: int8(height), size: size, - version: ver, nodeKey: nodeKey, key: key, } @@ -97,74 +98,106 @@ func MakeNode(nodeKey int64, buf []byte) (*Node, error) { return nil, fmt.Errorf("decoding node.value, %w", cause) } node.value = val + // ensure take the hash for the leaf node + if _, err := node._hash(node.nodeKey.version); err != nil { + return nil, fmt.Errorf("calculating hash error: %v", err) + } + } else { // Read children. - leftNodeKey, n, cause := encoding.DecodeVarint(buf) + node.hash, n, cause = encoding.DecodeBytes(buf) if cause != nil { - return nil, fmt.Errorf("decoding node.leftNodeKey, %w", cause) + return nil, fmt.Errorf("decoding node.hash, %w", cause) } buf = buf[n:] - leftHash, n, cause := encoding.DecodeBytes(buf) + + var ( + leftNodeKey, rightNodeKey NodeKey + nonce int64 + ) + leftNodeKey.version, n, cause = encoding.DecodeVarint(buf) if cause != nil { - return nil, fmt.Errorf("deocding node.leftHash, %w", cause) + return nil, fmt.Errorf("decoding node.leftNodeKey.version, %w", cause) } buf = buf[n:] + nonce, n, cause = encoding.DecodeVarint(buf) + if cause != nil { + return nil, fmt.Errorf("deocding node.leftNodeKey.nonce, %w", cause) + } + buf = buf[n:] + if nonce < int64(math.MinInt32) || nonce > int64(math.MaxInt32) { + return nil, errors.New("invalid nonce, must be int32") + } + leftNodeKey.nonce = int32(nonce) - rightNodeKey, n, cause := encoding.DecodeVarint(buf) + rightNodeKey.version, n, cause = encoding.DecodeVarint(buf) if cause != nil { - return nil, fmt.Errorf("decoding node.rightNodeKey, %w", cause) + return nil, fmt.Errorf("decoding node.rightNodeKey.version, %w", cause) } buf = buf[n:] - rightHash, _, cause := encoding.DecodeBytes(buf) + nonce, _, cause = encoding.DecodeVarint(buf) if cause != nil { - return nil, fmt.Errorf("decoding node.rightHash, %w", cause) + return nil, fmt.Errorf("decoding node.rightNodeKey.nonce, %w", cause) } + if nonce < int64(math.MinInt32) || nonce > int64(math.MaxInt32) { + return nil, errors.New("invalid nonce, must be int32") + } + rightNodeKey.nonce = int32(nonce) - node.leftNodeKey = leftNodeKey - node.leftHash = leftHash - node.rightNodeKey = rightNodeKey - node.rightHash = rightHash + node.leftNodeKey = &leftNodeKey + node.rightNodeKey = &rightNodeKey } return node, nil } -func (node *Node) GetKey() int64 { - return node.nodeKey +func (node *Node) GetKey() []byte { + return node.nodeKey.GetKey() } // String returns a string representation of the node. func (node *Node) String() string { - hashstr := "" - if len(node.hash) > 0 { - hashstr = fmt.Sprintf("%X", node.hash) + child := "" + if node.leftNode != nil && node.leftNode.nodeKey != nil { + child += fmt.Sprintf("{left %d, %d}", node.leftNode.nodeKey.version, node.leftNode.nodeKey.nonce) + } + if node.rightNode != nil && node.rightNode.nodeKey != nil { + child += fmt.Sprintf("{right %d, %d}", node.rightNode.nodeKey.version, node.rightNode.nodeKey.nonce) } - return fmt.Sprintf("Node{%s:%s@%d %d-%d:%d %d-%d}#%s", + return fmt.Sprintf("Node{%s:%s@ %v:%v-%v %d-%d}#%s", ColoredBytes(node.key, Green, Blue), ColoredBytes(node.value, Cyan, Blue), - node.version, node.nodeKey, node.leftNodeKey, node.rightNodeKey, - node.size, node.subtreeHeight, - hashstr) + node.size, node.subtreeHeight, child) } // clone creates a shallow copy of a node with its hash set to nil. -func (node *Node) clone(version int64) (*Node, error) { +func (node *Node) clone(t *ImmutableTree) (*Node, error) { if node.isLeaf() { return nil, ErrCloneLeafNode } + + // ensure get children + var err error + if node.nodeKey != nil { + node.leftNode, err = node.getLeftNode(t) + if err != nil { + return nil, err + } + node.rightNode, err = node.getRightNode(t) + if err != nil { + return nil, err + } + } + return &Node{ key: node.key, subtreeHeight: node.subtreeHeight, - version: version, size: node.size, hash: nil, - nodeKey: node.nodeKey, + nodeKey: nil, leftNodeKey: node.leftNodeKey, rightNodeKey: node.rightNodeKey, - leftHash: node.leftHash, - rightHash: node.rightHash, leftNode: node.leftNode, rightNode: node.rightNode, - persisted: false, }, nil } @@ -263,14 +296,14 @@ func (node *Node) getByIndex(t *ImmutableTree, index int64) (key []byte, value [ // Computes the hash of the node without computing its descendants. Must be // called on nodes which have descendant node hashes already computed. -func (node *Node) _hash() ([]byte, error) { +func (node *Node) _hash(version int64) ([]byte, error) { if node.hash != nil { return node.hash, nil } h := sha256.New() buf := new(bytes.Buffer) - if err := node.writeHashBytes(buf); err != nil { + if err := node.writeHashBytes(buf, version); err != nil { return nil, err } _, err := h.Write(buf.Bytes()) @@ -286,27 +319,27 @@ func (node *Node) _hash() ([]byte, error) { // descendant nodes. Returns the node hash and number of nodes hashed. // If the tree is empty (i.e. the node is nil), returns the hash of an empty input, // to conform with RFC-6962. -func (node *Node) hashWithCount() ([]byte, int64, error) { +func (node *Node) hashWithCount(version int64) ([]byte, error) { if node == nil { - return sha256.New().Sum(nil), 0, nil + return sha256.New().Sum(nil), nil } if node.hash != nil { - return node.hash, 0, nil + return node.hash, nil } h := sha256.New() buf := new(bytes.Buffer) - hashCount, err := node.writeHashBytesRecursively(buf) + err := node.writeHashBytesRecursively(buf, version) if err != nil { - return nil, 0, err + return nil, err } _, err = h.Write(buf.Bytes()) if err != nil { - return nil, 0, err + return nil, err } node.hash = h.Sum(nil) - return node.hash, hashCount + 1, nil + return node.hash, nil } // validate validates the node contents @@ -317,7 +350,10 @@ func (node *Node) validate() error { if node.key == nil { return errors.New("key cannot be nil") } - if node.version <= 0 { + if node.nodeKey == nil { + return errors.New("nodeKey cannot be nil") + } + if node.nodeKey.version <= 0 { return errors.New("version must be greater than 0") } if node.subtreeHeight < 0 { @@ -332,7 +368,7 @@ func (node *Node) validate() error { if node.value == nil { return errors.New("value cannot be nil for leaf node") } - if node.leftNodeKey != 0 || node.leftNode != nil || node.rightNodeKey != 0 || node.rightNode != nil { + if node.leftNodeKey != nil || node.leftNode != nil || node.rightNodeKey != nil || node.rightNode != nil { return errors.New("leaf node cannot have children") } if node.size != 1 { @@ -343,7 +379,7 @@ func (node *Node) validate() error { if node.value != nil { return errors.New("value must be nil for non-leaf node") } - if node.leftNodeKey == 0 && node.rightNodeKey == 0 { + if node.leftNodeKey == nil || node.rightNodeKey == nil { return errors.New("inner node must have children") } } @@ -352,7 +388,7 @@ func (node *Node) validate() error { // Writes the node's hash to the given io.Writer. This function expects // child hashes to be already set. -func (node *Node) writeHashBytes(w io.Writer) error { +func (node *Node) writeHashBytes(w io.Writer, version int64) error { err := encoding.EncodeVarint(w, int64(node.subtreeHeight)) if err != nil { return fmt.Errorf("writing height, %w", err) @@ -361,7 +397,7 @@ func (node *Node) writeHashBytes(w io.Writer) error { if err != nil { return fmt.Errorf("writing size, %w", err) } - err = encoding.EncodeVarint(w, node.version) + err = encoding.EncodeVarint(w, version) if err != nil { return fmt.Errorf("writing version, %w", err) } @@ -383,14 +419,14 @@ func (node *Node) writeHashBytes(w io.Writer) error { return fmt.Errorf("writing value, %w", err) } } else { - if node.leftHash == nil || node.rightHash == nil { - return ErrEmptyChildHash + if node.leftNode == nil || node.rightNode == nil { + return ErrEmptyChild } - err = encoding.EncodeBytes(w, node.leftHash) + err = encoding.EncodeBytes(w, node.leftNode.hash) if err != nil { return fmt.Errorf("writing left hash, %w", err) } - err = encoding.EncodeBytes(w, node.rightHash) + err = encoding.EncodeBytes(w, node.rightNode.hash) if err != nil { return fmt.Errorf("writing right hash, %w", err) } @@ -401,40 +437,30 @@ func (node *Node) writeHashBytes(w io.Writer) error { // Writes the node's hash to the given io.Writer. // This function has the side-effect of calling hashWithCount. -func (node *Node) writeHashBytesRecursively(w io.Writer) (hashCount int64, err error) { - if node.leftNode != nil { - hash, count, err := node.leftNode.hashWithCount() - if err != nil { - return 0, err - } - hashCount += count - node.leftHash = hash +func (node *Node) writeHashBytesRecursively(w io.Writer, version int64) error { + _, err := node.leftNode.hashWithCount(version) + if err != nil { + return err } - if node.rightNode != nil { - hash, count, err := node.rightNode.hashWithCount() - if err != nil { - return 0, err - } - hashCount += count - node.rightHash = hash + _, err = node.rightNode.hashWithCount(version) + if err != nil { + return err } - err = node.writeHashBytes(w) - - return + return node.writeHashBytes(w, version) } func (node *Node) encodedSize() int { n := 1 + encoding.EncodeVarintSize(node.size) + - encoding.EncodeVarintSize(node.version) + encoding.EncodeBytesSize(node.key) if node.isLeaf() { n += encoding.EncodeBytesSize(node.value) } else { - n += encoding.EncodeVarintSize(node.leftNodeKey) + - encoding.EncodeBytesSize(node.leftHash) + - encoding.EncodeVarintSize(node.rightNodeKey) + - encoding.EncodeBytesSize(node.rightHash) + n += encoding.EncodeBytesSize(node.hash) + + encoding.EncodeVarintSize(node.leftNodeKey.version) + + encoding.EncodeVarintSize(int64(node.leftNodeKey.nonce)) + + encoding.EncodeVarintSize(node.rightNodeKey.version) + + encoding.EncodeVarintSize(int64(node.rightNodeKey.nonce)) } return n } @@ -452,12 +478,8 @@ func (node *Node) writeBytes(w io.Writer) error { if cause != nil { return fmt.Errorf("writing size, %w", cause) } - cause = encoding.EncodeVarint(w, node.version) - if cause != nil { - return fmt.Errorf("writing version, %w", cause) - } - // Unlike writeHashBytes, nodeKey and key are written for inner nodes. + // Unlike writeHashByte, key is written for inner nodes. cause = encoding.EncodeBytes(w, node.key) if cause != nil { return fmt.Errorf("writing key, %w", cause) @@ -469,34 +491,32 @@ func (node *Node) writeBytes(w io.Writer) error { return fmt.Errorf("writing value, %w", cause) } } else { - if node.leftNodeKey == 0 { + cause = encoding.EncodeBytes(w, node.hash) + if cause != nil { + return fmt.Errorf("writing hash, %w", cause) + } + if node.leftNodeKey == nil { return ErrLeftNodeKeyEmpty } - cause = encoding.EncodeVarint(w, node.leftNodeKey) + cause = encoding.EncodeVarint(w, node.leftNodeKey.version) if cause != nil { - return fmt.Errorf("writing left node key, %w", cause) - } - if node.leftHash == nil { - return ErrLeftHashIsNil + return fmt.Errorf("writing the version of left node key, %w", cause) } - cause = encoding.EncodeBytes(w, node.leftHash) + cause = encoding.EncodeVarint(w, int64(node.leftNodeKey.nonce)) if cause != nil { - return fmt.Errorf("writing left hash, %w", cause) + return fmt.Errorf("writing the nonce of left node key, %w", cause) } - if node.rightNodeKey == 0 { + if node.rightNodeKey == nil { return ErrRightNodeKeyEmpty } - cause = encoding.EncodeVarint(w, node.rightNodeKey) + cause = encoding.EncodeVarint(w, node.rightNodeKey.version) if cause != nil { - return fmt.Errorf("writing right node key, %w", cause) + return fmt.Errorf("writing the version of right node key, %w", cause) } - if node.rightHash == nil { - return ErrRightHashIsNil - } - cause = encoding.EncodeBytes(w, node.rightHash) + cause = encoding.EncodeVarint(w, int64(node.rightNodeKey.nonce)) if cause != nil { - return fmt.Errorf("writing right hash, %w", cause) + return fmt.Errorf("writing the nonce of right node key, %w", cause) } } return nil @@ -510,7 +530,6 @@ func (node *Node) getLeftNode(t *ImmutableTree) (*Node, error) { if err != nil { return nil, err } - return leftNode, nil } @@ -522,7 +541,6 @@ func (node *Node) getRightNode(t *ImmutableTree) (*Node, error) { if err != nil { return nil, err } - return rightNode, nil } @@ -558,7 +576,6 @@ func (node *Node) calcBalance(t *ImmutableTree) (int, error) { } // traverse is a wrapper over traverseInRange when we want the whole tree -// nolint: unparam func (node *Node) traverse(t *ImmutableTree, ascending bool, cb func(*Node) bool) bool { return node.traverseInRange(t, nil, nil, ascending, false, false, func(node *Node) bool { return cb(node) @@ -587,7 +604,7 @@ func (node *Node) traverseInRange(tree *ImmutableTree, start, end []byte, ascend var ( ErrCloneLeafNode = fmt.Errorf("attempt to copy a leaf node") - ErrEmptyChildHash = fmt.Errorf("found an empty child hash") + ErrEmptyChild = fmt.Errorf("found an empty child") ErrLeftNodeKeyEmpty = fmt.Errorf("node.leftNodeKey was empty in writeBytes") ErrRightNodeKeyEmpty = fmt.Errorf("node.rightNodeKey was empty in writeBytes") ErrLeftHashIsNil = fmt.Errorf("node.leftHash was nil in writeBytes") diff --git a/node_test.go b/node_test.go index 130cb9bd9..3fc80804d 100644 --- a/node_test.go +++ b/node_test.go @@ -16,24 +16,31 @@ func TestNode_encodedSize(t *testing.T) { node := &Node{ key: iavlrand.RandBytes(10), value: iavlrand.RandBytes(10), - version: 1, subtreeHeight: 0, size: 100, hash: iavlrand.RandBytes(20), - nodeKey: 10, - leftNodeKey: 20, - leftNode: nil, - rightNodeKey: 30, - rightNode: nil, - persisted: false, + nodeKey: &NodeKey{ + version: 1, + nonce: 10, + }, + leftNodeKey: &NodeKey{ + version: 1, + nonce: 20, + }, + leftNode: nil, + rightNodeKey: &NodeKey{ + version: 1, + nonce: 30, + }, + rightNode: nil, } // leaf node - require.Equal(t, 26, node.encodedSize()) + require.Equal(t, 25, node.encodedSize()) // non-leaf node node.subtreeHeight = 1 - require.Equal(t, 19, node.encodedSize()) + require.Equal(t, 39, node.encodedSize()) } func TestNode_encode_decode(t *testing.T) { @@ -42,27 +49,36 @@ func TestNode_encode_decode(t *testing.T) { expectHex string expectError bool }{ - "nil": {nil, "", true}, - "empty": {&Node{}, "0000000000", false}, + "nil": {nil, "", true}, "inner": {&Node{ subtreeHeight: 3, - version: 2, size: 7, key: []byte("key"), - leftHash: []byte{0x70, 0x80, 0x90, 0xa0}, - rightHash: []byte{0x10, 0x20, 0x30, 0x40}, - nodeKey: 1, - leftNodeKey: 2, - rightNodeKey: 3, - }, "060e04036b65790404708090a0060410203040", false}, + nodeKey: &NodeKey{ + version: 2, + nonce: 1, + }, + leftNodeKey: &NodeKey{ + version: 1, + nonce: 2, + }, + rightNodeKey: &NodeKey{ + version: 1, + nonce: 3, + }, + hash: []byte{0x70, 0x80, 0x90, 0xa0}, + }, "060e036b657904708090a002040206", false}, "leaf": {&Node{ subtreeHeight: 0, - version: 3, size: 1, key: []byte("key"), value: []byte("value"), - nodeKey: 4, - }, "000206036b65790576616c7565", false}, + nodeKey: &NodeKey{ + version: 3, + nonce: 4, + }, + hash: []byte{0x7f, 0x68, 0x90, 0xca, 0x16, 0xde, 0xa6, 0xe8, 0x89, 0x3d, 0x96, 0xf0, 0xa3, 0xd, 0xa, 0x14, 0xe5, 0x55, 0x59, 0xfc, 0x9b, 0x83, 0x4, 0x91, 0xe3, 0xd2, 0x45, 0x1c, 0x81, 0xf6, 0xd1, 0xe}, + }, "0002036b65790576616c7565", false}, } for name, tc := range testcases { tc := tc @@ -93,36 +109,39 @@ func TestNode_encode_decode(t *testing.T) { func TestNode_validate(t *testing.T) { k := []byte("key") v := []byte("value") - nk := int64(10) - c := &Node{key: []byte("child"), value: []byte("x"), version: 1, size: 1} + nk := &NodeKey{ + version: 1, + nonce: 10, + } + c := &Node{key: []byte("child"), value: []byte("x"), size: 1} testcases := map[string]struct { node *Node valid bool }{ "nil node": {nil, false}, - "leaf": {&Node{key: k, value: v, version: 1, size: 1}, true}, - "leaf with nil key": {&Node{key: nil, value: v, version: 1, size: 1}, false}, - "leaf with empty key": {&Node{key: []byte{}, value: v, version: 1, size: 1}, true}, - "leaf with nil value": {&Node{key: k, value: nil, version: 1, size: 1}, false}, - "leaf with empty value": {&Node{key: k, value: []byte{}, version: 1, size: 1}, true}, - "leaf with version 0": {&Node{key: k, value: v, version: 0, size: 1}, false}, - "leaf with version -1": {&Node{key: k, value: v, version: -1, size: 1}, false}, - "leaf with size 0": {&Node{key: k, value: v, version: 1, size: 0}, false}, - "leaf with size 2": {&Node{key: k, value: v, version: 1, size: 2}, false}, - "leaf with size -1": {&Node{key: k, value: v, version: 1, size: -1}, false}, - "leaf with left node key": {&Node{key: k, value: v, version: 1, size: 1, leftNodeKey: nk}, false}, - "leaf with left child": {&Node{key: k, value: v, version: 1, size: 1, leftNode: c}, false}, - "leaf with right node key": {&Node{key: k, value: v, version: 1, size: 1, rightNodeKey: nk}, false}, - "leaf with right child": {&Node{key: k, value: v, version: 1, size: 1, rightNode: c}, false}, - "inner": {&Node{key: k, version: 1, size: 1, subtreeHeight: 1, leftNodeKey: nk, rightNodeKey: nk}, true}, - "inner with nil key": {&Node{key: nil, value: v, version: 1, size: 1, subtreeHeight: 1, leftNodeKey: nk, rightNodeKey: nk}, false}, - "inner with value": {&Node{key: k, value: v, version: 1, size: 1, subtreeHeight: 1, leftNodeKey: nk, rightNodeKey: nk}, false}, - "inner with empty value": {&Node{key: k, value: []byte{}, version: 1, size: 1, subtreeHeight: 1, leftNodeKey: nk, rightNodeKey: nk}, false}, - "inner with left child": {&Node{key: k, version: 1, size: 1, subtreeHeight: 1, leftNodeKey: nk}, true}, - "inner with right child": {&Node{key: k, version: 1, size: 1, subtreeHeight: 1, rightNodeKey: nk}, true}, - "inner with no child": {&Node{key: k, version: 1, size: 1, subtreeHeight: 1}, false}, - "inner with height 0": {&Node{key: k, version: 1, size: 1, subtreeHeight: 0, leftNodeKey: nk, rightNodeKey: nk}, false}, + "leaf": {&Node{key: k, value: v, nodeKey: nk, size: 1}, true}, + "leaf with nil key": {&Node{key: nil, value: v, size: 1}, false}, + "leaf with empty key": {&Node{key: []byte{}, value: v, nodeKey: nk, size: 1}, true}, + "leaf with nil value": {&Node{key: k, value: nil, size: 1}, false}, + "leaf with empty value": {&Node{key: k, value: []byte{}, nodeKey: nk, size: 1}, true}, + "leaf with version 0": {&Node{key: k, value: v, size: 1}, false}, + "leaf with version -1": {&Node{key: k, value: v, size: 1}, false}, + "leaf with size 0": {&Node{key: k, value: v, size: 0}, false}, + "leaf with size 2": {&Node{key: k, value: v, size: 2}, false}, + "leaf with size -1": {&Node{key: k, value: v, size: -1}, false}, + "leaf with left node key": {&Node{key: k, value: v, size: 1, leftNodeKey: nk}, false}, + "leaf with left child": {&Node{key: k, value: v, size: 1, leftNode: c}, false}, + "leaf with right node key": {&Node{key: k, value: v, size: 1, rightNodeKey: nk}, false}, + "leaf with right child": {&Node{key: k, value: v, size: 1, rightNode: c}, false}, + "inner": {&Node{key: k, size: 1, subtreeHeight: 1, nodeKey: nk, leftNodeKey: nk, rightNodeKey: nk}, true}, + "inner with nil key": {&Node{key: nil, value: v, size: 1, subtreeHeight: 1, leftNodeKey: nk, rightNodeKey: nk}, false}, + "inner with value": {&Node{key: k, value: v, size: 1, subtreeHeight: 1, leftNodeKey: nk, rightNodeKey: nk}, false}, + "inner with empty value": {&Node{key: k, value: []byte{}, size: 1, subtreeHeight: 1, leftNodeKey: nk, rightNodeKey: nk}, false}, + "inner with left child": {&Node{key: k, size: 1, subtreeHeight: 1, nodeKey: nk, leftNodeKey: nk}, false}, + "inner with right child": {&Node{key: k, size: 1, subtreeHeight: 1, nodeKey: nk, rightNodeKey: nk}, false}, + "inner with no child": {&Node{key: k, size: 1, subtreeHeight: 1}, false}, + "inner with height 0": {&Node{key: k, size: 1, subtreeHeight: 0, leftNodeKey: nk, rightNodeKey: nk}, false}, } for desc, tc := range testcases { @@ -139,14 +158,18 @@ func TestNode_validate(t *testing.T) { } func BenchmarkNode_encodedSize(b *testing.B) { + nk := &NodeKey{ + version: rand.Int63n(10000000), + nonce: rand.Int31n(10000000), + } node := &Node{ key: iavlrand.RandBytes(25), value: iavlrand.RandBytes(100), - version: rand.Int63n(10000000), + nodeKey: nk, subtreeHeight: 1, size: rand.Int63n(10000000), - leftNodeKey: rand.Int63n(10000000), - rightNodeKey: rand.Int63n(10000000), + leftNodeKey: nk, + rightNodeKey: nk, } b.ReportAllocs() b.ResetTimer() @@ -156,14 +179,18 @@ func BenchmarkNode_encodedSize(b *testing.B) { } func BenchmarkNode_WriteBytes(b *testing.B) { + nk := &NodeKey{ + version: rand.Int63n(10000000), + nonce: rand.Int31n(10000000), + } node := &Node{ key: iavlrand.RandBytes(25), value: iavlrand.RandBytes(100), - version: rand.Int63n(10000000), + nodeKey: nk, subtreeHeight: 1, size: rand.Int63n(10000000), - leftNodeKey: rand.Int63n(10000000), - rightNodeKey: rand.Int63n(10000000), + leftNodeKey: nk, + rightNodeKey: nk, } b.ResetTimer() b.Run("NoPreAllocate", func(sub *testing.B) { diff --git a/nodedb.go b/nodedb.go index 5b3f68596..6230938b4 100644 --- a/nodedb.go +++ b/nodedb.go @@ -3,7 +3,6 @@ package iavl import ( "bytes" "crypto/sha256" - "encoding/binary" "errors" "fmt" "math" @@ -22,6 +21,7 @@ import ( ) const ( + int32Size = 4 int64Size = 8 hashSize = sha256.Size genesisVersion = 1 @@ -40,8 +40,8 @@ const ( var ( // All node keys are prefixed with the byte 'n'. This ensures no collision is - // possible with the other keys, and makes them easier to traverse. They are indexed by the node hash. - nodeKeyFormat = keyformat.NewKeyFormat('a', int64Size) // n + // possible with the other keys, and makes them easier to traverse. They are indexed by the version and the node local nonce. + nodeKeyFormat = keyformat.NewKeyFormat('n', int64Size, int32Size) // n // Orphans are keyed in the database by their expected lifetime. // The first number represents the *last* version at which the orphan needs @@ -51,7 +51,7 @@ var ( // To clarify: // When I write to key {X} with value V and old value O, we orphan O with =time of write // and = version O was created at. - orphanKeyFormat = keyformat.NewKeyFormat('o', int64Size, int64Size, hashSize) // o + orphanKeyFormat = keyformat.NewKeyFormat('n', int64Size, int32Size, int64Size, int32Size) // n<0> // Key Format for making reads and iterates go through a data-locality preserving db. // The value at an entry will list what version it was written to. @@ -63,24 +63,24 @@ var ( // Key Format for storing metadata about the chain such as the vesion number. // The value at an entry will be in a variable format and up to the caller to // decide how to parse. - metadataKeyFormat = keyformat.NewKeyFormat('m', 0) // v + metadataKeyFormat = keyformat.NewKeyFormat('m', 0) // m - // Root nodes are indexed separately by their version - rootKeyFormat = keyformat.NewKeyFormat('r', int64Size) // r + // Keep alive versions. + versionKeyFormat = keyformat.NewKeyFormat('v', int64Size) // v ) var errInvalidFastStorageVersion = fmt.Sprintf("Fast storage version must be in the format %s", fastStorageVersionDelimiter) type nodeDB struct { - mtx sync.Mutex // Read/write lock. - db dbm.DB // Persistent node storage. - batch dbm.Batch // Batched writing buffer. - opts Options // Options to customize for pruning/writing - versionReaders map[int64]uint32 // Number of active version readers - storageVersion string // Storage version - latestVersion int64 // Latest version of nodeDB. - nodeCache cache.Cache[int64] // Cache for nodes in the regular tree that consists of key-value pairs at any version. - fastNodeCache cache.Cache[[]byte] // Cache for nodes in the fast index that represents only key-value pairs at the latest version. + mtx sync.Mutex // Read/write lock. + db dbm.DB // Persistent node storage. + batch dbm.Batch // Batched writing buffer. + opts Options // Options to customize for pruning/writing + versionReaders map[int64]uint32 // Number of active version readers + storageVersion string // Storage version + latestVersion int64 // Latest version of nodeDB. + nodeCache cache.Cache // Cache for nodes in the regular tree that consists of key-value pairs at any version. + fastNodeCache cache.Cache // Cache for nodes in the fast index that represents only key-value pairs at the latest version. } func newNodeDB(db dbm.DB, cacheSize int, opts *Options) *nodeDB { @@ -100,8 +100,8 @@ func newNodeDB(db dbm.DB, cacheSize int, opts *Options) *nodeDB { batch: db.NewBatch(), opts: *opts, latestVersion: 0, // initially invalid - nodeCache: cache.New[int64, int64](cacheSize), - fastNodeCache: cache.New[string, []byte](fastNodeCacheSize), + nodeCache: cache.New(cacheSize), + fastNodeCache: cache.New(fastNodeCacheSize), versionReaders: make(map[int64]uint32, 8), storageVersion: string(storeVersion), } @@ -109,16 +109,16 @@ func newNodeDB(db dbm.DB, cacheSize int, opts *Options) *nodeDB { // GetNode gets a node from memory or disk. If it is an inner node, it does not // load its children. -func (ndb *nodeDB) GetNode(nodeKey int64) (*Node, error) { +func (ndb *nodeDB) GetNode(nk *NodeKey) (*Node, error) { ndb.mtx.Lock() defer ndb.mtx.Unlock() - if nodeKey == 0 { + if nk == nil { return nil, nil } // Check the cache. - if cachedNode := ndb.nodeCache.Get(nodeKey); cachedNode != nil { + if cachedNode := ndb.nodeCache.Get(nk.GetKey()); cachedNode != nil { ndb.opts.Stat.IncCacheHitCnt() return cachedNode.(*Node), nil } @@ -126,25 +126,19 @@ func (ndb *nodeDB) GetNode(nodeKey int64) (*Node, error) { ndb.opts.Stat.IncCacheMissCnt() // Doesn't exist, load. - buf, err := ndb.db.Get(ndb.nodeKey(nodeKey)) + buf, err := ndb.db.Get(ndb.nodeKey(nk)) if err != nil { - return nil, fmt.Errorf("can't get node %d: %v", nodeKey, err) + return nil, fmt.Errorf("can't get node %v: %v", nk, err) } if buf == nil { - return nil, fmt.Errorf("Value missing for key %d corresponding to nodeKey %x", nodeKey, ndb.nodeKey(nodeKey)) + return nil, fmt.Errorf("Value missing for key %v corresponding to nodeKey %x", nk, ndb.nodeKey(nk)) } - node, err := MakeNode(nodeKey, buf) + node, err := MakeNode(nk, buf) if err != nil { return nil, fmt.Errorf("error reading Node. bytes: %x, error: %v", buf, err) } - _, err = node._hash() - if err != nil { - return nil, fmt.Errorf("error getting hash. error: %v", err) - } - - node.persisted = true ndb.nodeCache.Add(node) return node, nil @@ -192,14 +186,10 @@ func (ndb *nodeDB) SaveNode(node *Node) error { ndb.mtx.Lock() defer ndb.mtx.Unlock() - if node.nodeKey == 0 { + if node.nodeKey == nil { return ErrNodeMissingNodeKey } - if node.persisted { - return ErrNodeAlreadyPersisted - } - // Save node bytes to db. var buf bytes.Buffer buf.Grow(node.encodedSize()) @@ -211,20 +201,29 @@ func (ndb *nodeDB) SaveNode(node *Node) error { if err := ndb.batch.Set(ndb.nodeKey(node.nodeKey), buf.Bytes()); err != nil { return err } - logger.Debug("BATCH SAVE %X %d %d %p\n", node.hash, node.nodeKey, node.version, node) - node.persisted = true + + // resetBatch only working on generate a genesis block + if node.nodeKey.version <= genesisVersion { + if err := ndb.resetBatch(); err != nil { + return err + } + } + node.leftNode = nil + node.rightNode = nil + + logger.Debug("BATCH SAVE %+v\n", node) ndb.nodeCache.Add(node) return nil } -// SaveNode saves a FastNode to disk and add to cache. +// SaveFastNode saves a FastNode to disk and add to cache. func (ndb *nodeDB) SaveFastNode(node *fastnode.Node) error { ndb.mtx.Lock() defer ndb.mtx.Unlock() return ndb.saveFastNodeUnlocked(node, true) } -// SaveNode saves a FastNode to disk without adding to cache. +// SaveFastNodeNoCache saves a FastNode to disk without adding to cache. func (ndb *nodeDB) SaveFastNodeNoCache(node *fastnode.Node) error { ndb.mtx.Lock() defer ndb.mtx.Unlock() @@ -292,7 +291,7 @@ func (ndb *nodeDB) shouldForceFastStorageUpgrade() (bool, error) { return false, nil } -// SaveNode saves a FastNode to disk. +// saveFastNodeUnlocked saves a FastNode to disk. func (ndb *nodeDB) saveFastNodeUnlocked(node *fastnode.Node, shouldAddToCache bool) error { if node.GetKey() == nil { return fmt.Errorf("cannot have FastNode with a nil value for key") @@ -316,8 +315,8 @@ func (ndb *nodeDB) saveFastNodeUnlocked(node *fastnode.Node, shouldAddToCache bo } // Has checks if a hash exists in the database. -func (ndb *nodeDB) Has(nodeKey int64) (bool, error) { - key := ndb.nodeKey(nodeKey) +func (ndb *nodeDB) Has(nk *NodeKey) (bool, error) { + key := ndb.nodeKey(nk) if ldb, ok := ndb.db.(*dbm.GoLevelDB); ok { exists, err := ldb.DB().Has(key, nil) @@ -334,54 +333,6 @@ func (ndb *nodeDB) Has(nodeKey int64) (bool, error) { return value != nil, nil } -// SaveBranch saves the given node and all of its descendants. -// NOTE: This function clears leftNode/rigthNode recursively and -// calls _hash() on the given node. -// TODO refactor, maybe use nodeKeyWithCount() but provide a callback. -func (ndb *nodeDB) SaveBranch(node *Node) ([]byte, int64, error) { - if node.persisted { - return node.hash, node.nodeKey, nil - } - - var err error - if node.leftNode != nil { - node.leftHash, node.leftNodeKey, err = ndb.SaveBranch(node.leftNode) - } - - if err != nil { - return nil, 0, err - } - - if node.rightNode != nil { - node.rightHash, node.rightNodeKey, err = ndb.SaveBranch(node.rightNode) - } - - if err != nil { - return nil, 0, err - } - - _, err = node._hash() - if err != nil { - return nil, 0, err - } - - err = ndb.SaveNode(node) - if err != nil { - return nil, 0, err - } - - // resetBatch only working on generate a genesis block - if node.version <= genesisVersion { - if err = ndb.resetBatch(); err != nil { - return nil, 0, err - } - } - node.leftNode = nil - node.rightNode = nil - - return node.hash, node.nodeKey, nil -} - // resetBatch reset the db batch, keep low memory used func (ndb *nodeDB) resetBatch() error { var err error @@ -418,7 +369,7 @@ func (ndb *nodeDB) DeleteVersion(version int64, checkLatestVersion bool) error { return err } - err = ndb.deleteRoot(version, checkLatestVersion) + err = ndb.deleteVersion(version, checkLatestVersion) if err != nil { return err } @@ -434,10 +385,6 @@ func (ndb *nodeDB) DeleteVersionsFrom(version int64) error { if latest < version { return nil } - root, _, err := ndb.getRoot(latest) - if err != nil { - return err - } for v, r := range ndb.versionReaders { if v >= version && r != 0 { @@ -445,46 +392,22 @@ func (ndb *nodeDB) DeleteVersionsFrom(version int64) error { } } - // First, delete all active nodes in the current (latest) version whose node version is after - // the given version. - err = ndb.deleteNodesFrom(version, root) - if err != nil { - return err - } - - // Next, delete orphans: - // - Delete orphan entries *and referred nodes* with fromVersion >= version - // - Delete orphan entries with toVersion >= version-1 (since orphans at latest are not orphans) - err = ndb.traverseOrphans(func(key, value []byte) error { - var fromVersion, toVersion int64 - orphanKeyFormat.Scan(key, &toVersion, &fromVersion) - - if fromVersion >= version { - if err = ndb.batch.Delete(key); err != nil { - return err - } - nodeKey, _, err := encoding.DecodeVarint(value) - if err != nil { - return err - } - if err = ndb.batch.Delete(ndb.nodeKey(nodeKey)); err != nil { - return err - } - ndb.nodeCache.Remove(nodeKey) - } else if toVersion >= version-1 { - if err = ndb.batch.Delete(key); err != nil { - return err - } + // Delete the nodes and orphans + err = ndb.traverseRange(nodeKeyFormat.Key(version, int32(0)), nodeKeyFormat.Key(latest, int32(math.MaxInt32)), func(k, v []byte) error { + if err = ndb.batch.Delete(k); err != nil { + return err } return nil }) - - if err != nil { - return err - } - - // Delete the version root entries - err = ndb.traverseRange(rootKeyFormat.Key(version), rootKeyFormat.Key(int64(math.MaxInt64)), func(k, v []byte) error { + // Delete orphans for version-1 + err = ndb.traverseRange(orphanKeyFormat.Key(version-1, int32(0)), orphanKeyFormat.Key(version-1, int32(1)), func(k, v []byte) error { + if err = ndb.batch.Delete(k); err != nil { + return err + } + return nil + }) + // Delete the version entries + err = ndb.traverseRange(versionKeyFormat.Key(version), versionKeyFormat.Key(int64(math.MaxInt64)), func(k, v []byte) error { if err = ndb.batch.Delete(k); err != nil { return err } @@ -545,7 +468,7 @@ func (ndb *nodeDB) DeleteVersionsRange(fromVersion, toVersion int64) error { } for v, r := range ndb.versionReaders { - if v < toVersion && v > predecessor && r != 0 { + if v < toVersion && v >= fromVersion && r != 0 { return fmt.Errorf("unable to delete version %v with %v active readers", v, r) } } @@ -553,24 +476,24 @@ func (ndb *nodeDB) DeleteVersionsRange(fromVersion, toVersion int64) error { // If the predecessor is earlier than the beginning of the lifetime, we can delete the orphan. // Otherwise, we shorten its lifetime, by moving its endpoint to the predecessor version. for version := fromVersion; version < toVersion; version++ { - err := ndb.traverseOrphansVersion(version, func(key, value []byte) error { + err := ndb.traverseOrphansVersion(version, func(key, _ []byte) error { var from, to int64 - orphanKeyFormat.Scan(key, &to, &from) + var dummy, nonce int32 + orphanKeyFormat.Scan(key, &to, &dummy, &from, &nonce) + nk := &NodeKey{ + version: from, + nonce: nonce, + } if err := ndb.batch.Delete(key); err != nil { return err } - if from > predecessor { - nodeKey, _, err := encoding.DecodeVarint(value) - if err != nil { - return err - } - if err := ndb.batch.Delete(ndb.nodeKey(nodeKey)); err != nil { + if err := ndb.batch.Delete(ndb.nodeKey(nk)); err != nil { return err } - ndb.nodeCache.Remove(nodeKey) + ndb.nodeCache.Remove(nk.GetKey()) } else { - if err := ndb.saveOrphan(value, from, predecessor); err != nil { + if err := ndb.saveOrphan(nk, predecessor); err != nil { return err } } @@ -581,8 +504,8 @@ func (ndb *nodeDB) DeleteVersionsRange(fromVersion, toVersion int64) error { } } - // Delete the version root entries - err = ndb.traverseRange(rootKeyFormat.Key(fromVersion), rootKeyFormat.Key(toVersion), func(k, v []byte) error { + // Delete the version entries + err = ndb.traverseRange(versionKeyFormat.Key(fromVersion), versionKeyFormat.Key(toVersion), func(k, v []byte) error { if err := ndb.batch.Delete(k); err != nil { return err } @@ -605,44 +528,10 @@ func (ndb *nodeDB) DeleteFastNode(key []byte) error { return nil } -// deleteNodesFrom deletes the given node and any descendants that have versions after the given -// (inclusive). It is mainly used via LoadVersionForOverwriting, to delete the current version. -func (ndb *nodeDB) deleteNodesFrom(version, nodeKey int64) error { - if nodeKey == 0 { - return nil - } - - node, err := ndb.GetNode(nodeKey) - if err != nil { - return err - } - - if node.leftNodeKey != 0 { - if err := ndb.deleteNodesFrom(version, node.leftNodeKey); err != nil { - return err - } - } - if node.rightNodeKey != 0 { - if err := ndb.deleteNodesFrom(version, node.rightNodeKey); err != nil { - return err - } - } - - if node.version >= version { - if err := ndb.batch.Delete(ndb.nodeKey(nodeKey)); err != nil { - return err - } - - ndb.nodeCache.Remove(nodeKey) - } - - return nil -} - // Saves orphaned nodes to disk under a special prefix. // version: the new version being saved. // orphans: the orphan nodes created since version-1 -func (ndb *nodeDB) SaveOrphans(version int64, orphans map[int64]int64) error { +func (ndb *nodeDB) SaveOrphans(version int64, orphans []*NodeKey) error { ndb.mtx.Lock() defer ndb.mtx.Unlock() @@ -651,14 +540,9 @@ func (ndb *nodeDB) SaveOrphans(version int64, orphans map[int64]int64) error { return err } - for nodeKey, fromVersion := range orphans { - buf := new(bytes.Buffer) - err := encoding.EncodeVarint(buf, nodeKey) - if err != nil { - return err - } - logger.Debug("SAVEORPHAN %v-%v %d\n", fromVersion, toVersion, nodeKey) - err = ndb.saveOrphan(buf.Bytes(), fromVersion, toVersion) + for _, nk := range orphans { + logger.Debug("SAVEORPHAN %d-%v\n", toVersion, nk) + err := ndb.saveOrphan(nk, toVersion) if err != nil { return err } @@ -667,12 +551,12 @@ func (ndb *nodeDB) SaveOrphans(version int64, orphans map[int64]int64) error { } // Saves a single orphan to disk. -func (ndb *nodeDB) saveOrphan(value []byte, fromVersion, toVersion int64) error { - if fromVersion > toVersion { - return fmt.Errorf("orphan expires before it comes alive. %d > %d", fromVersion, toVersion) +func (ndb *nodeDB) saveOrphan(nk *NodeKey, toVersion int64) error { + if nk.version > toVersion { + return fmt.Errorf("orphan expires before it comes alive. %d > %d", nk.version, toVersion) } - key := ndb.orphanKey(fromVersion, toVersion, value) - if err := ndb.batch.Set(key, value); err != nil { + key := ndb.orphanKey(toVersion, nk) + if err := ndb.batch.Set(key, []byte{}); err != nil { return err } return nil @@ -689,13 +573,17 @@ func (ndb *nodeDB) deleteOrphans(version int64) error { // Traverse orphans with a lifetime ending at the version specified. // TODO optimize. - return ndb.traverseOrphansVersion(version, func(key, value []byte) error { + return ndb.traverseOrphansVersion(version, func(key, _ []byte) error { var fromVersion, toVersion int64 + var dummy, nonce int32 // See comment on `orphanKeyFmt`. Note that here, `version` and // `toVersion` are always equal. - orphanKeyFormat.Scan(key, &toVersion, &fromVersion) - + orphanKeyFormat.Scan(key, &toVersion, &dummy, &fromVersion, &nonce) + nk := &NodeKey{ + version: fromVersion, + nonce: nonce, + } // Delete orphan key and reverse-lookup key. if err := ndb.batch.Delete(key); err != nil { return err @@ -706,19 +594,15 @@ func (ndb *nodeDB) deleteOrphans(version int64) error { // spans a single version and that version is the one being deleted, we // can delete the orphan. Otherwise, we shorten its lifetime, by // moving its endpoint to the previous version. - if predecessor < fromVersion || fromVersion == toVersion { - nodeKey, _, err := encoding.DecodeVarint(value) - if err != nil { + if predecessor < fromVersion { + logger.Debug("DELETE toVersion:%d predecessor:%d node key:%v\n", toVersion, predecessor, nk) + if err := ndb.batch.Delete(ndb.nodeKey(nk)); err != nil { return err } - logger.Debug("DELETE nodeKey: %v predecessor:%v fromVersion:%v toVersion:%v %X\n", nodeKey, predecessor, fromVersion, toVersion, value) - if err := ndb.batch.Delete(ndb.nodeKey(nodeKey)); err != nil { - return err - } - ndb.nodeCache.Remove(nodeKey) + ndb.nodeCache.Remove(nk.GetKey()) } else { - logger.Debug("MOVE predecessor:%v fromVersion:%v toVersion:%v %X\n", predecessor, fromVersion, toVersion, value) - err := ndb.saveOrphan(value, fromVersion, predecessor) + logger.Debug("MOVE toVersion:%d predecessor:%d node key:%v\n", toVersion, predecessor, nk) + err := ndb.saveOrphan(nk, predecessor) if err != nil { return err } @@ -727,20 +611,20 @@ func (ndb *nodeDB) deleteOrphans(version int64) error { }) } -func (ndb *nodeDB) nodeKey(nodeKey int64) []byte { - return nodeKeyFormat.NodeKey(nodeKey) +func (ndb *nodeDB) nodeKey(nk *NodeKey) []byte { + return nodeKeyFormat.Key(nk.version, nk.nonce) } func (ndb *nodeDB) fastNodeKey(key []byte) []byte { return fastKeyFormat.KeyBytes(key) } -func (ndb *nodeDB) orphanKey(fromVersion, toVersion int64, hash []byte) []byte { - return orphanKeyFormat.Key(toVersion, fromVersion, hash) +func (ndb *nodeDB) orphanKey(toVersion int64, orphan *NodeKey) []byte { + return orphanKeyFormat.Key(toVersion, int32(0), orphan.version, orphan.nonce) } -func (ndb *nodeDB) rootKey(version int64) []byte { - return rootKeyFormat.Key(version) +func (ndb *nodeDB) versionKey(version int64) []byte { + return versionKeyFormat.Key(version) } func (ndb *nodeDB) getLatestVersion() (int64, error) { @@ -766,8 +650,8 @@ func (ndb *nodeDB) resetLatestVersion(version int64) { func (ndb *nodeDB) getPreviousVersion(version int64) (int64, error) { itr, err := ndb.db.ReverseIterator( - rootKeyFormat.Key(1), - rootKeyFormat.Key(version), + versionKeyFormat.Key(1), + versionKeyFormat.Key(version), ) if err != nil { return 0, err @@ -777,7 +661,7 @@ func (ndb *nodeDB) getPreviousVersion(version int64) (int64, error) { pversion := int64(-1) if itr.Valid() { k := itr.Key() - rootKeyFormat.Scan(k, &pversion) + versionKeyFormat.Scan(k, &pversion) return pversion, nil } @@ -788,8 +672,8 @@ func (ndb *nodeDB) getPreviousVersion(version int64) (int64, error) { return 0, nil } -// deleteRoot deletes the root entry from disk, but not the node it points to. -func (ndb *nodeDB) deleteRoot(version int64, checkLatestVersion bool) error { +// deleteVersion deletes the version entry from disk, but not the node it points to. +func (ndb *nodeDB) deleteVersion(version int64, checkLatestVersion bool) error { latestVersion, err := ndb.getLatestVersion() if err != nil { return err @@ -798,15 +682,66 @@ func (ndb *nodeDB) deleteRoot(version int64, checkLatestVersion bool) error { if checkLatestVersion && version == latestVersion { return errors.New("tried to delete latest version") } - if err := ndb.batch.Delete(ndb.rootKey(version)); err != nil { + if err := ndb.batch.Delete(ndb.versionKey(version)); err != nil { + return err + } + return nil +} + +func (ndb *nodeDB) HasVersion(version int64) (bool, error) { + return ndb.db.Has(ndb.versionKey(version)) +} + +func (ndb *nodeDB) getVersions() (versions []int64, err error) { + versions = make([]int64, 0) + err = ndb.traversePrefix(versionKeyFormat.Key(), func(k, _ []byte) error { + var version int64 + versionKeyFormat.Scan(k, &version) + versions = append(versions, version) + return nil + }) + return versions, err +} + +// GetRoot get the nodeKey of the root for the specific version. +func (ndb *nodeDB) GetRoot(version int64) (*NodeKey, error) { + value, err := ndb.db.Get(versionKeyFormat.Key(version)) + if err != nil { + return nil, err + } + rootVersion, _, err := encoding.DecodeVarint(value) + return &NodeKey{version: rootVersion, nonce: 1}, err +} + +// SaveRoot creates an entry on disk for the given root, so that it can be +// loaded later. +func (ndb *nodeDB) SaveRoot(version, rootVersion int64) error { + buf := new(bytes.Buffer) + if err := encoding.EncodeVarint(buf, rootVersion); err != nil { + return err + } + if err := ndb.batch.Set(ndb.versionKey(version), buf.Bytes()); err != nil { return err } + ndb.updateLatestVersion(version) + return nil } // Traverse orphans and return error if any, nil otherwise func (ndb *nodeDB) traverseOrphans(fn func(keyWithPrefix, v []byte) error) error { - return ndb.traversePrefix(orphanKeyFormat.Key(), fn) + ndb.resetLatestVersion(0) + latest, err := ndb.getLatestVersion() + if err != nil { + return err + } + + for version := int64(1); version <= latest; version++ { + if err := ndb.traversePrefix(orphanKeyFormat.Key(version, int32(0)), fn); err != nil { + return err + } + } + return nil } // Traverse fast nodes and return error if any, nil otherwise @@ -816,7 +751,7 @@ func (ndb *nodeDB) traverseFastNodes(fn func(k, v []byte) error) error { // Traverse orphans ending at a certain version. return error if any, nil otherwise func (ndb *nodeDB) traverseOrphansVersion(version int64, fn func(k, v []byte) error) error { - return ndb.traversePrefix(orphanKeyFormat.Key(version), fn) + return ndb.traversePrefix(orphanKeyFormat.Key(version, int32(0)), fn) } // Traverse all keys and return error if any, nil otherwise @@ -908,102 +843,6 @@ func (ndb *nodeDB) Commit() error { return nil } -func (ndb *nodeDB) HasRoot(version int64) (bool, error) { - return ndb.db.Has(ndb.rootKey(version)) -} - -func (ndb *nodeDB) getRoot(version int64) (int64, int64, error) { - buf, err := ndb.db.Get(ndb.rootKey(version)) - if err != nil { - return 0, 0, err - } - rootKey, n, err := encoding.DecodeVarint(buf) - if err != nil { - return 0, 0, err - } - buf = buf[n:] - nonce, _, err := encoding.DecodeVarint(buf) - return rootKey, nonce, err -} - -func (ndb *nodeDB) getRoots() (roots map[int64]struct { - rootKey int64 - nonce int64 -}, err error, -) { - roots = make(map[int64]struct { - rootKey int64 - nonce int64 - }) - err = ndb.traversePrefix(rootKeyFormat.Key(), func(k, v []byte) error { - var version int64 - rootKeyFormat.Scan(k, &version) - rootKey, n, err := encoding.DecodeVarint(v) - if err != nil { - return err - } - v = v[n:] - nonce, _, err := encoding.DecodeVarint(v) - if err != nil { - return err - } - roots[version] = struct { - rootKey int64 - nonce int64 - }{ - rootKey: rootKey, - nonce: nonce, - } - return nil - }) - return roots, err -} - -// SaveRoot creates an entry on disk for the given root, so that it can be -// loaded later. -func (ndb *nodeDB) SaveRoot(rootNodeKey, nonce, version int64) error { - if rootNodeKey == 0 { - return ErrRootMissingNodeKey - } - return ndb.saveRoot(rootNodeKey, nonce, version) -} - -// SaveEmptyRoot creates an entry on disk for an empty root. -func (ndb *nodeDB) SaveEmptyRoot(version int64) error { - return ndb.saveRoot(0, 0, version) -} - -func (ndb *nodeDB) saveRoot(nodeKey, nonce, version int64) error { - ndb.mtx.Lock() - defer ndb.mtx.Unlock() - - // We allow the initial version to be arbitrary - latest, err := ndb.getLatestVersion() - if err != nil { - return err - } - if latest > 0 && version != latest+1 { - return fmt.Errorf("must save consecutive versions; expected %d, got %d", latest+1, version) - } - buf := new(bytes.Buffer) - err = encoding.EncodeVarint(buf, nodeKey) - if err != nil { - return err - } - err = encoding.EncodeVarint(buf, nonce) - if err != nil { - return err - } - - if err := ndb.batch.Set(ndb.rootKey(version), buf.Bytes()); err != nil { - return err - } - - ndb.updateLatestVersion(version) - - return nil -} - func (ndb *nodeDB) incrVersionReaders(version int64) { ndb.mtx.Lock() defer ndb.mtx.Unlock() @@ -1024,7 +863,7 @@ func (ndb *nodeDB) decrVersionReaders(version int64) { func (ndb *nodeDB) leafNodes() ([]*Node, error) { leaves := []*Node{} - err := ndb.traverseNodes(func(hash []byte, node *Node) error { + err := ndb.traverseNodes(func(node *Node) error { if node.isLeaf() { leaves = append(leaves, node) } @@ -1041,7 +880,7 @@ func (ndb *nodeDB) leafNodes() ([]*Node, error) { func (ndb *nodeDB) nodes() ([]*Node, error) { nodes := []*Node{} - err := ndb.traverseNodes(func(hash []byte, node *Node) error { + err := ndb.traverseNodes(func(node *Node) error { nodes = append(nodes, node) return nil }) @@ -1057,7 +896,7 @@ func (ndb *nodeDB) orphans() ([][]byte, error) { orphans := [][]byte{} err := ndb.traverseOrphans(func(k, v []byte) error { - orphans = append(orphans, v) + orphans = append(orphans, k) return nil }) if err != nil { @@ -1084,20 +923,27 @@ func (ndb *nodeDB) size() int { return size } -func (ndb *nodeDB) traverseNodes(fn func(hash []byte, node *Node) error) error { - nodes := []*Node{} +func (ndb *nodeDB) traverseNodes(fn func(node *Node) error) error { + ndb.resetLatestVersion(0) + latest, err := ndb.getLatestVersion() + if err != nil { + return err + } - err := ndb.traversePrefix(nodeKeyFormat.Key(), func(key, value []byte) error { - nodeKey := int64(binary.BigEndian.Uint64(key[1:])) - node, err := MakeNode(nodeKey, value) - if err != nil { + nodes := []*Node{} + for version := int64(1); version <= latest; version++ { + if err := ndb.traverseRange(nodeKeyFormat.Key(version, int32(1)), nodeKeyFormat.Key(version, int32(math.MaxInt32)), func(key, value []byte) error { + var nk NodeKey + nodeKeyFormat.Scan(key, &nk.version, &nk.nonce) + node, err := MakeNode(&nk, value) + if err != nil { + return err + } + nodes = append(nodes, node) + return nil + }); err != nil { return err } - nodes = append(nodes, node) - return nil - }) - if err != nil { - return err } sort.Slice(nodes, func(i, j int) bool { @@ -1105,7 +951,7 @@ func (ndb *nodeDB) traverseNodes(fn func(hash []byte, node *Node) error) error { }) for _, n := range nodes { - if err := fn(n.hash, n); err != nil { + if err := fn(n); err != nil { return err } } @@ -1119,7 +965,7 @@ func (ndb *nodeDB) String() (string, error) { index := 0 - err := ndb.traversePrefix(rootKeyFormat.Key(), func(key, value []byte) error { + err := ndb.traversePrefix(versionKeyFormat.Key(), func(key, value []byte) error { fmt.Fprintf(buf, "%s: %x\n", key, value) return nil }) @@ -1140,18 +986,16 @@ func (ndb *nodeDB) String() (string, error) { buf.WriteByte('\n') - err = ndb.traverseNodes(func(hash []byte, node *Node) error { + err = ndb.traverseNodes(func(node *Node) error { switch { - case len(hash) == 0: - buf.WriteByte('\n') case node == nil: - fmt.Fprintf(buf, "%s%40x: \n", nodeKeyFormat.Prefix(), hash) + fmt.Fprintf(buf, "%s: \n", nodeKeyFormat.Prefix()) case node.value == nil && node.subtreeHeight > 0: - fmt.Fprintf(buf, "%s%40x: %s %-16s h=%d version=%d\n", - nodeKeyFormat.Prefix(), hash, node.key, "", node.subtreeHeight, node.version) + fmt.Fprintf(buf, "%s: %s %-16s h=%d nodeKey=%v\n", + nodeKeyFormat.Prefix(), node.key, "", node.subtreeHeight, node.nodeKey) default: - fmt.Fprintf(buf, "%s%40x: %s = %-16s h=%d version=%d\n", - nodeKeyFormat.Prefix(), hash, node.key, node.value, node.subtreeHeight, node.version) + fmt.Fprintf(buf, "%s: %s = %-16s h=%d nodeKey=%v\n", + nodeKeyFormat.Prefix(), node.key, node.value, node.subtreeHeight, node.nodeKey) } index++ return nil diff --git a/nodedb_test.go b/nodedb_test.go index 5196c2f82..35e7d0a21 100644 --- a/nodedb_test.go +++ b/nodedb_test.go @@ -14,19 +14,10 @@ import ( "github.com/cosmos/iavl/mock" ) -func BenchmarkNodeKey(b *testing.B) { - ndb := &nodeDB{} - keys := makeNodeKeys(b, 2432325) - for i := 0; i < b.N; i++ { - ndb.nodeKey(keys[i]) - } -} - func BenchmarkOrphanKey(b *testing.B) { ndb := &nodeDB{} - hashes := makeHashes(b, 2432325) for i := 0; i < b.N; i++ { - ndb.orphanKey(1234, 1239, hashes[i]) + ndb.orphanKey(1234, &NodeKey{version: 1239, nonce: int32(i)}) } } @@ -116,7 +107,7 @@ func TestSetStorageVersion_DBFailure_OldKept(t *testing.T) { // rIterMock is used to get the latest version from disk. We are mocking that rIterMock returns latestTreeVersion from disk rIterMock.EXPECT().Valid().Return(true).Times(1) - rIterMock.EXPECT().Key().Return(rootKeyFormat.Key(expectedFastCacheVersion)).Times(1) + rIterMock.EXPECT().Key().Return(versionKeyFormat.Key(expectedFastCacheVersion)).Times(1) rIterMock.EXPECT().Close().Return(nil).Times(1) dbMock.EXPECT().ReverseIterator(gomock.Any(), gomock.Any()).Return(rIterMock, nil).Times(1) @@ -261,17 +252,6 @@ func TestIsFastStorageEnabled_False(t *testing.T) { require.NoError(t, err) } -func makeNodeKeys(b *testing.B, seed int64) []int64 { - b.StopTimer() - rnd := rand.NewSource(seed) - keys := make([]int64, b.N) - for i := 0; i < b.N; i++ { - keys[i] = int64(rnd.Int63()) - } - b.StartTimer() - return keys -} - func makeHashes(b *testing.B, seed int64) [][]byte { b.StopTimer() rnd := rand.NewSource(seed) diff --git a/proof.go b/proof.go index 9f7c23649..4fdbc35e0 100644 --- a/proof.go +++ b/proof.go @@ -166,16 +166,16 @@ func (pln ProofLeafNode) Hash() ([]byte, error) { // If the key does not exist, returns the path to the next leaf left of key (w/ // path), except when key is less than the least item, in which case it returns // a path to the least item. -func (node *Node) PathToLeaf(t *ImmutableTree, key []byte) (PathToLeaf, *Node, error) { +func (node *Node) PathToLeaf(t *ImmutableTree, key []byte, version int64) (PathToLeaf, *Node, error) { path := new(PathToLeaf) - val, err := node.pathToLeaf(t, key, path) + val, err := node.pathToLeaf(t, key, version, path) return *path, val, err } // pathToLeaf is a helper which recursively constructs the PathToLeaf. // As an optimization the already constructed path is passed in as an argument // and is shared among recursive calls. -func (node *Node) pathToLeaf(t *ImmutableTree, key []byte, path *PathToLeaf) (*Node, error) { +func (node *Node) pathToLeaf(t *ImmutableTree, key []byte, version int64, path *PathToLeaf) (*Node, error) { if node.subtreeHeight == 0 { if bytes.Equal(node.key, key) { return node, nil @@ -183,6 +183,10 @@ func (node *Node) pathToLeaf(t *ImmutableTree, key []byte, path *PathToLeaf) (*N return node, errors.New("key does not exist") } + nodeVersion := version + if node.nodeKey != nil { + nodeVersion = node.nodeKey.version + } // Note that we do not store the left child in the ProofInnerNode when we're going to add the // left node as part of the path, similarly we don't store the right child info when going down // the right child node. This is done as an optimization since the child info is going to be @@ -197,7 +201,7 @@ func (node *Node) pathToLeaf(t *ImmutableTree, key []byte, path *PathToLeaf) (*N pin := ProofInnerNode{ Height: node.subtreeHeight, Size: node.size, - Version: node.version, + Version: nodeVersion, Left: nil, Right: rightNode.hash, } @@ -207,7 +211,7 @@ func (node *Node) pathToLeaf(t *ImmutableTree, key []byte, path *PathToLeaf) (*N if err != nil { return nil, err } - n, err := leftNode.pathToLeaf(t, key, path) + n, err := leftNode.pathToLeaf(t, key, version, path) return n, err } // right side @@ -219,7 +223,7 @@ func (node *Node) pathToLeaf(t *ImmutableTree, key []byte, path *PathToLeaf) (*N pin := ProofInnerNode{ Height: node.subtreeHeight, Size: node.size, - Version: node.version, + Version: nodeVersion, Left: leftNode.hash, Right: nil, } @@ -230,6 +234,6 @@ func (node *Node) pathToLeaf(t *ImmutableTree, key []byte, path *PathToLeaf) (*N return nil, err } - n, err := rightNode.pathToLeaf(t, key, path) + n, err := rightNode.pathToLeaf(t, key, version, path) return n, err } diff --git a/proof_ics23.go b/proof_ics23.go index 497a22934..981a9c1cd 100644 --- a/proof_ics23.go +++ b/proof_ics23.go @@ -108,11 +108,15 @@ func (t *ImmutableTree) createExistenceProof(key []byte) (*ics23.ExistenceProof, if err != nil { return nil, err } - path, node, err := t.root.PathToLeaf(t, key) + path, node, err := t.root.PathToLeaf(t, key, t.version+1) + nodeVersion := t.version + 1 + if node.nodeKey != nil { + nodeVersion = node.nodeKey.version + } return &ics23.ExistenceProof{ Key: node.key, Value: node.value, - Leaf: convertLeafOp(node.version), + Leaf: convertLeafOp(nodeVersion), Path: convertInnerOps(path), }, err } diff --git a/testutils_test.go b/testutils_test.go index a28fea73f..257a34bbd 100644 --- a/testutils_test.go +++ b/testutils_test.go @@ -51,22 +51,19 @@ func N(l, r interface{}) *Node { if _, ok := l.(*Node); ok { left = l.(*Node) } else { - left = NewNode(i2b(l.(int)), nil, 0, int64(rand.Intn(100000))) + left = NewNode(i2b(l.(int)), nil) } if _, ok := r.(*Node); ok { right = r.(*Node) } else { - right = NewNode(i2b(r.(int)), nil, 0, int64(rand.Intn(100000))) + right = NewNode(i2b(r.(int)), nil) } n := &Node{ - key: right.lmd(nil).key, - value: nil, - nodeKey: int64(rand.Intn(100000)), - leftNode: left, - leftNodeKey: left.nodeKey, - rightNode: right, - rightNodeKey: right.nodeKey, + key: right.lmd(nil).key, + value: nil, + leftNode: left, + rightNode: right, } n.calcHeightAndSize(nil) return n @@ -76,7 +73,7 @@ func N(l, r interface{}) *Node { func T(n *Node) (*MutableTree, error) { t, _ := getTestTree(0) - _, _, err := n.hashWithCount() + _, err := n.hashWithCount(t.version + 1) if err != nil { return nil, err } diff --git a/tree_dotgraph.go b/tree_dotgraph.go index bb38e61a2..dce74e0f7 100644 --- a/tree_dotgraph.go +++ b/tree_dotgraph.go @@ -45,7 +45,7 @@ func WriteDOTGraph(w io.Writer, tree *ImmutableTree, paths []PathToLeaf) { ctx := &graphContext{} // TODO: handle error - tree.root.hashWithCount() //nolint:errcheck + tree.root.hashWithCount(tree.version + 1) //nolint:errcheck tree.root.traverse(tree, true, func(node *Node) bool { graphNode := &graphNode{ Attrs: map[string]string{}, @@ -58,7 +58,7 @@ func WriteDOTGraph(w io.Writer, tree *ImmutableTree, paths []PathToLeaf) { graphNode.Label = mkLabel(unsafeToStr(node.key), 16, "sans-serif") graphNode.Label += mkLabel(shortHash, 10, "monospace") - graphNode.Label += mkLabel(fmt.Sprintf("version=%d", node.version), 10, "monospace") + graphNode.Label += mkLabel(fmt.Sprintf("nodeKey=%v", node.nodeKey), 10, "monospace") if node.value != nil { graphNode.Label += mkLabel(unsafeToStr(node.value), 10, "sans-serif") diff --git a/tree_random_test.go b/tree_random_test.go index 42f5e309f..239aadde3 100644 --- a/tree_random_test.go +++ b/tree_random_test.go @@ -350,7 +350,7 @@ func assertEmptyDatabase(t *testing.T, tree *MutableTree) { secondKey := foundKeys[1] require.True(t, strings.HasPrefix(firstKey, metadataKeyFormat.Prefix())) - require.True(t, strings.HasPrefix(secondKey, rootKeyFormat.Prefix())) + require.True(t, strings.HasPrefix(secondKey, versionKeyFormat.Prefix())) require.Equal(t, string(metadataKeyFormat.KeyBytes([]byte(storageVersionKey))), firstKey, "Unexpected storage version key") @@ -361,7 +361,7 @@ func assertEmptyDatabase(t *testing.T, tree *MutableTree) { require.Equal(t, fastStorageVersionValue+fastStorageVersionDelimiter+strconv.Itoa(int(latestVersion)), string(storageVersionValue)) var foundVersion int64 - rootKeyFormat.Scan([]byte(secondKey), &foundVersion) + versionKeyFormat.Scan([]byte(secondKey), &foundVersion) require.Equal(t, version, foundVersion, "Unexpected root version") } diff --git a/tree_test.go b/tree_test.go index ee9f1cabd..34b4806eb 100644 --- a/tree_test.go +++ b/tree_test.go @@ -67,7 +67,7 @@ func TestVersionedRandomTree(t *testing.T) { } tree.SaveVersion() } - roots, err := tree.ndb.getRoots() + roots, err := tree.ndb.getVersions() require.NoError(err) require.Equal(versions, len(roots), "wrong number of roots") @@ -366,7 +366,6 @@ func TestVersionedEmptyTree(t *testing.T) { require.False(tree.VersionExists(3)) tree.Set([]byte("k"), []byte("v")) - require.EqualValues(5, tree.root.version) // Now reload the tree. @@ -1284,7 +1283,8 @@ func TestOrphans(t *testing.T) { err = tree.ndb.traverseOrphans(func(k, v []byte) error { var fromVersion, toVersion int64 - orphanKeyFormat.Scan(k, &toVersion, &fromVersion) + var dummy int32 + orphanKeyFormat.Scan(k, &toVersion, &dummy, &fromVersion) require.True(fromVersion == int64(1) || toVersion == int64(99), fmt.Sprintf(`Unexpected orphan key exists: %v with fromVersion = %d and toVersion = %d.\n Any orphan remaining in db should have either fromVersion == 1 or toVersion == 99. Since Version 1 and 99 are only versions in db`, k, fromVersion, toVersion)) return nil @@ -1351,6 +1351,7 @@ func TestCopyValueSemantics(t *testing.T) { val[1] = '2' val, err = tree.Get([]byte("k")) + require.NoError(err) require.Equal([]byte("v2"), val) } @@ -1732,7 +1733,7 @@ func TestLoadVersionForOverwritingCase2(t *testing.T) { nodes, err := tree.ndb.nodes() require.NoError(err) for _, n := range nodes { - if n.version > 1 { + if n.nodeKey.version > 1 { removedNodes = append(removedNodes, n) } } @@ -1788,7 +1789,7 @@ func TestLoadVersionForOverwritingCase3(t *testing.T) { nodes, err := tree.ndb.nodes() require.NoError(err) for _, n := range nodes { - if n.version > 1 { + if n.nodeKey.version > 1 { removedNodes = append(removedNodes, n) } } @@ -2008,8 +2009,8 @@ func TestNodeCacheStatisic(t *testing.T) { cacheSize: numKeyVals, expectFastCacheHitCnt: numKeyVals, expectFastCacheMissCnt: 0, - expectCacheHitCnt: 1, - expectCacheMissCnt: 0, + expectCacheHitCnt: 0, + expectCacheMissCnt: 1, }, "without_cache": { cacheSize: 0, diff --git a/util.go b/util.go index 5d8e9a7f1..f9f08916f 100644 --- a/util.go +++ b/util.go @@ -24,7 +24,7 @@ func printNode(ndb *nodeDB, node *Node, indent int) error { } if node.rightNode != nil { printNode(ndb, node.rightNode, indent+1) //nolint:errcheck - } else if node.rightNodeKey != 0 { + } else if node.rightNodeKey != nil { rightNode, err := ndb.GetNode(node.rightNodeKey) if err != nil { return err @@ -32,7 +32,7 @@ func printNode(ndb *nodeDB, node *Node, indent int) error { printNode(ndb, rightNode, indent+1) //nolint:errcheck } - hash, err := node._hash() + hash, err := node._hash(node.nodeKey.version) if err != nil { return err } @@ -47,7 +47,7 @@ func printNode(ndb *nodeDB, node *Node, indent int) error { if err != nil { return err } - } else if node.leftNodeKey != 0 { + } else if node.leftNodeKey != nil { leftNode, err := ndb.GetNode(node.leftNodeKey) if err != nil { return err