diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b42bde17..4faa2f480 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ - [#640](https://github.com/cosmos/iavl/pull/640) commit `NodeDB` batch in `LoadVersionForOverwriting`. - [#636](https://github.com/cosmos/iavl/pull/636) Speed up rollback method: `LoadVersionForOverwriting`. - [#654](https://github.com/cosmos/iavl/pull/654) Add API `TraverseStateChanges` to extract state changes from iavl versions. +- [#638](https://github.com/cosmos/iavl/pull/638) Make LazyLoadVersion check the opts.InitialVersion, add API `LazyLoadVersionForOverwriting`. ## 0.19.4 (October 28, 2022) diff --git a/mutable_tree.go b/mutable_tree.go index 238c49969..ef1d732a7 100644 --- a/mutable_tree.go +++ b/mutable_tree.go @@ -462,17 +462,26 @@ func (tree *MutableTree) Load() (int64, error) { } // LazyLoadVersion attempts to lazy load only the specified target version -// without loading previous roots/versions. Lazy loading should be used in cases -// where only reads are expected. Any writes to a lazy loaded tree may result in -// unexpected behavior. If the targetVersion is non-positive, the latest version +// without loading previous roots/versions. If the targetVersion is non-positive, the latest version // will be loaded by default. If the latest version is non-positive, this method // performs a no-op. Otherwise, if the root does not exist, an error will be // returned. func (tree *MutableTree) LazyLoadVersion(targetVersion int64) (int64, error) { + firstVersion, err := tree.ndb.getFirstVersion() + if err != nil { + return 0, err + } + latestVersion, err := tree.ndb.getLatestVersion() if err != nil { return 0, err } + + 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) + } + if latestVersion < targetVersion { return latestVersion, fmt.Errorf("wanted to load target %d but only found up to %d", targetVersion, latestVersion) } @@ -613,10 +622,18 @@ func (tree *MutableTree) LoadVersion(targetVersion int64) (int64, error) { return latestVersion, nil } -// LoadVersionForOverwriting attempts to load a tree at a previously committed +// loadVersionForOverwriting attempts to load a tree at a previously committed // version, or the latest version below it. Any versions greater than targetVersion will be deleted. -func (tree *MutableTree) LoadVersionForOverwriting(targetVersion int64) (int64, error) { - latestVersion, err := tree.LoadVersion(targetVersion) +func (tree *MutableTree) loadVersionForOverwriting(targetVersion int64, lazy bool) (int64, error) { + var ( + latestVersion int64 + err error + ) + if lazy { + latestVersion, err = tree.LazyLoadVersion(targetVersion) + } else { + latestVersion, err = tree.LoadVersion(targetVersion) + } if err != nil { return latestVersion, err } @@ -653,6 +670,17 @@ func (tree *MutableTree) LoadVersionForOverwriting(targetVersion int64) (int64, return latestVersion, nil } +// LoadVersionForOverwriting attempts to load a tree at a previously committed +// version, or the latest version below it. Any versions greater than targetVersion will be deleted. +func (tree *MutableTree) LoadVersionForOverwriting(targetVersion int64) (int64, error) { + return tree.loadVersionForOverwriting(targetVersion, false) +} + +// LazyLoadVersionForOverwriting is the lazy version of `LoadVersionForOverwriting`. +func (tree *MutableTree) LazyLoadVersionForOverwriting(targetVersion int64) (int64, error) { + return tree.loadVersionForOverwriting(targetVersion, true) +} + // Returns true if the tree may be auto-upgraded, false otherwise // An example of when an upgrade may be performed is when we are enaling fast storage for the first time or // need to overwrite fast nodes due to mismatch with live state. diff --git a/mutable_tree_test.go b/mutable_tree_test.go index ffd2e1e84..13af8c885 100644 --- a/mutable_tree_test.go +++ b/mutable_tree_test.go @@ -385,12 +385,20 @@ func TestMutableTree_InitialVersion(t *testing.T) { require.NoError(t, err) assert.EqualValues(t, 10, version) + // Check `LazyLoadVersion` behaviors the same as `LoadVersion` + version, err = tree.LazyLoadVersion(0) + 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}, false) require.NoError(t, err) _, err = tree.Load() require.Error(t, err) + _, err = tree.LazyLoadVersion(0) + 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}, false) require.NoError(t, err) @@ -398,6 +406,10 @@ func TestMutableTree_InitialVersion(t *testing.T) { require.NoError(t, err) assert.EqualValues(t, 10, version) + version, err = tree.LazyLoadVersion(0) + require.NoError(t, err) + assert.EqualValues(t, 10, version) + _, err = tree.Set([]byte("c"), []byte{0x03}) require.NoError(t, err) _, version, err = tree.SaveVersion() diff --git a/nodedb.go b/nodedb.go index baf4fc313..e49ddb78b 100644 --- a/nodedb.go +++ b/nodedb.go @@ -761,6 +761,21 @@ func (ndb *nodeDB) getPreviousVersion(version int64) (int64, error) { return 0, nil } +// getFirstVersion returns the first version in the iavl tree, returns 0 if it's empty. +func (ndb *nodeDB) getFirstVersion() (int64, error) { + itr, err := dbm.IteratePrefix(ndb.db, rootKeyFormat.Key()) + if err != nil { + return 0, err + } + defer itr.Close() + if itr.Valid() { + var version int64 + rootKeyFormat.Scan(itr.Key(), &version) + return version, nil + } + 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 { latestVersion, err := ndb.getLatestVersion()