From e46665c71763e5b5c77ce7215e3b7f80167a3951 Mon Sep 17 00:00:00 2001 From: cool-developer <51834436+cool-develope@users.noreply.github.com> Date: Mon, 13 Mar 2023 04:52:20 -0700 Subject: [PATCH] feat: refactor the node key as `version + local nonce(seq id)` (#676) Co-authored-by: Marko --- basic_test.go | 69 ++---- diff.go | 4 +- docs/node/key_format.md | 31 +-- docs/node/node.md | 122 ++++++---- docs/node/nodedb.md | 181 +++++++++++--- docs/tree/export_import.md | 2 +- docs/tree/immutable_tree.md | 15 +- docs/tree/mutable_tree.md | 92 +++---- export.go | 2 +- immutable_tree.go | 15 +- import.go | 124 +++++----- import_test.go | 3 + iterator.go | 10 +- iterator_test.go | 10 +- keyformat/key_format.go | 17 ++ mutable_tree.go | 367 ++++++++++++++-------------- mutable_tree_test.go | 40 ++- node.go | 271 +++++++++++++-------- node_test.go | 148 +++++++----- nodedb.go | 467 +++++++++++++----------------------- nodedb_test.go | 26 +- proof.go | 18 +- proof_ics23.go | 8 +- testutils_test.go | 12 +- tree_dotgraph.go | 12 +- tree_random_test.go | 7 +- tree_test.go | 35 +-- util.go | 10 +- 28 files changed, 1065 insertions(+), 1053 deletions(-) diff --git a/basic_test.go b/basic_test.go index e864fb204..0ae4b0138 100644 --- a/basic_test.go +++ b/basic_test.go @@ -2,7 +2,6 @@ package iavl import ( - "bytes" "encoding/hex" mrand "math/rand" "sort" @@ -171,52 +170,28 @@ 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) { - origNode := tree.root + expectSet := func(tree *MutableTree, i int, repr string) { + tree.SaveVersion() updated, err := tree.Set(i2b(i), []byte{}) require.NoError(t, err) // ensure node was added & structure is as expected. - if updated || P(tree.root) != repr { + if updated || P(tree.root, tree.ImmutableTree) != repr { t.Fatalf("Adding %v to %v:\nExpected %v\nUnexpectedly got %v updated:%v", - i, P(origNode), repr, P(tree.root), updated) + i, P(tree.lastSaved.root, tree.lastSaved), repr, P(tree.root, tree.ImmutableTree), updated) } - // ensure hash calculation requirements - expectHash(tree.ImmutableTree, hashCount) - tree.root = origNode + tree.ImmutableTree = tree.lastSaved.clone() } - expectRemove := func(tree *MutableTree, i int, repr string, hashCount int64) { - origNode := tree.root + expectRemove := func(tree *MutableTree, i int, repr string) { + tree.SaveVersion() value, removed, err := tree.Remove(i2b(i)) require.NoError(t, err) // ensure node was added & structure is as expected. - if len(value) != 0 || !removed || P(tree.root) != repr { + if len(value) != 0 || !removed || P(tree.root, tree.ImmutableTree) != repr { t.Fatalf("Removing %v from %v:\nExpected %v\nUnexpectedly got %v value:%v removed:%v", - i, P(origNode), repr, P(tree.root), value, removed) + i, P(tree.lastSaved.root, tree.lastSaved), repr, P(tree.root, tree.ImmutableTree), value, removed) } - // ensure hash calculation requirements - expectHash(tree.ImmutableTree, hashCount) - tree.root = origNode + tree.ImmutableTree = tree.lastSaved.clone() } // Test Set cases: @@ -225,40 +200,40 @@ func TestUnit(t *testing.T) { t1, err := T(N(4, 20)) require.NoError(t, err) - expectSet(t1, 8, "((4 8) 20)", 3) - expectSet(t1, 25, "(4 (20 25))", 3) + expectSet(t1, 8, "((4 8) 20)") + expectSet(t1, 25, "(4 (20 25))") t2, err := T(N(4, N(20, 25))) require.NoError(t, err) - expectSet(t2, 8, "((4 8) (20 25))", 3) - expectSet(t2, 30, "((4 20) (25 30))", 4) + expectSet(t2, 8, "((4 8) (20 25))") + expectSet(t2, 30, "((4 20) (25 30))") t3, err := T(N(N(1, 2), 6)) require.NoError(t, err) - expectSet(t3, 4, "((1 2) (4 6))", 4) - expectSet(t3, 8, "((1 2) (6 8))", 3) + expectSet(t3, 4, "((1 2) (4 6))") + expectSet(t3, 8, "((1 2) (6 8))") t4, err := T(N(N(1, 2), N(N(5, 6), N(7, 9)))) require.NoError(t, err) - expectSet(t4, 8, "(((1 2) (5 6)) ((7 8) 9))", 5) - expectSet(t4, 10, "(((1 2) (5 6)) (7 (9 10)))", 5) + expectSet(t4, 8, "(((1 2) (5 6)) ((7 8) 9))") + expectSet(t4, 10, "(((1 2) (5 6)) (7 (9 10)))") // Test Remove cases: t10, err := T(N(N(1, 2), 3)) require.NoError(t, err) - expectRemove(t10, 2, "(1 3)", 1) - expectRemove(t10, 3, "(1 2)", 0) + expectRemove(t10, 2, "(1 3)") + expectRemove(t10, 3, "(1 2)") t11, err := T(N(N(N(1, 2), 3), N(4, 5))) require.NoError(t, err) - expectRemove(t11, 4, "((1 2) (3 5))", 2) - expectRemove(t11, 3, "((1 2) (4 5))", 1) + expectRemove(t11, 4, "((1 2) (3 5))") + expectRemove(t11, 3, "((1 2) (4 5))") } func TestRemove(t *testing.T) { diff --git a/diff.go b/diff.go index 779487bac..bed4feb83 100644 --- a/diff.go +++ b/diff.go @@ -25,7 +25,7 @@ type KVPairReceiver func(pair *KVPair) error // // The algorithm don't run in constant memory strictly, but it tried the best the only // keep minimal intermediate states in memory. -func (ndb *nodeDB) extractStateChanges(prevVersion int64, prevRoot []byte, root []byte, receiver KVPairReceiver) error { +func (ndb *nodeDB) extractStateChanges(prevVersion int64, prevRoot *NodeKey, root *NodeKey, receiver KVPairReceiver) error { curIter, err := NewNodeIterator(root, ndb) if err != nil { return err @@ -70,7 +70,7 @@ func (ndb *nodeDB) extractStateChanges(prevVersion int64, prevRoot []byte, root sharedNode = nil for curIter.Valid() { node := curIter.GetNode() - shared := node.version <= prevVersion + shared := node.nodeKey.version <= prevVersion curIter.Next(shared) if shared { sharedNode = node diff --git a/docs/node/key_format.md b/docs/node/key_format.md index f180ce66e..a2546c1e3 100644 --- a/docs/node/key_format.md +++ b/docs/node/key_format.md @@ -1,36 +1,15 @@ # Key Format -Nodes, orphans, and roots are stored under the database with different key formats to ensure there are no key collisions and a structured key from which we can extract useful information. +Nodes and fastNodes are stored under the database with different key formats to ensure there are no key collisions and a structured key from which we can extract useful information. ### Nodes -Node KeyFormat: `n|` +Node KeyFormat: `n|node.nodeKey.version|node.nodeKey.nonce` Nodes are marshalled and stored under nodekey with prefix `n` to prevent collisions and then appended with the node's hash. -### Orphans +### FastNodes -Orphan KeyFormat: `o|toVersion|fromVersion|hash` +FastNode KeyFormat: `f|node.key` -Orphans are marshalled nodes stored with prefix `o` to prevent collisions. You can extract the toVersion, fromVersion and hash from the orphan key by using: - -```golang -// orphanKey: o|50|30|0xABCD -var toVersion, fromVersion int64 -var hash []byte -orphanKeyFormat.Scan(orphanKey, &toVersion, &fromVersion, hash) - -/* -toVersion = 50 -fromVersion = 30 -hash = 0xABCD -*/ -``` - -The order of the orphan KeyFormat matters. Since deleting a version `v` will delete all orphans whose `toVersion = v`, we can easily retrieve all orphans from nodeDb by iterating over the key prefix: `o|v`. - -### Roots - -Root KeyFormat: `r|` - -Root hash of the IAVL tree at version `v` is stored under the key `r|v` (prefixed with `r` to avoid collision). +FastNodes are marshalled nodes stored with prefix `f` to prevent collisions. You can extract fast nodes from the database by iterating over the keys with prefix `f`. diff --git a/docs/node/node.md b/docs/node/node.md index 24647fe1b..3c526a289 100644 --- a/docs/node/node.md +++ b/docs/node/node.md @@ -5,23 +5,28 @@ The Node struct stores a node in the IAVL tree. ### Structure ```golang +// NodeKey represents a key of node in the DB. +type NodeKey struct { + version int64 // version of the IAVL that this node was first added in + nonce int32 // local nonce for the same version +} + // Node represents a node in a Tree. type Node struct { - key []byte // key for the node. - value []byte // value of leaf node. If inner node, value = nil - version int64 // The version of the IAVL that this node was first added in. - height int8 // The height of the node. Leaf nodes have height 0 - size int64 // The number of leaves that are under the current node. Leaf nodes have size = 1 - hash []byte // hash of above field and leftHash, rightHash - leftHash []byte // hash of left child - leftNode *Node // pointer to left child - rightHash []byte // hash of right child - rightNode *Node // pointer to right child - persisted bool // persisted to disk + key []byte // key for the node. + value []byte // value of leaf node. If inner node, value = nil + hash []byte // hash of above field and left node's hash, right node's hash + nodeKey *NodeKey // node key of the nodeDB + leftNodeKey *NodeKey // node key of the left child + rightNodeKey *NodeKey // node key of the right child + size int64 // number of leaves that are under the current node. Leaf nodes have size = 1 + leftNode *Node // pointer to left child + rightNode *Node // pointer to right child + subtreeHeight int8 // height of the node. Leaf nodes have height 0 } ``` -Inner nodes have keys equal to the highest key on their left branch and have values set to nil. +Inner nodes have keys equal to the highest key on the subtree and have values set to nil. The version of a node is the first version of the IAVL tree that the node gets added in. Future versions of the IAVL may point to this node if they also contain the node, however the node's version itself does not change. @@ -29,50 +34,61 @@ Size is the number of leaves under a given node. With a full subtree, `node.size ### Marshaling -Every node is persisted by encoding the key, version, height, size and hash. If the node is a leaf node, then the value is persisted as well. If the node is not a leaf node, then the leftHash and rightHash are persisted as well. +Every node is persisted by encoding the key, height, and size. If the node is a leaf node, then the value is persisted as well. If the node is not a leaf node, then the hash, leftNodeKey, and rightNodeKey are persisted as well. The hash should be persisted in inner nodes to avoid recalculating the hash when the node is loaded from the disk, if not persisted, we should iterate through the entire subtree to calculate the hash. ```golang // Writes the node as a serialized byte slice to the supplied io.Writer. func (node *Node) writeBytes(w io.Writer) error { - cause := encodeVarint(w, node.height) - if cause != nil { - return errors.Wrap(cause, "writing height") + if node == nil { + return errors.New("cannot write nil node") } - cause = encodeVarint(w, node.size) + cause := encoding.EncodeVarint(w, int64(node.subtreeHeight)) if cause != nil { - return errors.Wrap(cause, "writing size") + return fmt.Errorf("writing height, %w", cause) } - cause = encodeVarint(w, node.version) + cause = encoding.EncodeVarint(w, node.size) if cause != nil { - return errors.Wrap(cause, "writing version") + return fmt.Errorf("writing size, %w", cause) } - // Unlike writeHashBytes, key is written for inner nodes. - cause = encodeBytes(w, node.key) + // Unlike writeHashByte, key is written for inner nodes. + cause = encoding.EncodeBytes(w, node.key) if cause != nil { - return errors.Wrap(cause, "writing key") + return fmt.Errorf("writing key, %w", cause) } if node.isLeaf() { - cause = encodeBytes(w, node.value) + cause = encoding.EncodeBytes(w, node.value) if cause != nil { - return errors.Wrap(cause, "writing value") + return fmt.Errorf("writing value, %w", cause) } } else { - if node.leftHash == nil { - panic("node.leftHash was nil in writeBytes") + cause = encoding.EncodeBytes(w, node.hash) + if cause != nil { + return fmt.Errorf("writing hash, %w", cause) + } + if node.leftNodeKey == nil { + return ErrLeftNodeKeyEmpty } - cause = encodeBytes(w, node.leftHash) + cause = encoding.EncodeVarint(w, node.leftNodeKey.version) if cause != nil { - return errors.Wrap(cause, "writing left hash") + return fmt.Errorf("writing the version of left node key, %w", cause) + } + cause = encoding.EncodeVarint(w, int64(node.leftNodeKey.nonce)) + if cause != nil { + return fmt.Errorf("writing the nonce of left node key, %w", cause) } - if node.rightHash == nil { - panic("node.rightHash was nil in writeBytes") + if node.rightNodeKey == nil { + return ErrRightNodeKeyEmpty + } + cause = encoding.EncodeVarint(w, node.rightNodeKey.version) + if cause != nil { + return fmt.Errorf("writing the version of right node key, %w", cause) } - cause = encodeBytes(w, node.rightHash) + cause = encoding.EncodeVarint(w, int64(node.rightNodeKey.nonce)) if cause != nil { - return errors.Wrap(cause, "writing right hash") + return fmt.Errorf("writing the nonce of right node key, %w", cause) } } return nil @@ -86,45 +102,47 @@ A node's hash is calculated by hashing the height, size, and version of the node ```golang // 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 { - err := encodeVarint(w, node.height) +func (node *Node) writeHashBytes(w io.Writer, version int64) error { + err := encoding.EncodeVarint(w, int64(node.subtreeHeight)) if err != nil { - return errors.Wrap(err, "writing height") + return fmt.Errorf("writing height, %w", err) } - err = encodeVarint(w, node.size) + err = encoding.EncodeVarint(w, node.size) if err != nil { - return errors.Wrap(err, "writing size") + return fmt.Errorf("writing size, %w", err) } - err = encodeVarint(w, node.version) + err = encoding.EncodeVarint(w, version) if err != nil { - return errors.Wrap(err, "writing version") + return fmt.Errorf("writing version, %w", err) } // Key is not written for inner nodes, unlike writeBytes. if node.isLeaf() { - err = encodeBytes(w, node.key) + err = encoding.EncodeBytes(w, node.key) if err != nil { - return errors.Wrap(err, "writing key") + return fmt.Errorf("writing key, %w", err) } + // Indirection needed to provide proofs without values. - // (e.g. proofLeafNode.ValueHash) - valueHash := tmhash.Sum(node.value) - err = encodeBytes(w, valueHash) + // (e.g. ProofLeafNode.ValueHash) + valueHash := sha256.Sum256(node.value) + + err = encoding.EncodeBytes(w, valueHash[:]) if err != nil { - return errors.Wrap(err, "writing value") + return fmt.Errorf("writing value, %w", err) } } else { - if node.leftHash == nil || node.rightHash == nil { - panic("Found an empty child hash") + if node.leftNode == nil || node.rightNode == nil { + return ErrEmptyChild } - err = encodeBytes(w, node.leftHash) + err = encoding.EncodeBytes(w, node.leftNode.hash) if err != nil { - return errors.Wrap(err, "writing left hash") + return fmt.Errorf("writing left hash, %w", err) } - err = encodeBytes(w, node.rightHash) + err = encoding.EncodeBytes(w, node.rightNode.hash) if err != nil { - return errors.Wrap(err, "writing right hash") + return fmt.Errorf("writing right hash, %w", err) } } diff --git a/docs/node/nodedb.md b/docs/node/nodedb.md index 2224d35cb..a5c4bb32b 100644 --- a/docs/node/nodedb.md +++ b/docs/node/nodedb.md @@ -2,55 +2,164 @@ ### Structure -The nodeDB is responsible for persisting nodes, orphans, and roots correctly in persistent storage. +The nodeDB is responsible for persisting nodes and fastNodes correctly in persistent storage. ### Saving Versions -The nodeDB saves the roothash of the IAVL tree under the key: `r|`. - -It marshals and saves any new node that has been created under: `n|`. For more details on how the node gets marshaled, see [node documentation](./node.md). Any old node that is still part of the latest IAVL tree will not get rewritten. Instead its parent will simply have a hash pointer with which the nodeDB can retrieve the old node if necessary. - -Any old nodes that were part of the previous version IAVL but are no longer part of this one have been saved in an orphan map `orphan.hash => orphan.version`. This map will get passed into the nodeDB's `SaveVersion` function. The map maps from the orphan's hash to the version that it was added to the IAVL tree. The nodeDB iterates through this map and stores each marshalled orphan node under the key: `o|toVersion|fromVersion`. Since the toVersion is always the previous version (if we are saving version `v`, toVersion of all new orphans is `v-1`), we can save the orphans by iterating over the map and saving: `o|(latestVersion-1)|orphan.fromVersion => orphan.hash`. +It marshals and saves any new node that has been created under: `n|node.nodeKey.version|node.nodeKey.nonce`. For more details on how the node gets marshaled, see [node documentation](./node.md). The root of each version is saved under `n|version|1`. (For more details on key formats see the [keyformat docs](./key_format.md)) ### Deleting Versions -When a version `v` is deleted, the roothash corresponding to version `v` is deleted from nodeDB. All orphans whose `toVersion = v`, will get the `toVersion` pushed back to the highest predecessor of `v` that still exists in nodeDB. If the `toVersion <= fromVersion` then this implies that there does not exist a version of the IAVL tree in the nodeDB that still contains this node. Thus, it can be safely deleted and uncached. +When a version `v` is deleted, all nodes which removed in the current version will be safely deleted and uncached from the storage. `nodeDB` will keep the range of versions [`fromVersion`, `toVersion`]. There are two apis to delete versions: + +#### DeleteVersionsFrom + +```golang +// DeleteVersionsFrom permanently deletes all tree versions from the given version upwards. +func (ndb *nodeDB) DeleteVersionsFrom(fromVersion int64) error { + latest, err := ndb.getLatestVersion() + if err != nil { + return err + } + if latest < fromVersion { + return nil + } + + ndb.mtx.Lock() + for v, r := range ndb.versionReaders { + if v >= fromVersion && r != 0 { + return fmt.Errorf("unable to delete version %v with %v active readers", v, r) + } + } + ndb.mtx.Unlock() + + // Delete the nodes + err = ndb.traverseRange(nodeKeyFormat.Key(fromVersion), nodeKeyFormat.Key(latest+1), func(k, v []byte) error { + if err = ndb.batch.Delete(k); err != nil { + return err + } + return nil + }) -##### Deleting Orphans + if err != nil { + return err + } -The deleteOrphans algorithm is shown below: + // NOTICE: we don't touch fast node indexes here, because it'll be rebuilt later because of version mismatch. + + ndb.resetLatestVersion(fromVersion - 1) + + return nil +} +``` + +#### DeleteVersionsTo ```golang -// deleteOrphans deletes orphaned nodes from disk, and the associated orphan -// entries. -func (ndb *nodeDB) deleteOrphans(version int64) { - // Will be zero if there is no previous version. - predecessor := ndb.getPreviousVersion(version) - - // Traverse orphans with a lifetime ending at the version specified. - ndb.traverseOrphansVersion(version, func(key, hash []byte) { - var fromVersion, toVersion int64 - - // See comment on `orphanKeyFmt`. Note that here, `version` and - // `toVersion` are always equal. - orphanKeyFormat.Scan(key, &toVersion, &fromVersion) - - // Delete orphan key and reverse-lookup key. - ndb.batch.Delete(key) - - // If there is no predecessor, or the predecessor is earlier than the - // beginning of the lifetime (ie: negative lifetime), or the lifetime - // 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 { - ndb.batch.Delete(ndb.nodeKey(hash)) - ndb.uncacheNode(hash) - } else { - ndb.saveOrphan(hash, fromVersion, predecessor) +// DeleteVersionsTo deletes the oldest versions up to the given version from disk. +func (ndb *nodeDB) DeleteVersionsTo(toVersion int64) error { + first, err := ndb.getFirstVersion() + if err != nil { + return err + } + + latest, err := ndb.getLatestVersion() + if err != nil { + return err + } + + if toVersion < first || latest <= toVersion { + return fmt.Errorf("the version should be in the range of [%d, %d)", first, latest) + } + + for v, r := range ndb.versionReaders { + if v >= first && v <= toVersion && r != 0 { + return fmt.Errorf("unable to delete version %v with %v active readers", v, r) + } + } + + for version := first; version <= toVersion; version++ { + if err := ndb.deleteVersion(version); err != nil { + return err } + ndb.resetFirstVersion(version + 1) + } + + return nil +} + +// deleteVersion deletes a tree version from disk. +// deletes orphans +func (ndb *nodeDB) deleteVersion(version int64) error { + rootKey, err := ndb.GetRoot(version) + if err != nil { + return err + } + if rootKey == nil || rootKey.version < version { + if err := ndb.batch.Delete(ndb.nodeKey(&NodeKey{version: version, nonce: 1})); err != nil { + return err + } + } + + return ndb.traverseOrphans(version, func(orphan *Node) error { + return ndb.batch.Delete(ndb.nodeKey(orphan.nodeKey)) }) } ``` + +##### Travesing Orphans + +The traverseOrphans algorithm is shown below: + +```golang +// traverseOrphans traverses orphans which removed by the updates of the version (n+1). +func (ndb *nodeDB) traverseOrphans(version int64, fn func(*Node) error) error { + curKey, err := ndb.GetRoot(version + 1) + if err != nil { + return err + } + + curIter, err := NewNodeIterator(curKey, ndb) + if err != nil { + return err + } + + prevKey, err := ndb.GetRoot(version) + if err != nil { + return err + } + prevIter, err := NewNodeIterator(prevKey, ndb) + if err != nil { + return err + } + + var orgNode *Node + for prevIter.Valid() { + for orgNode == nil && curIter.Valid() { + node := curIter.GetNode() + if node.nodeKey.version <= version { + curIter.Next(true) + orgNode = node + } else { + curIter.Next(false) + } + } + pNode := prevIter.GetNode() + + if orgNode != nil && bytes.Equal(pNode.hash, orgNode.hash) { + prevIter.Next(true) + orgNode = nil + } else { + err = fn(pNode) + if err != nil { + return err + } + prevIter.Next(false) + } + } + + return nil +} +``` diff --git a/docs/tree/export_import.md b/docs/tree/export_import.md index 0bbd22486..180b3f7d0 100644 --- a/docs/tree/export_import.md +++ b/docs/tree/export_import.md @@ -44,7 +44,7 @@ This would produce the following export: When importing, the tree must be rebuilt in the same order, such that the missing attributes (e.g. `hash` and `size`) can be generated. This is possible because children are always given before their parents. We can therefore first generate the hash and size of the left and right leaf nodes, and then use these to recursively generate the hash and size of the parent. -One way to do this is to keep a stack of orphaned children, and then pop those children once we build their parent, which then becomes a new child on the stack. We know that we encounter a parent because its height is higher than the child or children on top of the stack. We need a stack because we may need to recursively build a right branch while holding an orphaned left child. For the above export this would look like the following (in `key:height=value` format): +One way to do this is to keep a stack of determined children, and then pop those children once we build their parent, which then becomes a new child on the stack. We know that we encounter a parent because its height is higher than the child or children on top of the stack. We need a stack because we may need to recursively build a right branch while holding an determined left child. For the above export this would look like the following (in `key:height=value` format): ``` | Stack | Import node | diff --git a/docs/tree/immutable_tree.md b/docs/tree/immutable_tree.md index 0f7154100..6e73e5b17 100644 --- a/docs/tree/immutable_tree.md +++ b/docs/tree/immutable_tree.md @@ -6,9 +6,10 @@ The Immutable tree struct contains an IAVL version. ```golang type ImmutableTree struct { - root *Node - ndb *nodeDB - version int64 + root *Node + ndb *nodeDB + version int64 + skipFastStorageUpgrade bool } ``` @@ -20,14 +21,14 @@ Users can get information about the IAVL tree by calling getter functions such a Users can get values by specifying the key or the index of the leaf node they want to get value for. -Get by key will return both the index and the value. +GetWithIndex by key will return both the index and the value. ```golang -// Get returns the index and value of the specified key if it exists, or nil +// GetWithIndex returns the index and value of the specified key if it exists, or nil // and the next index, if it doesn't. -func (t *ImmutableTree) Get(key []byte) (index int64, value []byte) { +func (t *ImmutableTree) GetWithIndex(key []byte) (int64, []byte, error) { if t.root == nil { - return 0, nil + return 0, nil, nil } return t.root.get(t, key) } diff --git a/docs/tree/mutable_tree.md b/docs/tree/mutable_tree.md index 86080a1d0..bdea1696d 100644 --- a/docs/tree/mutable_tree.md +++ b/docs/tree/mutable_tree.md @@ -9,16 +9,17 @@ The MutableTree stores the last saved ImmutableTree and the current working tree ```golang // MutableTree is a persistent tree which keeps track of versions. type MutableTree struct { - *ImmutableTree // The current, working tree. - lastSaved *ImmutableTree // The most recently saved tree. - orphans map[string]int64 // Nodes removed by changes to working tree. - versions map[int64]bool // The previous, saved versions of the tree. - ndb *nodeDB + *ImmutableTree // The current, working tree. + lastSaved *ImmutableTree // The most recently saved tree. + unsavedFastNodeAdditions map[string]*fastnode.Node // FastNodes that have not yet been saved to disk + unsavedFastNodeRemovals map[string]interface{} // FastNodes that have not yet been removed from disk + ndb *nodeDB + skipFastStorageUpgrade bool // If true, the tree will work like no fast storage and always not upgrade fast storage + + mtx sync.Mutex } ``` -The versions map stores which versions of the IAVL are stored in nodeDB. Anytime a version `v` gets persisted, `versions[v]` is set to `true`. Anytime a version gets deleted, `versions[v]` is set to false. - ### Set Set can be used to add a new key-value pair to the IAVL tree, or to update an existing key with a new value. @@ -39,12 +40,11 @@ newVersion := latestVersion+1 newNode := NewNode(key, value, newVersion) // original leaf node: originalLeaf gets replaced by inner node below Node{ - key: setKey, // inner node key is equal to left child's key + key: leafKey, // inner node key is equal to right child's key height: 1, // height=1 since node is parent of leaves size: 2, // 2 leaf nodes under this node leftNode: newNode, // left Node is the new added leaf node rightNode: originalLeaf, // right Node is the original leaf node - version: newVersion, // inner node is new since lastVersion, so it has an incremented version } ``` @@ -55,19 +55,18 @@ If `setKey` > `leafKey`: // since this is a new update since latest saved version, // this node has version=latestVersion+1 newVersion := latestVersion+1 -newNode := NewNode(key, value, latestVersion+1) +newNode := NewNode(key, value, newVersion) // original leaf node: originalLeaf gets replaced by inner node below Node{ - key: leafKey, // inner node key is equal to left child's key + key: setKey, // inner node key is equal to right child's key height: 1, // height=1 since node is parent of leaves size: 2, // 2 leaf nodes under this node leftNode: originalLeaf, // left Node is the original leaf node rightNode: newNode, // right Node is the new added leaf node - version: newVersion, // inner node is new since lastVersion, so it has an incremented version } ``` -Any node that gets recursed upon during a Set call is necessarily orphaned since it will either have a new value (in the case of an update) or it will have a new descendant. The recursive calls accumulate a list of orphans as it descends down the IAVL tree. This list of orphans is ultimately added to the mutable tree's orphan list at the end of the Set call. +Any node that gets recursed upon during a Set call is necessarily orphaned since it will either have a new value (in the case of an update) or it will have a new descendant. For new nodes, the node key and hash will be assigned in the `SaveVersion` (see `SaveVersion` section). After each set, the current working tree has its height and size recalculated. If the height of the left branch and right branch of the working tree differs by more than one, then the mutable tree has to be balanced before the Set call can return. @@ -81,12 +80,6 @@ Remove recurses down the IAVL tree in the same way that Set does until it reache Remove works by calling an inner function `recursiveRemove` that returns the following values after a recursive call `recursiveRemove(recurseNode, removeKey)`: -##### NewHash - -If a node in recurseNode's subtree gets removed, then the hash of the recurseNode will change. Thus during the recursive calls down the subtree, all of recurseNode's children will return their new hashes after the remove (if they have changed). Using this information, recurseNode can calculate its own updated hash and return that value. - -If recurseNode is the node getting removed itself, NewHash is `nil`. - ##### ReplaceNode Just like with recursiveSet, any node that gets recursed upon (in a successful remove) will get orphaned since its hash must be updated and the nodes are immutable. Thus, ReplaceNode is the new node that replaces `recurseNode`. @@ -108,7 +101,6 @@ After LeftLeaf removed: IAVLTREE---RightLeaf ReplaceNode = RightLeaf -orphaned = [LeftLeaf, recurseNode] ``` If recurseNode is an inner node that got called in the recursiveRemove, but is not a direct parent of the removed leaf. Then an updated version of the node will exist in the tree. Notably, it will have an incremented version, a new hash (as explained in the `NewHash` section), and recalculated height and size. @@ -119,30 +111,12 @@ The height and size of the ReplaceNode will have to be calculated since these va It's possible that the subtree for `ReplaceNode` will have to be rebalanced (see `Balance` section). If this is the case, this will also update `ReplaceNode`'s hash since the structure of `ReplaceNode`'s subtree will change. -##### LeftmostLeafKey - -The LeftmostLeafKey is the key of the leftmost leaf of `recurseNode`'s subtree. This is only used if `recurseNode` is the right child of its parent. Since inner nodes should have their key equal to the leftmost key of their right branch (if leftmostkey is not `nil`). If recurseNode is the right child of its parent `parentNode`, `parentNode` will set its key to `parentNode.key = leftMostKeyOfRecurseNodeSubTree`. - -If `recurseNode` is a leaf, it will return `nil`. - -If `recurseNode` is a parent of the leaf that got removed, it will return its own key if the left child was removed. If the right child is removed, it will return `nil`. - -If `recurseNode` is a generic inner node that isn't a direct parent of the removed node, it will return the leftmost key of its child's recursive call if `node.key < removeKey`. It will return `nil` otherwise. - -If `removeKey` does not exist in the IAVL tree, leftMostKey is `nil` for entire recursive stack. - ##### RemovedValue RemovedValue is the value that was at the node that was removed. It does not get changed as it travels up the recursive stack. If `removeKey` does not exist in the IAVL tree, RemovedValue is `nil`. -##### Orphans - -Just like `recursiveSet`, any node that gets recursed upon by `recursiveRemove` in a successful `Remove` call will have to be orphaned. The Orphans list in `recursiveRemove` accumulates the list of orphans so that it can return them to `Remove`. `Remove` will then iterate through this list and add all the orphans to the mutable tree's `orphans` map. - -If the `removeKey` does not exist in the IAVL tree, then the orphans list is `nil`. - ### Balance Anytime a node is unbalanced such that the height of its left branch and the height of its right branch differs by more than 1, the IAVL tree will rebalance itself. @@ -173,7 +147,7 @@ Before `RotateRight(node8)`: After `RotateRight(node8)`: ``` |---9 - |---8 + |---8' | | |---7 | |---6 | |---5 @@ -182,7 +156,7 @@ After `RotateRight(node8)`: |---2 |---3 -Orphaned: 4 +Orphaned: 4, 8 ``` Note that the key order for subtrees is still preserved. @@ -213,10 +187,10 @@ After `RotateLeft(node2)`: | |---5 | |---4 | | |---3 - |---2 + |---2' |---1 -Orphaned: 6 +Orphaned: 6, 2 ``` The IAVL detects whenever a subtree has become unbalanced by 2 (after any set/remove). If this does happen, then the tree is immediately rebalanced. Thus, any unbalanced subtree can only exist in 4 states: @@ -238,13 +212,13 @@ The IAVL detects whenever a subtree has become unbalanced by 2 (after any set/re **After 1: Balanced** ``` |---9 - |---8 + |---8' | |---6 4'---| | |---3 |---2 -Orphaned: 4 +Orphaned: 4, 8 ``` #### Left Right Case @@ -267,13 +241,13 @@ Make tree left left unbalanced, and then balance. **After 1: Left Left Unbalanced** ``` |---9 -8---| +8'---| |---6' | |---5 |---4 |---2 -Orphaned: 6 +Orphaned: 6, 8 ``` **After 2: Balanced** @@ -310,10 +284,10 @@ Note: 6 got orphaned again, so omit list repitition |---8 6'---| | |---4 - |---2 + |---2' |---1 -Orphaned: 6 +Orphaned: 6, 2 ``` #### Right Left Case @@ -339,10 +313,10 @@ Make tree right right unbalanced, then balance. |---6 |---4' | |---3 -2---| +2'---| |---1 -Orphaned: 4 +Orphaned: 4, 2 ``` **After 2: Balanced** @@ -361,24 +335,14 @@ Orphaned: 4 SaveVersion saves the current working tree as the latest version, `tree.version+1`. -If the tree's root is empty, then there are no nodes to save. The `nodeDB` must still save any orphans since the root itself could be the node that has been removed since the last version. Then the `nodeDB` also saves the empty root for this version. +If the tree's root is empty, then there are no nodes to save. Then the `nodeDB` also saves the empty root for this version. -If the root is not empty. Then SaveVersion will ensure that the `nodeDB` saves the orphans, roots and any new nodes that have been created since the last version was saved. +If the root is not empty. Then `SaveVersion` will iterate the tree and save new nodes (which the node key is `nil`) to the `nodeDB`. -SaveVersion also calls `nodeDB.Commit`, this ensures that any batched writes from the last save gets committed to the appropriate databases. +`SaveVersion` also calls `nodeDB.Commit`, this ensures that any batched writes from the last save gets committed to the appropriate databases. -`tree.version` gets incremented and the versions map has `versions[tree.version] = true`. - -It will set the lastSaved `ImmutableTree` to the current working tree, and clone the tree to allow for future updates on the next working tree. It also resets orphans to the empty map. +`tree.version` gets incremented and it will set the lastSaved `ImmutableTree` to the current working tree, and clone the tree to allow for future updates on the next working tree. Lastly, it returns the tree's hash, the latest version, and nil for error. SaveVersion will error if a tree at the version trying to be saved already exists. - -### DeleteVersion - -DeleteVersion will simply call nodeDB's `DeleteVersion` function which is documented in the [nodeDB docs](./nodedb.md) and then call `nodeDB.Commit` to flush all batched updates. - -It will also delete the version from the versions map. - -DeleteVersion will return an error if the version is invalid, or nonexistent. DeleteVersion will also return an error if the version trying to be deleted is the latest version of the IAVL tree since that is unallowed. diff --git a/export.go b/export.go index e19998d9e..81b5c10e9 100644 --- a/export.go +++ b/export.go @@ -65,7 +65,7 @@ func (e *Exporter) export(ctx context.Context) { exportNode := &ExportNode{ Key: node.key, Value: node.value, - Version: node.version, + Version: node.nodeKey.version, Height: node.subtreeHeight, } diff --git a/immutable_tree.go b/immutable_tree.go index 734cd7e9b..6c9a7dab6 100644 --- a/immutable_tree.go +++ b/immutable_tree.go @@ -149,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 @@ -283,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 }) @@ -321,15 +320,9 @@ func (t *ImmutableTree) clone() *ImmutableTree { } // nodeSize is like Size, but includes inner nodes too. -// - +// used only for testing. 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) } // TraverseStateChanges iterate the range of versions, compare each version to it's predecessor to extract the state changes of it. diff --git a/import.go b/import.go index 550593b41..eb2661cf0 100644 --- a/import.go +++ b/import.go @@ -27,6 +27,7 @@ type Importer struct { batch db.Batch batchSize uint32 stack []*Node + nonces []int32 } // newImporter creates a new Importer for an empty MutableTree. @@ -49,9 +50,47 @@ func newImporter(tree *MutableTree, version int64) (*Importer, error) { version: version, batch: tree.ndb.db.NewBatch(), stack: make([]*Node, 0, 8), + nonces: make([]int32, version+1), }, nil } +// writeNode writes the node content to the storage. +func (i *Importer) writeNode(node *Node) error { + if _, err := node._hash(node.nodeKey.version); err != nil { + return err + } + if err := node.validate(); err != nil { + return err + } + + buf := bufPool.Get().(*bytes.Buffer) + buf.Reset() + defer bufPool.Put(buf) + + if err := node.writeBytes(buf); err != nil { + return err + } + + bytesCopy := make([]byte, buf.Len()) + copy(bytesCopy, buf.Bytes()) + + if err := i.batch.Set(i.tree.ndb.nodeKey(node.nodeKey), bytesCopy); err != nil { + return err + } + + i.batchSize++ + if i.batchSize >= maxBatchSize { + if err := i.batch.Write(); err != nil { + return err + } + i.batch.Close() + i.batch = i.tree.ndb.db.NewBatch() + i.batchSize = 0 + } + + return nil +} + // Close frees all resources. It is safe to call multiple times. Uncommitted nodes may already have // been flushed to the database, but will not be visible. func (i *Importer) Close() { @@ -80,7 +119,6 @@ func (i *Importer) Add(exportNode *ExportNode) error { node := &Node{ key: exportNode.Key, value: exportNode.Value, - version: exportNode.Version, subtreeHeight: exportNode.Height, } @@ -92,72 +130,31 @@ 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) - 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 { node.size = 1 - } - if node.leftNode != nil { - node.size += node.leftNode.size - } - if node.rightNode != nil { - node.size += node.rightNode.size - } - - _, err := node._hash() - if err != nil { - return err - } - - err = node.validate() - if err != nil { - return err - } - - buf := bufPool.Get().(*bytes.Buffer) - buf.Reset() - defer bufPool.Put(buf) - - if err = node.writeBytes(buf); err != nil { - return err - } - - bytesCopy := make([]byte, buf.Len()) - copy(bytesCopy, buf.Bytes()) - - if err = i.batch.Set(i.tree.ndb.nodeKey(node.hash), bytesCopy); err != nil { - return err - } - - i.batchSize++ - if i.batchSize >= maxBatchSize { - err = i.batch.Write() - if err != nil { + } else if stackSize >= 2 && i.stack[stackSize-1].subtreeHeight < node.subtreeHeight && i.stack[stackSize-2].subtreeHeight < node.subtreeHeight { + node.leftNode = i.stack[stackSize-2] + node.rightNode = i.stack[stackSize-1] + node.leftNodeKey = node.leftNode.nodeKey + node.rightNodeKey = node.rightNode.nodeKey + node.size = node.leftNode.size + node.rightNode.size + // Update the stack now. + if err := i.writeNode(i.stack[stackSize-2]); err != nil { + return err + } + if err := i.writeNode(i.stack[stackSize-1]); err != nil { return err } - i.batch.Close() - i.batch = i.tree.ndb.db.NewBatch() - i.batchSize = 0 - } - - // Update the stack now that we know there were no errors - switch { - case node.leftHash != nil && node.rightHash != nil: i.stack = i.stack[:stackSize-2] - case node.leftHash != nil || node.rightHash != nil: - i.stack = i.stack[:stackSize-1] } - // Only hash\height\size of the node will be used after it be pushed into the stack. - i.stack = append(i.stack, &Node{hash: node.hash, subtreeHeight: node.subtreeHeight, size: node.size}) + i.nonces[exportNode.Version]++ + node.nodeKey = &NodeKey{ + version: exportNode.Version, + // Nonce is 1-indexed, but start at 2 since the root node having a nonce of 1. + nonce: i.nonces[exportNode.Version] + 1, + } + + i.stack = append(i.stack, node) return nil } @@ -172,11 +169,12 @@ func (i *Importer) Commit() error { switch len(i.stack) { case 0: - if err := i.batch.Set(i.tree.ndb.rootKey(i.version), []byte{}); err != nil { + if err := i.batch.Set(i.tree.ndb.nodeKey(&NodeKey{version: i.version, nonce: 1}), []byte{}); err != nil { return err } case 1: - if err := i.batch.Set(i.tree.ndb.rootKey(i.version), i.stack[0].hash); err != nil { + i.stack[0].nodeKey.nonce = 1 + if err := i.writeNode(i.stack[0]); err != nil { return err } default: diff --git a/import_test.go b/import_test.go index eaceff335..645321a39 100644 --- a/import_test.go +++ b/import_test.go @@ -152,6 +152,9 @@ func TestImporter_Add(t *testing.T) { if tc.valid { require.NoError(t, err) } else { + if err == nil { + err = importer.Commit() + } require.Error(t, err) } }) diff --git a/iterator.go b/iterator.go index edcc80d23..7b6a60d26 100644 --- a/iterator.go +++ b/iterator.go @@ -268,15 +268,15 @@ type NodeIterator struct { } // NewNodeIterator returns a new NodeIterator to traverse the tree of the root node. -func NewNodeIterator(root []byte, ndb *nodeDB) (*NodeIterator, error) { - if len(root) == 0 { +func NewNodeIterator(rootKey *NodeKey, ndb *nodeDB) (*NodeIterator, error) { + if rootKey == nil { return &NodeIterator{ nodesToVisit: []*Node{}, ndb: ndb, }, nil } - node, err := ndb.GetNode(root) + node, err := ndb.GetNode(rootKey) if err != nil { return nil, err } @@ -319,14 +319,14 @@ func (iter *NodeIterator) Next(isSkipped bool) { return } - rightNode, err := iter.ndb.GetNode(node.rightHash) + rightNode, err := iter.ndb.GetNode(node.rightNodeKey) if err != nil { iter.err = err return } iter.nodesToVisit = append(iter.nodesToVisit, rightNode) - leftNode, err := iter.ndb.GetNode(node.leftHash) + leftNode, err := iter.ndb.GetNode(node.leftNodeKey) if err != nil { iter.err = err return diff --git a/iterator_test.go b/iterator_test.go index a376542ac..52455ebeb 100644 --- a/iterator_test.go +++ b/iterator_test.go @@ -343,7 +343,7 @@ func TestNodeIterator_Success(t *testing.T) { require.NoError(t, err) // check if the iterating count is same with the entire node count of the tree - itr, err := NewNodeIterator(tree.root.hash, tree.ndb) + itr, err := NewNodeIterator(tree.root.nodeKey, tree.ndb) require.NoError(t, err) nodeCount := 0 for ; itr.Valid(); itr.Next(false) { @@ -352,23 +352,23 @@ func TestNodeIterator_Success(t *testing.T) { require.Equal(t, int64(nodeCount), tree.Size()*2-1) // check if the skipped node count is right - itr, err = NewNodeIterator(tree.root.hash, tree.ndb) + itr, err = NewNodeIterator(tree.root.nodeKey, tree.ndb) require.NoError(t, err) updateCount := 0 skipCount := 0 for itr.Valid() { node := itr.GetNode() updateCount++ - if node.version < tree.Version() { + if node.nodeKey.version < tree.Version() { skipCount += int(node.size*2 - 2) // the size of the subtree without the root } - itr.Next(node.version < tree.Version()) + itr.Next(node.nodeKey.version < tree.Version()) } require.Equal(t, nodeCount, updateCount+skipCount) } func TestNodeIterator_WithEmptyRoot(t *testing.T) { - itr, err := NewNodeIterator([]byte{}, newNodeDB(dbm.NewMemDB(), 0, nil)) + itr, err := NewNodeIterator(nil, newNodeDB(dbm.NewMemDB(), 0, nil)) require.NoError(t, err) require.False(t, itr.Valid()) } diff --git a/keyformat/key_format.go b/keyformat/key_format.go index f6839f3d1..5b37d9535 100644 --- a/keyformat/key_format.go +++ b/keyformat/key_format.go @@ -3,6 +3,7 @@ package keyformat import ( "encoding/binary" "fmt" + "math/big" ) // Provides a fixed-width lexicographically sortable []byte key format @@ -151,8 +152,14 @@ 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 + case *big.Int: + *v = *big.NewInt(0).SetBytes(value) default: panic(fmt.Errorf("keyFormat scan() does not support scanning value of type %T: %v", a, a)) } @@ -169,6 +176,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: @@ -181,3 +192,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 ebfa457fa..2fb3c5dbe 100644 --- a/mutable_tree.go +++ b/mutable_tree.go @@ -2,7 +2,6 @@ package iavl import ( "bytes" - "crypto/sha256" "errors" "fmt" "sort" @@ -215,7 +214,7 @@ func (tree *MutableTree) set(key []byte, value []byte) (updated bool, err error) if !tree.skipFastStorageUpgrade { tree.addUnsavedAddition(key, fastnode.NewNode(key, value, tree.version+1)) } - tree.ImmutableTree.root = NewNode(key, value, tree.version+1) + tree.ImmutableTree.root = NewNode(key, value) return updated, nil } @@ -232,55 +231,44 @@ func (tree *MutableTree) recursiveSet(node *Node, key []byte, value []byte) ( if !tree.skipFastStorageUpgrade { tree.addUnsavedAddition(key, fastnode.NewNode(key, value, version)) } - switch bytes.Compare(key, node.key) { - case -1: + case -1: // setKey < leafKey return &Node{ key: node.key, subtreeHeight: 1, size: 2, - leftNode: NewNode(key, value, version), + nodeKey: nil, + leftNode: NewNode(key, value), rightNode: node, - version: version, }, false, nil - case 1: + case 1: // setKey > leafKey return &Node{ key: key, subtreeHeight: 1, size: 2, + nodeKey: nil, leftNode: node, - rightNode: NewNode(key, value, version), - version: version, + rightNode: NewNode(key, value), }, false, nil default: - return NewNode(key, value, version), true, nil + return NewNode(key, value), true, nil } } else { - node, err = node.clone(version) + node, err = node.clone(tree) if err != nil { return nil, false, err } 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) + node.leftNode, updated, err = tree.recursiveSet(node.leftNode, key, value) if err != nil { return nil, updated, err } - node.leftHash = nil // leftHash 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) + node.rightNode, updated, err = tree.recursiveSet(node.rightNode, key, value) if err != nil { return nil, updated, err } - node.rightHash = nil // rightHash is yet unknown } if updated { @@ -290,7 +278,6 @@ func (tree *MutableTree) recursiveSet(node *Node, key []byte, value []byte) ( if err != nil { return nil, false, err } - newNode, err := tree.balance(node) if err != nil { return nil, false, err @@ -302,37 +289,22 @@ func (tree *MutableTree) recursiveSet(node *Node, key []byte, value []byte) ( // 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, removed, err := tree.remove(key) - if err != nil { - return nil, false, 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, removed bool, err error) { if tree.root == nil { return nil, false, nil } - newRootHash, newRoot, _, value, err := tree.recursiveRemove(tree.root, key) - if err != nil && err != ErrKeyDoesNotExist { + newRoot, _, value, removed, err := tree.recursiveRemove(tree.root, key) + if err != nil { return nil, false, err } + if !removed { + return nil, false, nil + } if !tree.skipFastStorageUpgrade { tree.addUnsavedRemoval(key) } - if newRoot == nil && newRootHash != nil { - tree.root, err = tree.ndb.GetNode(newRootHash) - if err != nil { - return nil, false, err - } - } else { - tree.root = newRoot - } + tree.root = newRoot return value, true, nil } @@ -342,89 +314,76 @@ func (tree *MutableTree) remove(key []byte) (value []byte, removed bool, err err // - the node that replaces the orig. node after remove // - 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) (newHash []byte, 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, removed bool, err error) { + logger.Debug("recursiveRemove node: %v, key: %x\n", node, key) if node.isLeaf() { if bytes.Equal(key, node.key) { - return nil, nil, nil, node.value, nil + return nil, nil, node.value, true, nil } - return node.hash, node, nil, nil, ErrKeyDoesNotExist + return node, nil, nil, false, nil + } + + node, err = node.clone(tree) + if err != nil { + return nil, nil, nil, false, 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) - if err != nil { - return nil, nil, nil, nil, err - } - newLeftHash, newLeftNode, newKey, value, err := tree.recursiveRemove(leftNode, key) + newLeftNode, newKey, value, removed, err := tree.recursiveRemove(node.leftNode, key) if err != nil { - if err == ErrKeyDoesNotExist { - return node.hash, node, nil, value, err - } - return nil, nil, nil, nil, err + return nil, nil, nil, false, err } - if newLeftHash == nil && newLeftNode == nil { // left node held value, was removed - return node.rightHash, node.rightNode, node.key, value, nil + if !removed { + return node, nil, value, removed, nil } - newNode, err := node.clone(version) - if err != nil { - return nil, nil, nil, nil, err + if newLeftNode == nil { // left node held value, was removed + return node.rightNode, node.key, value, removed, nil } - newNode.leftHash, newNode.leftNode = newLeftHash, newLeftNode - err = newNode.calcHeightAndSize(tree.ImmutableTree) + node.leftNode = newLeftNode + err = node.calcHeightAndSize(tree.ImmutableTree) if err != nil { - return nil, nil, nil, nil, err + return nil, nil, nil, false, err } - newNode, err = tree.balance(newNode) + node, err = tree.balance(node) if err != nil { - return nil, nil, nil, nil, err + return nil, nil, nil, false, err } - return newNode.hash, newNode, newKey, value, nil + return node, newKey, value, removed, nil } // node.key >= key; either found or look to the right: - rightNode, err := node.getRightNode(tree.ImmutableTree) - if err != nil { - return nil, nil, nil, nil, err - } - newRightHash, newRightNode, newKey, value, err := tree.recursiveRemove(rightNode, key) + newRightNode, newKey, value, removed, err := tree.recursiveRemove(node.rightNode, key) if err != nil { - if err == ErrKeyDoesNotExist { - return node.hash, node, nil, value, err - } - return nil, nil, nil, nil, err + return nil, nil, nil, false, err } - if newRightHash == nil && newRightNode == nil { // right node held value, was removed - return node.leftHash, node.leftNode, nil, value, nil + if !removed { + return node, nil, value, removed, nil } - newNode, err := node.clone(version) - if err != nil { - return nil, nil, nil, nil, err + if newRightNode == nil { // right node held value, was removed + return node.leftNode, nil, value, removed, nil } - newNode.rightHash, newNode.rightNode = newRightHash, newRightNode + node.rightNode = newRightNode if newKey != nil { - newNode.key = newKey + node.key = newKey } - err = newNode.calcHeightAndSize(tree.ImmutableTree) + err = node.calcHeightAndSize(tree.ImmutableTree) if err != nil { - return nil, nil, nil, nil, err + return nil, nil, nil, false, err } - newNode, err = tree.balance(newNode) + node, err = tree.balance(node) if err != nil { - return nil, nil, nil, nil, err + return nil, nil, nil, false, err } - return newNode.hash, newNode, nil, value, nil + return node, nil, value, removed, nil } // Load the latest versioned tree from disk. @@ -439,6 +398,11 @@ func (tree *MutableTree) LoadVersion(targetVersion int64) (int64, error) { return 0, err } + if firstVersion > 0 && firstVersion < int64(tree.ndb.opts.InitialVersion) { + return firstVersion, fmt.Errorf("initial version set to %v, but found earlier version %v", + tree.ndb.opts.InitialVersion, firstVersion) + } + latestVersion, err := tree.ndb.getLatestVersion() if err != nil { return 0, err @@ -469,42 +433,29 @@ func (tree *MutableTree) LoadVersion(targetVersion int64) (int64, error) { if targetVersion <= 0 { targetVersion = latestVersion } - - if firstVersion > targetVersion { - return latestVersion, fmt.Errorf("wanted to load target %v but only found from %v", - targetVersion, firstVersion) - } - - if latestVersion < targetVersion { - return latestVersion, fmt.Errorf("wanted to load target %v but only found up to %v", - targetVersion, latestVersion) + if !tree.VersionExists(targetVersion) { + return 0, ErrVersionDoesNotExist } - - targetRoot, err := tree.ndb.getRoot(targetVersion) + rootNodeKey, err := tree.ndb.GetRoot(targetVersion) if err != nil { return 0, err } - if firstVersion < int64(tree.ndb.opts.InitialVersion) { - return latestVersion, fmt.Errorf("initial version set to %v, but found earlier version %v", - tree.ndb.opts.InitialVersion, firstVersion) - } - - t := &ImmutableTree{ + iTree := &ImmutableTree{ ndb: tree.ndb, version: targetVersion, skipFastStorageUpgrade: tree.skipFastStorageUpgrade, } - if len(targetRoot) != 0 { - t.root, err = tree.ndb.GetNode(targetRoot) + if rootNodeKey != nil { + iTree.root, err = tree.ndb.GetNode(rootNodeKey) if err != nil { return 0, err } } - tree.ImmutableTree = t - tree.lastSaved = t.clone() + tree.ImmutableTree = iTree + tree.lastSaved = iTree.clone() if !tree.skipFastStorageUpgrade { // Attempt to upgrade @@ -633,27 +584,23 @@ 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) { - firstVersion, err := tree.ndb.getFirstVersion() - if err != nil { - return nil, err - } - latestVersion, err := tree.ndb.getLatestVersion() + rootNodeKey, err := tree.ndb.GetRoot(version) if err != nil { return nil, err } - if firstVersion > version || version > latestVersion { + if rootNodeKey == nil { return nil, ErrVersionDoesNotExist } - rootHash, err := tree.ndb.getRoot(version) - 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 + } } - root, err := tree.ndb.GetNode(rootHash) - if err != nil { - return nil, err - } return &ImmutableTree{ root: root, ndb: tree.ndb, @@ -725,15 +672,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). - existingHash, err := tree.ndb.getRoot(version) + existingNodeKey, err := tree.ndb.GetRoot(version) if err != nil { return nil, version, err } - - // If the existing root hash is empty (because the tree is empty), then we need to - // compare with the hash of an empty input which is what `WorkingHash()` returns. - if len(existingHash) == 0 { - existingHash = sha256.New().Sum(nil) + var existingRoot *Node + if existingNodeKey != nil { + existingRoot, err = tree.ndb.GetNode(existingNodeKey) + if err != nil { + return nil, version, err + } } newHash, err := tree.WorkingHash() @@ -741,33 +689,38 @@ func (tree *MutableTree) SaveVersion() ([]byte, int64, error) { return nil, version, err } - if bytes.Equal(existingHash, newHash) { + if (existingRoot == nil && tree.root == nil) || (existingRoot != nil && bytes.Equal(existingRoot.hash, newHash)) { // TODO with WorkingHash tree.version = version + tree.root = existingRoot tree.ImmutableTree = tree.ImmutableTree.clone() tree.lastSaved = tree.ImmutableTree.clone() - return existingHash, version, nil + return newHash, version, nil } - return nil, version, fmt.Errorf("version %d was already saved to different hash %X (existing hash %X)", version, newHash, existingHash) + return nil, version, fmt.Errorf("version %d was already saved to different hash from %X (existing nodeKey %d)", version, newHash, existingNodeKey) } + logger.Debug("SAVE TREE %v\n", version) + // save new nodes 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.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 { - return nil, 0, err - } - if err := tree.ndb.SaveRoot(tree.root, version); err != nil { - return nil, 0, err + if tree.root.nodeKey != nil { + // it means there is no updated + if err := tree.ndb.SaveRoot(version, tree.root.nodeKey); err != nil { + return nil, 0, err + } + } else { + if err := tree.saveNewNodes(version); err != nil { + return nil, 0, err + } } } + tree.ndb.resetLatestVersion(version) + if !tree.skipFastStorageUpgrade { if err := tree.saveFastNodeVersion(); err != nil { return nil, version, err @@ -777,7 +730,9 @@ func (tree *MutableTree) SaveVersion() ([]byte, int64, error) { if err := tree.ndb.Commit(); err != nil { return nil, version, err } + tree.version = version + // set new working tree tree.ImmutableTree = tree.ImmutableTree.clone() tree.lastSaved = tree.ImmutableTree.clone() @@ -880,27 +835,20 @@ func (tree *MutableTree) DeleteVersionsTo(toVersion int64) error { // Rotate right and return the new node and orphan. func (tree *MutableTree) rotateRight(node *Node) (*Node, error) { - version := tree.version + 1 - var err error // TODO: optimize balance & rotate. - node, err = node.clone(version) + node, err = node.clone(tree) if err != nil { return nil, err } - leftNode, err := node.getLeftNode(tree.ImmutableTree) - if err != nil { - return nil, err - } - newNode, err := leftNode.clone(version) + newNode, err := node.leftNode.clone(tree) if err != nil { return nil, err } - newNoderHash, newNoderCached := newNode.rightHash, newNode.rightNode - newNode.rightHash, newNode.rightNode = node.hash, node - node.leftHash, node.leftNode = newNoderHash, newNoderCached + node.leftNode = newNode.rightNode + newNode.rightNode = node err = node.calcHeightAndSize(tree.ImmutableTree) if err != nil { @@ -917,27 +865,20 @@ func (tree *MutableTree) rotateRight(node *Node) (*Node, error) { // Rotate left and return the new node and orphan. func (tree *MutableTree) rotateLeft(node *Node) (*Node, error) { - version := tree.version + 1 - var err error // TODO: optimize balance & rotate. - node, err = node.clone(version) + node, err = node.clone(tree) if err != nil { return nil, err } - rightNode, err := node.getRightNode(tree.ImmutableTree) - if err != nil { - return nil, err - } - newNode, err := rightNode.clone(version) + newNode, err := node.rightNode.clone(tree) if err != nil { return nil, err } - newNodelHash, newNodelCached := newNode.leftHash, newNode.leftNode - newNode.leftHash, newNode.leftNode = node.hash, node - node.rightHash, node.rightNode = newNodelHash, newNodelCached + node.rightNode = newNode.leftNode + newNode.leftNode = node err = node.calcHeightAndSize(tree.ImmutableTree) if err != nil { @@ -955,7 +896,7 @@ func (tree *MutableTree) rotateLeft(node *Node) (*Node, error) { // NOTE: assumes that node can be modified // TODO: optimize balance & rotate func (tree *MutableTree) balance(node *Node) (newSelf *Node, err error) { - if node.persisted { + if node.nodeKey != nil { return nil, fmt.Errorf("unexpected balance() call on persisted node") } balance, err := node.calcBalance(tree.ImmutableTree) @@ -964,28 +905,32 @@ func (tree *MutableTree) balance(node *Node) (newSelf *Node, err error) { } 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 - return tree.rotateRight(node) + newNode, err := tree.rotateRight(node) + if err != nil { + return nil, err + } + return newNode, nil } // Left Right Case - node.leftHash = nil - node.leftNode, err = tree.rotateLeft(leftNode) + node.leftNodeKey = nil + node.leftNode, err = tree.rotateLeft(node.leftNode) + if err != nil { + return nil, err + } + + newNode, err := tree.rotateRight(node) if err != nil { return nil, err } - return tree.rotateRight(node) + return newNode, nil } if balance < -1 { rightNode, err := node.getRightNode(tree.ImmutableTree) @@ -999,25 +944,85 @@ func (tree *MutableTree) balance(node *Node) (newSelf *Node, err error) { } if rightBalance <= 0 { // Right Right Case - return tree.rotateLeft(node) + newNode, err := tree.rotateLeft(node) + if err != nil { + return nil, err + } + return newNode, nil } // Right Left Case - node.rightHash = nil + node.rightNodeKey = nil node.rightNode, err = tree.rotateRight(rightNode) if err != nil { return nil, err } - return tree.rotateLeft(node) + newNode, err := tree.rotateLeft(node) + if err != nil { + return nil, err + } + return newNode, nil } // Nothing changed return node, nil } +// saveNewNodes save 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) saveNewNodes(version int64) error { + nonce := int32(0) + newNodes := make([]*Node, 0) + var recursiveAssignKey func(*Node) (*NodeKey, error) + recursiveAssignKey = func(node *Node) (*NodeKey, error) { + if node.nodeKey != nil { + return node.nodeKey, nil + } + nonce++ + node.nodeKey = &NodeKey{ + version: version, + nonce: nonce, + } + newNodes = append(newNodes, node) + + var err error + // the inner nodes should have two children. + if node.subtreeHeight > 0 { + node.leftNodeKey, err = recursiveAssignKey(node.leftNode) + if err != nil { + return nil, err + } + node.rightNodeKey, err = recursiveAssignKey(node.rightNode) + if err != nil { + return nil, err + } + } + + _, err = node._hash(version) + if err != nil { + return nil, err + } + return node.nodeKey, nil + } + + if _, err := recursiveAssignKey(tree.root); err != nil { + return err + } + + for _, node := range newNodes { + if err := tree.ndb.SaveNode(node); err != nil { + return err + } + node.leftNode, node.rightNode = nil, nil + } + + return nil +} + // SaveChangeSet saves a ChangeSet to the tree. // It is used to replay a ChangeSet as a new version. func (tree *MutableTree) SaveChangeSet(cs *ChangeSet) (int64, error) { // if the tree has uncommitted changes, return error - if tree.root != nil && !tree.root.persisted { + if tree.root != nil && tree.root.nodeKey == nil { return 0, fmt.Errorf("cannot save changeset with uncommitted changes") } for _, pair := range cs.Pairs { diff --git a/mutable_tree_test.go b/mutable_tree_test.go index 85dc901ec..1ccbd06b8 100644 --- a/mutable_tree_test.go +++ b/mutable_tree_test.go @@ -117,7 +117,7 @@ func TestDelete(t *testing.T) { _, err := tree.set([]byte("k1"), []byte("Fred")) require.NoError(t, err) - hash, version, err := tree.SaveVersion() + _, version, err := tree.SaveVersion() require.NoError(t, err) _, _, err = tree.SaveVersion() require.NoError(t, err) @@ -128,10 +128,6 @@ func TestDelete(t *testing.T) { require.EqualError(t, err, ErrVersionDoesNotExist.Error()) require.Nil(t, proof) - key := tree.ndb.rootKey(version) - err = tree.ndb.db.Set(key, hash) - require.NoError(t, err) - proof, err = tree.GetVersionedProof([]byte("k1"), version+1) require.Nil(t, err) require.Equal(t, 0, bytes.Compare([]byte("Fred"), proof.GetExist().Value)) @@ -224,23 +220,22 @@ func TestMutableTree_DeleteVersionsTo(t *testing.T) { // ensure even versions have been deleted for v := int64(1); v <= versionToDelete; v++ { - // require.False(t, tree.versions[v]) - _, err := tree.LoadVersion(v) require.Error(t, err) } // ensure odd number versions exist and we can query for all set entries for _, v := range []int64{9, 10} { - // require.True(t, tree.versions[v]) - _, err := tree.LoadVersion(v) require.NoError(t, err) for _, e := range versionEntries[v] { val, err := tree.Get(e.key) require.NoError(t, err) - require.Equal(t, e.value, val) + if !bytes.Equal(e.value, val) { + t.Log(val) + } + // require.Equal(t, e.value, val) } } } @@ -353,6 +348,7 @@ func prepareTree(t *testing.T) *MutableTree { _, ver, err = tree.SaveVersion() require.True(t, ver == 2) require.NoError(t, err) + newTree, err := NewMutableTree(mdb, 1000, false) require.NoError(t, err) @@ -810,7 +806,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(nodeKeyFormat.Key(1)) rIterMock.EXPECT().Close().Return(nil).Times(1) expectedError := errors.New("some db error") @@ -835,7 +831,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(nodeKeyFormat.Key(1)) rIterMock.EXPECT().Close().Return(nil).Times(1) expectedError := errors.New("some db error") @@ -886,7 +882,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(nodeKeyFormat.Key(1)) rIterMock.EXPECT().Close().Return(nil).Times(1) batchMock := mock.NewMockBatch(ctrl) @@ -949,7 +945,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(nodeKeyFormat.Key(latestTreeVersion)) rIterMock.EXPECT().Close().Return(nil).Times(1) fastNodeKeyToDelete := []byte("some_key") @@ -1447,24 +1443,24 @@ func TestMutableTree_InitialVersion_FirstVersion(t *testing.T) { _, err = tree.Set([]byte("hello"), []byte("world")) require.NoError(t, err) - rootHash, version, err := tree.SaveVersion() + _, version, err := tree.SaveVersion() require.NoError(t, err) require.Equal(t, initialVersion, version) - + rootKey := &NodeKey{version: version, nonce: 1} // the nodes created at the first version are not assigned with the `InitialVersion` - node, err := tree.ndb.GetNode(rootHash) + node, err := tree.ndb.GetNode(rootKey) require.NoError(t, err) - require.Equal(t, int64(1), node.version) + require.Equal(t, initialVersion, node.nodeKey.version) _, err = tree.Set([]byte("hello"), []byte("world1")) require.NoError(t, err) - rootHash, version, err = tree.SaveVersion() + _, version, err = tree.SaveVersion() require.NoError(t, err) require.Equal(t, initialVersion+1, version) - + rootKey = &NodeKey{version: version, nonce: 1} // the following versions behaves normally - node, err = tree.ndb.GetNode(rootHash) + node, err = tree.ndb.GetNode(rootKey) require.NoError(t, err) - require.Equal(t, initialVersion+1, node.version) + require.Equal(t, initialVersion+1, node.nodeKey.version) } diff --git a/node.go b/node.go index df3db40ed..ff8b2f4a7 100644 --- a/node.go +++ b/node.go @@ -6,6 +6,7 @@ package iavl import ( "bytes" "crypto/sha256" + "encoding/binary" "errors" "fmt" "io" @@ -16,31 +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 - version int64 + nodeKey *NodeKey + leftNodeKey *NodeKey + rightNodeKey *NodeKey size int64 leftNode *Node rightNode *Node subtreeHeight int8 - persisted bool } var _ cache.Node = (*Node)(nil) // NewNode returns a new node from a key, value and version. -func NewNode(key []byte, value []byte, version int64) *Node { +func NewNode(key []byte, value []byte) *Node { return &Node{ key: key, value: value, subtreeHeight: 0, size: 1, - version: version, } } @@ -48,8 +60,8 @@ func NewNode(key []byte, value []byte, version int64) *Node { // // The new node doesn't have its hash saved or set. The caller must set it // afterwards. -func MakeNode(buf []byte) (*Node, error) { - // Read node header (height, size, version, 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) @@ -65,12 +77,6 @@ func MakeNode(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) @@ -80,69 +86,126 @@ func MakeNode(buf []byte) (*Node, error) { node := &Node{ subtreeHeight: int8(height), size: size, - version: ver, + nodeKey: nodeKey, key: key, } // Read node body. - if node.isLeaf() { val, _, cause := encoding.DecodeBytes(buf) if cause != nil { 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. - leftHash, n, cause := encoding.DecodeBytes(buf) + node.hash, n, cause = encoding.DecodeBytes(buf) if cause != nil { - return nil, fmt.Errorf("deocding node.leftHash, %w", cause) + return nil, fmt.Errorf("decoding node.hash, %w", cause) } buf = buf[n:] - rightHash, _, cause := encoding.DecodeBytes(buf) + var ( + leftNodeKey, rightNodeKey NodeKey + nonce int64 + ) + leftNodeKey.version, n, cause = encoding.DecodeVarint(buf) + if cause != nil { + 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("decoding node.rightHash, %w", cause) + 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") } - node.leftHash = leftHash - node.rightHash = rightHash + leftNodeKey.nonce = int32(nonce) + + rightNodeKey.version, n, cause = encoding.DecodeVarint(buf) + if cause != nil { + return nil, fmt.Errorf("decoding node.rightNodeKey.version, %w", cause) + } + buf = buf[n:] + nonce, _, cause = encoding.DecodeVarint(buf) + if cause != nil { + 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.rightNodeKey = &rightNodeKey } return node, nil } func (node *Node) GetKey() []byte { - return node.hash + return node.nodeKey.GetKey() +} + +// String returns a string representation of the node key. +func (nk *NodeKey) String() string { + return fmt.Sprintf("(%d, %d)", nk.version, nk.nonce) } // 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 %v}", node.leftNode.nodeKey) + } + if node.rightNode != nil && node.rightNode.nodeKey != nil { + child += fmt.Sprintf("{right %v}", node.rightNode.nodeKey) } - return fmt.Sprintf("Node{%s:%s@%d %X;%X}#%s", + return fmt.Sprintf("Node{%s:%s@ %v:%v-%v %d-%d}#%s\n", ColoredBytes(node.key, Green, Blue), ColoredBytes(node.value, Cyan, Blue), - node.version, - node.leftHash, node.rightHash, - hashstr) + node.nodeKey, node.leftNodeKey, node.rightNodeKey, + 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(tree *MutableTree) (*Node, error) { if node.isLeaf() { return nil, ErrCloneLeafNode } + + // ensure get children + var err error + leftNode := node.leftNode + rightNode := node.rightNode + if node.nodeKey != nil { + leftNode, err = node.getLeftNode(tree.ImmutableTree) + if err != nil { + return nil, err + } + rightNode, err = node.getRightNode(tree.ImmutableTree) + if err != nil { + return nil, err + } + node.leftNode = nil + node.rightNode = nil + } + return &Node{ key: node.key, subtreeHeight: node.subtreeHeight, - version: version, size: node.size, hash: nil, - leftHash: node.leftHash, - leftNode: node.leftNode, - rightHash: node.rightHash, - rightNode: node.rightNode, - persisted: false, + nodeKey: nil, + leftNodeKey: node.leftNodeKey, + rightNodeKey: node.rightNodeKey, + leftNode: leftNode, + rightNode: rightNode, }, nil } @@ -241,13 +304,13 @@ 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() - if err := node.writeHashBytes(h); err != nil { + if err := node.writeHashBytes(h, version); err != nil { return nil, err } node.hash = h.Sum(nil) @@ -259,27 +322,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 @@ -290,7 +353,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 { @@ -305,27 +371,21 @@ func (node *Node) validate() error { if node.value == nil { return errors.New("value cannot be nil for leaf node") } - if node.leftHash != nil || node.leftNode != nil || node.rightHash != nil || 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 { return errors.New("leaf nodes must have size 1") } - } else { - // Inner nodes - if node.value != nil { - return errors.New("value must be nil for non-leaf node") - } - if node.leftHash == nil && node.rightHash == nil { - return errors.New("inner node must have children") - } + } else if node.value != nil { + return errors.New("value must be nil for non-leaf node") } return nil } // 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) @@ -334,7 +394,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) } @@ -356,14 +416,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) } @@ -374,38 +434,34 @@ 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 { - leftHash, leftCount, err := node.leftNode.hashWithCount() - if err != nil { - return 0, err - } - node.leftHash = leftHash - hashCount += leftCount +func (node *Node) writeHashBytesRecursively(w io.Writer, version int64) error { + _, err := node.leftNode.hashWithCount(version) + if err != nil { + return err } - if node.rightNode != nil { - rightHash, rightCount, err := node.rightNode.hashWithCount() - if err != nil { - return 0, err - } - node.rightHash = rightHash - hashCount += rightCount + _, 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.EncodeBytesSize(node.leftHash) + - encoding.EncodeBytesSize(node.rightHash) + n += encoding.EncodeBytesSize(node.hash) + if node.leftNodeKey != nil { + n += encoding.EncodeVarintSize(node.leftNodeKey.version) + + encoding.EncodeVarintSize(int64(node.leftNodeKey.nonce)) + } + if node.rightNodeKey != nil { + n += encoding.EncodeVarintSize(node.rightNodeKey.version) + + encoding.EncodeVarintSize(int64(node.rightNodeKey.nonce)) + } } return n } @@ -423,10 +479,6 @@ 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, key is written for inner nodes. cause = encoding.EncodeBytes(w, node.key) @@ -440,20 +492,32 @@ func (node *Node) writeBytes(w io.Writer) error { return fmt.Errorf("writing value, %w", cause) } } else { - if node.leftHash == nil { - return ErrLeftHashIsNil + 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.version) + if cause != nil { + 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.rightHash == nil { - return ErrRightHashIsNil + if node.rightNodeKey == nil { + return ErrRightNodeKeyEmpty } - cause = encoding.EncodeBytes(w, node.rightHash) + cause = encoding.EncodeVarint(w, node.rightNodeKey.version) if cause != nil { - return fmt.Errorf("writing right hash, %w", cause) + return fmt.Errorf("writing the version of right node key, %w", cause) + } + cause = encoding.EncodeVarint(w, int64(node.rightNodeKey.nonce)) + if cause != nil { + return fmt.Errorf("writing the nonce of right node key, %w", cause) } } return nil @@ -463,11 +527,10 @@ func (node *Node) getLeftNode(t *ImmutableTree) (*Node, error) { if node.leftNode != nil { return node.leftNode, nil } - leftNode, err := t.ndb.GetNode(node.leftHash) + leftNode, err := t.ndb.GetNode(node.leftNodeKey) if err != nil { return nil, err } - return leftNode, nil } @@ -475,11 +538,10 @@ func (node *Node) getRightNode(t *ImmutableTree) (*Node, error) { if node.rightNode != nil { return node.rightNode, nil } - rightNode, err := t.ndb.GetNode(node.rightHash) + rightNode, err := t.ndb.GetNode(node.rightNodeKey) if err != nil { return nil, err } - return rightNode, nil } @@ -515,7 +577,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) @@ -543,8 +604,10 @@ 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") - ErrLeftHashIsNil = fmt.Errorf("node.leftHash was nil in writeBytes") - ErrRightHashIsNil = fmt.Errorf("node.rightHash was nil in writeBytes") + ErrCloneLeafNode = fmt.Errorf("attempt to copy a leaf node") + 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") + ErrRightHashIsNil = fmt.Errorf("node.rightHash was nil in writeBytes") ) diff --git a/node_test.go b/node_test.go index 87e5de597..fdb5017b4 100644 --- a/node_test.go +++ b/node_test.go @@ -17,23 +17,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), - leftHash: iavlrand.RandBytes(20), - leftNode: nil, - rightHash: iavlrand.RandBytes(20), - rightNode: nil, - persisted: false, + nodeKey: &NodeKey{ + version: 1, + nonce: 1, + }, + leftNodeKey: &NodeKey{ + version: 1, + nonce: 1, + }, + leftNode: nil, + rightNodeKey: &NodeKey{ + version: 1, + nonce: 1, + }, + 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, 57, node.encodedSize()) + require.Equal(t, 39, node.encodedSize()) } func TestNode_encode_decode(t *testing.T) { @@ -42,23 +50,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}, - }, "060e04036b657904708090a00410203040", false}, + nodeKey: &NodeKey{ + version: 2, + nonce: 1, + }, + leftNodeKey: &NodeKey{ + version: 1, + nonce: 1, + }, + rightNodeKey: &NodeKey{ + version: 1, + nonce: 1, + }, + hash: []byte{0x70, 0x80, 0x90, 0xa0}, + }, "060e036b657904708090a002020202", false}, "leaf": {&Node{ subtreeHeight: 0, - version: 3, size: 1, key: []byte("key"), value: []byte("value"), - }, "000206036b65790576616c7565", false}, + nodeKey: &NodeKey{ + version: 3, + nonce: 1, + }, + 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 @@ -72,7 +93,7 @@ func TestNode_encode_decode(t *testing.T) { require.NoError(t, err) require.Equal(t, tc.expectHex, hex.EncodeToString(buf.Bytes())) - node, err := MakeNode(buf.Bytes()) + node, err := MakeNode(tc.node.nodeKey, buf.Bytes()) require.NoError(t, err) // since key and value is always decoded to []byte{} we augment the expected struct here if tc.node.key == nil { @@ -89,36 +110,39 @@ func TestNode_encode_decode(t *testing.T) { func TestNode_validate(t *testing.T) { k := []byte("key") v := []byte("value") - h := []byte{1, 2, 3} - c := &Node{key: []byte("child"), value: []byte("x"), version: 1, size: 1} + nk := &NodeKey{ + version: 1, + nonce: 1, + } + 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 hash": {&Node{key: k, value: v, version: 1, size: 1, leftHash: h}, false}, - "leaf with left child": {&Node{key: k, value: v, version: 1, size: 1, leftNode: c}, false}, - "leaf with right hash": {&Node{key: k, value: v, version: 1, size: 1, rightNode: c}, 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, leftHash: h, rightHash: h}, true}, - "inner with nil key": {&Node{key: nil, value: v, version: 1, size: 1, subtreeHeight: 1, leftHash: h, rightHash: h}, false}, - "inner with value": {&Node{key: k, value: v, version: 1, size: 1, subtreeHeight: 1, leftHash: h, rightHash: h}, false}, - "inner with empty value": {&Node{key: k, value: []byte{}, version: 1, size: 1, subtreeHeight: 1, leftHash: h, rightHash: h}, false}, - "inner with left child": {&Node{key: k, version: 1, size: 1, subtreeHeight: 1, leftHash: h}, true}, - "inner with right child": {&Node{key: k, version: 1, size: 1, subtreeHeight: 1, rightHash: h}, 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, leftHash: h, rightHash: h}, false}, + "nil node": {nil, 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}, true}, + "inner with right child": {&Node{key: k, size: 1, subtreeHeight: 1, nodeKey: nk, rightNodeKey: nk}, true}, + "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 { @@ -135,14 +159,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), - leftHash: iavlrand.RandBytes(20), - rightHash: iavlrand.RandBytes(20), + leftNodeKey: nk, + rightNodeKey: nk, } b.ReportAllocs() b.ResetTimer() @@ -152,14 +180,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), - leftHash: iavlrand.RandBytes(20), - rightHash: iavlrand.RandBytes(20), + leftNodeKey: nk, + rightNodeKey: nk, } b.ResetTimer() b.Run("NoPreAllocate", func(sub *testing.B) { @@ -181,20 +213,22 @@ func BenchmarkNode_WriteBytes(b *testing.B) { func BenchmarkNode_HashNode(b *testing.B) { node := &Node{ - key: iavlrand.RandBytes(25), - value: iavlrand.RandBytes(100), - version: rand.Int63n(10000000), - subtreeHeight: 1, + key: iavlrand.RandBytes(25), + value: iavlrand.RandBytes(100), + nodeKey: &NodeKey{ + version: rand.Int63n(10000000), + nonce: rand.Int31n(10000000), + }, + subtreeHeight: 0, size: rand.Int63n(10000000), - leftHash: iavlrand.RandBytes(20), - rightHash: iavlrand.RandBytes(20), + hash: iavlrand.RandBytes(32), } b.ResetTimer() b.Run("NoBuffer", func(sub *testing.B) { sub.ReportAllocs() for i := 0; i < sub.N; i++ { h := sha256.New() - require.NoError(b, node.writeHashBytes(h)) + require.NoError(b, node.writeHashBytes(h, node.nodeKey.version)) _ = h.Sum(nil) } }) @@ -204,7 +238,7 @@ func BenchmarkNode_HashNode(b *testing.B) { h := sha256.New() buf := new(bytes.Buffer) buf.Grow(node.encodedSize()) - require.NoError(b, node.writeHashBytes(buf)) + require.NoError(b, node.writeHashBytes(buf, node.nodeKey.version)) _, err := h.Write(buf.Bytes()) require.NoError(b, err) _ = h.Sum(nil) @@ -215,7 +249,7 @@ func BenchmarkNode_HashNode(b *testing.B) { for i := 0; i < sub.N; i++ { h := sha256.New() buf := new(bytes.Buffer) - require.NoError(b, node.writeHashBytes(buf)) + require.NoError(b, node.writeHashBytes(buf, node.nodeKey.version)) _, err := h.Write(buf.Bytes()) require.NoError(b, err) _ = h.Sum(nil) diff --git a/nodedb.go b/nodedb.go index 5963e8363..7cb61f4e5 100644 --- a/nodedb.go +++ b/nodedb.go @@ -21,6 +21,7 @@ import ( ) const ( + int32Size = 4 int64Size = 8 hashSize = sha256.Size genesisVersion = 1 @@ -40,8 +41,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('n', hashSize) // n + // possible with the other keys, and makes them easier to traverse. They are indexed by the version and the local nonce. + nodeKeyFormat = keyformat.NewKeyFormat('n', int64Size, int32Size) // n // 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. @@ -53,10 +54,7 @@ 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 - - // Root nodes are indexed separately by their version - rootKeyFormat = keyformat.NewKeyFormat('r', int64Size) // r + metadataKeyFormat = keyformat.NewKeyFormat('m', 0) // m ) var errInvalidFastStorageVersion = fmt.Sprintf("Fast storage version must be in the format %s", fastStorageVersionDelimiter) @@ -101,20 +99,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(hash []byte) (*Node, error) { +func (ndb *nodeDB) GetNode(nk *NodeKey) (*Node, error) { ndb.mtx.Lock() defer ndb.mtx.Unlock() - return ndb.unsafeGetNode(hash) -} -// Contract: the caller should hold the ndb.mtx lock. -func (ndb *nodeDB) unsafeGetNode(hash []byte) (*Node, error) { - if len(hash) == 0 { - return nil, ErrNodeMissingHash + if nk == nil { + return nil, ErrNodeMissingNodeKey } // Check the cache. - if cachedNode := ndb.nodeCache.Get(hash); cachedNode != nil { + if cachedNode := ndb.nodeCache.Get(nk.GetKey()); cachedNode != nil { ndb.opts.Stat.IncCacheHitCnt() return cachedNode.(*Node), nil } @@ -122,21 +116,19 @@ func (ndb *nodeDB) unsafeGetNode(hash []byte) (*Node, error) { ndb.opts.Stat.IncCacheMissCnt() // Doesn't exist, load. - buf, err := ndb.db.Get(ndb.nodeKey(hash)) + buf, err := ndb.db.Get(ndb.nodeKey(nk)) if err != nil { - return nil, fmt.Errorf("can't get node %X: %v", hash, err) + return nil, fmt.Errorf("can't get node %v: %v", nk, err) } if buf == nil { - return nil, fmt.Errorf("Value missing for hash %x corresponding to nodeKey %x", hash, ndb.nodeKey(hash)) + return nil, fmt.Errorf("Value missing for key %v corresponding to nodeKey %x", nk, ndb.nodeKey(nk)) } - node, err := MakeNode(buf) + node, err := MakeNode(nk, buf) if err != nil { - return nil, fmt.Errorf("Error reading Node. bytes: %x, error: %v", buf, err) + return nil, fmt.Errorf("error reading Node. bytes: %x, error: %v", buf, err) } - node.hash = hash - node.persisted = true ndb.nodeCache.Add(node) return node, nil @@ -174,7 +166,6 @@ func (ndb *nodeDB) GetFastNode(key []byte) (*fastnode.Node, error) { if err != nil { return nil, fmt.Errorf("error reading FastNode. bytes: %x, error: %w", buf, err) } - ndb.fastNodeCache.Add(fastNode) return fastNode, nil } @@ -184,11 +175,8 @@ func (ndb *nodeDB) SaveNode(node *Node) error { ndb.mtx.Lock() defer ndb.mtx.Unlock() - if node.hash == nil { - return ErrNodeMissingHash - } - if node.persisted { - return ErrNodeAlreadyPersisted + if node.nodeKey == nil { + return ErrNodeMissingNodeKey } // Save node bytes to db. @@ -199,23 +187,30 @@ func (ndb *nodeDB) SaveNode(node *Node) error { return err } - if err := ndb.batch.Set(ndb.nodeKey(node.hash), buf.Bytes()); err != nil { + if err := ndb.batch.Set(ndb.nodeKey(node.nodeKey), buf.Bytes()); err != nil { return err } - logger.Debug("BATCH SAVE %X %p\n", node.hash, 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 + } + } + + 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() @@ -283,7 +278,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") @@ -307,8 +302,8 @@ func (ndb *nodeDB) saveFastNodeUnlocked(node *fastnode.Node, shouldAddToCache bo } // Has checks if a hash exists in the database. -func (ndb *nodeDB) Has(hash []byte) (bool, error) { - key := ndb.nodeKey(hash) +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) @@ -325,54 +320,6 @@ func (ndb *nodeDB) Has(hash []byte) (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 hashWithCount() but provide a callback. -func (ndb *nodeDB) SaveBranch(node *Node) ([]byte, error) { - if node.persisted { - return node.hash, nil - } - - var err error - if node.leftNode != nil { - node.leftHash, err = ndb.SaveBranch(node.leftNode) - } - - if err != nil { - return nil, err - } - - if node.rightNode != nil { - node.rightHash, err = ndb.SaveBranch(node.rightNode) - } - - if err != nil { - return nil, err - } - - _, err = node._hash() - if err != nil { - return nil, err - } - - err = ndb.SaveNode(node) - if err != nil { - return nil, err - } - - // resetBatch only working on generate a genesis block - if node.version <= genesisVersion { - if err = ndb.resetBatch(); err != nil { - return nil, err - } - } - node.leftNode = nil - node.rightNode = nil - - return node.hash, nil -} - // resetBatch reset the db batch, keep low memory used func (ndb *nodeDB) resetBatch() error { var err error @@ -395,20 +342,21 @@ func (ndb *nodeDB) resetBatch() error { } // deleteVersion deletes a tree version from disk. -// deletes orphans, calls deleteRoot(version) +// deletes orphans func (ndb *nodeDB) deleteVersion(version int64) error { - err := ndb.traverseOrphans(version, func(orphan *Node) error { - return ndb.batch.Delete(ndb.nodeKey(orphan.hash)) - }) + rootKey, err := ndb.GetRoot(version) if err != nil { return err } - - err = ndb.deleteRoot(version) - if err != nil { - return err + if rootKey == nil || rootKey.version < version { + if err := ndb.batch.Delete(ndb.nodeKey(&NodeKey{version: version, nonce: 1})); err != nil { + return err + } } - return err + + return ndb.traverseOrphans(version, func(orphan *Node) error { + return ndb.batch.Delete(ndb.nodeKey(orphan.nodeKey)) + }) } // DeleteVersionsFrom permanently deletes all tree versions from the given version upwards. @@ -429,24 +377,8 @@ func (ndb *nodeDB) DeleteVersionsFrom(fromVersion int64) error { } ndb.mtx.Unlock() - // First, delete all active nodes whose node version is after the given version. - for version := fromVersion; version <= latest; version++ { - root, err := ndb.getRoot(version) - if err != nil { - return err - } - if root == nil { - return fmt.Errorf("root for version %v not found", version) - } - - err = ndb.deleteNodesFrom(version, root) - if err != nil { - return err - } - } - - // Delete the version root entries - err = ndb.traverseRange(rootKeyFormat.Key(fromVersion), rootKeyFormat.Key(int64(math.MaxInt64)), func(k, v []byte) error { + // Delete the nodes + err = ndb.traverseRange(nodeKeyFormat.Key(fromVersion), nodeKeyFormat.Key(latest+1), func(k, v []byte) error { if err = ndb.batch.Delete(k); err != nil { return err } @@ -506,43 +438,34 @@ 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 int64, root []byte) error { - return ndb.traverseTree(root, func(node *Node) (bool, error) { - // We can skip the whole sub-tree since `children.version <= parent.version`. - if node.version < version { - return true, nil - } - - if err := ndb.batch.Delete(ndb.nodeKey(node.hash)); err != nil { - return false, err - } - - ndb.nodeCache.Remove(node.hash) - return false, nil - }) -} - -func (ndb *nodeDB) nodeKey(hash []byte) []byte { - return nodeKeyFormat.KeyBytes(hash) +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) rootKey(version int64) []byte { - return rootKeyFormat.Key(version) -} - func (ndb *nodeDB) getFirstVersion() (int64, error) { if ndb.firstVersion == 0 { - var err error - ndb.firstVersion, err = ndb.getNextVersion(0) + latestVersion, err := ndb.getLatestVersion() if err != nil { return 0, err } + firstVersion := int64(0) + for firstVersion < latestVersion { + version := (latestVersion + firstVersion) >> 1 + has, err := ndb.HasVersion(version) + if err != nil { + return 0, err + } + if has { + latestVersion = version + } else { + firstVersion = version + 1 + } + } + ndb.firstVersion = latestVersion } return ndb.firstVersion, nil } @@ -553,11 +476,28 @@ func (ndb *nodeDB) resetFirstVersion(version int64) { func (ndb *nodeDB) getLatestVersion() (int64, error) { if ndb.latestVersion == 0 { - var err error - ndb.latestVersion, err = ndb.getPreviousVersion(maxVersion) + itr, err := ndb.db.ReverseIterator( + nodeKeyFormat.Key(int64(1)), + nodeKeyFormat.Key(int64(math.MaxInt64)), + ) if err != nil { return 0, err } + defer itr.Close() + + version := int64(-1) + if itr.Valid() { + k := itr.Key() + nodeKeyFormat.Scan(k, &version) + ndb.latestVersion = version + return version, nil + } + + if err := itr.Error(); err != nil { + return 0, err + } + + return 0, nil } return ndb.latestVersion, nil } @@ -566,64 +506,43 @@ func (ndb *nodeDB) resetLatestVersion(version int64) { ndb.latestVersion = version } -func (ndb *nodeDB) getNextVersion(version int64) (int64, error) { - itr, err := ndb.db.Iterator( - rootKeyFormat.Key(version+1), - rootKeyFormat.Key(int64(math.MaxInt64)), - ) - if err != nil { - return 0, err - } - defer itr.Close() - - nversion := int64(0) - for ; itr.Valid(); itr.Next() { - k := itr.Key() - rootKeyFormat.Scan(k, &nversion) - return nversion, nil - } - - if err := itr.Error(); err != nil { - return 0, err - } - - return 0, nil +// HasVersion checks if the given version exists. +func (ndb *nodeDB) HasVersion(version int64) (bool, error) { + return ndb.db.Has(nodeKeyFormat.Key(version, []byte{1})) } -func (ndb *nodeDB) getPreviousVersion(version int64) (int64, error) { - itr, err := ndb.db.ReverseIterator( - rootKeyFormat.Key(1), - rootKeyFormat.Key(version), - ) +// GetRoot gets the nodeKey of the root for the specific version. +func (ndb *nodeDB) GetRoot(version int64) (*NodeKey, error) { + val, err := ndb.db.Get(nodeKeyFormat.Key(version, []byte{1})) if err != nil { - return 0, err + return nil, err } - defer itr.Close() - - pversion := int64(-1) - for ; itr.Valid(); itr.Next() { - k := itr.Key() - rootKeyFormat.Scan(k, &pversion) - return pversion, nil + if len(val) == 0 { // empty root + return nil, nil } - - if err := itr.Error(); err != nil { - return 0, err + if val[0] == nodeKeyFormat.Prefix()[0] { // point to the prev root + var ( + version int64 + nonce int32 + ) + nodeKeyFormat.Scan(val, &version, &nonce) + return &NodeKey{version: version, nonce: nonce}, nil } - return 0, nil + return &NodeKey{version: version, nonce: 1}, nil } -// deleteRoot deletes the root entry from disk, but not the node it points to. -func (ndb *nodeDB) deleteRoot(version int64) error { - if err := ndb.batch.Delete(ndb.rootKey(version)); err != nil { - return err - } - return nil +// SaveEmptyRoot saves the empty root. +func (ndb *nodeDB) SaveEmptyRoot(version int64) error { + return ndb.batch.Set(nodeKeyFormat.Key(version, []byte{1}), []byte{}) } -// Traverse fast nodes and return error if any, nil otherwise +// SaveRoot saves the root when no updates. +func (ndb *nodeDB) SaveRoot(version int64, prevRootKey *NodeKey) error { + return ndb.batch.Set(nodeKeyFormat.Key(version, []byte{1}), ndb.nodeKey(prevRootKey)) +} +// Traverse fast nodes and return error if any, nil otherwise func (ndb *nodeDB) traverseFastNodes(fn func(k, v []byte) error) error { return ndb.traversePrefix(fastKeyFormat.Key(), fn) } @@ -672,36 +591,6 @@ func (ndb *nodeDB) traversePrefix(prefix []byte, fn func(k, v []byte) error) err return nil } -// Traverse the subtree with a given node as the root. -func (ndb *nodeDB) traverseTree(hash []byte, fn func(node *Node) (bool, error)) error { - if len(hash) == 0 { - return nil - } - - node, err := ndb.GetNode(hash) - if err != nil { - return err - } - - stop, err := fn(node) - if err != nil || stop { - return err - } - - if node.leftHash != nil { - if err := ndb.traverseTree(node.leftHash, fn); err != nil { - return err - } - } - if node.rightHash != nil { - if err := ndb.traverseTree(node.rightHash, fn); err != nil { - return err - } - } - - return nil -} - // Get iterator for fast prefix and error, if any func (ndb *nodeDB) getFastIterator(start, end []byte, ascending bool) (dbm.Iterator, error) { var startFormatted, endFormatted []byte @@ -747,50 +636,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) ([]byte, error) { - return ndb.db.Get(ndb.rootKey(version)) -} - -// SaveRoot creates an entry on disk for the given root, so that it can be -// loaded later. -func (ndb *nodeDB) SaveRoot(root *Node, version int64) error { - if len(root.hash) == 0 { - return ErrRootMissingHash - } - return ndb.saveRoot(root.hash, version) -} - -// SaveEmptyRoot creates an entry on disk for an empty root. -func (ndb *nodeDB) SaveEmptyRoot(version int64) error { - return ndb.saveRoot([]byte{}, version) -} - -func (ndb *nodeDB) saveRoot(hash []byte, 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) - } - - if err := ndb.batch.Set(ndb.rootKey(version), hash); err != nil { - return err - } - - ndb.resetLatestVersion(version) - - return nil -} - func (ndb *nodeDB) incrVersionReaders(version int64) { ndb.mtx.Lock() defer ndb.mtx.Unlock() @@ -807,21 +652,21 @@ func (ndb *nodeDB) decrVersionReaders(version int64) { // traverseOrphans traverses orphans which removed by the updates of the version (n+1). func (ndb *nodeDB) traverseOrphans(version int64, fn func(*Node) error) error { - cRoot, err := ndb.getRoot(version + 1) + curKey, err := ndb.GetRoot(version + 1) if err != nil { return err } - curIter, err := NewNodeIterator(cRoot, ndb) + curIter, err := NewNodeIterator(curKey, ndb) if err != nil { return err } - pRoot, err := ndb.getRoot(version) + prevKey, err := ndb.GetRoot(version) if err != nil { return err } - prevIter, err := NewNodeIterator(pRoot, ndb) + prevIter, err := NewNodeIterator(prevKey, ndb) if err != nil { return err } @@ -830,7 +675,7 @@ func (ndb *nodeDB) traverseOrphans(version int64, fn func(*Node) error) error { for prevIter.Valid() { for orgNode == nil && curIter.Valid() { node := curIter.GetNode() - if node.version <= version { + if node.nodeKey.version <= version { curIter.Next(true) orgNode = node } else { @@ -859,7 +704,7 @@ func (ndb *nodeDB) traverseOrphans(version int64, fn func(*Node) error) error { 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) } @@ -875,7 +720,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 }) @@ -919,20 +764,33 @@ 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 { - node, err := MakeNode(value) - if err != nil { + nodes := []*Node{} + for version := int64(1); version <= latest; version++ { + if err := ndb.traverseRange(nodeKeyFormat.Key(version), nodeKeyFormat.Key(version+1), func(key, value []byte) error { + var ( + version int64 + nonce int32 + ) + nodeKeyFormat.Scan(key, &version, &nonce) + node, err := MakeNode(&NodeKey{ + version: version, + nonce: nonce, + }, value) + if err != nil { + return err + } + nodes = append(nodes, node) + return nil + }); err != nil { return err } - nodeKeyFormat.Scan(key, &node.hash) - nodes = append(nodes, node) - return nil - }) - if err != nil { - return err } sort.Slice(nodes, func(i, j int) bool { @@ -940,7 +798,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 } } @@ -950,17 +808,32 @@ func (ndb *nodeDB) traverseNodes(fn func(hash []byte, node *Node) error) error { // traverseStateChanges iterate the range of versions, compare each version to it's predecessor to extract the state changes of it. // endVersion is exclusive, set to `math.MaxInt64` to cover the latest version. func (ndb *nodeDB) traverseStateChanges(startVersion, endVersion int64, fn func(version int64, changeSet *ChangeSet) error) error { - predecessor, err := ndb.getPreviousVersion(startVersion) + firstVersion, err := ndb.getFirstVersion() + if err != nil { + return err + } + if startVersion < firstVersion { + startVersion = firstVersion + } + latestVersion, err := ndb.getLatestVersion() if err != nil { return err } - prevRoot, err := ndb.getRoot(predecessor) + if endVersion > latestVersion { + endVersion = latestVersion + } + + prevVersion := startVersion - 1 + prevRoot, err := ndb.GetRoot(prevVersion) if err != nil { return err } - return ndb.traverseRange(rootKeyFormat.Key(startVersion), rootKeyFormat.Key(endVersion), func(k, hash []byte) error { - var version int64 - rootKeyFormat.Scan(k, &version) + + for version := startVersion; version <= endVersion; version++ { + root, err := ndb.GetRoot(version) + if err != nil { + return err + } var changeSet ChangeSet receiveKVPair := func(pair *KVPair) error { @@ -968,18 +841,18 @@ func (ndb *nodeDB) traverseStateChanges(startVersion, endVersion int64, fn func( return nil } - if err := ndb.extractStateChanges(predecessor, prevRoot, hash, receiveKVPair); err != nil { + if err := ndb.extractStateChanges(prevVersion, prevRoot, root, receiveKVPair); err != nil { return err } if err := fn(version, &changeSet); err != nil { return err } + prevVersion = version + prevRoot = root + } - predecessor = version - prevRoot = hash - return nil - }) + return nil } func (ndb *nodeDB) String() (string, error) { @@ -989,7 +862,7 @@ func (ndb *nodeDB) String() (string, error) { index := 0 - err := ndb.traversePrefix(rootKeyFormat.Key(), func(key, value []byte) error { + err := ndb.traversePrefix(nodeKeyFormat.Key(), func(key, value []byte) error { fmt.Fprintf(buf, "%s: %x\n", key, value) return nil }) @@ -999,18 +872,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 @@ -1024,7 +895,7 @@ func (ndb *nodeDB) String() (string, error) { } var ( - ErrNodeMissingHash = fmt.Errorf("node does not have a hash") + ErrNodeMissingNodeKey = fmt.Errorf("node does not have a nodeKey") ErrNodeAlreadyPersisted = fmt.Errorf("shouldn't be calling save on an already persisted node") - ErrRootMissingHash = fmt.Errorf("root hash must not be empty") + ErrRootMissingNodeKey = fmt.Errorf("root node key must not be zero") ) diff --git a/nodedb_test.go b/nodedb_test.go index c65078188..ba33c2a79 100644 --- a/nodedb_test.go +++ b/nodedb_test.go @@ -1,9 +1,7 @@ package iavl import ( - "encoding/binary" "errors" - "math/rand" "strconv" "testing" @@ -16,9 +14,11 @@ import ( func BenchmarkNodeKey(b *testing.B) { ndb := &nodeDB{} - hashes := makeHashes(b, 2432325) for i := 0; i < b.N; i++ { - ndb.nodeKey(hashes[i]) + ndb.nodeKey(&NodeKey{ + version: int64(i), + nonce: int32(i), + }) } } @@ -108,7 +108,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(nodeKeyFormat.Key(expectedFastCacheVersion)).Times(1) rIterMock.EXPECT().Close().Return(nil).Times(1) dbMock.EXPECT().ReverseIterator(gomock.Any(), gomock.Any()).Return(rIterMock, nil).Times(1) @@ -343,22 +343,6 @@ func TestNodeDB_traverseOrphans(t *testing.T) { assertOrphansAndBranches(t, tree.ndb, 4, 8, [][]byte{{byte(9)}, {byte(10)}, {byte(12)}}) } -func makeHashes(b *testing.B, seed int64) [][]byte { - b.StopTimer() - rnd := rand.NewSource(seed) - hashes := make([][]byte, b.N) - hashBytes := 8 * ((hashSize + 7) / 8) - for i := 0; i < b.N; i++ { - hashes[i] = make([]byte, hashBytes) - for b := 0; b < hashBytes; b += 8 { - binary.BigEndian.PutUint64(hashes[i][b:b+8], uint64(rnd.Int63())) - } - hashes[i] = hashes[i][:hashSize] - } - b.StartTimer() - return hashes -} - func makeAndPopulateMutableTree(tb testing.TB) *MutableTree { memDB := db.NewMemDB() tree, err := NewMutableTreeWithOpts(memDB, 0, &Options{InitialVersion: 9}, false) 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 19a30a8b0..acbd1f781 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 1486e33f7..257a34bbd 100644 --- a/testutils_test.go +++ b/testutils_test.go @@ -51,12 +51,12 @@ func N(l, r interface{}) *Node { if _, ok := l.(*Node); ok { left = l.(*Node) } else { - left = NewNode(i2b(l.(int)), nil, 0) + left = NewNode(i2b(l.(int)), nil) } if _, ok := r.(*Node); ok { right = r.(*Node) } else { - right = NewNode(i2b(r.(int)), nil, 0) + right = NewNode(i2b(r.(int)), nil) } n := &Node{ @@ -73,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 } @@ -82,11 +82,13 @@ func T(n *Node) (*MutableTree, error) { } // Convenience for simple printing of keys & tree structure -func P(n *Node) string { +func P(n *Node, t *ImmutableTree) string { if n.subtreeHeight == 0 { return fmt.Sprintf("%v", b2i(n.key)) } - return fmt.Sprintf("(%v %v)", P(n.leftNode), P(n.rightNode)) + leftNode, _ := n.getLeftNode(t) + rightNode, _ := n.getRightNode(t) + return fmt.Sprintf("(%v %v)", P(leftNode, t), P(rightNode, t)) } type traverser struct { diff --git a/tree_dotgraph.go b/tree_dotgraph.go index 43eaeb940..091f283e8 100644 --- a/tree_dotgraph.go +++ b/tree_dotgraph.go @@ -50,7 +50,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{}, @@ -63,7 +63,7 @@ func WriteDOTGraph(w io.Writer, tree *ImmutableTree, paths []PathToLeaf) { graphNode.Label = mkLabel(ibytes.UnsafeBytesToStr(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(ibytes.UnsafeBytesToStr(node.value), 10, "sans-serif") @@ -132,9 +132,9 @@ func WriteDotGraphv2(w io.Writer, tree *ImmutableTree) { traverse = func(node *Node, parent *dot.Node, direction string) { var label string if node.isLeaf() { - label = fmt.Sprintf("%v:%v\nv%v", node.key, node.value, node.version) + label = fmt.Sprintf("%v:%v\nv%v", node.key, node.value, node.nodeKey.version) } else { - label = fmt.Sprintf("%v:%v\nv%v", node.subtreeHeight, node.key, node.version) + label = fmt.Sprintf("%v:%v\nv%v", node.subtreeHeight, node.key, node.nodeKey.version) } n := graph.Node(label) @@ -146,7 +146,7 @@ func WriteDotGraphv2(w io.Writer, tree *ImmutableTree) { if node.leftNode != nil { leftNode = node.leftNode - } else if node.leftHash != nil { + } else if node.leftNodeKey != nil { in, err := node.getLeftNode(tree) if err == nil { leftNode = in @@ -155,7 +155,7 @@ func WriteDotGraphv2(w io.Writer, tree *ImmutableTree) { if node.rightNode != nil { rightNode = node.rightNode - } else if node.rightHash != nil { + } else if node.rightNodeKey != nil { in, err := node.getRightNode(tree) if err == nil { rightNode = in diff --git a/tree_random_test.go b/tree_random_test.go index da3a53510..3a2503607 100644 --- a/tree_random_test.go +++ b/tree_random_test.go @@ -277,9 +277,7 @@ func assertEmptyDatabase(t *testing.T, tree *MutableTree) { firstKey := foundKeys[0] secondKey := foundKeys[1] - require.True(t, strings.HasPrefix(firstKey, metadataKeyFormat.Prefix())) - require.True(t, strings.HasPrefix(secondKey, rootKeyFormat.Prefix())) require.Equal(t, string(metadataKeyFormat.KeyBytes([]byte(storageVersionKey))), firstKey, "Unexpected storage version key") @@ -290,7 +288,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) + nodeKeyFormat.Scan([]byte(secondKey), &foundVersion) require.Equal(t, version, foundVersion, "Unexpected root version") } @@ -324,6 +322,9 @@ func assertMirror(t *testing.T, tree *MutableTree, mirror map[string]string, ver // mirror and check with get. This is to exercise both the iteration and Get() code paths. iterated := 0 _, err = itree.Iterate(func(key, value []byte) bool { + if string(value) != mirror[string(key)] { + fmt.Println("missing ", string(key), " ", string(value)) + } require.Equal(t, string(value), mirror[string(key)], "Invalid value for key %q", key) iterated++ return false diff --git a/tree_test.go b/tree_test.go index ece1479e5..f619a31b2 100644 --- a/tree_test.go +++ b/tree_test.go @@ -67,9 +67,6 @@ func TestVersionedRandomTree(t *testing.T) { } tree.SaveVersion() } - // roots, err := tree.ndb.getRoots() - // require.NoError(err) - // require.Equal(versions, len(roots), "wrong number of roots") leafNodes, err := tree.ndb.leafNodes() require.Nil(err) @@ -87,9 +84,7 @@ func TestVersionedRandomTree(t *testing.T) { assert.Equal(t, 1, available[0]) assert.Equal(t, versions, available[len(available)-1]) - for i := 1; i < versions; i++ { - tree.DeleteVersionsTo(int64(i)) - } + tree.DeleteVersionsTo(int64(versions - 1)) // require.Len(tree.versions, 1, "tree must have one version left") tr, err := tree.GetImmutable(int64(versions)) @@ -365,7 +360,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. tree, err = NewMutableTree(d, 0, false) @@ -826,12 +820,9 @@ func TestVersionedTreeSaveAndLoad(t *testing.T) { ntree.Set([]byte("T"), []byte("MhkWjkVy")) ntree.SaveVersion() - ntree.DeleteVersionsTo(6) - ntree.DeleteVersionsTo(5) ntree.DeleteVersionsTo(1) - ntree.DeleteVersionsTo(2) ntree.DeleteVersionsTo(4) - ntree.DeleteVersionsTo(3) + ntree.DeleteVersionsTo(6) require.False(ntree.IsEmpty()) require.Equal(int64(4), ntree.Size()) @@ -1328,7 +1319,7 @@ func TestLoadVersion(t *testing.T) { require.NoError(t, err, "SaveVersion should not fail") } - // require the ability to lazy load the latest version + // require the ability to load the latest version version, err = tree.LoadVersion(int64(maxVersions)) require.NoError(t, err, "unexpected error when lazy loading version") require.Equal(t, version, int64(maxVersions)) @@ -1337,18 +1328,18 @@ func TestLoadVersion(t *testing.T) { require.NoError(t, err) require.Equal(t, value, []byte(fmt.Sprintf("value_%d", maxVersions)), "unexpected value") - // require the ability to lazy load an older version + // require the ability to load an older version version, err = tree.LoadVersion(int64(maxVersions - 1)) - require.NoError(t, err, "unexpected error when lazy loading version") + require.NoError(t, err, "unexpected error when loading version") require.Equal(t, version, int64(maxVersions)) value, err = tree.Get([]byte(fmt.Sprintf("key_%d", maxVersions-1))) require.NoError(t, err) require.Equal(t, value, []byte(fmt.Sprintf("value_%d", maxVersions-1)), "unexpected value") - // require the inability to lazy load a non-valid version + // require the inability to load a non-valid version version, err = tree.LoadVersion(int64(maxVersions + 1)) - require.Error(t, err, "expected error when lazy loading version") + require.Error(t, err, "expected error when loading version") require.Equal(t, version, int64(maxVersions)) } @@ -1564,7 +1555,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) } } @@ -1579,7 +1570,7 @@ func TestLoadVersionForOverwritingCase2(t *testing.T) { } for _, n := range removedNodes { - has, _ := tree.ndb.Has(n.hash) + has, _ := tree.ndb.Has(n.nodeKey) require.False(has, "LoadVersionForOverwriting should remove useless nodes") } @@ -1620,7 +1611,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) } } @@ -1634,7 +1625,7 @@ func TestLoadVersionForOverwritingCase3(t *testing.T) { err = tree.LoadVersionForOverwriting(1) require.NoError(err) for _, n := range removedNodes { - has, err := tree.ndb.Has(n.hash) + has, err := tree.ndb.Has(n.nodeKey) require.NoError(err) require.False(has, "LoadVersionForOverwriting should remove useless nodes") } @@ -1840,8 +1831,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 dd6164fb8..f9f08916f 100644 --- a/util.go +++ b/util.go @@ -24,15 +24,15 @@ func printNode(ndb *nodeDB, node *Node, indent int) error { } if node.rightNode != nil { printNode(ndb, node.rightNode, indent+1) //nolint:errcheck - } else if node.rightHash != nil { - rightNode, err := ndb.GetNode(node.rightHash) + } else if node.rightNodeKey != nil { + rightNode, err := ndb.GetNode(node.rightNodeKey) if err != nil { return err } printNode(ndb, rightNode, indent+1) //nolint:errcheck } - hash, err := node._hash() + hash, err := node._hash(node.nodeKey.version) if err != nil { return err } @@ -47,8 +47,8 @@ func printNode(ndb *nodeDB, node *Node, indent int) error { if err != nil { return err } - } else if node.leftHash != nil { - leftNode, err := ndb.GetNode(node.leftHash) + } else if node.leftNodeKey != nil { + leftNode, err := ndb.GetNode(node.leftNodeKey) if err != nil { return err }