Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: replace the node key #597

Closed
wants to merge 15 commits into from
10 changes: 5 additions & 5 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -55,18 +55,18 @@ 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

# fullbench is extra tests needing lots of memory and to run locally
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
Expand Down
41 changes: 8 additions & 33 deletions basic_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
package iavl

import (
"bytes"
"encoding/hex"
mrand "math/rand"
"sort"
Expand Down Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion benchmarks/bench_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
4 changes: 2 additions & 2 deletions export.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ var ErrorExportDone = errors.New("export is complete")
type ExportNode struct {
Key []byte
Value []byte
Version int64
NodeKey *NodeKey
Height int8
}

Expand Down Expand Up @@ -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,
}

Expand Down
18 changes: 9 additions & 9 deletions export_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
1 change: 0 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 0 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down
20 changes: 3 additions & 17 deletions immutable_tree.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ type ImmutableTree struct {
root *Node
ndb *nodeDB
version int64
nonce int64
skipFastStorageUpgrade bool
}

Expand Down Expand Up @@ -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 {
Expand All @@ -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
Expand Down Expand Up @@ -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
})
Expand Down Expand Up @@ -323,18 +315,12 @@ func (t *ImmutableTree) clone() *ImmutableTree {
root: t.root,
ndb: t.ndb,
version: t.version,
nonce: t.nonce,
}
}

// nodeSize is like Size, but includes inner nodes too.
//
//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)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is only correct if the tree is a full one?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

right, this API is only used for the test purpose right now

}
27 changes: 14 additions & 13 deletions import.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
}

Expand All @@ -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 {
Expand All @@ -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
}
Expand All @@ -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
}

Expand All @@ -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)
Expand All @@ -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:
Expand Down
18 changes: 9 additions & 9 deletions import_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
}
Expand All @@ -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()
Expand All @@ -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()
Expand All @@ -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()
Expand Down
Loading