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(runtime(v2)): add sanity checks on store upgrades #21748

Merged
merged 5 commits into from
Sep 17, 2024
Merged
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
42 changes: 42 additions & 0 deletions runtime/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package runtime

import (
"context"
"errors"
"fmt"
"io"

"cosmossdk.io/core/store"
Expand Down Expand Up @@ -184,6 +186,11 @@ func KVStoreAdapter(store store.KVStore) storetypes.KVStore {
// UpgradeStoreLoader is used to prepare baseapp with a fixed StoreLoader
// pattern. This is useful for custom upgrade loading logic.
func UpgradeStoreLoader(upgradeHeight int64, storeUpgrades *store.StoreUpgrades) baseapp.StoreLoader {
// sanity checks on store upgrades
if err := checkStoreUpgrade(storeUpgrades); err != nil {
panic(err)
}

return func(ms storetypes.CommitMultiStore) error {
if upgradeHeight == ms.LastCommitID().Version+1 {
// Check if the current commit version and upgrade height matches
Expand All @@ -200,3 +207,38 @@ func UpgradeStoreLoader(upgradeHeight int64, storeUpgrades *store.StoreUpgrades)
return baseapp.DefaultStoreLoader(ms)
}
}

// checkStoreUpgrade performs sanity checks on the store upgrades
func checkStoreUpgrade(storeUpgrades *store.StoreUpgrades) error {
if storeUpgrades == nil {
return errors.New("store upgrades cannot be nil")
}

// check for duplicates
exists := make(map[string]bool)
for _, key := range storeUpgrades.Added {
if exists[key] {
return fmt.Errorf("store upgrade has duplicate key %s in added", key)
}

if storeUpgrades.IsDeleted(key) {
return fmt.Errorf("store upgrade has key %s in both added and deleted", key)
}

exists[key] = true
}
exists = make(map[string]bool)
for _, key := range storeUpgrades.Deleted {
if exists[key] {
return fmt.Errorf("store upgrade has duplicate key %s in deleted", key)
}

if storeUpgrades.IsAdded(key) {
return fmt.Errorf("store upgrade has key %s in both added and deleted", key)
}

exists[key] = true
}

return nil
}
65 changes: 65 additions & 0 deletions runtime/store_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package runtime

import (
"testing"

"github.com/stretchr/testify/require"

corestore "cosmossdk.io/core/store"
)

func TestCheckStoreUpgrade(t *testing.T) {
tests := []struct {
name string
storeUpgrades *corestore.StoreUpgrades
errMsg string
}{
{
name: "Nil StoreUpgrades",
storeUpgrades: nil,
errMsg: "store upgrades cannot be nil",
},
{
name: "Valid StoreUpgrades",
storeUpgrades: &corestore.StoreUpgrades{
Added: []string{"store1", "store2"},
Deleted: []string{"store3", "store4"},
},
},
{
name: "Duplicate key in Added",
storeUpgrades: &corestore.StoreUpgrades{
Added: []string{"store1", "store2", "store1"},
Deleted: []string{"store3"},
},
errMsg: "store upgrade has duplicate key store1 in added",
},
{
name: "Duplicate key in Deleted",
storeUpgrades: &corestore.StoreUpgrades{
Added: []string{"store1"},
Deleted: []string{"store2", "store3", "store2"},
},
errMsg: "store upgrade has duplicate key store2 in deleted",
},
{
name: "Key in both Added and Deleted",
storeUpgrades: &corestore.StoreUpgrades{
Added: []string{"store1", "store2"},
Deleted: []string{"store2", "store3"},
},
errMsg: "store upgrade has key store2 in both added and deleted",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := checkStoreUpgrade(tt.storeUpgrades)
if tt.errMsg == "" {
require.NoError(t, err)
} else {
require.ErrorContains(t, err, tt.errMsg)
}
})
}
}
41 changes: 41 additions & 0 deletions runtime/v2/store.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package runtime

import (
"errors"
"fmt"

"cosmossdk.io/core/store"
Expand Down Expand Up @@ -69,6 +70,11 @@ func DefaultStoreLoader(store Store) error {
// UpgradeStoreLoader upgrades the store if the upgrade height matches the current version, it is used as a replacement
// for the DefaultStoreLoader when there are store upgrades
func UpgradeStoreLoader(upgradeHeight int64, storeUpgrades *store.StoreUpgrades) StoreLoader {
// sanity checks on store upgrades
if err := checkStoreUpgrade(storeUpgrades); err != nil {
panic(err)
}

return func(store Store) error {
latestVersion, err := store.GetLatestVersion()
if err != nil {
Expand All @@ -88,3 +94,38 @@ func UpgradeStoreLoader(upgradeHeight int64, storeUpgrades *store.StoreUpgrades)
return DefaultStoreLoader(store)
}
}

// checkStoreUpgrade performs sanity checks on the store upgrades
func checkStoreUpgrade(storeUpgrades *store.StoreUpgrades) error {
if storeUpgrades == nil {
return errors.New("store upgrades cannot be nil")
}

// check for duplicates
exists := make(map[string]bool)
for _, key := range storeUpgrades.Added {
if exists[key] {
return fmt.Errorf("store upgrade has duplicate key %s in added", key)
}

if storeUpgrades.IsDeleted(key) {
return fmt.Errorf("store upgrade has key %s in both added and deleted", key)
}

exists[key] = true
}
exists = make(map[string]bool)
for _, key := range storeUpgrades.Deleted {
if exists[key] {
return fmt.Errorf("store upgrade has duplicate key %s in deleted", key)
}

if storeUpgrades.IsAdded(key) {
return fmt.Errorf("store upgrade has key %s in both added and deleted", key)
}

exists[key] = true
}

return nil
}
71 changes: 71 additions & 0 deletions runtime/v2/store_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package runtime

import (
"testing"

corestore "cosmossdk.io/core/store"
)

func TestCheckStoreUpgrade(t *testing.T) {
tests := []struct {
name string
storeUpgrades *corestore.StoreUpgrades
wantErr bool
errMsg string
}{
{
name: "Nil StoreUpgrades",
storeUpgrades: nil,
wantErr: true,
errMsg: "store upgrades cannot be nil",
},
{
name: "Valid StoreUpgrades",
storeUpgrades: &corestore.StoreUpgrades{
Added: []string{"store1", "store2"},
Deleted: []string{"store3", "store4"},
},
wantErr: false,
},
{
name: "Duplicate key in Added",
storeUpgrades: &corestore.StoreUpgrades{
Added: []string{"store1", "store2", "store1"},
Deleted: []string{"store3"},
},
wantErr: true,
errMsg: "store upgrade has duplicate key store1 in added",
},
{
name: "Duplicate key in Deleted",
storeUpgrades: &corestore.StoreUpgrades{
Added: []string{"store1"},
Deleted: []string{"store2", "store3", "store2"},
},
wantErr: true,
errMsg: "store upgrade has duplicate key store2 in deleted",
},
{
name: "Key in both Added and Deleted",
storeUpgrades: &corestore.StoreUpgrades{
Added: []string{"store1", "store2"},
Deleted: []string{"store2", "store3"},
},
wantErr: true,
errMsg: "store upgrade has key store2 in both added and deleted",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := checkStoreUpgrade(tt.storeUpgrades)
if (err != nil) != tt.wantErr {
t.Errorf("checkStoreUpgrade() error = %v, wantErr %v", err, tt.wantErr)
return
}
if err != nil && err.Error() != tt.errMsg {
t.Errorf("checkStoreUpgrade() error message = %v, want %v", err.Error(), tt.errMsg)
}
})
}
}
Loading