feat(store/v2): implement the feature to upgrade the store keys (#20453)
Co-authored-by: Matt Kocubinski <mkocubinski@gmail.com>
This commit is contained in:
co-authored by
Matt Kocubinski
parent
0ddf5c0bfa
commit
6d4097bfb7
+33
-11
@@ -109,12 +109,12 @@ func CreateRootStore(opts *FactoryOptions) (store.RootStore, error) {
|
||||
}
|
||||
ss = storage.NewStorageStore(ssDb, opts.Logger)
|
||||
|
||||
metadata := commitment.NewMetadataStore(opts.SCRawDB)
|
||||
latestVersion, err := metadata.GetLatestVersion()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(opts.StoreKeys) == 0 {
|
||||
metadata := commitment.NewMetadataStore(opts.SCRawDB)
|
||||
latestVersion, err := metadata.GetLatestVersion()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
lastCommitInfo, err := metadata.GetCommitInfo(latestVersion)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -126,21 +126,43 @@ func CreateRootStore(opts *FactoryOptions) (store.RootStore, error) {
|
||||
opts.StoreKeys = append(opts.StoreKeys, string(si.Name))
|
||||
}
|
||||
}
|
||||
removedStoreKeys, err := metadata.GetRemovedStoreKeys(latestVersion)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
trees := make(map[string]commitment.Tree)
|
||||
for _, key := range opts.StoreKeys {
|
||||
newTreeFn := func(key string) (commitment.Tree, error) {
|
||||
if internal.IsMemoryStoreKey(key) {
|
||||
trees[key] = mem.New()
|
||||
return mem.New(), nil
|
||||
} else {
|
||||
switch storeOpts.SCType {
|
||||
case SCTypeIavl:
|
||||
trees[key] = iavl.NewIavlTree(db.NewPrefixDB(opts.SCRawDB, []byte(key)), opts.Logger, storeOpts.IavlConfig)
|
||||
return iavl.NewIavlTree(db.NewPrefixDB(opts.SCRawDB, []byte(key)), opts.Logger, storeOpts.IavlConfig), nil
|
||||
case SCTypeIavlV2:
|
||||
return nil, errors.New("iavl v2 not supported")
|
||||
return nil, fmt.Errorf("iavl v2 not supported")
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported commitment store type")
|
||||
}
|
||||
}
|
||||
}
|
||||
sc, err = commitment.NewCommitStore(trees, opts.SCRawDB, opts.Logger)
|
||||
|
||||
trees := make(map[string]commitment.Tree, len(opts.StoreKeys))
|
||||
for _, key := range opts.StoreKeys {
|
||||
tree, err := newTreeFn(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
trees[key] = tree
|
||||
}
|
||||
oldTrees := make(map[string]commitment.Tree, len(opts.StoreKeys))
|
||||
for _, key := range removedStoreKeys {
|
||||
tree, err := newTreeFn(string(key))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
oldTrees[string(key)] = tree
|
||||
}
|
||||
sc, err = commitment.NewCommitStore(trees, oldTrees, opts.SCRawDB, opts.Logger)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ func (s *MigrateStoreTestSuite) SetupTest() {
|
||||
prefixDB := dbm.NewPrefixDB(mdb, []byte(storeKey))
|
||||
multiTrees[storeKey] = iavl.NewIavlTree(prefixDB, nopLog, iavl.DefaultConfig())
|
||||
}
|
||||
orgSC, err := commitment.NewCommitStore(multiTrees, mdb, testLog)
|
||||
orgSC, err := commitment.NewCommitStore(multiTrees, nil, mdb, testLog)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// apply changeset against the original store
|
||||
@@ -70,7 +70,7 @@ func (s *MigrateStoreTestSuite) SetupTest() {
|
||||
for _, storeKey := range storeKeys {
|
||||
multiTrees1[storeKey] = iavl.NewIavlTree(dbm.NewMemDB(), nopLog, iavl.DefaultConfig())
|
||||
}
|
||||
sc, err := commitment.NewCommitStore(multiTrees1, dbm.NewMemDB(), testLog)
|
||||
sc, err := commitment.NewCommitStore(multiTrees1, nil, dbm.NewMemDB(), testLog)
|
||||
s.Require().NoError(err)
|
||||
|
||||
snapshotsStore, err := snapshots.NewStore(s.T().TempDir())
|
||||
|
||||
+54
-8
@@ -20,7 +20,10 @@ import (
|
||||
"cosmossdk.io/store/v2/pruning"
|
||||
)
|
||||
|
||||
var _ store.RootStore = (*Store)(nil)
|
||||
var (
|
||||
_ store.RootStore = (*Store)(nil)
|
||||
_ store.UpgradeableStore = (*Store)(nil)
|
||||
)
|
||||
|
||||
// Store defines the SDK's default RootStore implementation. It contains a single
|
||||
// State Storage (SS) backend and a single State Commitment (SC) backend. The SC
|
||||
@@ -225,7 +228,7 @@ func (s *Store) LoadLatestVersion() error {
|
||||
return err
|
||||
}
|
||||
|
||||
return s.loadVersion(lv)
|
||||
return s.loadVersion(lv, nil)
|
||||
}
|
||||
|
||||
func (s *Store) LoadVersion(version uint64) error {
|
||||
@@ -234,14 +237,57 @@ func (s *Store) LoadVersion(version uint64) error {
|
||||
defer s.telemetry.MeasureSince(now, "root_store", "load_version")
|
||||
}
|
||||
|
||||
return s.loadVersion(version)
|
||||
return s.loadVersion(version, nil)
|
||||
}
|
||||
|
||||
func (s *Store) loadVersion(v uint64) error {
|
||||
// LoadVersionAndUpgrade implements the UpgradeableStore interface.
|
||||
//
|
||||
// NOTE: It cannot be called while the store is migrating.
|
||||
func (s *Store) LoadVersionAndUpgrade(version uint64, upgrades *corestore.StoreUpgrades) error {
|
||||
if upgrades == nil {
|
||||
return fmt.Errorf("upgrades cannot be nil")
|
||||
}
|
||||
|
||||
if s.telemetry != nil {
|
||||
defer s.telemetry.MeasureSince(time.Now(), "root_store", "load_version_and_upgrade")
|
||||
}
|
||||
|
||||
if s.isMigrating {
|
||||
return fmt.Errorf("cannot upgrade while migrating")
|
||||
}
|
||||
|
||||
if err := s.loadVersion(version, upgrades); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// if the state storage implements the UpgradableDatabase interface, prune the
|
||||
// deleted store keys
|
||||
upgradableDatabase, ok := s.stateStorage.(store.UpgradableDatabase)
|
||||
if ok {
|
||||
if err := upgradableDatabase.PruneStoreKeys(upgrades.Deleted, version); err != nil {
|
||||
return fmt.Errorf("failed to prune store keys %v: %w", upgrades.Deleted, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Store) loadVersion(v uint64, upgrades *corestore.StoreUpgrades) error {
|
||||
s.logger.Debug("loading version", "version", v)
|
||||
|
||||
if err := s.stateCommitment.LoadVersion(v); err != nil {
|
||||
return fmt.Errorf("failed to load SC version %d: %w", v, err)
|
||||
if upgrades == nil {
|
||||
if err := s.stateCommitment.LoadVersion(v); err != nil {
|
||||
return fmt.Errorf("failed to load SC version %d: %w", v, err)
|
||||
}
|
||||
} else {
|
||||
// if upgrades are provided, we need to load the version and apply the upgrades
|
||||
upgradeableStore, ok := s.stateCommitment.(store.UpgradeableStore)
|
||||
if !ok {
|
||||
return fmt.Errorf("SC store does not support upgrades")
|
||||
}
|
||||
if err := upgradeableStore.LoadVersionAndUpgrade(v, upgrades); err != nil {
|
||||
return fmt.Errorf("failed to load SS version with upgrades %d: %w", v, err)
|
||||
}
|
||||
}
|
||||
|
||||
s.commitHeader = nil
|
||||
@@ -301,8 +347,8 @@ func (s *Store) WorkingHash(cs *corestore.Changeset) ([]byte, error) {
|
||||
|
||||
// Commit commits all state changes to the underlying SS and SC backends. It
|
||||
// writes a batch of the changeset to the SC tree, and retrieves the CommitInfo
|
||||
// from the SC tree. Finally, it commits the SC tree and returns the hash of the
|
||||
// CommitInfo.
|
||||
// from the SC tree. Finally, it commits the SC tree and returns the hash of
|
||||
// the CommitInfo.
|
||||
func (s *Store) Commit(cs *corestore.Changeset) ([]byte, error) {
|
||||
if s.telemetry != nil {
|
||||
now := time.Now()
|
||||
|
||||
@@ -55,7 +55,7 @@ func (s *RootStoreTestSuite) SetupTest() {
|
||||
tree := iavl.NewIavlTree(dbm.NewMemDB(), noopLog, iavl.DefaultConfig())
|
||||
tree2 := iavl.NewIavlTree(dbm.NewMemDB(), noopLog, iavl.DefaultConfig())
|
||||
tree3 := iavl.NewIavlTree(dbm.NewMemDB(), noopLog, iavl.DefaultConfig())
|
||||
sc, err := commitment.NewCommitStore(map[string]commitment.Tree{testStoreKey: tree, testStoreKey2: tree2, testStoreKey3: tree3}, dbm.NewMemDB(), noopLog)
|
||||
sc, err := commitment.NewCommitStore(map[string]commitment.Tree{testStoreKey: tree, testStoreKey2: tree2, testStoreKey3: tree3}, nil, dbm.NewMemDB(), noopLog)
|
||||
s.Require().NoError(err)
|
||||
|
||||
pm := pruning.NewManager(sc, ss, nil, nil)
|
||||
@@ -79,7 +79,7 @@ func (s *RootStoreTestSuite) newStoreWithPruneConfig(config *store.PruningOption
|
||||
multiTrees[storeKey] = iavl.NewIavlTree(prefixDB, noopLog, iavl.DefaultConfig())
|
||||
}
|
||||
|
||||
sc, err := commitment.NewCommitStore(multiTrees, dbm.NewMemDB(), noopLog)
|
||||
sc, err := commitment.NewCommitStore(multiTrees, nil, dbm.NewMemDB(), noopLog)
|
||||
s.Require().NoError(err)
|
||||
|
||||
pm := pruning.NewManager(sc, ss, config, config)
|
||||
@@ -563,7 +563,7 @@ func (s *RootStoreTestSuite) TestMultiStore_PruningRestart() {
|
||||
ss := storage.NewStorageStore(sqliteDB, noopLog)
|
||||
|
||||
tree := iavl.NewIavlTree(mdb1, noopLog, iavl.DefaultConfig())
|
||||
sc, err := commitment.NewCommitStore(map[string]commitment.Tree{testStoreKey: tree}, mdb2, noopLog)
|
||||
sc, err := commitment.NewCommitStore(map[string]commitment.Tree{testStoreKey: tree}, nil, mdb2, noopLog)
|
||||
s.Require().NoError(err)
|
||||
|
||||
pm := pruning.NewManager(sc, ss, pruneOpt, pruneOpt)
|
||||
@@ -593,7 +593,7 @@ func (s *RootStoreTestSuite) TestMultiStore_PruningRestart() {
|
||||
ss = storage.NewStorageStore(sqliteDB, noopLog)
|
||||
|
||||
tree = iavl.NewIavlTree(mdb1, noopLog, iavl.DefaultConfig())
|
||||
sc, err = commitment.NewCommitStore(map[string]commitment.Tree{testStoreKey: tree}, mdb2, noopLog)
|
||||
sc, err = commitment.NewCommitStore(map[string]commitment.Tree{testStoreKey: tree}, nil, mdb2, noopLog)
|
||||
s.Require().NoError(err)
|
||||
|
||||
pm = pruning.NewManager(sc, ss, pruneOpt, pruneOpt)
|
||||
@@ -624,7 +624,7 @@ func (s *RootStoreTestSuite) TestMultiStore_PruningRestart() {
|
||||
|
||||
for v := uint64(1); v <= actualHeightToPrune; v++ {
|
||||
checkErr := func() bool {
|
||||
if err = s.rootStore.LoadVersion(v); err != nil {
|
||||
if _, err = s.rootStore.StateAt(v); err != nil {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
@@ -650,7 +650,7 @@ func (s *RootStoreTestSuite) TestMultiStoreRestart() {
|
||||
multiTrees[storeKey] = iavl.NewIavlTree(prefixDB, noopLog, iavl.DefaultConfig())
|
||||
}
|
||||
|
||||
sc, err := commitment.NewCommitStore(multiTrees, mdb2, noopLog)
|
||||
sc, err := commitment.NewCommitStore(multiTrees, nil, mdb2, noopLog)
|
||||
s.Require().NoError(err)
|
||||
|
||||
pm := pruning.NewManager(sc, ss, nil, nil)
|
||||
@@ -737,7 +737,7 @@ func (s *RootStoreTestSuite) TestMultiStoreRestart() {
|
||||
multiTrees[storeKey] = iavl.NewIavlTree(prefixDB, noopLog, iavl.DefaultConfig())
|
||||
}
|
||||
|
||||
sc, err = commitment.NewCommitStore(multiTrees, mdb2, noopLog)
|
||||
sc, err = commitment.NewCommitStore(multiTrees, nil, mdb2, noopLog)
|
||||
s.Require().NoError(err)
|
||||
|
||||
pm = pruning.NewManager(sc, ss, nil, nil)
|
||||
|
||||
@@ -0,0 +1,157 @@
|
||||
package root
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
||||
corestore "cosmossdk.io/core/store"
|
||||
coretesting "cosmossdk.io/core/testing"
|
||||
"cosmossdk.io/log"
|
||||
"cosmossdk.io/store/v2"
|
||||
"cosmossdk.io/store/v2/commitment"
|
||||
"cosmossdk.io/store/v2/commitment/iavl"
|
||||
dbm "cosmossdk.io/store/v2/db"
|
||||
"cosmossdk.io/store/v2/pruning"
|
||||
"cosmossdk.io/store/v2/storage"
|
||||
"cosmossdk.io/store/v2/storage/sqlite"
|
||||
)
|
||||
|
||||
type UpgradeStoreTestSuite struct {
|
||||
suite.Suite
|
||||
|
||||
commitDB corestore.KVStoreWithBatch
|
||||
rootStore store.RootStore
|
||||
}
|
||||
|
||||
func TestUpgradeStoreTestSuite(t *testing.T) {
|
||||
suite.Run(t, &UpgradeStoreTestSuite{})
|
||||
}
|
||||
|
||||
func (s *UpgradeStoreTestSuite) SetupTest() {
|
||||
testLog := log.NewTestLogger(s.T())
|
||||
nopLog := coretesting.NewNopLogger()
|
||||
|
||||
s.commitDB = dbm.NewMemDB()
|
||||
multiTrees := make(map[string]commitment.Tree)
|
||||
newTreeFn := func(storeKey string) (commitment.Tree, error) {
|
||||
prefixDB := dbm.NewPrefixDB(s.commitDB, []byte(storeKey))
|
||||
return iavl.NewIavlTree(prefixDB, nopLog, iavl.DefaultConfig()), nil
|
||||
}
|
||||
for _, storeKey := range storeKeys {
|
||||
multiTrees[storeKey], _ = newTreeFn(storeKey)
|
||||
}
|
||||
|
||||
// create storage and commitment stores
|
||||
sqliteDB, err := sqlite.New(s.T().TempDir())
|
||||
s.Require().NoError(err)
|
||||
ss := storage.NewStorageStore(sqliteDB, testLog)
|
||||
sc, err := commitment.NewCommitStore(multiTrees, nil, s.commitDB, testLog)
|
||||
s.Require().NoError(err)
|
||||
pm := pruning.NewManager(sc, ss, nil, nil)
|
||||
s.rootStore, err = New(testLog, ss, sc, pm, nil, nil)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// commit changeset
|
||||
toVersion := uint64(20)
|
||||
keyCount := 10
|
||||
for version := uint64(1); version <= toVersion; version++ {
|
||||
cs := corestore.NewChangeset()
|
||||
for _, storeKey := range storeKeys {
|
||||
for i := 0; i < keyCount; i++ {
|
||||
cs.Add([]byte(storeKey), []byte(fmt.Sprintf("key-%d-%d", version, i)), []byte(fmt.Sprintf("value-%d-%d", version, i)), false)
|
||||
}
|
||||
}
|
||||
_, err = s.rootStore.Commit(cs)
|
||||
s.Require().NoError(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *UpgradeStoreTestSuite) loadWithUpgrades(upgrades *corestore.StoreUpgrades) {
|
||||
testLog := log.NewTestLogger(s.T())
|
||||
nopLog := coretesting.NewNopLogger()
|
||||
|
||||
// create a new commitment store
|
||||
multiTrees := make(map[string]commitment.Tree)
|
||||
oldTrees := make(map[string]commitment.Tree)
|
||||
newTreeFn := func(storeKey string) (commitment.Tree, error) {
|
||||
prefixDB := dbm.NewPrefixDB(s.commitDB, []byte(storeKey))
|
||||
return iavl.NewIavlTree(prefixDB, nopLog, iavl.DefaultConfig()), nil
|
||||
}
|
||||
for _, storeKey := range storeKeys {
|
||||
multiTrees[storeKey], _ = newTreeFn(storeKey)
|
||||
}
|
||||
for _, added := range upgrades.Added {
|
||||
multiTrees[added], _ = newTreeFn(added)
|
||||
}
|
||||
for _, deleted := range upgrades.Deleted {
|
||||
oldTrees[deleted], _ = newTreeFn(deleted)
|
||||
}
|
||||
|
||||
sc, err := commitment.NewCommitStore(multiTrees, oldTrees, s.commitDB, testLog)
|
||||
s.Require().NoError(err)
|
||||
pm := pruning.NewManager(sc, s.rootStore.GetStateStorage().(store.Pruner), nil, nil)
|
||||
s.rootStore, err = New(testLog, s.rootStore.GetStateStorage(), sc, pm, nil, nil)
|
||||
s.Require().NoError(err)
|
||||
}
|
||||
|
||||
func (s *UpgradeStoreTestSuite) TestLoadVersionAndUpgrade() {
|
||||
// upgrade store keys
|
||||
upgrades := &corestore.StoreUpgrades{
|
||||
Added: []string{"newStore1", "newStore2"},
|
||||
Deleted: []string{"store3"},
|
||||
}
|
||||
s.loadWithUpgrades(upgrades)
|
||||
|
||||
// load the store with the upgrades
|
||||
v, err := s.rootStore.GetLatestVersion()
|
||||
s.Require().NoError(err)
|
||||
err = s.rootStore.(store.UpgradeableStore).LoadVersionAndUpgrade(v, upgrades)
|
||||
s.Require().NoError(err)
|
||||
|
||||
keyCount := 10
|
||||
// check old store keys are queryable
|
||||
oldStoreKeys := []string{"store1", "store3"}
|
||||
for _, storeKey := range oldStoreKeys {
|
||||
for version := uint64(1); version <= v; version++ {
|
||||
for i := 0; i < keyCount; i++ {
|
||||
proof, err := s.rootStore.Query([]byte(storeKey), version, []byte(fmt.Sprintf("key-%d-%d", version, i)), true)
|
||||
s.Require().NoError(err)
|
||||
s.Require().NotNil(proof)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// commit changeset
|
||||
newStoreKeys := []string{"newStore1", "newStore2"}
|
||||
toVersion := uint64(40)
|
||||
for version := v + 1; version <= toVersion; version++ {
|
||||
cs := corestore.NewChangeset()
|
||||
for _, storeKey := range newStoreKeys {
|
||||
for i := 0; i < keyCount; i++ {
|
||||
cs.Add([]byte(storeKey), []byte(fmt.Sprintf("key-%d-%d", version, i)), []byte(fmt.Sprintf("value-%d-%d", version, i)), false)
|
||||
}
|
||||
}
|
||||
_, err = s.rootStore.Commit(cs)
|
||||
s.Require().NoError(err)
|
||||
}
|
||||
|
||||
// check new store keys are queryable
|
||||
for _, storeKey := range newStoreKeys {
|
||||
for version := v + 1; version <= toVersion; version++ {
|
||||
for i := 0; i < keyCount; i++ {
|
||||
_, err := s.rootStore.Query([]byte(storeKey), version, []byte(fmt.Sprintf("key-%d-%d", version, i)), true)
|
||||
s.Require().NoError(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check the original store key is queryable
|
||||
for version := uint64(1); version <= toVersion; version++ {
|
||||
for i := 0; i < keyCount; i++ {
|
||||
_, err := s.rootStore.Query([]byte("store2"), version, []byte(fmt.Sprintf("key-%d-%d", version, i)), true)
|
||||
s.Require().NoError(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user