From 9398380724ef257bcb08d41053a71971cee489dc Mon Sep 17 00:00:00 2001 From: Cong Zhao Date: Thu, 31 Jan 2019 11:42:56 +0800 Subject: [PATCH 1/6] Squashed commit of the following: commit 628785d8f35822f84ca710eb73e6358b590d74db Author: Cong Zhao Date: Fri Jan 25 21:07:05 2019 +0800 second iteration: directly write node into db before reconstruct tree in memory commit 27a6faeaa4d765e578fe73a701fd910ab7c732ca Author: Cong Zhao Date: Sat Dec 29 19:19:12 2018 +0800 save tree at specific version for syncing state --- immutable_tree.go | 37 ++++++++++++++++++++++++++++++++++++- mutable_tree.go | 11 ++++++++++- node.go | 45 +++++++++++++++++++++++++++++++++++++++++++-- nodedb.go | 8 ++++---- tree_dotgraph.go | 4 ++-- 5 files changed, 95 insertions(+), 10 deletions(-) diff --git a/immutable_tree.go b/immutable_tree.go index 5abc918..aaa5030 100644 --- a/immutable_tree.go +++ b/immutable_tree.go @@ -1,6 +1,7 @@ package iavl import ( + "bytes" "fmt" "strings" @@ -24,7 +25,7 @@ func NewImmutableTree(db dbm.DB, cacheSize int) *ImmutableTree { } return &ImmutableTree{ // NodeDB-backed Tree. - ndb: newNodeDB(db, cacheSize), + ndb: NewNodeDB(db, cacheSize), } } @@ -51,6 +52,12 @@ func (t *ImmutableTree) Version() int64 { return t.version } +// TODO: revisit +// Set version for state reactor +func (t *ImmutableTree) SetVersion(ver int64) { + t.version = ver +} + // Height returns the height of the tree. func (t *ImmutableTree) Height() int8 { if t.root == nil { @@ -114,6 +121,34 @@ func (t *ImmutableTree) Iterate(fn func(key []byte, value []byte) bool) (stopped }) } +// used by state syncing +func (t *ImmutableTree) IterateFirst(fn func(nodeBytes []byte)) { + if t.root == nil { + return + } + t.root.traverseFirst(t, true, func(node *Node) bool { + var b bytes.Buffer + if err := node.writeBytes(&b); err != nil { + panic(err) + } + fn(b.Bytes()) + return false + }) +} + +// used by state syncing, assuming IterateFirst has alread loaded nodes from db +func (t *ImmutableTree) IterateMiddle(fn func(key []byte, value []byte, version int64)) { + if t.root != nil { + t.root.traverseMiddle(func(node *Node) { + fn(node.key, node.value, node.version) + }) + } +} + +func (t *ImmutableTree) RecoverFromRemoteNodes(rootFirst []*Node, rootMiddleKeys [][]byte) { + t.root = recoverFromRemoteNodes(rootFirst, rootMiddleKeys, 0, len(rootFirst), 0, len(rootMiddleKeys)) +} + // IterateRange makes a callback for all nodes with key between start and end non-inclusive. // If either are nil, then it is open on that side (nil, nil is the same as Iterate) func (t *ImmutableTree) IterateRange(start, end []byte, ascending bool, fn func(key []byte, value []byte) bool) (stopped bool) { diff --git a/mutable_tree.go b/mutable_tree.go index a753502..eb726a0 100644 --- a/mutable_tree.go +++ b/mutable_tree.go @@ -22,7 +22,7 @@ type MutableTree struct { // NewMutableTree returns a new tree with the specified cache size and datastore. func NewMutableTree(db dbm.DB, cacheSize int) *MutableTree { - ndb := newNodeDB(db, cacheSize) + ndb := NewNodeDB(db, cacheSize) head := &ImmutableTree{ndb: ndb} return &MutableTree{ @@ -321,7 +321,15 @@ func (tree *MutableTree) GetVersioned(key []byte, version int64) ( // the tree. Returns the hash and new version number. func (tree *MutableTree) SaveVersion() ([]byte, int64, error) { version := tree.version + 1 + return tree.saveVersionImpl(version) +} + +// directly save tree at specific version (for initial state sync usage) +func (tree *MutableTree) SaveVersionAt(version int64) ([]byte, int64, error) { + return tree.saveVersionImpl(version) +} +func (tree *MutableTree) saveVersionImpl(version int64) ([]byte, int64, error) { if tree.versions[version] { //version already exists, throw an error if attempting to overwrite // Same hash means idempotent. Return success. @@ -361,6 +369,7 @@ func (tree *MutableTree) SaveVersion() ([]byte, int64, error) { tree.orphans = map[string]int64{} return tree.Hash(), version, nil + } // DeleteVersion deletes a tree version from disk. The version can then no diff --git a/node.go b/node.go index 863751b..7fe573a 100644 --- a/node.go +++ b/node.go @@ -192,6 +192,10 @@ func (node *Node) getByIndex(t *ImmutableTree, index int64) (key []byte, value [ return node.getRightNode(t).getByIndex(t, index-leftNode.size) } +func (node *Node) Hash() []byte { + return node._hash() +} + // 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 { @@ -345,14 +349,16 @@ func (node *Node) getLeftNode(t *ImmutableTree) *Node { if node.leftNode != nil { return node.leftNode } - return t.ndb.GetNode(node.leftHash) + node.leftNode = t.ndb.GetNode(node.leftHash) + return node.leftNode } func (node *Node) getRightNode(t *ImmutableTree) *Node { if node.rightNode != nil { return node.rightNode } - return t.ndb.GetNode(node.rightHash) + node.rightNode = t.ndb.GetNode(node.rightHash) + return node.rightNode } // NOTE: mutates height and size @@ -372,6 +378,41 @@ func (node *Node) traverse(t *ImmutableTree, ascending bool, cb func(*Node) bool }) } +// traverseFirst is a wrapper over traverseInRange when we want the whole tree and will traverse the leaf nodes +func (node *Node) traverseFirst(t *ImmutableTree, ascending bool, cb func(*Node) bool) bool { + return node.traverseInRange(t, nil, nil, ascending, false, 0, func(node *Node, depth uint8) bool { + return cb(node) + }) +} + +// only used by IterateMiddle with same pre-requirement must be met +func (node *Node) traverseMiddle(cb func(*Node)) { + if node.leftNode != nil { + node.leftNode.traverseMiddle(cb) + } + cb(node) + if node.rightNode != nil { + node.rightNode.traverseMiddle(cb) + } +} + +func recoverFromRemoteNodes(preOrder []*Node, inOrder [][]byte, prebeg, preend, inbeg, inend int) *Node { + if prebeg < preend && inbeg < inend { + rev := preOrder[prebeg] + var mid int + for mid = inbeg; mid < inend; mid++ { + if bytes.Compare(rev.key, inOrder[mid]) == 0 { + break + } + } + span := mid - inbeg + rev.leftNode = recoverFromRemoteNodes(preOrder, inOrder, prebeg+1, prebeg+1+span, inbeg, inbeg+span) + rev.rightNode = recoverFromRemoteNodes(preOrder, inOrder, prebeg+1+span, preend, inbeg+span+1, inend) + return rev + } + return nil +} + func (node *Node) traverseWithDepth(t *ImmutableTree, ascending bool, cb func(*Node, uint8) bool) bool { return node.traverseInRange(t, nil, nil, ascending, false, 0, cb) } diff --git a/nodedb.go b/nodedb.go index c41a1c0..35a6e71 100644 --- a/nodedb.go +++ b/nodedb.go @@ -43,7 +43,7 @@ type nodeDB struct { nodeCacheQueue *list.List // LRU queue of cache elements. Used for deletion. } -func newNodeDB(db dbm.DB, cacheSize int) *nodeDB { +func NewNodeDB(db dbm.DB, cacheSize int) *nodeDB { ndb := &nodeDB{ db: db, batch: db.NewBatch(), @@ -366,9 +366,9 @@ func (ndb *nodeDB) saveRoot(hash []byte, version int64) error { ndb.mtx.Lock() defer ndb.mtx.Unlock() - if version != ndb.getLatestVersion()+1 { - return fmt.Errorf("Must save consecutive versions. Expected %d, got %d", ndb.getLatestVersion()+1, version) - } + //if version != ndb.getLatestVersion()+1 { + // return fmt.Errorf("Must save consecutive versions. Expected %d, got %d", ndb.getLatestVersion()+1, version) + //} key := ndb.rootKey(version) ndb.batch.Set(key, hash) diff --git a/tree_dotgraph.go b/tree_dotgraph.go index c6f5037..e751d57 100644 --- a/tree_dotgraph.go +++ b/tree_dotgraph.go @@ -55,12 +55,12 @@ func WriteDOTGraph(w io.Writer, tree *ImmutableTree, paths []PathToLeaf) { } shortHash := graphNode.Hash[:7] - graphNode.Label = mkLabel(fmt.Sprintf("%s", node.key), 16, "sans-serif") + graphNode.Label = mkLabel(fmt.Sprintf("%x", node.key), 16, "sans-serif") graphNode.Label += mkLabel(shortHash, 10, "monospace") graphNode.Label += mkLabel(fmt.Sprintf("version=%d", node.version), 10, "monospace") if node.value != nil { - graphNode.Label += mkLabel(string(node.value), 10, "sans-serif") + graphNode.Label += mkLabel(fmt.Sprintf("%x", node.value), 10, "sans-serif") } if node.height == 0 { From 682e8f0513523505a790ebc26c06d491149799d6 Mon Sep 17 00:00:00 2001 From: rickyyangz Date: Wed, 16 Jan 2019 10:09:32 +0800 Subject: [PATCH 2/6] export some util functions for immutable_tree and node --- immutable_tree.go | 4 ++++ node.go | 7 +++++++ 2 files changed, 11 insertions(+) diff --git a/immutable_tree.go b/immutable_tree.go index 5abc918..3b3efb0 100644 --- a/immutable_tree.go +++ b/immutable_tree.go @@ -28,6 +28,10 @@ func NewImmutableTree(db dbm.DB, cacheSize int) *ImmutableTree { } } +func GetRoot(t *ImmutableTree) *Node { + return t.root +} + // String returns a string representation of Tree. func (t *ImmutableTree) String() string { leaves := []string{} diff --git a/node.go b/node.go index 863751b..ae9947c 100644 --- a/node.go +++ b/node.go @@ -135,6 +135,10 @@ func (node *Node) clone(version int64) *Node { } } +func Key(node *Node) []byte { return node.key } +func Value(node *Node) []byte { return node.value } + +func IsLeaf(node *Node) bool { return node.isLeaf() } func (node *Node) isLeaf() bool { return node.height == 0 } @@ -194,6 +198,7 @@ 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 Hash(node *Node) []byte { return node._hash() } func (node *Node) _hash() []byte { if node.hash != nil { return node.hash @@ -341,6 +346,7 @@ func (node *Node) writeBytes(w io.Writer) cmn.Error { return nil } +func GetLeftNode(node *Node, t *ImmutableTree) *Node { return node.getLeftNode(t) } func (node *Node) getLeftNode(t *ImmutableTree) *Node { if node.leftNode != nil { return node.leftNode @@ -348,6 +354,7 @@ func (node *Node) getLeftNode(t *ImmutableTree) *Node { return t.ndb.GetNode(node.leftHash) } +func GetRightNode(node *Node, t *ImmutableTree) *Node { return node.getRightNode(t) } func (node *Node) getRightNode(t *ImmutableTree) *Node { if node.rightNode != nil { return node.rightNode From 68cef5fc9bfc05ced4b14f033d8abb2bf1272488 Mon Sep 17 00:00:00 2001 From: Cong Zhao Date: Thu, 21 Feb 2019 11:45:03 +0800 Subject: [PATCH 3/6] expose versions field to support pruning --- mutable_tree.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/mutable_tree.go b/mutable_tree.go index eb726a0..0eec23a 100644 --- a/mutable_tree.go +++ b/mutable_tree.go @@ -270,7 +270,7 @@ func (tree *MutableTree) LoadVersionForOverwriting(targetVersion int64) (int64, if err != nil { return latestVersion, err } - tree.deleteVersionsFrom(targetVersion+1) + tree.deleteVersionsFrom(targetVersion + 1) return targetVersion, nil } @@ -510,3 +510,7 @@ func (tree *MutableTree) addOrphans(orphans []*Node) { tree.orphans[string(node.hash)] = node.version } } + +func (tree *MutableTree) GetVersions() map[int64]bool { + return tree.versions +} From 67e42af9743b5678ee7bf7712f617fff42bf4856 Mon Sep 17 00:00:00 2001 From: Cong Zhao Date: Thu, 28 Feb 2019 10:13:32 +0800 Subject: [PATCH 4/6] clean unused code --- immutable_tree.go | 19 ------------------- node.go | 28 ---------------------------- 2 files changed, 47 deletions(-) diff --git a/immutable_tree.go b/immutable_tree.go index aaa5030..d80f04b 100644 --- a/immutable_tree.go +++ b/immutable_tree.go @@ -52,12 +52,6 @@ func (t *ImmutableTree) Version() int64 { return t.version } -// TODO: revisit -// Set version for state reactor -func (t *ImmutableTree) SetVersion(ver int64) { - t.version = ver -} - // Height returns the height of the tree. func (t *ImmutableTree) Height() int8 { if t.root == nil { @@ -136,19 +130,6 @@ func (t *ImmutableTree) IterateFirst(fn func(nodeBytes []byte)) { }) } -// used by state syncing, assuming IterateFirst has alread loaded nodes from db -func (t *ImmutableTree) IterateMiddle(fn func(key []byte, value []byte, version int64)) { - if t.root != nil { - t.root.traverseMiddle(func(node *Node) { - fn(node.key, node.value, node.version) - }) - } -} - -func (t *ImmutableTree) RecoverFromRemoteNodes(rootFirst []*Node, rootMiddleKeys [][]byte) { - t.root = recoverFromRemoteNodes(rootFirst, rootMiddleKeys, 0, len(rootFirst), 0, len(rootMiddleKeys)) -} - // IterateRange makes a callback for all nodes with key between start and end non-inclusive. // If either are nil, then it is open on that side (nil, nil is the same as Iterate) func (t *ImmutableTree) IterateRange(start, end []byte, ascending bool, fn func(key []byte, value []byte) bool) (stopped bool) { diff --git a/node.go b/node.go index 7fe573a..bf9979f 100644 --- a/node.go +++ b/node.go @@ -385,34 +385,6 @@ func (node *Node) traverseFirst(t *ImmutableTree, ascending bool, cb func(*Node) }) } -// only used by IterateMiddle with same pre-requirement must be met -func (node *Node) traverseMiddle(cb func(*Node)) { - if node.leftNode != nil { - node.leftNode.traverseMiddle(cb) - } - cb(node) - if node.rightNode != nil { - node.rightNode.traverseMiddle(cb) - } -} - -func recoverFromRemoteNodes(preOrder []*Node, inOrder [][]byte, prebeg, preend, inbeg, inend int) *Node { - if prebeg < preend && inbeg < inend { - rev := preOrder[prebeg] - var mid int - for mid = inbeg; mid < inend; mid++ { - if bytes.Compare(rev.key, inOrder[mid]) == 0 { - break - } - } - span := mid - inbeg - rev.leftNode = recoverFromRemoteNodes(preOrder, inOrder, prebeg+1, prebeg+1+span, inbeg, inbeg+span) - rev.rightNode = recoverFromRemoteNodes(preOrder, inOrder, prebeg+1+span, preend, inbeg+span+1, inend) - return rev - } - return nil -} - func (node *Node) traverseWithDepth(t *ImmutableTree, ascending bool, cb func(*Node, uint8) bool) bool { return node.traverseInRange(t, nil, nil, ascending, false, 0, cb) } From 7811a3f0ad5d6d9b6eeed0dc62f1e7626ad88d2b Mon Sep 17 00:00:00 2001 From: Cong Zhao Date: Fri, 1 Mar 2019 14:07:07 +0800 Subject: [PATCH 5/6] refactor according to review comments --- mutable_tree.go | 13 ++----------- nodedb.go | 16 ++++++++-------- tree_test.go | 1 + 3 files changed, 11 insertions(+), 19 deletions(-) diff --git a/mutable_tree.go b/mutable_tree.go index 0eec23a..50dd688 100644 --- a/mutable_tree.go +++ b/mutable_tree.go @@ -321,15 +321,7 @@ func (tree *MutableTree) GetVersioned(key []byte, version int64) ( // the tree. Returns the hash and new version number. func (tree *MutableTree) SaveVersion() ([]byte, int64, error) { version := tree.version + 1 - return tree.saveVersionImpl(version) -} - -// directly save tree at specific version (for initial state sync usage) -func (tree *MutableTree) SaveVersionAt(version int64) ([]byte, int64, error) { - return tree.saveVersionImpl(version) -} -func (tree *MutableTree) saveVersionImpl(version int64) ([]byte, int64, error) { if tree.versions[version] { //version already exists, throw an error if attempting to overwrite // Same hash means idempotent. Return success. @@ -351,13 +343,13 @@ func (tree *MutableTree) saveVersionImpl(version int64) ([]byte, int64, error) { // removed. debug("SAVE EMPTY TREE %v\n", version) tree.ndb.SaveOrphans(version, tree.orphans) - tree.ndb.SaveEmptyRoot(version) + tree.ndb.SaveEmptyRoot(version, false) } else { debug("SAVE TREE %v\n", version) // Save the current tree. tree.ndb.SaveBranch(tree.root) tree.ndb.SaveOrphans(version, tree.orphans) - tree.ndb.SaveRoot(tree.root, version) + tree.ndb.SaveRoot(tree.root, version, false) } tree.ndb.Commit() tree.version = version @@ -369,7 +361,6 @@ func (tree *MutableTree) saveVersionImpl(version int64) ([]byte, int64, error) { tree.orphans = map[string]int64{} return tree.Hash(), version, nil - } // DeleteVersion deletes a tree version from disk. The version can then no diff --git a/nodedb.go b/nodedb.go index 35a6e71..0aafb43 100644 --- a/nodedb.go +++ b/nodedb.go @@ -350,25 +350,25 @@ func (ndb *nodeDB) getRoots() (map[int64][]byte, error) { // 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 { +func (ndb *nodeDB) SaveRoot(root *Node, version int64, isFirstVersion bool) error { if len(root.hash) == 0 { panic("Hash should not be empty") } - return ndb.saveRoot(root.hash, version) + return ndb.saveRoot(root.hash, version, isFirstVersion) } // 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) SaveEmptyRoot(version int64, isFirstVersion bool) error { + return ndb.saveRoot([]byte{}, version, isFirstVersion) } -func (ndb *nodeDB) saveRoot(hash []byte, version int64) error { +func (ndb *nodeDB) saveRoot(hash []byte, version int64, isFirstVersion bool) error { ndb.mtx.Lock() defer ndb.mtx.Unlock() - //if version != ndb.getLatestVersion()+1 { - // return fmt.Errorf("Must save consecutive versions. Expected %d, got %d", ndb.getLatestVersion()+1, version) - //} + if !isFirstVersion && version != ndb.getLatestVersion()+1 { + return fmt.Errorf("Must save consecutive versions. Expected %d, got %d", ndb.getLatestVersion()+1, version) + } key := ndb.rootKey(version) ndb.batch.Set(key, hash) diff --git a/tree_test.go b/tree_test.go index 5a1b6d1..e13c575 100644 --- a/tree_test.go +++ b/tree_test.go @@ -74,6 +74,7 @@ func TestVersionedRandomTree(t *testing.T) { require.Len(tree.versions, 1, "tree must have one version left") tr, err := tree.GetImmutable(int64(versions)) + tr.nodeSize() // deliberately link left/right nodes with parents require.NoError(err, "GetImmutable should not error for version %d", versions) require.Equal(tr.root, tree.root) From 1e35b68c4aa480fcbc6297e675794266d8eb5200 Mon Sep 17 00:00:00 2001 From: Cong Zhao Date: Mon, 4 Mar 2019 19:16:16 +0800 Subject: [PATCH 6/6] resolve release conflict --- node.go | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/node.go b/node.go index 22e9bc3..2dc6e63 100644 --- a/node.go +++ b/node.go @@ -135,7 +135,7 @@ func (node *Node) clone(version int64) *Node { } } -func Key(node *Node) []byte { return node.key } +func Key(node *Node) []byte { return node.key } func Value(node *Node) []byte { return node.value } func IsLeaf(node *Node) bool { return node.isLeaf() } @@ -196,10 +196,6 @@ func (node *Node) getByIndex(t *ImmutableTree, index int64) (key []byte, value [ return node.getRightNode(t).getByIndex(t, index-leftNode.size) } -func (node *Node) Hash() []byte { - return node._hash() -} - // Computes the hash of the node without computing its descendants. Must be // called on nodes which have descendant node hashes already computed. func Hash(node *Node) []byte { return node._hash() }