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): backward compatibility store v1 historical queries #23145

Open
wants to merge 21 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 25 additions & 4 deletions store/v2/commitment/metadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ import (
const (
commitInfoKeyFmt = "c/%d" // c/<version>
latestVersionKey = "c/latest"
removedStoreKeyPrefix = "c/removed/" // c/removed/<version>/<store-name>
v2MigrationHeightKey = "c/v2migration" // c/v2migration height where the migration to store v2 happened
removedStoreKeyPrefix = "c/removed/" // c/removed/<version>/<store-name>
)

// MetadataStore is a store for metadata related to the commitment store.
Expand All @@ -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
}
Expand All @@ -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))
Expand All @@ -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))
Expand Down
42 changes: 42 additions & 0 deletions store/v2/commitment/metadata_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
15 changes: 15 additions & 0 deletions store/v2/commitment/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
6 changes: 6 additions & 0 deletions store/v2/migration/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down
61 changes: 55 additions & 6 deletions store/v2/root/migrate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand All @@ -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)
Expand All @@ -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)
}
Expand All @@ -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)
Expand Down Expand Up @@ -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)
}
Expand All @@ -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)
Expand All @@ -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)
}
}
}
}
49 changes: 39 additions & 10 deletions store/v2/root/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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

Expand Down Expand Up @@ -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
Copy link
Member

Choose a reason for hiding this comment

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

we should check if the old store is still present, it may have been removed.

} 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 {
Expand Down Expand Up @@ -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
Expand Down
Loading