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(store/v2): handle store upgrades in RootStore #18277

Merged
merged 16 commits into from
Nov 1, 2023
10 changes: 5 additions & 5 deletions store/kv/branch/store.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package branch

import (
"fmt"
"io"
"slices"
"sync"
Expand Down Expand Up @@ -84,17 +85,16 @@ func (s *Store) GetChangeset() *store.Changeset {
return store.NewChangeset(pairs...)
}

func (s *Store) Reset() error {
func (s *Store) Reset(toVersion uint64) error {
s.mu.Lock()
defer s.mu.Unlock()

latestVersion, err := s.storage.GetLatestVersion()
if err != nil {
return err
if err := s.storage.SetLatestVersion(toVersion); err != nil {
return fmt.Errorf("failed to set SS latest version %d: %w", toVersion, err)
}

clear(s.changeset)
s.version = latestVersion
s.version = toVersion

return nil
}
Expand Down
2 changes: 1 addition & 1 deletion store/kv/branch/store_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ func (s *StoreTestSuite) TestGetChangeset() {
}

func (s *StoreTestSuite) TestReset() {
s.Require().NoError(s.kvStore.Reset())
s.Require().NoError(s.kvStore.Reset(1))

cs := s.kvStore.GetChangeset()
s.Require().Zero(cs.Size())
Expand Down
4 changes: 2 additions & 2 deletions store/kv/gas/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,8 @@ func (s *Store) GetChangeset() *store.Changeset {
return s.parent.GetChangeset()
}

func (s *Store) Reset() error {
return s.parent.Reset()
func (s *Store) Reset(toVersion uint64) error {
return s.parent.Reset(toVersion)
}

func (s *Store) Write() {
Expand Down
2 changes: 1 addition & 1 deletion store/kv/gas/store_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ func (s *StoreTestSuite) SetupTest() {
}

func (s *StoreTestSuite) TearDownTest() {
err := s.gasKVStore.Reset()
err := s.gasKVStore.Reset(1)
s.Require().NoError(err)
}

Expand Down
2 changes: 1 addition & 1 deletion store/kv/mem/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ func (s *Store) GetChangeset() *store.Changeset {
return store.NewChangeset(kvPairs...)
}

func (s *Store) Reset() error {
func (s *Store) Reset(_ uint64) error {
s.tree.Clear()
return nil
}
alexanderbez marked this conversation as resolved.
Show resolved Hide resolved
Expand Down
2 changes: 1 addition & 1 deletion store/kv/mem/store_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ func (s *StoreTestSuite) TestGetChangeset() {
}

func (s *StoreTestSuite) TestReset() {
s.Require().NoError(s.kvStore.Reset())
s.Require().NoError(s.kvStore.Reset(1))

cs := s.kvStore.GetChangeset()
s.Require().Zero(cs.Size())
Expand Down
4 changes: 2 additions & 2 deletions store/kv/trace/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,8 @@ func (s *Store) Delete(key []byte) {
s.parent.Delete(key)
}

func (s *Store) Reset() error {
return s.parent.Reset()
func (s *Store) Reset(toVersion uint64) error {
return s.parent.Reset(toVersion)
}

func (s *Store) Write() {
Expand Down
50 changes: 27 additions & 23 deletions store/root/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,15 +101,6 @@ func (s *Store) GetSCStore(_ string) store.Tree {
return s.stateCommitment
}

func (s *Store) LoadLatestVersion() error {
lv, err := s.GetLatestVersion()
if err != nil {
return err
}

return s.loadVersion(lv, nil)
}

// LastCommitID returns a CommitID based off of the latest internal CommitInfo.
// If an internal CommitInfo is not set, a new one will be returned with only the
// latest version set, which is based off of the SS view.
Comment on lines 101 to 106
Copy link
Contributor

Choose a reason for hiding this comment

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

The function LastCommitID is returning a new CommitID with only the latest version set when lastCommitInfo is nil. This might lead to unexpected behavior if the caller expects a fully populated CommitID. Consider returning an error in this case.

Expand Down Expand Up @@ -173,11 +164,6 @@ func (s *Store) Query(storeKey string, version uint64, key []byte, prove bool) (
return result, nil
}

// LoadVersion loads a specific version returning an error upon failure.
func (s *Store) LoadVersion(v uint64) (err error) {
return s.loadVersion(v, nil)
}

// GetKVStore returns the store's root KVStore. Any writes to this store without
// branching will be committed to SC and SS upon Commit(). Branching will create
// a branched KVStore that allow writes to be discarded and propagated to the
Expand All @@ -198,17 +184,37 @@ func (s *Store) GetBranchedKVStore(_ string) store.BranchedKVStore {
return s.rootKVStore
}

func (s *Store) loadVersion(v uint64, upgrades any) error {
func (s *Store) LoadLatestVersion() error {
lv, err := s.GetLatestVersion()
if err != nil {
return err
}

return s.loadVersion(lv, nil)
}

func (s *Store) LoadVersion(version uint64) error {
return s.loadVersion(version, nil)
}

func (s *Store) loadVersion(v uint64, _ *store.StoreUpgrades) error {
Copy link
Contributor

Choose a reason for hiding this comment

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

It is a WIP? couldn't see the call of loadVersion with upgrades.

Copy link
Contributor Author

@alexanderbez alexanderbez Nov 1, 2023

Choose a reason for hiding this comment

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

This implementation doesn't support store upgrades since it's all a single tree/store key. In other words, store keys are ignored in the single tree implementation.

s.logger.Debug("loading version", "version", v)

// Reset the root KVStore s.t. the latest version is v. Any writes will
// overwrite existing versions.
if err := s.rootKVStore.Reset(v); err != nil {
return err
}

if err := s.stateCommitment.LoadVersion(v); err != nil {
return fmt.Errorf("failed to load SS version %d: %w", v, err)
}

// TODO: Complete this method to handle upgrades. See legacy RMS loadVersion()
// for reference.
//
// Ref: https://github.com/cosmos/cosmos-sdk/issues/17314
s.workingHash = nil
s.commitHeader = nil

// set lastCommitInfo explicitly s.t. Commit commits the correct version, i.e. v+1
s.lastCommitInfo = &store.CommitInfo{Version: v}

return nil
}
Copy link
Contributor

Choose a reason for hiding this comment

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

The function loadVersion is ignoring the StoreUpgrades parameter. If this is intentional, consider documenting this behavior in the function comment to avoid confusion.

Expand Down Expand Up @@ -305,7 +311,7 @@ func (s *Store) Commit() ([]byte, error) {
s.lastCommitInfo.Timestamp = s.commitHeader.GetTime()
}

if err := s.rootKVStore.Reset(); err != nil {
if err := s.rootKVStore.Reset(version); err != nil {
return nil, fmt.Errorf("failed to reset root KVStore: %w", err)
}

Comment on lines 311 to 317
Copy link
Contributor

Choose a reason for hiding this comment

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

The function Commit is resetting the rootKVStore to the current version after committing the changes. This might lead to data loss if there are uncommitted changes in the rootKVStore. Consider adding a check to ensure that there are no uncommitted changes before resetting the rootKVStore.

Expand Down Expand Up @@ -341,16 +347,14 @@ func (s *Store) writeSC() error {
version = previousHeight + 1
}

workingHash := s.stateCommitment.WorkingHash()

s.lastCommitInfo = &store.CommitInfo{
Version: version,
StoreInfos: []store.StoreInfo{
{
Name: defaultStoreKey,
CommitID: store.CommitID{
Version: version,
Hash: workingHash,
Hash: s.stateCommitment.WorkingHash(),
},
},
},
Comment on lines 347 to 360
Copy link
Contributor

Choose a reason for hiding this comment

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

The function writeSC is creating a new CommitInfo with only one StoreInfo for the default store key. This might lead to unexpected behavior if the caller expects a CommitInfo with all the StoreInfos. Consider adding a check to ensure that all the StoreInfos are included in the CommitInfo.

Expand Down
67 changes: 67 additions & 0 deletions store/root/store_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,73 @@ func (s *RootStoreTestSuite) TestBranch() {
s.Require().Equal([]byte("updated_bar"), bs.Get([]byte("foo")))
}

func (s *RootStoreTestSuite) TestLoadVersion() {
// write and commit a few changesets
for v := 1; v <= 5; v++ {
bs := s.rootStore.GetBranchedKVStore("")
val := fmt.Sprintf("val%03d", v) // val001, val002, ..., val005
bs.Set([]byte("key"), []byte(val))

workingHash, err := s.rootStore.WorkingHash()
s.Require().NoError(err)
s.Require().NotNil(workingHash)

commitHash, err := s.rootStore.Commit()
s.Require().NoError(err)
s.Require().NotNil(commitHash)
s.Require().Equal(workingHash, commitHash)
}

// ensure the latest version is correct
latest, err := s.rootStore.GetLatestVersion()
s.Require().NoError(err)
s.Require().Equal(uint64(5), latest)

// attempt to load a non-existent version
err = s.rootStore.LoadVersion(6)
s.Require().Error(err)

// attempt to load a previously committed version
err = s.rootStore.LoadVersion(3)
s.Require().NoError(err)

// ensure the latest version is correct
latest, err = s.rootStore.GetLatestVersion()
s.Require().NoError(err)
s.Require().Equal(uint64(3), latest)

// query state and ensure values returned are based on the loaded version
kvStore := s.rootStore.GetKVStore("")
val := kvStore.Get([]byte("key"))
s.Require().Equal([]byte("val003"), val)

// attempt to write and commit a few changesets
for v := 4; v <= 5; v++ {
bs := s.rootStore.GetBranchedKVStore("")
val := fmt.Sprintf("overwritten_val%03d", v) // overwritten_val004, overwritten_val005
bs.Set([]byte("key"), []byte(val))

workingHash, err := s.rootStore.WorkingHash()
s.Require().NoError(err)
s.Require().NotNil(workingHash)

commitHash, err := s.rootStore.Commit()
s.Require().NoError(err)
s.Require().NotNil(commitHash)
s.Require().Equal(workingHash, commitHash)
}

// ensure the latest version is correct
latest, err = s.rootStore.GetLatestVersion()
s.Require().NoError(err)
s.Require().Equal(uint64(5), latest)

// query state and ensure values returned are based on the loaded version
kvStore = s.rootStore.GetKVStore("")
val = kvStore.Get([]byte("key"))
s.Require().Equal([]byte("overwritten_val005"), val)
}

func (s *RootStoreTestSuite) TestMultiBranch() {
// write and commit a changeset
bs := s.rootStore.GetKVStore("")
Expand Down
19 changes: 18 additions & 1 deletion store/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,10 @@ type RootStore interface {
// TracingEnabled returns true if tracing is enabled on the RootStore.
TracingEnabled() bool

// LoadVersion loads the RootStore to the given version.
LoadVersion(version uint64) error
// LoadLatestVersion behaves identically to LoadVersion except it loads the
// latest version implicitly.
LoadLatestVersion() error

// GetLatestVersion returns the latest version, i.e. height, committed.
Expand Down Expand Up @@ -80,6 +83,20 @@ type RootStore interface {
io.Closer
}

// UpgradeableRootStore extends the RootStore interface to support loading versions
// with upgrades.
type UpgradeableRootStore interface {
RootStore

// LoadVersionAndUpgrade behaves identically to LoadVersion except it also
// accepts a StoreUpgrades object that defines a series of transformations to
// apply to store keys (if any).
//
// Note, handling StoreUpgrades is optional depending on the underlying RootStore
// implementation.
LoadVersionAndUpgrade(version uint64, upgrades *StoreUpgrades) error
}

// BranchedRootStore defines an extension of the RootStore interface that allows
// for nested branching and flushing of writes. It extends RootStore by allowing
// a caller to call Branch() which should return a BranchedRootStore that has all
Expand Down Expand Up @@ -117,7 +134,7 @@ type KVStore interface {
GetChangeset() *Changeset

// Reset resets the store, which is implementation dependent.
Reset() error
Reset(toVersion uint64) error

// Iterator creates a new Iterator over the domain [start, end). Note:
//
Expand Down
53 changes: 53 additions & 0 deletions store/upgrade.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package store

import "golang.org/x/exp/slices"

// StoreUpgrades defines a series of transformations to apply the RootStore upon
// loading a version.
type StoreUpgrades struct {
Add []string `json:"add"`
Rename []StoreRename `json:"rename"`
Delete []string `json:"delete"`
}

// StoreRename defines a change in a store key. All data previously stored under
// the current store key should be migrated to the new store key, while also
// deleting the old store key.
type StoreRename struct {
OldKey string `json:"old_key"`
NewKey string `json:"new_key"`
}

// IsAdded returns true if the given key should be added.
func (s *StoreUpgrades) IsAdded(key string) bool {
alexanderbez marked this conversation as resolved.
Show resolved Hide resolved
if s == nil {
return false
}

return slices.Contains(s.Add, key)
}

// IsDeleted returns true if the given key should be deleted.
func (s *StoreUpgrades) IsDeleted(key string) bool {
alexanderbez marked this conversation as resolved.
Show resolved Hide resolved
if s == nil {
return false
}

return slices.Contains(s.Delete, key)
}

// RenamedFrom returns the oldKey if it was renamed. It returns an empty string
// if it was not renamed.
func (s *StoreUpgrades) RenamedFrom(key string) string {
if s == nil {
return ""
}

for _, re := range s.Rename {
if re.NewKey == key {
return re.OldKey
}
}

return ""
}
alexanderbez marked this conversation as resolved.
Show resolved Hide resolved
Loading