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

add InitialVersion option #299

Merged
merged 5 commits into from
Jul 30, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,11 @@ https://github.com/cosmos/iavl. This also affects the module import path, which
### Improvements

- Proofs are now encoded using Protobuf instead of Amino. The binary encoding is identical.
- Introduced new methods `CreateMembershipProof` and `CreateNonMembershipProof` to return
ics23 ExistenceProof and NonExistenceProofs respectively

- Introduced new methods `GetMembershipProof` and `GetNonMembershipProof` on `ImmutableTree` to return
ics23 ExistenceProof and NonExistenceProofs respectively.

- Added `Options.InitialVersion` to specify the initial version to start new IAVL trees from.

### Bug Fixes

Expand Down
12 changes: 12 additions & 0 deletions mutable_tree.go
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,7 @@ func (tree *MutableTree) LoadVersion(targetVersion int64) (int64, error) {
return 0, nil
}

firstVersion := int64(0)
latestVersion := int64(0)

var latestRoot []byte
Expand All @@ -337,13 +338,21 @@ func (tree *MutableTree) LoadVersion(targetVersion int64) (int64, error) {
latestVersion = version
latestRoot = r
}
if firstVersion == 0 || version < firstVersion {
firstVersion = version
}
}

if !(targetVersion == 0 || latestVersion == targetVersion) {
return latestVersion, fmt.Errorf("wanted to load target %v but only found up to %v",
targetVersion, latestVersion)
}

if firstVersion > 0 && firstVersion < int64(tree.ndb.opts.InitialVersion) {
return latestVersion, fmt.Errorf("initial version set to %v, but found earlier version %v",
tree.ndb.opts.InitialVersion, firstVersion)
}

t := &ImmutableTree{
ndb: tree.ndb,
version: latestVersion,
Expand Down Expand Up @@ -439,6 +448,9 @@ func (tree *MutableTree) GetVersioned(key []byte, version int64) (
// the tree. Returns the hash and new version number.
func (tree *MutableTree) SaveVersion() ([]byte, int64, error) {
version := tree.version + 1
if version == 1 && tree.ndb.opts.InitialVersion > 0 {
version = int64(tree.ndb.opts.InitialVersion)
}

if tree.versions[version] {
// If the version already exists, return an error as we're attempting to overwrite.
Expand Down
42 changes: 42 additions & 0 deletions mutable_tree_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"runtime"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

db "github.com/tendermint/tm-db"
Expand Down Expand Up @@ -105,6 +106,47 @@ func TestMutableTree_DeleteVersions(t *testing.T) {
}
}

func TestMutableTree_InitialVersion(t *testing.T) {
memDB := db.NewMemDB()
tree, err := NewMutableTreeWithOpts(memDB, 0, &Options{InitialVersion: 9})
require.NoError(t, err)

tree.Set([]byte("a"), []byte{0x01})
_, version, err := tree.SaveVersion()
require.NoError(t, err)
assert.EqualValues(t, 9, version)

tree.Set([]byte("b"), []byte{0x02})
_, version, err = tree.SaveVersion()
require.NoError(t, err)
assert.EqualValues(t, 10, version)

// Reloading the tree with the same initial version is fine
tree, err = NewMutableTreeWithOpts(memDB, 0, &Options{InitialVersion: 9})
require.NoError(t, err)
version, err = tree.Load()
require.NoError(t, err)
assert.EqualValues(t, 10, version)

// Reloading the tree with an initial version beyond the lowest should error
tree, err = NewMutableTreeWithOpts(memDB, 0, &Options{InitialVersion: 10})
require.NoError(t, err)
_, err = tree.Load()
require.Error(t, err)

// Reloading the tree with a lower initial version is fine, and new versions can be produced
tree, err = NewMutableTreeWithOpts(memDB, 0, &Options{InitialVersion: 3})
require.NoError(t, err)
version, err = tree.Load()
require.NoError(t, err)
assert.EqualValues(t, 10, version)

tree.Set([]byte("c"), []byte{0x03})
_, version, err = tree.SaveVersion()
require.NoError(t, err)
assert.EqualValues(t, 11, version)
}

func BenchmarkMutableTree_Set(b *testing.B) {
db, err := db.NewDB("test", db.MemDBBackend, "")
require.NoError(b, err)
Expand Down
6 changes: 4 additions & 2 deletions nodedb.go
Original file line number Diff line number Diff line change
Expand Up @@ -520,8 +520,10 @@ func (ndb *nodeDB) saveRoot(hash []byte, version int64) error {
ndb.mtx.Lock()
defer ndb.mtx.Unlock()

if version != ndb.getLatestVersion()+1 {
return fmt.Errorf("must save consecutive versions; expected %d, got %d", ndb.getLatestVersion()+1, version)
// We allow the initial version to be arbitrary
latest := ndb.getLatestVersion()
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 {
Expand Down
11 changes: 8 additions & 3 deletions options.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,17 @@ package iavl

// Options define tree options.
type Options struct {
// Sync synchronously flushes all writes to storage, using e.g. the fsync syscall.
// Disabling this significantly improves performance, but can lose data on e.g. power loss.
Sync bool

// InitialVersion specifies the initial version number. If any versions already exist below
// this, an error is returned when loading the tree. Only used for the initial SaveVersion()
// call.
InitialVersion uint64
}

// DefaultOptions returns the default options for IAVL.
func DefaultOptions() *Options {
return &Options{
Sync: false,
}
return &Options{}
}