diff --git a/Makefile b/Makefile index b3813eeeb..c19a5352a 100644 --- a/Makefile +++ b/Makefile @@ -55,8 +55,8 @@ lint-fix: # bench is the basic tests that shouldn't crash an aws instance bench: cd benchmarks && \ - go test $(LDFLAGS) -tags cleveldb,rocksdb,pebbledb -run=NOTEST -bench=Small . && \ - go test $(LDFLAGS) -tags cleveldb,rocksdb,pebbledb -run=NOTEST -bench=Medium . && \ + go test $(LDFLAGS) -tags pebbledb -run=NOTEST -bench=Small . && \ + go test $(LDFLAGS) -tags pebbledb -run=NOTEST -bench=Medium . && \ go test $(LDFLAGS) -run=NOTEST -bench=RandomBytes . .PHONY: bench @@ -64,9 +64,9 @@ bench: fullbench: cd benchmarks && \ go test $(LDFLAGS) -run=NOTEST -bench=RandomBytes . && \ - go test $(LDFLAGS) -tags cleveldb,rocksdb,pebbledb -run=NOTEST -bench=Small . && \ - go test $(LDFLAGS) -tags cleveldb,rocksdb,pebbledb -run=NOTEST -bench=Medium . && \ - go test $(LDFLAGS) -tags cleveldb,rocksdb,pebbledb -run=NOTEST -timeout=30m -bench=Large . && \ + go test $(LDFLAGS) -tags pebbledb -run=NOTEST -bench=Small . && \ + go test $(LDFLAGS) -tags pebbledb -run=NOTEST -bench=Medium . && \ + go test $(LDFLAGS) -tags pebbledb -run=NOTEST -timeout=30m -bench=Large . && \ go test $(LDFLAGS) -run=NOTEST -bench=Mem . && \ go test $(LDFLAGS) -run=NOTEST -timeout=60m -bench=LevelDB . .PHONY: fullbench diff --git a/basic_test.go b/basic_test.go index 8e5e9d0be..94d4baf73 100644 --- a/basic_test.go +++ b/basic_test.go @@ -2,7 +2,6 @@ package iavl import ( - "bytes" "encoding/hex" mrand "math/rand" "sort" @@ -171,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 + 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 + 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: diff --git a/benchmarks/bench_test.go b/benchmarks/bench_test.go index b445a07ab..dae07935f 100644 --- a/benchmarks/bench_test.go +++ b/benchmarks/bench_test.go @@ -308,7 +308,7 @@ func BenchmarkLevelDBLargeData(b *testing.B) { {"goleveldb", 50000, 100, 32, 100}, {"goleveldb", 50000, 100, 32, 1000}, {"goleveldb", 50000, 100, 32, 10000}, - {"goleveldb", 50000, 100, 32, 100000}, + // {"goleveldb", 50000, 100, 32, 100000}, } runBenchmarks(b, benchmarks) } diff --git a/export.go b/export.go index ae9f294f2..88ba22550 100644 --- a/export.go +++ b/export.go @@ -17,7 +17,7 @@ var ErrorExportDone = errors.New("export is complete") type ExportNode struct { Key []byte Value []byte - Version int64 + NodeKey *NodeKey Height int8 } @@ -53,7 +53,7 @@ func (e *Exporter) export(ctx context.Context) { exportNode := &ExportNode{ Key: node.key, Value: node.value, - Version: node.version, + NodeKey: node.nodeKey, Height: node.subtreeHeight, } diff --git a/export_test.go b/export_test.go index 4f380f787..c80e53364 100644 --- a/export_test.go +++ b/export_test.go @@ -148,15 +148,15 @@ func TestExporter(t *testing.T) { tree := setupExportTreeBasic(t) expect := []*ExportNode{ - {Key: []byte("a"), Value: []byte{1}, Version: 1, Height: 0}, - {Key: []byte("b"), Value: []byte{2}, Version: 3, Height: 0}, - {Key: []byte("b"), Value: nil, Version: 3, Height: 1}, - {Key: []byte("c"), Value: []byte{3}, Version: 3, Height: 0}, - {Key: []byte("c"), Value: nil, Version: 3, Height: 2}, - {Key: []byte("d"), Value: []byte{4}, Version: 2, Height: 0}, - {Key: []byte("e"), Value: []byte{5}, Version: 3, Height: 0}, - {Key: []byte("e"), Value: nil, Version: 3, Height: 1}, - {Key: []byte("d"), Value: nil, Version: 3, Height: 3}, + {Key: []byte("a"), Value: []byte{1}, NodeKey: &NodeKey{version: 1, nonce: 3}, Height: 0}, + {Key: []byte("b"), Value: []byte{2}, NodeKey: &NodeKey{version: 3, nonce: 4}, Height: 0}, + {Key: []byte("b"), Value: nil, NodeKey: &NodeKey{version: 3, nonce: 3}, Height: 1}, + {Key: []byte("c"), Value: []byte{3}, NodeKey: &NodeKey{version: 3, nonce: 5}, Height: 0}, + {Key: []byte("c"), Value: nil, NodeKey: &NodeKey{version: 3, nonce: 2}, Height: 2}, + {Key: []byte("d"), Value: []byte{4}, NodeKey: &NodeKey{version: 2, nonce: 5}, Height: 0}, + {Key: []byte("e"), Value: []byte{5}, NodeKey: &NodeKey{version: 3, nonce: 7}, Height: 0}, + {Key: []byte("e"), Value: nil, NodeKey: &NodeKey{version: 3, nonce: 6}, Height: 1}, + {Key: []byte("d"), Value: nil, NodeKey: &NodeKey{version: 3, nonce: 1}, Height: 3}, } actual := make([]*ExportNode, 0, len(expect)) diff --git a/go.mod b/go.mod index d0e5c6f8c..da27c7bdc 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,6 @@ go 1.18 require ( github.com/confio/ics23/go v0.7.0 github.com/cosmos/cosmos-db v0.0.0-20220822060143-23a8145386c0 - github.com/cosmos/gogoproto v1.4.2 github.com/golang/mock v1.6.0 github.com/golangci/golangci-lint v1.50.0 github.com/stretchr/testify v1.8.0 diff --git a/go.sum b/go.sum index 3d65999a5..39229689a 100644 --- a/go.sum +++ b/go.sum @@ -136,8 +136,6 @@ github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8Nz github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/cosmos/cosmos-db v0.0.0-20220822060143-23a8145386c0 h1:OMu+dCsWVVsHodR4ykMKEj0VtwkNL+xOtyv0vmCmZVQ= github.com/cosmos/cosmos-db v0.0.0-20220822060143-23a8145386c0/go.mod h1:n5af5ISKZ7tP0q9hP1TW6MnWh7GrVrNfCLZhg+22gzg= -github.com/cosmos/gogoproto v1.4.2 h1:UeGRcmFW41l0G0MiefWhkPEVEwvu78SZsHBvI78dAYw= -github.com/cosmos/gogoproto v1.4.2/go.mod h1:cLxOsn1ljAHSV527CHOtaIP91kK6cCrZETRBrkzItWU= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= diff --git a/immutable_tree.go b/immutable_tree.go index 81539c1af..cf0876045 100644 --- a/immutable_tree.go +++ b/immutable_tree.go @@ -17,7 +17,6 @@ type ImmutableTree struct { root *Node ndb *nodeDB version int64 - nonce int64 skipFastStorageUpgrade bool } @@ -132,12 +131,6 @@ func (t *ImmutableTree) Version() int64 { return t.version } -// IncreaseNonce increases the nonce by 1 and returns. -func (t *ImmutableTree) IncreaseNonce() int64 { - t.nonce++ - return t.nonce -} - // Height returns the height of the tree. func (t *ImmutableTree) Height() int8 { if t.root == nil { @@ -156,8 +149,7 @@ func (t *ImmutableTree) Has(key []byte) (bool, error) { // Hash returns the root hash. func (t *ImmutableTree) Hash() ([]byte, error) { - hash, _, err := t.root.hashWithCount() - return hash, err + return t.root.hashWithCount(t.version + 1) } // Export returns an iterator that exports tree nodes as ExportNodes. These nodes can be @@ -290,7 +282,7 @@ func (t *ImmutableTree) IterateRangeInclusive(start, end []byte, ascending bool, } return t.root.traverseInRange(t, start, end, ascending, true, false, func(node *Node) bool { if node.subtreeHeight == 0 { - return fn(node.key, node.value, node.version) + return fn(node.key, node.value, node.nodeKey.version) } return false }) @@ -323,7 +315,6 @@ func (t *ImmutableTree) clone() *ImmutableTree { root: t.root, ndb: t.ndb, version: t.version, - nonce: t.nonce, } } @@ -331,10 +322,5 @@ func (t *ImmutableTree) clone() *ImmutableTree { // //nolint:unused func (t *ImmutableTree) nodeSize() int { - size := 0 - t.root.traverse(t, true, func(n *Node) bool { - size++ - return false - }) - return size + return int(t.root.size*2 - 1) } diff --git a/import.go b/import.go index 790fe93f8..2d9fe1632 100644 --- a/import.go +++ b/import.go @@ -6,6 +6,7 @@ import ( "fmt" db "github.com/cosmos/cosmos-db" + "github.com/cosmos/iavl/internal/encoding" ) // maxBatchSize is the maximum size of the import batch before flushing it to the database @@ -72,15 +73,15 @@ func (i *Importer) Add(exportNode *ExportNode) error { if exportNode == nil { return errors.New("node cannot be nil") } - if exportNode.Version > i.version { + if exportNode.NodeKey.version > i.version { return fmt.Errorf("node version %v can't be greater than import version %v", - exportNode.Version, i.version) + exportNode.NodeKey.version, i.version) } node := &Node{ key: exportNode.Key, value: exportNode.Value, - version: exportNode.Version, + nodeKey: exportNode.NodeKey, subtreeHeight: exportNode.Height, } @@ -92,16 +93,12 @@ func (i *Importer) Add(exportNode *ExportNode) error { // We don't modify the stack until we've verified the built node, to avoid leaving the // importer in an inconsistent state when we return an error. stackSize := len(i.stack) - node.nodeKey = i.tree.IncreaseNonce() switch { case stackSize >= 2 && i.stack[stackSize-1].subtreeHeight < node.subtreeHeight && i.stack[stackSize-2].subtreeHeight < node.subtreeHeight: node.leftNode = i.stack[stackSize-2] - node.leftHash = node.leftNode.hash node.rightNode = i.stack[stackSize-1] - node.rightHash = node.rightNode.hash case stackSize >= 1 && i.stack[stackSize-1].subtreeHeight < node.subtreeHeight: node.leftNode = i.stack[stackSize-1] - node.leftHash = node.leftNode.hash } if node.subtreeHeight == 0 { @@ -116,7 +113,7 @@ func (i *Importer) Add(exportNode *ExportNode) error { node.rightNodeKey = node.rightNode.nodeKey } - _, err := node._hash() + _, err := node._hash(exportNode.NodeKey.version) if err != nil { return err } @@ -137,7 +134,7 @@ func (i *Importer) Add(exportNode *ExportNode) error { bytesCopy := make([]byte, buf.Len()) copy(bytesCopy, buf.Bytes()) - if err = i.batch.Set(i.tree.ndb.nodeKey(node.hash), bytesCopy); err != nil { + if err = i.batch.Set(i.tree.ndb.nodeKey(node.nodeKey), bytesCopy); err != nil { return err } @@ -154,9 +151,9 @@ func (i *Importer) Add(exportNode *ExportNode) error { // Update the stack now that we know there were no errors switch { - case node.leftHash != nil && node.rightHash != nil: + case node.leftNode != nil && node.rightNode != nil: i.stack = i.stack[:stackSize-2] - case node.leftHash != nil || node.rightHash != nil: + case node.leftNode != nil || node.rightNode != nil: i.stack = i.stack[:stackSize-1] } i.stack = append(i.stack, node) @@ -174,11 +171,15 @@ func (i *Importer) Commit() error { switch len(i.stack) { case 0: - if err := i.batch.Set(i.tree.ndb.rootKey(i.version), []byte{}); err != nil { + if err := i.batch.Set(i.tree.ndb.versionKey(i.version), []byte{0}); err != nil { return err } case 1: - if err := i.batch.Set(i.tree.ndb.rootKey(i.version), i.stack[0].hash); err != nil { + buf := new(bytes.Buffer) + if err := encoding.EncodeVarint(buf, i.stack[0].nodeKey.version); err != nil { + return err + } + if err := i.batch.Set(i.tree.ndb.versionKey(i.version), buf.Bytes()); err != nil { return err } default: diff --git a/import_test.go b/import_test.go index 39c9863a0..0b5eae33e 100644 --- a/import_test.go +++ b/import_test.go @@ -116,11 +116,11 @@ func TestImporter_Add(t *testing.T) { valid bool }{ "nil node": {nil, false}, - "valid": {&ExportNode{Key: k, Value: v, Version: 1, Height: 0}, true}, - "no key": {&ExportNode{Key: nil, Value: v, Version: 1, Height: 0}, false}, - "no value": {&ExportNode{Key: k, Value: nil, Version: 1, Height: 0}, false}, - "version too large": {&ExportNode{Key: k, Value: v, Version: 2, Height: 0}, false}, - "no version": {&ExportNode{Key: k, Value: v, Version: 0, Height: 0}, false}, + "valid": {&ExportNode{Key: k, Value: v, NodeKey: &NodeKey{version: 1, nonce: 1}, Height: 0}, true}, + "no key": {&ExportNode{Key: nil, Value: v, NodeKey: &NodeKey{version: 1, nonce: 2}, Height: 0}, false}, + "no value": {&ExportNode{Key: k, Value: nil, NodeKey: &NodeKey{version: 1, nonce: 3}, Height: 0}, false}, + "version too large": {&ExportNode{Key: k, Value: v, NodeKey: &NodeKey{version: 2, nonce: 1}, Height: 0}, false}, + "no version": {&ExportNode{Key: k, Value: v, NodeKey: &NodeKey{version: 0, nonce: 1}, Height: 0}, false}, // further cases will be handled by Node.validate() } for desc, tc := range testcases { @@ -149,7 +149,7 @@ func TestImporter_Add_Closed(t *testing.T) { require.NoError(t, err) importer.Close() - err = importer.Add(&ExportNode{Key: []byte("key"), Value: []byte("value"), Version: 1, Height: 0}) + err = importer.Add(&ExportNode{Key: []byte("key"), Value: []byte("value"), NodeKey: &NodeKey{version: 1, nonce: 1}, Height: 0}) require.Error(t, err) require.Equal(t, ErrNoImport, err) } @@ -160,7 +160,7 @@ func TestImporter_Close(t *testing.T) { importer, err := tree.Import(1) require.NoError(t, err) - err = importer.Add(&ExportNode{Key: []byte("key"), Value: []byte("value"), Version: 1, Height: 0}) + err = importer.Add(&ExportNode{Key: []byte("key"), Value: []byte("value"), NodeKey: &NodeKey{version: 1, nonce: 1}, Height: 0}) require.NoError(t, err) importer.Close() @@ -177,7 +177,7 @@ func TestImporter_Commit(t *testing.T) { importer, err := tree.Import(1) require.NoError(t, err) - err = importer.Add(&ExportNode{Key: []byte("key"), Value: []byte("value"), Version: 1, Height: 0}) + err = importer.Add(&ExportNode{Key: []byte("key"), Value: []byte("value"), NodeKey: &NodeKey{version: 1, nonce: 1}, Height: 0}) require.NoError(t, err) err = importer.Commit() @@ -193,7 +193,7 @@ func TestImporter_Commit_Closed(t *testing.T) { importer, err := tree.Import(1) require.NoError(t, err) - err = importer.Add(&ExportNode{Key: []byte("key"), Value: []byte("value"), Version: 1, Height: 0}) + err = importer.Add(&ExportNode{Key: []byte("key"), Value: []byte("value"), NodeKey: &NodeKey{version: 1, nonce: 1}, Height: 0}) require.NoError(t, err) importer.Close() diff --git a/keyformat/key_format.go b/keyformat/key_format.go index f6839f3d1..3e22db7b9 100644 --- a/keyformat/key_format.go +++ b/keyformat/key_format.go @@ -105,6 +105,13 @@ func (kf *KeyFormat) Key(args ...interface{}) []byte { return kf.KeyBytes(segments...) } +func (kf *KeyFormat) NodeKey(nodeKey int64) []byte { + b := make([]byte, 9) + b[0] = kf.prefix + binary.BigEndian.PutUint64(b[1:], uint64(nodeKey)) + return b +} + // Reads out the bytes associated with each segment of the key format from key. func (kf *KeyFormat) ScanBytes(key []byte) [][]byte { segments := make([][]byte, len(kf.layout)) @@ -151,6 +158,10 @@ func scan(a interface{}, value []byte) { *v = int64(binary.BigEndian.Uint64(value)) case *uint64: *v = binary.BigEndian.Uint64(value) + case *uint32: + *v = binary.BigEndian.Uint32(value) + case *int32: + *v = int32(binary.BigEndian.Uint32(value)) case *[]byte: *v = value default: @@ -169,6 +180,10 @@ func format(a interface{}) []byte { return formatUint64(uint64(v)) case int: return formatUint64(uint64(v)) + case uint32: + return formatUint32(v) + case int32: + return formatUint32(uint32(v)) case []byte: return v default: @@ -181,3 +196,9 @@ func formatUint64(v uint64) []byte { binary.BigEndian.PutUint64(bs, v) return bs } + +func formatUint32(v uint32) []byte { + bs := make([]byte, 4) + binary.BigEndian.PutUint32(bs, v) + return bs +} diff --git a/mutable_tree.go b/mutable_tree.go index a6150628f..f4ca1794e 100644 --- a/mutable_tree.go +++ b/mutable_tree.go @@ -2,7 +2,6 @@ package iavl import ( "bytes" - "crypto/sha256" "errors" "fmt" "sort" @@ -31,7 +30,7 @@ var ErrVersionDoesNotExist = errors.New("version does not exist") type MutableTree struct { *ImmutableTree // The current, working tree. lastSaved *ImmutableTree // The most recently saved tree. - orphans map[string]int64 // Nodes removed by changes to working tree. + orphans []*NodeKey // Nodes removed by updates of working tree. versions map[int64]bool // The previous, saved versions of the tree. allRootLoaded bool // Whether all roots are loaded or not(by LazyLoadVersion) unsavedFastNodeAdditions map[string]*fastnode.Node // FastNodes that have not yet been saved to disk @@ -55,7 +54,7 @@ func NewMutableTreeWithOpts(db dbm.DB, cacheSize int, opts *Options, skipFastSto return &MutableTree{ ImmutableTree: head, lastSaved: head.clone(), - orphans: map[string]int64{}, + orphans: []*NodeKey{}, versions: map[int64]bool{}, allRootLoaded: false, unsavedFastNodeAdditions: make(map[string]*fastnode.Node), @@ -84,7 +83,7 @@ func (tree *MutableTree) VersionExists(version int64) bool { if ok { return has } - has, _ = tree.ndb.HasRoot(version) + has, _ = tree.ndb.HasVersion(version) tree.versions[version] = has return has } @@ -120,26 +119,15 @@ func (tree *MutableTree) String() (string, error) { return tree.ndb.String() } -// Set/Remove will orphan at most tree.Height nodes, -// balancing the tree after a Set/Remove will orphan at most 3 nodes. -func (tree *MutableTree) prepareOrphansSlice() []*Node { - return make([]*Node, 0, tree.Height()+3) -} - // Set sets a key in the working tree. Nil values are invalid. The given // key/value byte slices must not be modified after this call, since they point // to slices stored within IAVL. It returns true when an existing value was // updated, while false means it was a new key. func (tree *MutableTree) Set(key, value []byte) (updated bool, err error) { - var orphaned []*Node - orphaned, updated, err = tree.set(key, value) + updated, err = tree.set(key, value) if err != nil { return false, err } - err = tree.addOrphans(orphaned) - if err != nil { - return updated, err - } return updated, nil } @@ -221,25 +209,24 @@ func (tree *MutableTree) Iterator(start, end []byte, ascending bool) (dbm.Iterat return tree.ImmutableTree.Iterator(start, end, ascending) } -func (tree *MutableTree) set(key []byte, value []byte) (orphans []*Node, updated bool, err error) { +func (tree *MutableTree) set(key []byte, value []byte) (updated bool, err error) { if value == nil { - return nil, updated, fmt.Errorf("attempt to store nil value at key '%s'", key) + return updated, fmt.Errorf("attempt to store nil value at key '%s'", key) } if tree.ImmutableTree.root == nil { if !tree.skipFastStorageUpgrade { tree.addUnsavedAddition(key, fastnode.NewNode(key, value, tree.version+1)) } - tree.ImmutableTree.root = NewNode(key, value, tree.version+1, tree.IncreaseNonce()) - return nil, updated, nil + tree.ImmutableTree.root = NewNode(key, value) + return updated, nil } - orphans = tree.prepareOrphansSlice() - tree.ImmutableTree.root, updated, err = tree.recursiveSet(tree.ImmutableTree.root, key, value, &orphans) - return orphans, updated, err + tree.ImmutableTree.root, updated, err = tree.recursiveSet(tree.ImmutableTree.root, key, value) + return updated, err } -func (tree *MutableTree) recursiveSet(node *Node, key []byte, value []byte, orphans *[]*Node) ( +func (tree *MutableTree) recursiveSet(node *Node, key []byte, value []byte) ( newSelf *Node, updated bool, err error, ) { version := tree.version + 1 @@ -248,65 +235,47 @@ func (tree *MutableTree) recursiveSet(node *Node, key []byte, value []byte, orph if !tree.skipFastStorageUpgrade { tree.addUnsavedAddition(key, fastnode.NewNode(key, value, version)) } - switch bytes.Compare(key, node.key) { case -1: return &Node{ key: node.key, subtreeHeight: 1, size: 2, - nodeKey: tree.IncreaseNonce(), + nodeKey: nil, + leftNode: NewNode(key, value), rightNode: node, - version: version, - leftNode: NewNode(key, value, version, tree.IncreaseNonce()), }, false, nil case 1: return &Node{ key: key, subtreeHeight: 1, size: 2, + nodeKey: nil, leftNode: node, - nodeKey: tree.IncreaseNonce(), - version: version, - rightNode: NewNode(key, value, version, tree.IncreaseNonce()), + rightNode: NewNode(key, value), }, false, nil default: - *orphans = append(*orphans, node) - return NewNode(key, value, version, tree.IncreaseNonce()), true, nil + if node.nodeKey != nil { + tree.orphans = append(tree.orphans, node.nodeKey) + } + return NewNode(key, value), true, nil } } else { - *orphans = append(*orphans, node) - if node.persisted { - node.nodeKey = tree.IncreaseNonce() - } - 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, orphans) + node.leftNode, updated, err = tree.recursiveSet(node.leftNode, key, value) if err != nil { return nil, updated, err } - node.leftHash = nil // leftHash is yet unknown - node.leftNodeKey = 0 } else { - rightNode, err := node.getRightNode(tree.ImmutableTree) - if err != nil { - return nil, false, err - } - node.rightNode, updated, err = tree.recursiveSet(rightNode, key, value, orphans) + node.rightNode, updated, err = tree.recursiveSet(node.rightNode, key, value) if err != nil { return nil, updated, err } - node.rightHash = nil // rightHash is yet unknown - node.rightNodeKey = 0 } if updated { @@ -316,7 +285,7 @@ func (tree *MutableTree) recursiveSet(node *Node, key []byte, value []byte, orph if err != nil { return nil, false, err } - newNode, err := tree.balance(node, orphans) + newNode, err := tree.balance(node) if err != nil { return nil, false, err } @@ -327,46 +296,23 @@ func (tree *MutableTree) recursiveSet(node *Node, key []byte, value []byte, orph // Remove removes a key from the working tree. The given key byte slice should not be modified // after this call, since it may point to data stored inside IAVL. func (tree *MutableTree) Remove(key []byte) ([]byte, bool, error) { - val, orphaned, removed, err := tree.remove(key) - if err != nil { - return nil, false, err - } - - err = tree.addOrphans(orphaned) - if err != nil { - return val, removed, err - } - return val, removed, nil -} - -// remove tries to remove a key from the tree and if removed, returns its -// value, nodes orphaned and 'true'. -func (tree *MutableTree) remove(key []byte) (value []byte, orphaned []*Node, removed bool, err error) { if tree.root == nil { - return nil, nil, false, nil + return nil, false, nil } - orphaned = tree.prepareOrphansSlice() - newRootHash, _, newRoot, _, value, err := tree.recursiveRemove(tree.root, key, &orphaned) + newRoot, _, value, err := tree.recursiveRemove(tree.root, key) if err != nil { - return nil, nil, false, err + return nil, false, err } - if len(orphaned) == 0 { - return nil, nil, false, nil + if value == nil { + 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, nil, false, err - } - } else { - tree.root = newRoot - } - return value, orphaned, true, nil + tree.root = newRoot + return value, true, nil } // removes the node corresponding to the passed key and balances the tree. @@ -376,96 +322,78 @@ func (tree *MutableTree) remove(key []byte) (value []byte, orphaned []*Node, rem // - new leftmost leaf key for tree after successfully removing 'key' if changed. // - the removed value // - the orphaned nodes. -func (tree *MutableTree) recursiveRemove(node *Node, key []byte, orphans *[]*Node) (newHash []byte, newNodeKey int64, newSelf *Node, newKey []byte, newValue []byte, err error) { - version := tree.version + 1 - +func (tree *MutableTree) recursiveRemove(node *Node, key []byte) (newSelf *Node, newKey []byte, newValue []byte, err error) { + logger.Debug("recursiveRemove node: %v, key: %x\n", node, key) if node.isLeaf() { if bytes.Equal(key, node.key) { - *orphans = append(*orphans, node) - return nil, 0, nil, nil, node.value, nil + if node.nodeKey != nil { + tree.orphans = append(tree.orphans, node.nodeKey) + } + return nil, nil, node.value, nil } - return node.hash, node.nodeKey, node, nil, nil, nil + return node, nil, nil, nil + } + + node, err = node.clone(tree) + if err != nil { + return nil, nil, nil, err } // node.key < key; we go to the left to find the key: if bytes.Compare(key, node.key) < 0 { - leftNode, err := node.getLeftNode(tree.ImmutableTree) + newLeftNode, newKey, value, err := tree.recursiveRemove(node.leftNode, key) if err != nil { - return nil, 0, nil, nil, nil, err - } - newLeftHash, newLeftNodeKey, newLeftNode, newKey, value, err := tree.recursiveRemove(leftNode, key, orphans) - if err != nil { - return nil, 0, nil, nil, nil, err + return nil, nil, nil, err } - if len(*orphans) == 0 { - return node.hash, node.nodeKey, node, nil, value, nil - } - *orphans = append(*orphans, node) - if newLeftHash == nil && newLeftNode == nil { // left node held value, was removed - return node.rightHash, node.rightNodeKey, node.rightNode, node.key, value, nil + if value == nil { + return node, nil, value, nil } - if node.persisted { - node.nodeKey = tree.IncreaseNonce() - } - node, err = node.clone(version) - if err != nil { - return nil, 0, nil, nil, nil, err + if newLeftNode == nil { // left node held value, was removed + return node.rightNode, node.key, value, nil } - node.leftHash, node.leftNodeKey, node.leftNode = newLeftHash, newLeftNodeKey, newLeftNode + node.leftNode = newLeftNode err = node.calcHeightAndSize(tree.ImmutableTree) if err != nil { - return nil, 0, nil, nil, nil, err + return nil, nil, nil, err } - node, err = tree.balance(node, orphans) + node, err = tree.balance(node) if err != nil { - return nil, 0, nil, nil, nil, err + return nil, nil, nil, err } - return node.hash, node.nodeKey, node, newKey, value, nil + return node, newKey, value, nil } // node.key >= key; either found or look to the right: - rightNode, err := node.getRightNode(tree.ImmutableTree) - if err != nil { - return nil, 0, nil, nil, nil, err - } - newRightHash, newRightNodeKey, newRightNode, newKey, value, err := tree.recursiveRemove(rightNode, key, orphans) + newRightNode, newKey, value, err := tree.recursiveRemove(node.rightNode, key) if err != nil { - return nil, 0, nil, nil, nil, err + return nil, nil, nil, err } - if len(*orphans) == 0 { - return node.hash, node.nodeKey, node, nil, value, nil - } - *orphans = append(*orphans, node) - if newRightHash == nil && newRightNode == nil { // right node held value, was removed - return node.leftHash, node.nodeKey, node.leftNode, nil, value, nil + if value == nil { + return node, nil, value, nil } - if node.persisted { - node.nodeKey = tree.IncreaseNonce() - } - node, err = node.clone(version) - if err != nil { - return nil, 0, nil, nil, nil, err + if newRightNode == nil { // right node held value, was removed + return node.leftNode, nil, value, nil } - node.rightHash, node.rightNodeKey, node.rightNode = newRightHash, newRightNodeKey, newRightNode + node.rightNode = newRightNode if newKey != nil { node.key = newKey } err = node.calcHeightAndSize(tree.ImmutableTree) if err != nil { - return nil, 0, nil, nil, nil, err + return nil, nil, nil, err } - node, err = tree.balance(node, orphans) + node, err = tree.balance(node) if err != nil { - return nil, 0, nil, nil, nil, err + return nil, nil, nil, err } - return node.hash, node.nodeKey, node, nil, value, nil + return node, nil, value, nil } // Load the latest versioned tree from disk. @@ -508,11 +436,11 @@ func (tree *MutableTree) LazyLoadVersion(targetVersion int64) (int64, error) { targetVersion = latestVersion } - rootHash, err := tree.ndb.getRoot(targetVersion) + rootNodeKey, err := tree.ndb.GetRoot(targetVersion) if err != nil { return 0, err } - if rootHash == nil { + if rootNodeKey == nil { return latestVersion, ErrVersionDoesNotExist } @@ -526,16 +454,17 @@ func (tree *MutableTree) LazyLoadVersion(targetVersion int64) (int64, error) { version: targetVersion, skipFastStorageUpgrade: tree.skipFastStorageUpgrade, } - if len(rootHash) > 0 { - // If rootHash is empty then root of tree should be nil - // This makes `LazyLoadVersion` to do the same thing as `LoadVersion` - iTree.root, err = tree.ndb.GetNode(rootHash) + + // This makes `LazyLoadVersion` to do the same thing as `LoadVersion` + // rootNodeKey.version = 0 means the root is a nil + if rootNodeKey.version != 0 { + iTree.root, err = tree.ndb.GetNode(rootNodeKey) if err != nil { return 0, err } } - tree.orphans = map[string]int64{} + tree.orphans = []*NodeKey{} tree.ImmutableTree = iTree tree.lastSaved = iTree.clone() @@ -551,12 +480,12 @@ func (tree *MutableTree) LazyLoadVersion(targetVersion int64) (int64, error) { // Returns the version number of the latest version found func (tree *MutableTree) LoadVersion(targetVersion int64) (int64, error) { - roots, err := tree.ndb.getRoots() + versions, err := tree.ndb.getVersions() if err != nil { return 0, err } - if len(roots) == 0 { + if len(versions) == 0 { if targetVersion <= 0 { if !tree.skipFastStorageUpgrade { tree.mtx.Lock() @@ -575,12 +504,10 @@ func (tree *MutableTree) LoadVersion(targetVersion int64) (int64, error) { tree.mtx.Lock() defer tree.mtx.Unlock() - var latestRoot []byte - for version, r := range roots { + for _, version := range versions { tree.versions[version] = true if version > latestVersion && (targetVersion == 0 || version <= targetVersion) { latestVersion = version - latestRoot = r } if firstVersion == 0 || version < firstVersion { firstVersion = version @@ -603,14 +530,23 @@ func (tree *MutableTree) LoadVersion(targetVersion int64) (int64, error) { skipFastStorageUpgrade: tree.skipFastStorageUpgrade, } - if len(latestRoot) != 0 { - t.root, err = tree.ndb.GetNode(latestRoot) + rootNodeKey, err := tree.ndb.GetRoot(latestVersion) + if err != nil { + return 0, err + } + if rootNodeKey == nil { + return 0, ErrVersionDoesNotExist + } + + // rootNodeKey.version = 0 means root is a nil + if rootNodeKey.version != 0 { + t.root, err = tree.ndb.GetNode(rootNodeKey) if err != nil { return 0, err } } - tree.orphans = map[string]int64{} + tree.orphans = []*NodeKey{} tree.ImmutableTree = t tree.lastSaved = t.clone() tree.allRootLoaded = true @@ -752,30 +688,27 @@ 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) { - rootHash, err := tree.ndb.getRoot(version) + rootNodeKey, err := tree.ndb.GetRoot(version) if err != nil { return nil, err } - if rootHash == nil { + if rootNodeKey == nil { return nil, ErrVersionDoesNotExist } tree.mtx.Lock() defer tree.mtx.Unlock() - if len(rootHash) == 0 { - tree.versions[version] = true - return &ImmutableTree{ - ndb: tree.ndb, - version: version, - skipFastStorageUpgrade: tree.skipFastStorageUpgrade, - }, nil - } tree.versions[version] = true - root, err := tree.ndb.GetNode(rootHash) - if err != nil { - return nil, err + var root *Node + // rootNodeKey.version = 0 means root is a nil + if rootNodeKey.version != 0 { + root, err = tree.ndb.GetNode(rootNodeKey) + if err != nil { + return nil, err + } } + return &ImmutableTree{ root: root, ndb: tree.ndb, @@ -796,7 +729,7 @@ func (tree *MutableTree) Rollback() { skipFastStorageUpgrade: tree.skipFastStorageUpgrade, } } - tree.orphans = map[string]int64{} + tree.orphans = []*NodeKey{} if !tree.skipFastStorageUpgrade { tree.unsavedFastNodeAdditions = map[string]*fastnode.Node{} tree.unsavedFastNodeRemovals = map[string]interface{}{} @@ -848,15 +781,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.version != 0 { + existingRoot, err = tree.ndb.GetNode(existingNodeKey) + if err != nil { + return nil, version, err + } } newHash, err := tree.WorkingHash() @@ -864,39 +798,43 @@ 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() - tree.orphans = map[string]int64{} - return existingHash, version, nil + tree.orphans = []*NodeKey{} + 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) } - if tree.root == nil { - // There can still be orphans, for example if the root is the node being - // removed. - logger.Debug("SAVE EMPTY TREE %v\n", version) - if err := tree.ndb.SaveOrphans(version, tree.orphans); err != nil { - return nil, 0, err - } - if err := tree.ndb.SaveEmptyRoot(version); err != nil { - return nil, 0, err - } - } else { - logger.Debug("SAVE TREE %v\n", version) - if _, _, err := tree.ndb.SaveBranch(tree.root); err != nil { - return nil, 0, err - } - if err := tree.ndb.SaveOrphans(version, tree.orphans); err != nil { + logger.Debug("SAVE TREE %v\n", version) + // save orphans + if err := tree.ndb.SaveOrphans(version, tree.orphans); err != nil { + return nil, 0, err + } + + // save new nodes + if tree.root != nil { + if err := tree.saveNewNodes(); err != nil { return nil, 0, err } - if err := tree.ndb.SaveRoot(tree.root, version); err != nil { - return nil, 0, err + } + + // save root + rootVersion := int64(0) + if tree.root != nil { + if tree.root.nodeKey != nil { + rootVersion = tree.root.nodeKey.version + } else { + rootVersion = version } } + if err := tree.ndb.SaveRoot(version, rootVersion); err != nil { + return nil, 0, err + } if !tree.skipFastStorageUpgrade { if err := tree.saveFastNodeVersion(); err != nil { @@ -916,7 +854,7 @@ func (tree *MutableTree) SaveVersion() ([]byte, int64, error) { // set new working tree tree.ImmutableTree = tree.ImmutableTree.clone() tree.lastSaved = tree.ImmutableTree.clone() - tree.orphans = map[string]int64{} + tree.orphans = []*NodeKey{} if !tree.skipFastStorageUpgrade { tree.unsavedFastNodeAdditions = make(map[string]*fastnode.Node) tree.unsavedFastNodeRemovals = make(map[string]interface{}) @@ -1090,83 +1028,69 @@ func (tree *MutableTree) DeleteVersion(version int64) error { } // Rotate right and return the new node and orphan. -func (tree *MutableTree) rotateRight(node *Node) (*Node, *Node, error) { - version := tree.version + 1 - +func (tree *MutableTree) rotateRight(node *Node) (*Node, error) { var err error // TODO: optimize balance & rotate. - node, err = node.clone(version) + node, err = node.clone(tree) if err != nil { - return nil, nil, err + return nil, err } - orphaned, err := node.getLeftNode(tree.ImmutableTree) + newNode, err := node.leftNode.clone(tree) if err != nil { - return nil, nil, err - } - newNode, err := orphaned.clone(version) - if err != nil { - return nil, nil, err + return nil, err } - node.nodeKey, newNode.nodeKey = newNode.nodeKey, node.nodeKey - node.leftHash, node.leftNodeKey, node.leftNode = newNode.rightHash, newNode.rightNodeKey, newNode.rightNode + node.leftNode = newNode.rightNode newNode.rightNode = node err = node.calcHeightAndSize(tree.ImmutableTree) if err != nil { - return nil, nil, err + return nil, err } err = newNode.calcHeightAndSize(tree.ImmutableTree) if err != nil { - return nil, nil, err + return nil, err } - return newNode, orphaned, nil + return newNode, nil } // Rotate left and return the new node and orphan. -func (tree *MutableTree) rotateLeft(node *Node) (*Node, *Node, error) { - version := tree.version + 1 - +func (tree *MutableTree) rotateLeft(node *Node) (*Node, error) { var err error // TODO: optimize balance & rotate. - node, err = node.clone(version) + node, err = node.clone(tree) if err != nil { - return nil, nil, err + return nil, err } - orphaned, err := node.getRightNode(tree.ImmutableTree) - if err != nil { - return nil, nil, err - } - newNode, err := orphaned.clone(version) + newNode, err := node.rightNode.clone(tree) if err != nil { - return nil, nil, err + return nil, err } - node.nodeKey, newNode.nodeKey = newNode.nodeKey, node.nodeKey - node.rightHash, node.rightNodeKey, node.rightNode = newNode.leftHash, newNode.leftNodeKey, newNode.leftNode + node.rightNode = newNode.leftNode newNode.leftNode = node err = node.calcHeightAndSize(tree.ImmutableTree) if err != nil { - return nil, nil, err + return nil, err } err = newNode.calcHeightAndSize(tree.ImmutableTree) if err != nil { - return nil, nil, err + return nil, err } - return newNode, orphaned, nil + return newNode, nil } // NOTE: assumes that node can be modified // TODO: optimize balance & rotate -func (tree *MutableTree) balance(node *Node, orphans *[]*Node) (newSelf *Node, err error) { - if node.persisted { +func (tree *MutableTree) balance(node *Node) (newSelf *Node, err error) { + if node.nodeKey != nil { return nil, fmt.Errorf("unexpected balance() call on persisted node") } balance, err := node.calcBalance(tree.ImmutableTree) @@ -1175,44 +1099,31 @@ func (tree *MutableTree) balance(node *Node, orphans *[]*Node) (newSelf *Node, e } if balance > 1 { - leftNode, err := node.getLeftNode(tree.ImmutableTree) - if err != nil { - return nil, err - } - - lftBalance, err := leftNode.calcBalance(tree.ImmutableTree) + lftBalance, err := node.leftNode.calcBalance(tree.ImmutableTree) if err != nil { return nil, err } if lftBalance >= 0 { // Left Left Case - newNode, orphaned, err := tree.rotateRight(node) + newNode, err := tree.rotateRight(node) if err != nil { return nil, err } - *orphans = append(*orphans, orphaned) return newNode, nil } // Left Right Case - var leftOrphaned *Node - - left, err := node.getLeftNode(tree.ImmutableTree) - if err != nil { - return nil, err - } - node.leftHash = nil - node.leftNodeKey = 0 - node.leftNode, leftOrphaned, err = tree.rotateLeft(left) + node.leftNodeKey = nil + node.leftNode, err = tree.rotateLeft(node.leftNode) if err != nil { return nil, err } - newNode, rightOrphaned, err := tree.rotateRight(node) + newNode, err := tree.rotateRight(node) if err != nil { return nil, err } - *orphans = append(*orphans, left, leftOrphaned, rightOrphaned) + return newNode, nil } if balance < -1 { @@ -1227,48 +1138,94 @@ func (tree *MutableTree) balance(node *Node, orphans *[]*Node) (newSelf *Node, e } if rightBalance <= 0 { // Right Right Case - newNode, orphaned, err := tree.rotateLeft(node) + newNode, err := tree.rotateLeft(node) if err != nil { return nil, err } - *orphans = append(*orphans, orphaned) return newNode, nil } // Right Left Case - var rightOrphaned *Node - - right, err := node.getRightNode(tree.ImmutableTree) + node.rightNodeKey = nil + node.rightNode, err = tree.rotateRight(rightNode) if err != nil { return nil, err } - node.rightHash = nil - node.rightNodeKey = 0 - node.rightNode, rightOrphaned, err = tree.rotateRight(right) + newNode, err := tree.rotateLeft(node) if err != nil { return nil, err } - newNode, leftOrphaned, err := tree.rotateLeft(node) - if err != nil { - return nil, err - } - - *orphans = append(*orphans, right, leftOrphaned, rightOrphaned) return newNode, nil } // Nothing changed return node, nil } -func (tree *MutableTree) addOrphans(orphans []*Node) error { - for _, node := range orphans { - if !node.persisted { - // We don't need to orphan nodes that were never persisted. - continue +// 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() error { + version := tree.version + 1 + + nonce := int32(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, + } + + var err error + if node.leftNode != nil { + node.leftNodeKey, err = recursiveAssignKey(node.leftNode) + if err != nil { + return nil, err + } + } + + if node.rightNode != nil { + node.rightNodeKey, err = recursiveAssignKey(node.rightNode) + if err != nil { + return nil, err + } } - if len(node.hash) == 0 { - return fmt.Errorf("expected to find node hash, but was empty") + + _, err = node._hash(version) + if err != nil { + return nil, err } - tree.orphans[unsafeToStr(node.hash)] = node.version + return node.nodeKey, nil } - return nil + + if _, err := recursiveAssignKey(tree.root); err != nil { + return err + } + + var recursiveSave func(*Node) error + recursiveSave = func(node *Node) error { + if node.nodeKey.version < version { + return nil + } + if err := tree.ndb.SaveNode(node); err != nil { + return err + } + if node.leftNode != nil { + if err := recursiveSave(node.leftNode); err != nil { + return err + } + node.leftNode = nil + } + if node.rightNode != nil { + if err := recursiveSave(node.rightNode); err != nil { + return err + } + node.rightNode = nil + } + return nil + } + + return recursiveSave(tree.root) } diff --git a/mutable_tree_test.go b/mutable_tree_test.go index 0d4d49bf5..84b4ac18e 100644 --- a/mutable_tree_test.go +++ b/mutable_tree_test.go @@ -113,7 +113,7 @@ func TestDelete(t *testing.T) { tree := setupMutableTree(t, false) tree.set([]byte("k1"), []byte("Fred")) - hash, version, err := tree.SaveVersion() + _, version, err := tree.SaveVersion() require.NoError(t, err) _, _, err = tree.SaveVersion() require.NoError(t, err) @@ -124,9 +124,9 @@ 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) + err = tree.ndb.SaveRoot(version, version) require.NoError(t, err) + require.NoError(t, tree.ndb.Commit()) tree.versions[version] = true proof, err = tree.GetVersionedProof([]byte("k1"), version) @@ -895,7 +895,7 @@ func TestUpgradeStorageToFast_DbErrorConstructor_Failure(t *testing.T) { // rIterMock is used to get the latest version from disk. We are mocking that rIterMock returns latestTreeVersion from disk rIterMock.EXPECT().Valid().Return(true).Times(1) - rIterMock.EXPECT().Key().Return(rootKeyFormat.Key([]byte(defaultStorageVersionValue))) + rIterMock.EXPECT().Key().Return(versionKeyFormat.Key([]byte(defaultStorageVersionValue))) rIterMock.EXPECT().Close().Return(nil).Times(1) expectedError := errors.New("some db error") @@ -920,7 +920,7 @@ func TestUpgradeStorageToFast_DbErrorEnableFastStorage_Failure(t *testing.T) { // rIterMock is used to get the latest version from disk. We are mocking that rIterMock returns latestTreeVersion from disk rIterMock.EXPECT().Valid().Return(true).Times(1) - rIterMock.EXPECT().Key().Return(rootKeyFormat.Key([]byte(defaultStorageVersionValue))) + rIterMock.EXPECT().Key().Return(versionKeyFormat.Key([]byte(defaultStorageVersionValue))) rIterMock.EXPECT().Close().Return(nil).Times(1) expectedError := errors.New("some db error") @@ -971,7 +971,7 @@ func TestFastStorageReUpgradeProtection_NoForceUpgrade_Success(t *testing.T) { // rIterMock is used to get the latest version from disk. We are mocking that rIterMock returns latestTreeVersion from disk rIterMock.EXPECT().Valid().Return(true).Times(1) - rIterMock.EXPECT().Key().Return(rootKeyFormat.Key(latestTreeVersion)) + rIterMock.EXPECT().Key().Return(versionKeyFormat.Key(latestTreeVersion)) rIterMock.EXPECT().Close().Return(nil).Times(1) batchMock := mock.NewMockBatch(ctrl) @@ -1034,7 +1034,7 @@ func TestFastStorageReUpgradeProtection_ForceUpgradeFirstTime_NoForceSecondTime_ // rIterMock is used to get the latest version from disk. We are mocking that rIterMock returns latestTreeVersion from disk rIterMock.EXPECT().Valid().Return(true).Times(1) - rIterMock.EXPECT().Key().Return(rootKeyFormat.Key(latestTreeVersion)) + rIterMock.EXPECT().Key().Return(versionKeyFormat.Key(latestTreeVersion)) rIterMock.EXPECT().Close().Return(nil).Times(1) fastNodeKeyToDelete := []byte("some_key") diff --git a/node.go b/node.go index eafc8161e..febf89a87 100644 --- a/node.go +++ b/node.go @@ -6,6 +6,7 @@ package iavl import ( "bytes" "crypto/sha256" + "encoding/binary" "errors" "fmt" "io" @@ -16,35 +17,42 @@ import ( "github.com/cosmos/iavl/internal/encoding" ) +// NodeKey represents a key of node in the DB. +type NodeKey struct { + version int64 + nonce int32 +} + +func (nk *NodeKey) GetKey() []byte { + b := make([]byte, 12) + binary.BigEndian.PutUint64(b, uint64(nk.version)) + binary.BigEndian.PutUint32(b[8:], uint32(nk.nonce)) + return b +} + // Node represents a node in a Tree. type Node struct { key []byte value []byte hash []byte - leftHash []byte - rightHash []byte - nodeKey int64 - leftNodeKey int64 - rightNodeKey int64 - version int64 + nodeKey *NodeKey + leftNodeKey *NodeKey + rightNodeKey *NodeKey size int64 leftNode *Node rightNode *Node subtreeHeight int8 - persisted bool } var _ cache.Node = (*Node)(nil) // NewNode returns a new node from a key, value and version. -func NewNode(key []byte, value []byte, version, nodeKey int64) *Node { +func NewNode(key []byte, value []byte) *Node { return &Node{ key: key, value: value, subtreeHeight: 0, size: 1, - version: version, - nodeKey: nodeKey, } } @@ -52,8 +60,8 @@ func NewNode(key []byte, value []byte, version, nodeKey int64) *Node { // // The new node doesn't have its hash saved or set. The caller must set it // afterwards. -func MakeNode(buf []byte) (*Node, error) { - // Read node header (height, size, version, nodeKey, key). +func MakeNode(nodeKey *NodeKey, buf []byte) (*Node, error) { + // Read node header (height, size, key). height, n, cause := encoding.DecodeVarint(buf) if cause != nil { return nil, fmt.Errorf("decoding node.height, %w", cause) @@ -69,18 +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:] - - nodeKey, n, cause := encoding.DecodeVarint(buf) - if cause != nil { - return nil, fmt.Errorf("decoding node.nodeKey, %w", cause) - } - buf = buf[n:] - key, n, cause := encoding.DecodeBytes(buf) if cause != nil { return nil, fmt.Errorf("decoding node.key, %w", cause) @@ -90,7 +86,6 @@ func MakeNode(buf []byte) (*Node, error) { node := &Node{ subtreeHeight: int8(height), size: size, - version: ver, nodeKey: nodeKey, key: key, } @@ -103,73 +98,111 @@ func MakeNode(buf []byte) (*Node, error) { return nil, fmt.Errorf("decoding node.value, %w", cause) } node.value = val + // ensure take the hash for the leaf node + if _, err := node._hash(node.nodeKey.version); err != nil { + return nil, fmt.Errorf("calculating hash error: %v", err) + } + } else { // Read children. - 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:] - leftNodeKey, n, cause := encoding.DecodeVarint(buf) + + var ( + leftNodeKey, rightNodeKey NodeKey + nonce int64 + ) + leftNodeKey.version, n, cause = encoding.DecodeVarint(buf) if cause != nil { - return nil, fmt.Errorf("decoding node.leftNodeKey, %w", cause) + return nil, fmt.Errorf("decoding node.leftNodeKey.version, %w", cause) } buf = buf[n:] + nonce, n, cause = encoding.DecodeVarint(buf) + if cause != nil { + return nil, fmt.Errorf("deocding node.leftNodeKey.nonce, %w", cause) + } + buf = buf[n:] + if nonce < int64(math.MinInt32) || nonce > int64(math.MaxInt32) { + return nil, errors.New("invalid nonce, must be int32") + } + leftNodeKey.nonce = int32(nonce) - rightHash, n, cause := encoding.DecodeBytes(buf) + rightNodeKey.version, n, cause = encoding.DecodeVarint(buf) if cause != nil { - return nil, fmt.Errorf("decoding node.rightHash, %w", cause) + return nil, fmt.Errorf("decoding node.rightNodeKey.version, %w", cause) } buf = buf[n:] - rightNodeKey, _, cause := encoding.DecodeVarint(buf) + nonce, _, cause = encoding.DecodeVarint(buf) if cause != nil { - return nil, fmt.Errorf("decoding node.rightNodeKey, %w", cause) + return nil, fmt.Errorf("decoding node.rightNodeKey.nonce, %w", cause) } + if nonce < int64(math.MinInt32) || nonce > int64(math.MaxInt32) { + return nil, errors.New("invalid nonce, must be int32") + } + rightNodeKey.nonce = int32(nonce) - node.leftHash = leftHash - node.leftNodeKey = leftNodeKey - node.rightHash = rightHash - node.rightNodeKey = rightNodeKey + 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. func (node *Node) String() string { - hashstr := "" - if len(node.hash) > 0 { - hashstr = fmt.Sprintf("%X", node.hash) + child := "" + if node.leftNode != nil && node.leftNode.nodeKey != nil { + child += fmt.Sprintf("{left %d, %d}", node.leftNode.nodeKey.version, node.leftNode.nodeKey.nonce) + } + if node.rightNode != nil && node.rightNode.nodeKey != nil { + child += fmt.Sprintf("{right %d, %d}", node.rightNode.nodeKey.version, node.rightNode.nodeKey.nonce) } - return fmt.Sprintf("Node{%s:%s@%d %X;%X}#%s", + return fmt.Sprintf("Node{%s:%s@ %v:%v-%v %d-%d}#%s", ColoredBytes(node.key, Green, Blue), ColoredBytes(node.value, Cyan, Blue), - node.version, - node.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 { + tree.orphans = append(tree.orphans, node.nodeKey) + 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, - nodeKey: node.nodeKey, + nodeKey: nil, leftNodeKey: node.leftNodeKey, rightNodeKey: node.rightNodeKey, - leftHash: node.leftHash, - leftNode: node.leftNode, - rightHash: node.rightHash, - rightNode: node.rightNode, - persisted: false, + leftNode: leftNode, + rightNode: rightNode, }, nil } @@ -268,14 +301,14 @@ func (node *Node) getByIndex(t *ImmutableTree, index int64) (key []byte, value [ // Computes the hash of the node without computing its descendants. Must be // called on nodes which have descendant node hashes already computed. -func (node *Node) _hash() ([]byte, error) { +func (node *Node) _hash(version int64) ([]byte, error) { if node.hash != nil { return node.hash, nil } h := sha256.New() buf := new(bytes.Buffer) - if err := node.writeHashBytes(buf); err != nil { + if err := node.writeHashBytes(buf, version); err != nil { return nil, err } _, err := h.Write(buf.Bytes()) @@ -291,27 +324,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 @@ -322,7 +355,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 { @@ -337,7 +373,7 @@ 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 { @@ -348,7 +384,7 @@ func (node *Node) validate() error { if node.value != nil { return errors.New("value must be nil for non-leaf node") } - if node.leftHash == nil && node.rightHash == nil { + if node.leftNodeKey == nil || node.rightNodeKey == nil { return errors.New("inner node must have children") } } @@ -357,7 +393,7 @@ func (node *Node) validate() error { // Writes the node's hash to the given io.Writer. This function expects // child hashes to be already set. -func (node *Node) writeHashBytes(w io.Writer) error { +func (node *Node) writeHashBytes(w io.Writer, version int64) error { err := encoding.EncodeVarint(w, int64(node.subtreeHeight)) if err != nil { return fmt.Errorf("writing height, %w", err) @@ -366,7 +402,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) } @@ -388,14 +424,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) } @@ -406,40 +442,30 @@ func (node *Node) writeHashBytes(w io.Writer) error { // Writes the node's hash to the given io.Writer. // This function has the side-effect of calling hashWithCount. -func (node *Node) writeHashBytesRecursively(w io.Writer) (hashCount int64, err error) { - if node.leftNode != nil { - leftHash, leftCount, err := node.leftNode.hashWithCount() - if err != nil { - return 0, err - } - node.leftHash = leftHash - node.leftNodeKey = node.leftNode.nodeKey - 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 - node.rightNodeKey = node.rightNode.nodeKey - 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) + + encoding.EncodeVarintSize(node.leftNodeKey.version) + + encoding.EncodeVarintSize(int64(node.leftNodeKey.nonce)) + + encoding.EncodeVarintSize(node.rightNodeKey.version) + + encoding.EncodeVarintSize(int64(node.rightNodeKey.nonce)) } return n } @@ -457,15 +483,8 @@ func (node *Node) writeBytes(w io.Writer) error { if cause != nil { return fmt.Errorf("writing size, %w", cause) } - cause = encoding.EncodeVarint(w, node.version) - if cause != nil { - return fmt.Errorf("writing version, %w", cause) - } - cause = encoding.EncodeVarint(w, node.nodeKey) - if cause != nil { - return fmt.Errorf("writing nodeKey, %w", cause) - } - // Unlike writeHashBytes, key is written for inner nodes. + + // Unlike writeHashByte, key is written for inner nodes. cause = encoding.EncodeBytes(w, node.key) if cause != nil { return fmt.Errorf("writing key, %w", cause) @@ -477,34 +496,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.leftHash) + cause = encoding.EncodeBytes(w, node.hash) if cause != nil { - return fmt.Errorf("writing left hash, %w", cause) + return fmt.Errorf("writing hash, %w", cause) } - if node.leftNodeKey == 0 { + if node.leftNodeKey == nil { return ErrLeftNodeKeyEmpty } - cause = encoding.EncodeVarint(w, node.leftNodeKey) + cause = encoding.EncodeVarint(w, node.leftNodeKey.version) if cause != nil { - return fmt.Errorf("writing left node key, %w", cause) + return fmt.Errorf("writing the version of left node key, %w", cause) } - - if node.rightHash == nil { - return ErrRightHashIsNil - } - cause = encoding.EncodeBytes(w, node.rightHash) + cause = encoding.EncodeVarint(w, int64(node.leftNodeKey.nonce)) if cause != nil { - return fmt.Errorf("writing right hash, %w", cause) + return fmt.Errorf("writing the nonce of left node key, %w", cause) } - if node.rightNodeKey == 0 { + + if node.rightNodeKey == nil { return ErrRightNodeKeyEmpty } - cause = encoding.EncodeVarint(w, node.rightNodeKey) + cause = encoding.EncodeVarint(w, node.rightNodeKey.version) if cause != nil { - return fmt.Errorf("writing right node key, %w", cause) + return fmt.Errorf("writing the version of right node key, %w", cause) + } + 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 @@ -514,11 +531,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 } @@ -526,11 +542,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 } @@ -566,7 +581,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) @@ -595,9 +609,9 @@ 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") + ErrEmptyChild = fmt.Errorf("found an empty child") ErrLeftNodeKeyEmpty = fmt.Errorf("node.leftNodeKey was empty in writeBytes") - ErrRightHashIsNil = fmt.Errorf("node.rightHash was nil 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 61aac133a..3fc80804d 100644 --- a/node_test.go +++ b/node_test.go @@ -16,23 +16,31 @@ func TestNode_encodedSize(t *testing.T) { node := &Node{ key: iavlrand.RandBytes(10), value: iavlrand.RandBytes(10), - version: 1, subtreeHeight: 0, size: 100, hash: iavlrand.RandBytes(20), - leftHash: iavlrand.RandBytes(20), - leftNode: nil, - rightHash: iavlrand.RandBytes(20), - rightNode: nil, - persisted: false, + nodeKey: &NodeKey{ + version: 1, + nonce: 10, + }, + leftNodeKey: &NodeKey{ + version: 1, + nonce: 20, + }, + leftNode: nil, + rightNodeKey: &NodeKey{ + version: 1, + nonce: 30, + }, + rightNode: nil, } // leaf node - require.Equal(t, 26, node.encodedSize()) + require.Equal(t, 25, node.encodedSize()) // non-leaf node node.subtreeHeight = 1 - require.Equal(t, 57, node.encodedSize()) + require.Equal(t, 39, node.encodedSize()) } func TestNode_encode_decode(t *testing.T) { @@ -41,27 +49,36 @@ func TestNode_encode_decode(t *testing.T) { expectHex string expectError bool }{ - "nil": {nil, "", true}, - "empty": {&Node{}, "000000000000", false}, + "nil": {nil, "", true}, "inner": {&Node{ subtreeHeight: 3, - version: 2, size: 7, key: []byte("key"), - leftHash: []byte{0x70, 0x80, 0x90, 0xa0}, - rightHash: []byte{0x10, 0x20, 0x30, 0x40}, - nodeKey: 1, - leftNodeKey: 2, - rightNodeKey: 3, - }, "060e0402036b657904708090a004041020304006", false}, + nodeKey: &NodeKey{ + version: 2, + nonce: 1, + }, + leftNodeKey: &NodeKey{ + version: 1, + nonce: 2, + }, + rightNodeKey: &NodeKey{ + version: 1, + nonce: 3, + }, + hash: []byte{0x70, 0x80, 0x90, 0xa0}, + }, "060e036b657904708090a002040206", false}, "leaf": {&Node{ subtreeHeight: 0, - version: 3, size: 1, key: []byte("key"), value: []byte("value"), - nodeKey: 4, - }, "00020608036b65790576616c7565", false}, + nodeKey: &NodeKey{ + version: 3, + nonce: 4, + }, + hash: []byte{0x7f, 0x68, 0x90, 0xca, 0x16, 0xde, 0xa6, 0xe8, 0x89, 0x3d, 0x96, 0xf0, 0xa3, 0xd, 0xa, 0x14, 0xe5, 0x55, 0x59, 0xfc, 0x9b, 0x83, 0x4, 0x91, 0xe3, 0xd2, 0x45, 0x1c, 0x81, 0xf6, 0xd1, 0xe}, + }, "0002036b65790576616c7565", false}, } for name, tc := range testcases { tc := tc @@ -75,7 +92,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 { @@ -92,36 +109,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: 10, + } + c := &Node{key: []byte("child"), value: []byte("x"), size: 1} testcases := map[string]struct { node *Node valid bool }{ - "nil node": {nil, false}, - "leaf": {&Node{key: k, value: v, version: 1, size: 1}, true}, - "leaf with nil key": {&Node{key: nil, value: v, version: 1, size: 1}, false}, - "leaf with empty key": {&Node{key: []byte{}, value: v, version: 1, size: 1}, true}, - "leaf with nil value": {&Node{key: k, value: nil, version: 1, size: 1}, false}, - "leaf with empty value": {&Node{key: k, value: []byte{}, version: 1, size: 1}, true}, - "leaf with version 0": {&Node{key: k, value: v, version: 0, size: 1}, false}, - "leaf with version -1": {&Node{key: k, value: v, version: -1, size: 1}, false}, - "leaf with size 0": {&Node{key: k, value: v, version: 1, size: 0}, false}, - "leaf with size 2": {&Node{key: k, value: v, version: 1, size: 2}, false}, - "leaf with size -1": {&Node{key: k, value: v, version: 1, size: -1}, false}, - "leaf with left 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}, false}, + "inner with right child": {&Node{key: k, size: 1, subtreeHeight: 1, nodeKey: nk, rightNodeKey: nk}, false}, + "inner with no child": {&Node{key: k, size: 1, subtreeHeight: 1}, false}, + "inner with height 0": {&Node{key: k, size: 1, subtreeHeight: 0, leftNodeKey: nk, rightNodeKey: nk}, false}, } for desc, tc := range testcases { @@ -138,14 +158,18 @@ func TestNode_validate(t *testing.T) { } func BenchmarkNode_encodedSize(b *testing.B) { + nk := &NodeKey{ + version: rand.Int63n(10000000), + nonce: rand.Int31n(10000000), + } node := &Node{ key: iavlrand.RandBytes(25), value: iavlrand.RandBytes(100), - version: rand.Int63n(10000000), + nodeKey: nk, subtreeHeight: 1, size: rand.Int63n(10000000), - leftHash: iavlrand.RandBytes(20), - rightHash: iavlrand.RandBytes(20), + leftNodeKey: nk, + rightNodeKey: nk, } b.ReportAllocs() b.ResetTimer() @@ -155,14 +179,18 @@ func BenchmarkNode_encodedSize(b *testing.B) { } func BenchmarkNode_WriteBytes(b *testing.B) { + nk := &NodeKey{ + version: rand.Int63n(10000000), + nonce: rand.Int31n(10000000), + } node := &Node{ key: iavlrand.RandBytes(25), value: iavlrand.RandBytes(100), - version: rand.Int63n(10000000), + nodeKey: nk, subtreeHeight: 1, size: rand.Int63n(10000000), - leftHash: iavlrand.RandBytes(20), - rightHash: iavlrand.RandBytes(20), + leftNodeKey: nk, + rightNodeKey: nk, } b.ResetTimer() b.Run("NoPreAllocate", func(sub *testing.B) { diff --git a/nodedb.go b/nodedb.go index 6a137ad57..74204759c 100644 --- a/nodedb.go +++ b/nodedb.go @@ -15,11 +15,13 @@ import ( "github.com/cosmos/iavl/cache" "github.com/cosmos/iavl/fastnode" + "github.com/cosmos/iavl/internal/encoding" "github.com/cosmos/iavl/internal/logger" "github.com/cosmos/iavl/keyformat" ) const ( + int32Size = 4 int64Size = 8 hashSize = sha256.Size genesisVersion = 1 @@ -38,8 +40,8 @@ const ( var ( // All node keys are prefixed with the byte 'n'. This ensures no collision is - // possible with the other keys, and makes them easier to traverse. They are indexed by the node hash. - nodeKeyFormat = keyformat.NewKeyFormat('n', hashSize) // n + // possible with the other keys, and makes them easier to traverse. They are indexed by the version and the node local nonce. + nodeKeyFormat = keyformat.NewKeyFormat('n', int64Size, int32Size) // n // Orphans are keyed in the database by their expected lifetime. // The first number represents the *last* version at which the orphan needs @@ -49,7 +51,7 @@ var ( // To clarify: // When I write to key {X} with value V and old value O, we orphan O with =time of write // and = version O was created at. - orphanKeyFormat = keyformat.NewKeyFormat('o', int64Size, int64Size, hashSize) // o + orphanKeyFormat = keyformat.NewKeyFormat('n', int64Size, int32Size, int64Size, int32Size) // n<0> // Key Format for making reads and iterates go through a data-locality preserving db. // The value at an entry will list what version it was written to. @@ -61,10 +63,10 @@ var ( // Key Format for storing metadata about the chain such as the vesion number. // The value at an entry will be in a variable format and up to the caller to // decide how to parse. - metadataKeyFormat = keyformat.NewKeyFormat('m', 0) // v + metadataKeyFormat = keyformat.NewKeyFormat('m', 0) // m - // Root nodes are indexed separately by their version - rootKeyFormat = keyformat.NewKeyFormat('r', int64Size) // r + // Keep alive versions. + versionKeyFormat = keyformat.NewKeyFormat('v', int64Size) // v ) var errInvalidFastStorageVersion = fmt.Sprintf("Fast storage version must be in the format %s", fastStorageVersionDelimiter) @@ -107,16 +109,16 @@ func newNodeDB(db dbm.DB, cacheSize int, opts *Options) *nodeDB { // GetNode gets a node from memory or disk. If it is an inner node, it does not // load its children. -func (ndb *nodeDB) GetNode(hash []byte) (*Node, error) { +func (ndb *nodeDB) GetNode(nk *NodeKey) (*Node, error) { ndb.mtx.Lock() defer ndb.mtx.Unlock() - 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 } @@ -124,21 +126,19 @@ func (ndb *nodeDB) GetNode(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 @@ -186,11 +186,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. @@ -201,23 +198,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() @@ -285,7 +289,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") @@ -309,8 +313,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) @@ -327,54 +331,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, int64, error) { - if node.persisted { - return node.hash, node.nodeKey, nil - } - - var err error - if node.leftNode != nil { - node.leftHash, node.leftNodeKey, err = ndb.SaveBranch(node.leftNode) - } - - if err != nil { - return nil, 0, err - } - - if node.rightNode != nil { - node.rightHash, node.rightNodeKey, err = ndb.SaveBranch(node.rightNode) - } - - if err != nil { - return nil, 0, err - } - - _, err = node._hash() - if err != nil { - return nil, 0, err - } - - err = ndb.SaveNode(node) - if err != nil { - return nil, 0, err - } - - // resetBatch only working on generate a genesis block - if node.version <= genesisVersion { - if err = ndb.resetBatch(); err != nil { - return nil, 0, err - } - } - node.leftNode = nil - node.rightNode = nil - - return node.hash, node.nodeKey, nil -} - // resetBatch reset the db batch, keep low memory used func (ndb *nodeDB) resetBatch() error { var err error @@ -411,7 +367,7 @@ func (ndb *nodeDB) DeleteVersion(version int64, checkLatestVersion bool) error { return err } - err = ndb.deleteRoot(version, checkLatestVersion) + err = ndb.deleteVersion(version, checkLatestVersion) if err != nil { return err } @@ -427,13 +383,6 @@ func (ndb *nodeDB) DeleteVersionsFrom(version int64) error { if latest < version { return nil } - root, err := ndb.getRoot(latest) - if err != nil { - return err - } - if root == nil { - return fmt.Errorf("root for version %v not found", latest) - } for v, r := range ndb.versionReaders { if v >= version && r != 0 { @@ -441,42 +390,22 @@ func (ndb *nodeDB) DeleteVersionsFrom(version int64) error { } } - // First, delete all active nodes in the current (latest) version whose node version is after - // the given version. - err = ndb.deleteNodesFrom(version, root) - if err != nil { - return err - } - - // Next, delete orphans: - // - Delete orphan entries *and referred nodes* with fromVersion >= version - // - Delete orphan entries with toVersion >= version-1 (since orphans at latest are not orphans) - err = ndb.traverseOrphans(func(key, hash []byte) error { - var fromVersion, toVersion int64 - orphanKeyFormat.Scan(key, &toVersion, &fromVersion) - - if fromVersion >= version { - if err = ndb.batch.Delete(key); err != nil { - return err - } - if err = ndb.batch.Delete(ndb.nodeKey(hash)); err != nil { - return err - } - ndb.nodeCache.Remove(hash) - } else if toVersion >= version-1 { - if err = ndb.batch.Delete(key); err != nil { - return err - } + // Delete the nodes and orphans + err = ndb.traverseRange(nodeKeyFormat.Key(version, int32(0)), nodeKeyFormat.Key(latest, int32(math.MaxInt32)), func(k, v []byte) error { + if err = ndb.batch.Delete(k); err != nil { + return err } return nil }) - - if err != nil { - return err - } - - // Delete the version root entries - err = ndb.traverseRange(rootKeyFormat.Key(version), rootKeyFormat.Key(int64(math.MaxInt64)), func(k, v []byte) error { + // Delete orphans for version-1 + err = ndb.traverseRange(orphanKeyFormat.Key(version-1, int32(0)), orphanKeyFormat.Key(version-1, int32(1)), func(k, v []byte) error { + if err = ndb.batch.Delete(k); err != nil { + return err + } + return nil + }) + // Delete the version entries + err = ndb.traverseRange(versionKeyFormat.Key(version), versionKeyFormat.Key(int64(math.MaxInt64)), func(k, v []byte) error { if err = ndb.batch.Delete(k); err != nil { return err } @@ -537,7 +466,7 @@ func (ndb *nodeDB) DeleteVersionsRange(fromVersion, toVersion int64) error { } for v, r := range ndb.versionReaders { - if v < toVersion && v > predecessor && r != 0 { + if v < toVersion && v >= fromVersion && r != 0 { return fmt.Errorf("unable to delete version %v with %v active readers", v, r) } } @@ -545,19 +474,24 @@ func (ndb *nodeDB) DeleteVersionsRange(fromVersion, toVersion int64) error { // If the predecessor is earlier than the beginning of the lifetime, we can delete the orphan. // Otherwise, we shorten its lifetime, by moving its endpoint to the predecessor version. for version := fromVersion; version < toVersion; version++ { - err := ndb.traverseOrphansVersion(version, func(key, hash []byte) error { + err := ndb.traverseOrphansVersion(version, func(key, _ []byte) error { var from, to int64 - orphanKeyFormat.Scan(key, &to, &from) + var dummy, nonce int32 + orphanKeyFormat.Scan(key, &to, &dummy, &from, &nonce) + nk := &NodeKey{ + version: from, + nonce: nonce, + } if err := ndb.batch.Delete(key); err != nil { return err } if from > predecessor { - if err := ndb.batch.Delete(ndb.nodeKey(hash)); err != nil { + if err := ndb.batch.Delete(ndb.nodeKey(nk)); err != nil { return err } - ndb.nodeCache.Remove(hash) + ndb.nodeCache.Remove(nk.GetKey()) } else { - if err := ndb.saveOrphan(hash, from, predecessor); err != nil { + if err := ndb.saveOrphan(nk, predecessor); err != nil { return err } } @@ -568,8 +502,8 @@ func (ndb *nodeDB) DeleteVersionsRange(fromVersion, toVersion int64) error { } } - // Delete the version root entries - err = ndb.traverseRange(rootKeyFormat.Key(fromVersion), rootKeyFormat.Key(toVersion), func(k, v []byte) error { + // Delete the version entries + err = ndb.traverseRange(versionKeyFormat.Key(fromVersion), versionKeyFormat.Key(toVersion), func(k, v []byte) error { if err := ndb.batch.Delete(k); err != nil { return err } @@ -592,44 +526,10 @@ func (ndb *nodeDB) DeleteFastNode(key []byte) error { return nil } -// deleteNodesFrom deletes the given node and any descendants that have versions after the given -// (inclusive). It is mainly used via LoadVersionForOverwriting, to delete the current version. -func (ndb *nodeDB) deleteNodesFrom(version int64, hash []byte) error { - if len(hash) == 0 { - return nil - } - - node, err := ndb.GetNode(hash) - if err != nil { - return err - } - - if node.leftHash != nil { - if err := ndb.deleteNodesFrom(version, node.leftHash); err != nil { - return err - } - } - if node.rightHash != nil { - if err := ndb.deleteNodesFrom(version, node.rightHash); err != nil { - return err - } - } - - if node.version >= version { - if err := ndb.batch.Delete(ndb.nodeKey(hash)); err != nil { - return err - } - - ndb.nodeCache.Remove(hash) - } - - return nil -} - // Saves orphaned nodes to disk under a special prefix. // version: the new version being saved. // orphans: the orphan nodes created since version-1 -func (ndb *nodeDB) SaveOrphans(version int64, orphans map[string]int64) error { +func (ndb *nodeDB) SaveOrphans(version int64, orphans []*NodeKey) error { ndb.mtx.Lock() defer ndb.mtx.Unlock() @@ -638,9 +538,9 @@ func (ndb *nodeDB) SaveOrphans(version int64, orphans map[string]int64) error { return err } - for hash, fromVersion := range orphans { - logger.Debug("SAVEORPHAN %v-%v %X\n", fromVersion, toVersion, hash) - err := ndb.saveOrphan([]byte(hash), fromVersion, toVersion) + for _, nk := range orphans { + logger.Debug("SAVEORPHAN %d-%v\n", toVersion, nk) + err := ndb.saveOrphan(nk, toVersion) if err != nil { return err } @@ -649,12 +549,12 @@ func (ndb *nodeDB) SaveOrphans(version int64, orphans map[string]int64) error { } // Saves a single orphan to disk. -func (ndb *nodeDB) saveOrphan(hash []byte, fromVersion, toVersion int64) error { - if fromVersion > toVersion { - return fmt.Errorf("orphan expires before it comes alive. %d > %d", fromVersion, toVersion) +func (ndb *nodeDB) saveOrphan(nk *NodeKey, toVersion int64) error { + if nk.version > toVersion { + return fmt.Errorf("orphan expires before it comes alive. %d > %d", nk.version, toVersion) } - key := ndb.orphanKey(fromVersion, toVersion, hash) - if err := ndb.batch.Set(key, hash); err != nil { + key := ndb.orphanKey(toVersion, nk) + if err := ndb.batch.Set(key, []byte{}); err != nil { return err } return nil @@ -671,13 +571,17 @@ func (ndb *nodeDB) deleteOrphans(version int64) error { // Traverse orphans with a lifetime ending at the version specified. // TODO optimize. - return ndb.traverseOrphansVersion(version, func(key, hash []byte) error { + return ndb.traverseOrphansVersion(version, func(key, _ []byte) error { var fromVersion, toVersion int64 + var dummy, nonce int32 // See comment on `orphanKeyFmt`. Note that here, `version` and // `toVersion` are always equal. - orphanKeyFormat.Scan(key, &toVersion, &fromVersion) - + orphanKeyFormat.Scan(key, &toVersion, &dummy, &fromVersion, &nonce) + nk := &NodeKey{ + version: fromVersion, + nonce: nonce, + } // Delete orphan key and reverse-lookup key. if err := ndb.batch.Delete(key); err != nil { return err @@ -688,15 +592,15 @@ func (ndb *nodeDB) deleteOrphans(version int64) error { // spans a single version and that version is the one being deleted, we // can delete the orphan. Otherwise, we shorten its lifetime, by // moving its endpoint to the previous version. - if predecessor < fromVersion || fromVersion == toVersion { - logger.Debug("DELETE predecessor:%v fromVersion:%v toVersion:%v %X\n", predecessor, fromVersion, toVersion, hash) - if err := ndb.batch.Delete(ndb.nodeKey(hash)); err != nil { + if predecessor < fromVersion { + logger.Debug("DELETE toVersion:%d predecessor:%d node key:%v\n", toVersion, predecessor, nk) + if err := ndb.batch.Delete(ndb.nodeKey(nk)); err != nil { return err } - ndb.nodeCache.Remove(hash) + ndb.nodeCache.Remove(nk.GetKey()) } else { - logger.Debug("MOVE predecessor:%v fromVersion:%v toVersion:%v %X\n", predecessor, fromVersion, toVersion, hash) - err := ndb.saveOrphan(hash, fromVersion, predecessor) + logger.Debug("MOVE toVersion:%d predecessor:%d node key:%v\n", toVersion, predecessor, nk) + err := ndb.saveOrphan(nk, predecessor) if err != nil { return err } @@ -705,20 +609,20 @@ func (ndb *nodeDB) deleteOrphans(version int64) error { }) } -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) orphanKey(fromVersion, toVersion int64, hash []byte) []byte { - return orphanKeyFormat.Key(toVersion, fromVersion, hash) +func (ndb *nodeDB) orphanKey(toVersion int64, orphan *NodeKey) []byte { + return orphanKeyFormat.Key(toVersion, int32(0), orphan.version, orphan.nonce) } -func (ndb *nodeDB) rootKey(version int64) []byte { - return rootKeyFormat.Key(version) +func (ndb *nodeDB) versionKey(version int64) []byte { + return versionKeyFormat.Key(version) } func (ndb *nodeDB) getLatestVersion() (int64, error) { @@ -744,8 +648,8 @@ func (ndb *nodeDB) resetLatestVersion(version int64) { func (ndb *nodeDB) getPreviousVersion(version int64) (int64, error) { itr, err := ndb.db.ReverseIterator( - rootKeyFormat.Key(1), - rootKeyFormat.Key(version), + versionKeyFormat.Key(1), + versionKeyFormat.Key(version), ) if err != nil { return 0, err @@ -753,9 +657,9 @@ func (ndb *nodeDB) getPreviousVersion(version int64) (int64, error) { defer itr.Close() pversion := int64(-1) - for ; itr.Valid(); itr.Next() { + if itr.Valid() { k := itr.Key() - rootKeyFormat.Scan(k, &pversion) + versionKeyFormat.Scan(k, &pversion) return pversion, nil } @@ -766,8 +670,8 @@ func (ndb *nodeDB) getPreviousVersion(version int64) (int64, error) { return 0, nil } -// deleteRoot deletes the root entry from disk, but not the node it points to. -func (ndb *nodeDB) deleteRoot(version int64, checkLatestVersion bool) error { +// deleteVersion deletes the version entry from disk, but not the node it points to. +func (ndb *nodeDB) deleteVersion(version int64, checkLatestVersion bool) error { latestVersion, err := ndb.getLatestVersion() if err != nil { return err @@ -776,15 +680,66 @@ func (ndb *nodeDB) deleteRoot(version int64, checkLatestVersion bool) error { if checkLatestVersion && version == latestVersion { return errors.New("tried to delete latest version") } - if err := ndb.batch.Delete(ndb.rootKey(version)); err != nil { + if err := ndb.batch.Delete(ndb.versionKey(version)); err != nil { + return err + } + return nil +} + +func (ndb *nodeDB) HasVersion(version int64) (bool, error) { + return ndb.db.Has(ndb.versionKey(version)) +} + +func (ndb *nodeDB) getVersions() (versions []int64, err error) { + versions = make([]int64, 0) + err = ndb.traversePrefix(versionKeyFormat.Key(), func(k, _ []byte) error { + var version int64 + versionKeyFormat.Scan(k, &version) + versions = append(versions, version) + return nil + }) + return versions, err +} + +// GetRoot get the nodeKey of the root for the specific version. +func (ndb *nodeDB) GetRoot(version int64) (*NodeKey, error) { + value, err := ndb.db.Get(versionKeyFormat.Key(version)) + if err != nil { + return nil, err + } + rootVersion, _, err := encoding.DecodeVarint(value) + return &NodeKey{version: rootVersion, nonce: 1}, err +} + +// SaveRoot creates an entry on disk for the given root, so that it can be +// loaded later. +func (ndb *nodeDB) SaveRoot(version, rootVersion int64) error { + buf := new(bytes.Buffer) + if err := encoding.EncodeVarint(buf, rootVersion); err != nil { + return err + } + if err := ndb.batch.Set(ndb.versionKey(version), buf.Bytes()); err != nil { return err } + ndb.updateLatestVersion(version) + return nil } // Traverse orphans and return error if any, nil otherwise func (ndb *nodeDB) traverseOrphans(fn func(keyWithPrefix, v []byte) error) error { - return ndb.traversePrefix(orphanKeyFormat.Key(), fn) + ndb.resetLatestVersion(0) + latest, err := ndb.getLatestVersion() + if err != nil { + return err + } + + for version := int64(1); version <= latest; version++ { + if err := ndb.traversePrefix(orphanKeyFormat.Key(version, int32(0)), fn); err != nil { + return err + } + } + return nil } // Traverse fast nodes and return error if any, nil otherwise @@ -794,7 +749,7 @@ func (ndb *nodeDB) traverseFastNodes(fn func(k, v []byte) error) error { // Traverse orphans ending at a certain version. return error if any, nil otherwise func (ndb *nodeDB) traverseOrphansVersion(version int64, fn func(k, v []byte) error) error { - return ndb.traversePrefix(orphanKeyFormat.Key(version), fn) + return ndb.traversePrefix(orphanKeyFormat.Key(version, int32(0)), fn) } // Traverse all keys and return error if any, nil otherwise @@ -886,61 +841,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)) -} - -func (ndb *nodeDB) getRoots() (roots map[int64][]byte, err error) { - roots = make(map[int64][]byte) - err = ndb.traversePrefix(rootKeyFormat.Key(), func(k, v []byte) error { - var version int64 - rootKeyFormat.Scan(k, &version) - roots[version] = v - return nil - }) - return roots, err -} - -// SaveRoot creates an entry on disk for the given root, so that it can be -// loaded later. -func (ndb *nodeDB) SaveRoot(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.updateLatestVersion(version) - - return nil -} - func (ndb *nodeDB) incrVersionReaders(version int64) { ndb.mtx.Lock() defer ndb.mtx.Unlock() @@ -961,7 +861,7 @@ func (ndb *nodeDB) decrVersionReaders(version int64) { func (ndb *nodeDB) leafNodes() ([]*Node, error) { leaves := []*Node{} - err := ndb.traverseNodes(func(hash []byte, node *Node) error { + err := ndb.traverseNodes(func(node *Node) error { if node.isLeaf() { leaves = append(leaves, node) } @@ -978,7 +878,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 }) @@ -994,7 +894,7 @@ func (ndb *nodeDB) orphans() ([][]byte, error) { orphans := [][]byte{} err := ndb.traverseOrphans(func(k, v []byte) error { - orphans = append(orphans, v) + orphans = append(orphans, k) return nil }) if err != nil { @@ -1021,20 +921,27 @@ func (ndb *nodeDB) size() int { return size } -func (ndb *nodeDB) traverseNodes(fn func(hash []byte, node *Node) error) error { - nodes := []*Node{} +func (ndb *nodeDB) traverseNodes(fn func(node *Node) error) error { + ndb.resetLatestVersion(0) + latest, err := ndb.getLatestVersion() + if err != nil { + return err + } - err := ndb.traversePrefix(nodeKeyFormat.Key(), func(key, value []byte) error { - node, err := MakeNode(value) - if err != nil { + nodes := []*Node{} + for version := int64(1); version <= latest; version++ { + if err := ndb.traverseRange(nodeKeyFormat.Key(version, int32(1)), nodeKeyFormat.Key(version, int32(math.MaxInt32)), func(key, value []byte) error { + var nk NodeKey + nodeKeyFormat.Scan(key, &nk.version, &nk.nonce) + node, err := MakeNode(&nk, value) + if err != nil { + return err + } + nodes = append(nodes, node) + return nil + }); err != nil { return err } - nodeKeyFormat.Scan(key, &node.hash) - nodes = append(nodes, node) - return nil - }) - if err != nil { - return err } sort.Slice(nodes, func(i, j int) bool { @@ -1042,7 +949,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 } } @@ -1056,7 +963,7 @@ func (ndb *nodeDB) String() (string, error) { index := 0 - err := ndb.traversePrefix(rootKeyFormat.Key(), func(key, value []byte) error { + err := ndb.traversePrefix(versionKeyFormat.Key(), func(key, value []byte) error { fmt.Fprintf(buf, "%s: %x\n", key, value) return nil }) @@ -1077,18 +984,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 @@ -1102,7 +1007,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 0f2beecd1..35e7d0a21 100644 --- a/nodedb_test.go +++ b/nodedb_test.go @@ -14,19 +14,10 @@ import ( "github.com/cosmos/iavl/mock" ) -func BenchmarkNodeKey(b *testing.B) { - ndb := &nodeDB{} - hashes := makeHashes(b, 2432325) - for i := 0; i < b.N; i++ { - ndb.nodeKey(hashes[i]) - } -} - func BenchmarkOrphanKey(b *testing.B) { ndb := &nodeDB{} - hashes := makeHashes(b, 2432325) for i := 0; i < b.N; i++ { - ndb.orphanKey(1234, 1239, hashes[i]) + ndb.orphanKey(1234, &NodeKey{version: 1239, nonce: int32(i)}) } } @@ -116,7 +107,7 @@ func TestSetStorageVersion_DBFailure_OldKept(t *testing.T) { // rIterMock is used to get the latest version from disk. We are mocking that rIterMock returns latestTreeVersion from disk rIterMock.EXPECT().Valid().Return(true).Times(1) - rIterMock.EXPECT().Key().Return(rootKeyFormat.Key(expectedFastCacheVersion)).Times(1) + rIterMock.EXPECT().Key().Return(versionKeyFormat.Key(expectedFastCacheVersion)).Times(1) rIterMock.EXPECT().Close().Return(nil).Times(1) dbMock.EXPECT().ReverseIterator(gomock.Any(), gomock.Any()).Return(rIterMock, nil).Times(1) diff --git a/proof.go b/proof.go index 9f7c23649..4fdbc35e0 100644 --- a/proof.go +++ b/proof.go @@ -166,16 +166,16 @@ func (pln ProofLeafNode) Hash() ([]byte, error) { // If the key does not exist, returns the path to the next leaf left of key (w/ // path), except when key is less than the least item, in which case it returns // a path to the least item. -func (node *Node) PathToLeaf(t *ImmutableTree, key []byte) (PathToLeaf, *Node, error) { +func (node *Node) PathToLeaf(t *ImmutableTree, key []byte, version int64) (PathToLeaf, *Node, error) { path := new(PathToLeaf) - val, err := node.pathToLeaf(t, key, path) + val, err := node.pathToLeaf(t, key, version, path) return *path, val, err } // pathToLeaf is a helper which recursively constructs the PathToLeaf. // As an optimization the already constructed path is passed in as an argument // and is shared among recursive calls. -func (node *Node) pathToLeaf(t *ImmutableTree, key []byte, path *PathToLeaf) (*Node, error) { +func (node *Node) pathToLeaf(t *ImmutableTree, key []byte, version int64, path *PathToLeaf) (*Node, error) { if node.subtreeHeight == 0 { if bytes.Equal(node.key, key) { return node, nil @@ -183,6 +183,10 @@ func (node *Node) pathToLeaf(t *ImmutableTree, key []byte, path *PathToLeaf) (*N return node, errors.New("key does not exist") } + nodeVersion := version + if node.nodeKey != nil { + nodeVersion = node.nodeKey.version + } // Note that we do not store the left child in the ProofInnerNode when we're going to add the // left node as part of the path, similarly we don't store the right child info when going down // the right child node. This is done as an optimization since the child info is going to be @@ -197,7 +201,7 @@ func (node *Node) pathToLeaf(t *ImmutableTree, key []byte, path *PathToLeaf) (*N pin := ProofInnerNode{ Height: node.subtreeHeight, Size: node.size, - Version: node.version, + Version: nodeVersion, Left: nil, Right: rightNode.hash, } @@ -207,7 +211,7 @@ func (node *Node) pathToLeaf(t *ImmutableTree, key []byte, path *PathToLeaf) (*N if err != nil { return nil, err } - n, err := leftNode.pathToLeaf(t, key, path) + n, err := leftNode.pathToLeaf(t, key, version, path) return n, err } // right side @@ -219,7 +223,7 @@ func (node *Node) pathToLeaf(t *ImmutableTree, key []byte, path *PathToLeaf) (*N pin := ProofInnerNode{ Height: node.subtreeHeight, Size: node.size, - Version: node.version, + Version: nodeVersion, Left: leftNode.hash, Right: nil, } @@ -230,6 +234,6 @@ func (node *Node) pathToLeaf(t *ImmutableTree, key []byte, path *PathToLeaf) (*N return nil, err } - n, err := rightNode.pathToLeaf(t, key, path) + n, err := rightNode.pathToLeaf(t, key, version, path) return n, err } diff --git a/proof_ics23.go b/proof_ics23.go index 497a22934..981a9c1cd 100644 --- a/proof_ics23.go +++ b/proof_ics23.go @@ -108,11 +108,15 @@ func (t *ImmutableTree) createExistenceProof(key []byte) (*ics23.ExistenceProof, if err != nil { return nil, err } - path, node, err := t.root.PathToLeaf(t, key) + path, node, err := t.root.PathToLeaf(t, key, t.version+1) + nodeVersion := t.version + 1 + if node.nodeKey != nil { + nodeVersion = node.nodeKey.version + } return &ics23.ExistenceProof{ Key: node.key, Value: node.value, - Leaf: convertLeafOp(node.version), + Leaf: convertLeafOp(nodeVersion), Path: convertInnerOps(path), }, err } diff --git a/testutils_test.go b/testutils_test.go index 9ba0c2541..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, 0) + left = NewNode(i2b(l.(int)), nil) } if _, ok := r.(*Node); ok { right = r.(*Node) } else { - right = NewNode(i2b(r.(int)), nil, 0, 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/third_party/google/api/annotations.proto b/third_party/google/api/annotations.proto deleted file mode 100644 index 85c361b47..000000000 --- a/third_party/google/api/annotations.proto +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) 2015, Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -syntax = "proto3"; - -package google.api; - -import "google/api/http.proto"; -import "google/protobuf/descriptor.proto"; - -option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; -option java_multiple_files = true; -option java_outer_classname = "AnnotationsProto"; -option java_package = "com.google.api"; -option objc_class_prefix = "GAPI"; - -extend google.protobuf.MethodOptions { - // See `HttpRule`. - HttpRule http = 72295728; -} diff --git a/third_party/google/api/http.proto b/third_party/google/api/http.proto deleted file mode 100644 index a43cff771..000000000 --- a/third_party/google/api/http.proto +++ /dev/null @@ -1,376 +0,0 @@ -// Copyright 2019 Google LLC. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -syntax = "proto3"; - -package google.api; - -option cc_enable_arenas = true; -option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; -option java_multiple_files = true; -option java_outer_classname = "HttpProto"; -option java_package = "com.google.api"; -option objc_class_prefix = "GAPI"; - -// Defines the HTTP configuration for an API service. It contains a list of -// [HttpRule][google.api.HttpRule], each specifying the mapping of an RPC method -// to one or more HTTP REST API methods. -message Http { - // A list of HTTP configuration rules that apply to individual API methods. - // - // **NOTE:** All service configuration rules follow "last one wins" order. - repeated HttpRule rules = 1; - - // When set to true, URL path parameters will be fully URI-decoded except in - // cases of single segment matches in reserved expansion, where "%2F" will be - // left encoded. - // - // The default behavior is to not decode RFC 6570 reserved characters in multi - // segment matches. - bool fully_decode_reserved_expansion = 2; -} - -// # gRPC Transcoding -// -// gRPC Transcoding is a feature for mapping between a gRPC method and one or -// more HTTP REST endpoints. It allows developers to build a single API service -// that supports both gRPC APIs and REST APIs. Many systems, including [Google -// APIs](https://github.com/googleapis/googleapis), -// [Cloud Endpoints](https://cloud.google.com/endpoints), [gRPC -// Gateway](https://github.com/grpc-ecosystem/grpc-gateway), -// and [Envoy](https://github.com/envoyproxy/envoy) proxy support this feature -// and use it for large scale production services. -// -// `HttpRule` defines the schema of the gRPC/REST mapping. The mapping specifies -// how different portions of the gRPC request message are mapped to the URL -// path, URL query parameters, and HTTP request body. It also controls how the -// gRPC response message is mapped to the HTTP response body. `HttpRule` is -// typically specified as an `google.api.http` annotation on the gRPC method. -// -// Each mapping specifies a URL path template and an HTTP method. The path -// template may refer to one or more fields in the gRPC request message, as long -// as each field is a non-repeated field with a primitive (non-message) type. -// The path template controls how fields of the request message are mapped to -// the URL path. -// -// Example: -// -// service Messaging { -// rpc GetMessage(GetMessageRequest) returns (Message) { -// option (google.api.http) = { -// get: "/v1/{name=messages/*}" -// }; -// } -// } -// message GetMessageRequest { -// string name = 1; // Mapped to URL path. -// } -// message Message { -// string text = 1; // The resource content. -// } -// -// This enables an HTTP REST to gRPC mapping as below: -// -// HTTP | gRPC -// -----|----- -// `GET /v1/messages/123456` | `GetMessage(name: "messages/123456")` -// -// Any fields in the request message which are not bound by the path template -// automatically become HTTP query parameters if there is no HTTP request body. -// For example: -// -// service Messaging { -// rpc GetMessage(GetMessageRequest) returns (Message) { -// option (google.api.http) = { -// get:"/v1/messages/{message_id}" -// }; -// } -// } -// message GetMessageRequest { -// message SubMessage { -// string subfield = 1; -// } -// string message_id = 1; // Mapped to URL path. -// int64 revision = 2; // Mapped to URL query parameter `revision`. -// SubMessage sub = 3; // Mapped to URL query parameter `sub.subfield`. -// } -// -// This enables a HTTP JSON to RPC mapping as below: -// -// HTTP | gRPC -// -----|----- -// `GET /v1/messages/123456?revision=2&sub.subfield=foo` | -// `GetMessage(message_id: "123456" revision: 2 sub: SubMessage(subfield: -// "foo"))` -// -// Note that fields which are mapped to URL query parameters must have a -// primitive type or a repeated primitive type or a non-repeated message type. -// In the case of a repeated type, the parameter can be repeated in the URL -// as `...?param=A¶m=B`. In the case of a message type, each field of the -// message is mapped to a separate parameter, such as -// `...?foo.a=A&foo.b=B&foo.c=C`. -// -// For HTTP methods that allow a request body, the `body` field -// specifies the mapping. Consider a REST update method on the -// message resource collection: -// -// service Messaging { -// rpc UpdateMessage(UpdateMessageRequest) returns (Message) { -// option (google.api.http) = { -// patch: "/v1/messages/{message_id}" -// body: "message" -// }; -// } -// } -// message UpdateMessageRequest { -// string message_id = 1; // mapped to the URL -// Message message = 2; // mapped to the body -// } -// -// The following HTTP JSON to RPC mapping is enabled, where the -// representation of the JSON in the request body is determined by -// protos JSON encoding: -// -// HTTP | gRPC -// -----|----- -// `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: -// "123456" message { text: "Hi!" })` -// -// The special name `*` can be used in the body mapping to define that -// every field not bound by the path template should be mapped to the -// request body. This enables the following alternative definition of -// the update method: -// -// service Messaging { -// rpc UpdateMessage(Message) returns (Message) { -// option (google.api.http) = { -// patch: "/v1/messages/{message_id}" -// body: "*" -// }; -// } -// } -// message Message { -// string message_id = 1; -// string text = 2; -// } -// -// -// The following HTTP JSON to RPC mapping is enabled: -// -// HTTP | gRPC -// -----|----- -// `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: -// "123456" text: "Hi!")` -// -// Note that when using `*` in the body mapping, it is not possible to -// have HTTP parameters, as all fields not bound by the path end in -// the body. This makes this option more rarely used in practice when -// defining REST APIs. The common usage of `*` is in custom methods -// which don't use the URL at all for transferring data. -// -// It is possible to define multiple HTTP methods for one RPC by using -// the `additional_bindings` option. Example: -// -// service Messaging { -// rpc GetMessage(GetMessageRequest) returns (Message) { -// option (google.api.http) = { -// get: "/v1/messages/{message_id}" -// additional_bindings { -// get: "/v1/users/{user_id}/messages/{message_id}" -// } -// }; -// } -// } -// message GetMessageRequest { -// string message_id = 1; -// string user_id = 2; -// } -// -// This enables the following two alternative HTTP JSON to RPC mappings: -// -// HTTP | gRPC -// -----|----- -// `GET /v1/messages/123456` | `GetMessage(message_id: "123456")` -// `GET /v1/users/me/messages/123456` | `GetMessage(user_id: "me" message_id: -// "123456")` -// -// ## Rules for HTTP mapping -// -// 1. Leaf request fields (recursive expansion nested messages in the request -// message) are classified into three categories: -// - Fields referred by the path template. They are passed via the URL path. -// - Fields referred by the [HttpRule.body][google.api.HttpRule.body]. They are passed via the HTTP -// request body. -// - All other fields are passed via the URL query parameters, and the -// parameter name is the field path in the request message. A repeated -// field can be represented as multiple query parameters under the same -// name. -// 2. If [HttpRule.body][google.api.HttpRule.body] is "*", there is no URL query parameter, all fields -// are passed via URL path and HTTP request body. -// 3. If [HttpRule.body][google.api.HttpRule.body] is omitted, there is no HTTP request body, all -// fields are passed via URL path and URL query parameters. -// -// ### Path template syntax -// -// Template = "/" Segments [ Verb ] ; -// Segments = Segment { "/" Segment } ; -// Segment = "*" | "**" | LITERAL | Variable ; -// Variable = "{" FieldPath [ "=" Segments ] "}" ; -// FieldPath = IDENT { "." IDENT } ; -// Verb = ":" LITERAL ; -// -// The syntax `*` matches a single URL path segment. The syntax `**` matches -// zero or more URL path segments, which must be the last part of the URL path -// except the `Verb`. -// -// The syntax `Variable` matches part of the URL path as specified by its -// template. A variable template must not contain other variables. If a variable -// matches a single path segment, its template may be omitted, e.g. `{var}` -// is equivalent to `{var=*}`. -// -// The syntax `LITERAL` matches literal text in the URL path. If the `LITERAL` -// contains any reserved character, such characters should be percent-encoded -// before the matching. -// -// If a variable contains exactly one path segment, such as `"{var}"` or -// `"{var=*}"`, when such a variable is expanded into a URL path on the client -// side, all characters except `[-_.~0-9a-zA-Z]` are percent-encoded. The -// server side does the reverse decoding. Such variables show up in the -// [Discovery -// Document](https://developers.google.com/discovery/v1/reference/apis) as -// `{var}`. -// -// If a variable contains multiple path segments, such as `"{var=foo/*}"` -// or `"{var=**}"`, when such a variable is expanded into a URL path on the -// client side, all characters except `[-_.~/0-9a-zA-Z]` are percent-encoded. -// The server side does the reverse decoding, except "%2F" and "%2f" are left -// unchanged. Such variables show up in the -// [Discovery -// Document](https://developers.google.com/discovery/v1/reference/apis) as -// `{+var}`. -// -// ## Using gRPC API Service Configuration -// -// gRPC API Service Configuration (service config) is a configuration language -// for configuring a gRPC service to become a user-facing product. The -// service config is simply the YAML representation of the `google.api.Service` -// proto message. -// -// As an alternative to annotating your proto file, you can configure gRPC -// transcoding in your service config YAML files. You do this by specifying a -// `HttpRule` that maps the gRPC method to a REST endpoint, achieving the same -// effect as the proto annotation. This can be particularly useful if you -// have a proto that is reused in multiple services. Note that any transcoding -// specified in the service config will override any matching transcoding -// configuration in the proto. -// -// Example: -// -// http: -// rules: -// # Selects a gRPC method and applies HttpRule to it. -// - selector: example.v1.Messaging.GetMessage -// get: /v1/messages/{message_id}/{sub.subfield} -// -// ## Special notes -// -// When gRPC Transcoding is used to map a gRPC to JSON REST endpoints, the -// proto to JSON conversion must follow the [proto3 -// specification](https://developers.google.com/protocol-buffers/docs/proto3#json). -// -// While the single segment variable follows the semantics of -// [RFC 6570](https://tools.ietf.org/html/rfc6570) Section 3.2.2 Simple String -// Expansion, the multi segment variable **does not** follow RFC 6570 Section -// 3.2.3 Reserved Expansion. The reason is that the Reserved Expansion -// does not expand special characters like `?` and `#`, which would lead -// to invalid URLs. As the result, gRPC Transcoding uses a custom encoding -// for multi segment variables. -// -// The path variables **must not** refer to any repeated or mapped field, -// because client libraries are not capable of handling such variable expansion. -// -// The path variables **must not** capture the leading "/" character. The reason -// is that the most common use case "{var}" does not capture the leading "/" -// character. For consistency, all path variables must share the same behavior. -// -// Repeated message fields must not be mapped to URL query parameters, because -// no client library can support such complicated mapping. -// -// If an API needs to use a JSON array for request or response body, it can map -// the request or response body to a repeated field. However, some gRPC -// Transcoding implementations may not support this feature. -message HttpRule { - // Selects a method to which this rule applies. - // - // Refer to [selector][google.api.DocumentationRule.selector] for syntax details. - string selector = 1; - - // Determines the URL pattern is matched by this rules. This pattern can be - // used with any of the {get|put|post|delete|patch} methods. A custom method - // can be defined using the 'custom' field. - oneof pattern { - // Maps to HTTP GET. Used for listing and getting information about - // resources. - string get = 2; - - // Maps to HTTP PUT. Used for replacing a resource. - string put = 3; - - // Maps to HTTP POST. Used for creating a resource or performing an action. - string post = 4; - - // Maps to HTTP DELETE. Used for deleting a resource. - string delete = 5; - - // Maps to HTTP PATCH. Used for updating a resource. - string patch = 6; - - // The custom pattern is used for specifying an HTTP method that is not - // included in the `pattern` field, such as HEAD, or "*" to leave the - // HTTP method unspecified for this rule. The wild-card rule is useful - // for services that provide content to Web (HTML) clients. - CustomHttpPattern custom = 8; - } - - // The name of the request field whose value is mapped to the HTTP request - // body, or `*` for mapping all request fields not captured by the path - // pattern to the HTTP body, or omitted for not having any HTTP request body. - // - // NOTE: the referred field must be present at the top-level of the request - // message type. - string body = 7; - - // Optional. The name of the response field whose value is mapped to the HTTP - // response body. When omitted, the entire response message will be used - // as the HTTP response body. - // - // NOTE: The referred field must be present at the top-level of the response - // message type. - string response_body = 12; - - // Additional HTTP bindings for the selector. Nested bindings must - // not contain an `additional_bindings` field themselves (that is, - // the nesting may only be one level deep). - repeated HttpRule additional_bindings = 11; -} - -// A custom pattern is used for defining custom HTTP verb. -message CustomHttpPattern { - // The name of this custom HTTP verb. - string kind = 1; - - // The path matched by this custom verb. - string path = 2; -} \ No newline at end of file diff --git a/tree_dotgraph.go b/tree_dotgraph.go index bb38e61a2..dce74e0f7 100644 --- a/tree_dotgraph.go +++ b/tree_dotgraph.go @@ -45,7 +45,7 @@ func WriteDOTGraph(w io.Writer, tree *ImmutableTree, paths []PathToLeaf) { ctx := &graphContext{} // TODO: handle error - tree.root.hashWithCount() //nolint:errcheck + tree.root.hashWithCount(tree.version + 1) //nolint:errcheck tree.root.traverse(tree, true, func(node *Node) bool { graphNode := &graphNode{ Attrs: map[string]string{}, @@ -58,7 +58,7 @@ func WriteDOTGraph(w io.Writer, tree *ImmutableTree, paths []PathToLeaf) { graphNode.Label = mkLabel(unsafeToStr(node.key), 16, "sans-serif") graphNode.Label += mkLabel(shortHash, 10, "monospace") - graphNode.Label += mkLabel(fmt.Sprintf("version=%d", node.version), 10, "monospace") + graphNode.Label += mkLabel(fmt.Sprintf("nodeKey=%v", node.nodeKey), 10, "monospace") if node.value != nil { graphNode.Label += mkLabel(unsafeToStr(node.value), 10, "sans-serif") diff --git a/tree_random_test.go b/tree_random_test.go index 42f5e309f..239aadde3 100644 --- a/tree_random_test.go +++ b/tree_random_test.go @@ -350,7 +350,7 @@ func assertEmptyDatabase(t *testing.T, tree *MutableTree) { secondKey := foundKeys[1] require.True(t, strings.HasPrefix(firstKey, metadataKeyFormat.Prefix())) - require.True(t, strings.HasPrefix(secondKey, rootKeyFormat.Prefix())) + require.True(t, strings.HasPrefix(secondKey, versionKeyFormat.Prefix())) require.Equal(t, string(metadataKeyFormat.KeyBytes([]byte(storageVersionKey))), firstKey, "Unexpected storage version key") @@ -361,7 +361,7 @@ func assertEmptyDatabase(t *testing.T, tree *MutableTree) { require.Equal(t, fastStorageVersionValue+fastStorageVersionDelimiter+strconv.Itoa(int(latestVersion)), string(storageVersionValue)) var foundVersion int64 - rootKeyFormat.Scan([]byte(secondKey), &foundVersion) + versionKeyFormat.Scan([]byte(secondKey), &foundVersion) require.Equal(t, version, foundVersion, "Unexpected root version") } diff --git a/tree_test.go b/tree_test.go index 85b2415d3..34b4806eb 100644 --- a/tree_test.go +++ b/tree_test.go @@ -67,7 +67,7 @@ func TestVersionedRandomTree(t *testing.T) { } tree.SaveVersion() } - roots, err := tree.ndb.getRoots() + roots, err := tree.ndb.getVersions() require.NoError(err) require.Equal(versions, len(roots), "wrong number of roots") @@ -366,7 +366,6 @@ func TestVersionedEmptyTree(t *testing.T) { require.False(tree.VersionExists(3)) tree.Set([]byte("k"), []byte("v")) - require.EqualValues(5, tree.root.version) // Now reload the tree. @@ -1284,7 +1283,8 @@ func TestOrphans(t *testing.T) { err = tree.ndb.traverseOrphans(func(k, v []byte) error { var fromVersion, toVersion int64 - orphanKeyFormat.Scan(k, &toVersion, &fromVersion) + var dummy int32 + orphanKeyFormat.Scan(k, &toVersion, &dummy, &fromVersion) require.True(fromVersion == int64(1) || toVersion == int64(99), fmt.Sprintf(`Unexpected orphan key exists: %v with fromVersion = %d and toVersion = %d.\n Any orphan remaining in db should have either fromVersion == 1 or toVersion == 99. Since Version 1 and 99 are only versions in db`, k, fromVersion, toVersion)) return nil @@ -1351,6 +1351,7 @@ func TestCopyValueSemantics(t *testing.T) { val[1] = '2' val, err = tree.Get([]byte("k")) + require.NoError(err) require.Equal([]byte("v2"), val) } @@ -1732,7 +1733,7 @@ func TestLoadVersionForOverwritingCase2(t *testing.T) { nodes, err := tree.ndb.nodes() require.NoError(err) for _, n := range nodes { - if n.version > 1 { + if n.nodeKey.version > 1 { removedNodes = append(removedNodes, n) } } @@ -1747,7 +1748,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") } @@ -1788,7 +1789,7 @@ func TestLoadVersionForOverwritingCase3(t *testing.T) { nodes, err := tree.ndb.nodes() require.NoError(err) for _, n := range nodes { - if n.version > 1 { + if n.nodeKey.version > 1 { removedNodes = append(removedNodes, n) } } @@ -1802,7 +1803,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") } @@ -2008,8 +2009,8 @@ func TestNodeCacheStatisic(t *testing.T) { cacheSize: numKeyVals, expectFastCacheHitCnt: numKeyVals, expectFastCacheMissCnt: 0, - expectCacheHitCnt: 1, - expectCacheMissCnt: 0, + expectCacheHitCnt: 0, + expectCacheMissCnt: 1, }, "without_cache": { cacheSize: 0, diff --git a/util.go b/util.go index 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 }