Skip to content

Commit

Permalink
feat: make LazyLoadVersion validate InitialVersion the same as `L…
Browse files Browse the repository at this point in the history
…oadVersion` (backport cosmos#638) (cosmos#666)

Co-authored-by: yihuang <huang@crypto.com>
Co-authored-by: Marko <marbar3778@yahoo.com>
  • Loading branch information
3 people authored and ankurdotb committed Feb 28, 2023
1 parent f812636 commit fa59db2
Show file tree
Hide file tree
Showing 4 changed files with 64 additions and 7 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
40 changes: 34 additions & 6 deletions mutable_tree.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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.
Expand Down
15 changes: 14 additions & 1 deletion mutable_tree_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -299,20 +299,33 @@ 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)
version, err = tree.Load()
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)
Expand Down
15 changes: 15 additions & 0 deletions nodedb.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down

0 comments on commit fa59db2

Please sign in to comment.