diff --git a/store/v2/commitment/metadata.go b/store/v2/commitment/metadata.go index a054acf26e89..60c17f24fba5 100644 --- a/store/v2/commitment/metadata.go +++ b/store/v2/commitment/metadata.go @@ -13,7 +13,8 @@ import ( const ( commitInfoKeyFmt = "c/%d" // c/ latestVersionKey = "c/latest" - removedStoreKeyPrefix = "c/removed/" // c/removed// + v2MigrationHeightKey = "c/v2migration" // c/v2migration height where the migration to store v2 happened + removedStoreKeyPrefix = "c/removed/" // c/removed// ) // MetadataStore is a store for metadata related to the commitment store. @@ -29,9 +30,9 @@ func NewMetadataStore(kv corestore.KVStoreWithBatch) *MetadataStore { } } -// GetLatestVersion returns the latest committed version. -func (m *MetadataStore) GetLatestVersion() (uint64, error) { - value, err := m.kv.Get([]byte(latestVersionKey)) +// getVersion is an internal helper method to retrieve and decode a version from the store. +func (m *MetadataStore) getVersion(key string) (uint64, error) { + value, err := m.kv.Get([]byte(key)) if err != nil { return 0, err } @@ -47,6 +48,11 @@ func (m *MetadataStore) GetLatestVersion() (uint64, error) { return version, nil } +// GetLatestVersion returns the latest committed version. +func (m *MetadataStore) GetLatestVersion() (uint64, error) { + return m.getVersion(latestVersionKey) +} + func (m *MetadataStore) setLatestVersion(version uint64) error { var buf bytes.Buffer buf.Grow(encoding.EncodeUvarintSize(version)) @@ -56,6 +62,21 @@ func (m *MetadataStore) setLatestVersion(version uint64) error { return m.kv.Set([]byte(latestVersionKey), buf.Bytes()) } +// GetV2MigrationHeight retrieves the height at which the migration to store v2 occurred. +func (m *MetadataStore) GetV2MigrationHeight() (uint64, error) { + return m.getVersion(v2MigrationHeightKey) +} + +// setV2MigrationHeight sets the v2 migration height. +func (m *MetadataStore) setV2MigrationHeight(height uint64) error { + var buf bytes.Buffer + buf.Grow(encoding.EncodeUvarintSize(height)) + if err := encoding.EncodeUvarint(&buf, height); err != nil { + return err + } + return m.kv.Set([]byte(v2MigrationHeightKey), buf.Bytes()) +} + // GetCommitInfo returns the commit info for the given version. func (m *MetadataStore) GetCommitInfo(version uint64) (*proof.CommitInfo, error) { key := []byte(fmt.Sprintf(commitInfoKeyFmt, version)) diff --git a/store/v2/commitment/metadata_test.go b/store/v2/commitment/metadata_test.go new file mode 100644 index 000000000000..a86ae13182d2 --- /dev/null +++ b/store/v2/commitment/metadata_test.go @@ -0,0 +1,42 @@ +package commitment + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + dbm "cosmossdk.io/store/v2/db" +) + +func TestMetadataStore_GetLatestVersion(t *testing.T) { + db := dbm.NewMemDB() + ms := NewMetadataStore(db) + + version, err := ms.GetLatestVersion() + assert.NoError(t, err) + assert.Equal(t, uint64(0), version) + + // set latest version + err = ms.setLatestVersion(10) + assert.NoError(t, err) + + version, err = ms.GetLatestVersion() + assert.NoError(t, err) + assert.Equal(t, uint64(10), version) +} + +func TestMetadataStore_GetV2MigrationHeight(t *testing.T) { + db := dbm.NewMemDB() + ms := NewMetadataStore(db) + + version, err := ms.GetV2MigrationHeight() + assert.NoError(t, err) + assert.Equal(t, uint64(0), version) + + err = ms.setV2MigrationHeight(10) + assert.NoError(t, err) + + version, err = ms.GetV2MigrationHeight() + assert.NoError(t, err) + assert.Equal(t, uint64(10), version) +} diff --git a/store/v2/commitment/store.go b/store/v2/commitment/store.go index 757fb45851c1..37d23eeabfb4 100644 --- a/store/v2/commitment/store.go +++ b/store/v2/commitment/store.go @@ -289,6 +289,21 @@ func (c *CommitStore) SetInitialVersion(version uint64) error { return nil } +// SetV2MigrationHeight sets the migration height for v2 store. +func (c *CommitStore) SetV2MigrationHeight(height uint64) error { + err := c.metadata.setV2MigrationHeight(height) + if err != nil { + return err + } + + return nil +} + +// GetV2MigrationHeight returns the migration height for v2 store. +func (c *CommitStore) GetV2MigrationHeight() (uint64, error) { + return c.metadata.GetV2MigrationHeight() +} + // GetProof returns a proof for the given key and version. func (c *CommitStore) GetProof(storeKey []byte, version uint64, key []byte) ([]proof.CommitmentOp, error) { rawStoreKey := conv.UnsafeBytesToStr(storeKey) diff --git a/store/v2/migration/manager.go b/store/v2/migration/manager.go index 5365e8eb6a11..addb50b362ab 100644 --- a/store/v2/migration/manager.go +++ b/store/v2/migration/manager.go @@ -107,6 +107,12 @@ func (m *Manager) Migrate(height uint64) error { m.migratedVersion.Store(height) + // Updates migration height to support historical queries + err := m.stateCommitment.SetV2MigrationHeight(height) + if err != nil { + return err + } + return nil } diff --git a/store/v2/root/migrate_test.go b/store/v2/root/migrate_test.go index 3b431bdb24f6..123ee9164e2c 100644 --- a/store/v2/root/migrate_test.go +++ b/store/v2/root/migrate_test.go @@ -68,7 +68,13 @@ func (s *MigrateStoreTestSuite) SetupTest() { snapshotsStore, err := snapshots.NewStore(s.T().TempDir()) s.Require().NoError(err) - snapshotManager := snapshots.NewManager(snapshotsStore, snapshots.NewSnapshotOptions(1500, 2), orgSC, nil, testLog) + snapshotManager := snapshots.NewManager( + snapshotsStore, + snapshots.NewSnapshotOptions(1500, 2), + orgSC, + nil, + testLog, + ) migrationManager := migration.NewManager(dbm.NewMemDB(), snapshotManager, sc, testLog) pm := pruning.NewManager(sc, nil) @@ -78,7 +84,7 @@ func (s *MigrateStoreTestSuite) SetupTest() { } func (s *MigrateStoreTestSuite) TestMigrateState() { - err := s.rootStore.LoadLatestVersion() + err := s.rootStore.LoadLatestVersion() // here it starts the migration s.Require().NoError(err) originalLatestVersion, err := s.rootStore.GetLatestVersion() s.Require().NoError(err) @@ -87,7 +93,12 @@ func (s *MigrateStoreTestSuite) TestMigrateState() { for version := uint64(1); version <= originalLatestVersion; version++ { for _, storeKey := range storeKeys { for i := 0; i < 10; i++ { - res, err := s.rootStore.Query([]byte(storeKey), version, []byte(fmt.Sprintf("key-%d-%d", version, i)), true) + res, err := s.rootStore.Query( + []byte(storeKey), + version, + []byte(fmt.Sprintf("key-%d-%d", version, i)), + true, + ) s.Require().NoError(err) s.Require().Equal([]byte(fmt.Sprintf("value-%d-%d", version, i)), res.Value) } @@ -101,7 +112,12 @@ func (s *MigrateStoreTestSuite) TestMigrateState() { cs := corestore.NewChangeset(latestVersion) for _, storeKey := range storeKeys { for i := 0; i < keyCount; i++ { - cs.Add([]byte(storeKey), []byte(fmt.Sprintf("key-%d-%d", latestVersion, i)), []byte(fmt.Sprintf("value-%d-%d", latestVersion, i)), false) + cs.Add( + []byte(storeKey), + []byte(fmt.Sprintf("key-%d-%d", latestVersion, i)), + []byte(fmt.Sprintf("value-%d-%d", latestVersion, i)), + false, + ) } } _, err = s.rootStore.Commit(cs) @@ -131,7 +147,12 @@ func (s *MigrateStoreTestSuite) TestMigrateState() { if version < originalLatestVersion { targetVersion = originalLatestVersion } - res, err := s.rootStore.Query([]byte(storeKey), targetVersion, []byte(fmt.Sprintf("key-%d-%d", version, i)), true) + res, err := s.rootStore.Query( + []byte(storeKey), + targetVersion, + []byte(fmt.Sprintf("key-%d-%d", version, i)), + true, + ) s.Require().NoError(err) s.Require().Equal([]byte(fmt.Sprintf("value-%d-%d", version, i)), res.Value) } @@ -143,7 +164,12 @@ func (s *MigrateStoreTestSuite) TestMigrateState() { cs := corestore.NewChangeset(version) 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) + 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) @@ -153,4 +179,27 @@ func (s *MigrateStoreTestSuite) TestMigrateState() { version, err = s.rootStore.GetLatestVersion() s.Require().NoError(err) s.Require().Equal(latestVersion+10, version) + + commitStore, ok := s.rootStore.GetStateCommitment().(*commitment.CommitStore) + s.Require().True(ok) + height, err := commitStore.GetV2MigrationHeight() + s.Require().NoError(err) + fmt.Printf("commit store height %d\n", height) + s.Require().Equal(height, originalLatestVersion) + + // check if the Query fallback to the original SC after the migration without proofs + for version := uint64(1); version <= originalLatestVersion; version++ { + for _, storeKey := range storeKeys { + for i := 0; i < 10; i++ { + res, err := s.rootStore.Query( + []byte(storeKey), + version, + []byte(fmt.Sprintf("key-%d-%d", version, i)), + true, + ) + s.Require().NoError(err) + s.Require().Equal([]byte(fmt.Sprintf("value-%d-%d", version, i)), res.Value) + } + } + } } diff --git a/store/v2/root/store.go b/store/v2/root/store.go index 3104260d11b3..d509a50a7abd 100644 --- a/store/v2/root/store.go +++ b/store/v2/root/store.go @@ -11,6 +11,7 @@ import ( corelog "cosmossdk.io/core/log" corestore "cosmossdk.io/core/store" "cosmossdk.io/store/v2" + "cosmossdk.io/store/v2/commitment" "cosmossdk.io/store/v2/metrics" "cosmossdk.io/store/v2/migration" "cosmossdk.io/store/v2/proof" @@ -35,6 +36,9 @@ type Store struct { // stateCommitment reflects the state commitment (SC) backend stateCommitment store.Committer + // v1StateCommitment reflects the v1 state commitment prior to migration + v1StateCommitment store.Committer + // lastCommitInfo reflects the last version/hash that has been committed lastCommitInfo *proof.CommitInfo @@ -172,25 +176,45 @@ func (s *Store) Query(storeKey []byte, version uint64, key []byte, prove bool) ( defer s.telemetry.MeasureSince(time.Now(), "root_store", "query") } - val, err := s.stateCommitment.Get(storeKey, version, key) + var cs store.Committer + // if is V2 means that the store is using iavlv2, it has been migrated from iavlv1 to iavlv2 + if v2Commitment, isV2 := s.stateCommitment.(*commitment.CommitStore); isV2 { + // if the commitment structure is iavlv2 store, we need to check if the version is less than or equal to the v2 migration height + v2UpgradeHeight, err := v2Commitment.GetV2MigrationHeight() + if err != nil { + return store.QueryResult{}, fmt.Errorf("failed to get v2 migration height: %w", err) + } + + // if the version is less than or equal to the v2 migration height, we need to use the v1 state commitment + if version <= v2UpgradeHeight && s.v1StateCommitment != nil { + cs = s.v1StateCommitment + } else { // if the version is greater than the v2 migration height, we need to use the v2 state commitment + cs = s.stateCommitment + } + } else { // if is V1 means that the store is a v1 store + cs = s.stateCommitment + } + + val, err := cs.Get(storeKey, version, key) if err != nil { return store.QueryResult{}, fmt.Errorf("failed to query SC store: %w", err) } - result := store.QueryResult{ - Key: key, - Value: val, - Version: version, - } + var proofOps []proof.CommitmentOp if prove { - result.ProofOps, err = s.stateCommitment.GetProof(storeKey, version, key) + proofOps, err = cs.GetProof(storeKey, version, key) if err != nil { return store.QueryResult{}, fmt.Errorf("failed to get SC store proof: %w", err) } } - return result, nil + return store.QueryResult{ + Key: key, + Value: val, + Version: version, + ProofOps: proofOps, + }, nil } func (s *Store) LoadLatestVersion() error { @@ -362,10 +386,15 @@ func (s *Store) handleMigration(cs *corestore.Changeset) error { close(s.chDone) close(s.chChangeset) s.isMigrating = false - // close the old state commitment and replace it with the new one + + /*// close the old state commitment and replace it with the new one if err := s.stateCommitment.Close(); err != nil { return fmt.Errorf("failed to close the old SC store: %w", err) - } + } */ + + // set old state commitment as v1StateCommitment + s.v1StateCommitment = s.stateCommitment + newStateCommitment := s.migrationManager.GetStateCommitment() if newStateCommitment != nil { s.stateCommitment = newStateCommitment