From fa59db28796b6aca7698a90b6cd5cf84aa7ee5fe Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 1 Feb 2023 14:39:40 +0100 Subject: [PATCH] feat: make `LazyLoadVersion` validate `InitialVersion` the same as `LoadVersion` (backport #638) (#666) Co-authored-by: yihuang Co-authored-by: Marko --- CHANGELOG.md | 1 + mutable_tree.go | 40 ++++++++++++++++++++++++++++++++++------ mutable_tree_test.go | 15 ++++++++++++++- nodedb.go | 15 +++++++++++++++ 4 files changed, 64 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c0fad7bf6..d50da107a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,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 831a142e4..f5856125b 100644 --- a/mutable_tree.go +++ b/mutable_tree.go @@ -460,17 +460,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) } @@ -611,10 +620,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 } @@ -651,6 +668,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 b2c2c8ac5..8ba2d7db6 100644 --- a/mutable_tree_test.go +++ b/mutable_tree_test.go @@ -299,12 +299,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) @@ -312,7 +320,12 @@ func TestMutableTree_InitialVersion(t *testing.T) { require.NoError(t, err) assert.EqualValues(t, 10, version) - tree.Set([]byte("c"), []byte{0x03}) + 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() require.NoError(t, err) assert.EqualValues(t, 11, version) diff --git a/nodedb.go b/nodedb.go index cdb5956d8..96c8f7e49 100644 --- a/nodedb.go +++ b/nodedb.go @@ -762,6 +762,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()