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:
cool-developer
2024-08-05 12:34:36 +00:00
committed by GitHub
co-authored by Matt Kocubinski
parent 0ddf5c0bfa
commit 6d4097bfb7
41 changed files with 1218 additions and 192 deletions
+33 -11
View File
@@ -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
}
+2 -2
View File
@@ -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
View File
@@ -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()
+7 -7
View File
@@ -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)
+157
View File
@@ -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)
}
}
}