Skip to content
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
2 changes: 1 addition & 1 deletion .github/actions/setup-go/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ runs:
echo "version=$VERSION" >> "$GITHUB_OUTPUT"

- name: Setup Go
uses: actions/setup-go@v6
uses: actions/setup-go@v6.2.0
with:
go-version: ${{ steps.determine.outputs.version }}
cache: true
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,18 @@

---

## 1.10.1

Changes included since `v1.10.0` (range: `v1.10.0..v1.10.1`).

- Upgrade safety: add a conditional store loader for `v1.10.1` that only renames the legacy `Consensus` store when needed and avoids panics when the new `consensus` store already exists.
- Consensus params: ensure `x/consensus` params are present and repair missing/incomplete values during the upgrade.
- Devnet tooling: adaptive store loader can be enabled to reconcile on-disk stores against expected modules when skipping intermediate upgrades.
- Devnet tests: validator IBC tests now auto-detect the validator container (RPC + key) instead of assuming `supernova_validator_1`.
- Upgrade guidance: do not apply `v1.10.0` on testnet or mainnet.

---

## 1.10.0

Changes included since `v1.9.1` (range: `v1.9.1..v1.10.0`).
Expand Down
34 changes: 30 additions & 4 deletions Makefile.devnet
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ devnet-build-default: _check-devnet-default-cfg
EXTERNAL_GENESIS_FILE="$$(realpath $(DEFAULT_GENESIS_FILE))" \
EXTERNAL_CLAIMS_FILE="$$(realpath $(DEFAULT_CLAIMS_FILE))"

.PHONY: devnet-build-172 _check-devnet-172-cfg
.PHONY: devnet-build-172 _check-devnet-172-cfg devnet-build-191 _check-devnet-191-cfg
devnet-build-172:
@$(MAKE) devnet-build \
DEVNET_BUILD_LUMERA=0 \
Expand All @@ -121,6 +121,17 @@ _check-devnet-172-cfg:
@[ -f "$$(realpath $(ORIG_GENESIS_FILE))" ] || (echo "Missing ORIG_GENESIS_FILE: $$(realpath $(ORIG_GENESIS_FILE))"; exit 1)
@[ -f "$$(realpath $(DEFAULT_CLAIMS_FILE))" ] || (echo "Missing DEFAULT_CLAIMS_FILE: $$(realpath $(DEFAULT_CLAIMS_FILE))"; exit 1)

devnet-build-191:
@$(MAKE) devnet-build \
DEVNET_BUILD_LUMERA=0 \
DEVNET_BIN_DIR=devnet/bin-v1.9.1 \
EXTERNAL_GENESIS_FILE="$$(realpath $(DEFAULT_GENESIS_FILE))" \
EXTERNAL_CLAIMS_FILE="$$(realpath $(DEFAULT_CLAIMS_FILE))"

_check-devnet-191-cfg:
@[ -f "$$(realpath $(DEFAULT_GENESIS_FILE))" ] || (echo "Missing DEFAULT_GENESIS_FILE: $$(realpath $(DEFAULT_GENESIS_FILE))"; exit 1)
@[ -f "$$(realpath $(DEFAULT_CLAIMS_FILE))" ] || (echo "Missing DEFAULT_CLAIMS_FILE: $$(realpath $(DEFAULT_CLAIMS_FILE))"; exit 1)

_check-devnet-default-cfg:
@[ -f "$$(realpath $(DEFAULT_GENESIS_FILE))" ] || (echo "Missing DEFAULT_GENESIS_FILE: $$(realpath $(DEFAULT_GENESIS_FILE))"; exit 1)
@[ -f "$$(realpath $(DEFAULT_CLAIMS_FILE))" ] || (echo "Missing DEFAULT_CLAIMS_FILE: $$(realpath $(DEFAULT_CLAIMS_FILE))"; exit 1)
Expand Down Expand Up @@ -288,12 +299,19 @@ devnet-update-scripts:
echo "No containers were updated. Ensure the devnet is running."; \
fi

.PHONY: devnet-new-172 devnet-upgrade-180 devnet-upgrade-184
.PHONY: devnet-new-172 devnet-new-191 devnet-upgrade-180 devnet-upgrade-191 devnet-upgrade-1100 devnet-upgrade-1101

devnet-upgrade-180:
@cd devnet/scripts && ./upgrade.sh v1.8.0 auto-height ../bin-v1.8.0

devnet-upgrade-184:
@cd devnet/scripts && ./upgrade.sh v1.8.4 auto-height ../bin-v1.8.4
devnet-upgrade-191:
@cd devnet/scripts && ./upgrade.sh v1.9.1 auto-height ../bin-v1.9.1

devnet-upgrade-1100:
@cd devnet/scripts && ./upgrade.sh v1.10.0 auto-height ../bin-v1.10.0

devnet-upgrade-1101:
@cd devnet/scripts && ./upgrade.sh v1.10.1 auto-height ../bin

devnet-new-172:
$(MAKE) devnet-down
Expand All @@ -302,6 +320,14 @@ devnet-new-172:
sleep 10
$(MAKE) devnet-up


devnet-new-191:
$(MAKE) devnet-down
$(MAKE) devnet-clean
$(MAKE) devnet-build-191
sleep 10
$(MAKE) devnet-up

devnet-deploy-tar:
# Ensure required files exist from previous build
@if [ ! -f "devnet/docker-compose.yml" ] || [ ! -f "devnet/bin/lumerad" ] || [ ! -f "devnet/bin/libwasmvm.x86_64.so" ]; then \
Expand Down
31 changes: 27 additions & 4 deletions app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import (
_ "cosmossdk.io/x/feegrant/module" // import for side-effects
_ "cosmossdk.io/x/upgrade" // import for side-effects
upgradekeeper "cosmossdk.io/x/upgrade/keeper"
upgradetypes "cosmossdk.io/x/upgrade/types"
abci "github.com/cometbft/cometbft/abci/types"
tmproto "github.com/cometbft/cometbft/proto/tendermint/types"
dbm "github.com/cosmos/cosmos-db"
Expand Down Expand Up @@ -360,7 +359,8 @@ func (app *App) setupUpgrades() {
panic(fmt.Sprintf("upgrade plan %q is scheduled at height %d but not registered in this binary", upgradeInfo.Name, upgradeInfo.Height))
}

if upgradeConfig.StoreUpgrade == nil {
useAdaptiveStoreUpgrades := upgrades.ShouldEnableStoreUpgradeManager(params.ChainID)
if upgradeConfig.StoreUpgrade == nil && !useAdaptiveStoreUpgrades {
app.Logger().Info("No store upgrades registered for pending plan", "name", upgradeInfo.Name)
return
}
Expand All @@ -370,8 +370,31 @@ func (app *App) setupUpgrades() {
return
}

app.SetStoreLoader(upgradetypes.UpgradeStoreLoader(upgradeInfo.Height, upgradeConfig.StoreUpgrade))
app.Logger().Info("Configured store loader for upgrade", "name", upgradeInfo.Name, "height", upgradeInfo.Height)
if useAdaptiveStoreUpgrades {
expectedStoreNames := upgrades.KVStoreNames(app.GetStoreKeys())
selection := upgrades.StoreLoaderForUpgrade(
upgradeInfo.Name,
upgradeInfo.Height,
upgradeConfig.StoreUpgrade,
expectedStoreNames,
app.Logger(),
true,
)
app.SetStoreLoader(selection.Loader)
app.Logger().Info(selection.LogMessage(), "name", upgradeInfo.Name, "height", upgradeInfo.Height)
return
}

selection := upgrades.StoreLoaderForUpgrade(
upgradeInfo.Name,
upgradeInfo.Height,
upgradeConfig.StoreUpgrade,
nil,
app.Logger(),
false,
)
app.SetStoreLoader(selection.Loader)
app.Logger().Info(selection.LogMessage(), "name", upgradeInfo.Name, "height", upgradeInfo.Height)
}

// LegacyAmino returns App's amino codec.
Expand Down
188 changes: 188 additions & 0 deletions app/upgrades/consensus_store_loader.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
package upgrades

import (
"sort"

"cosmossdk.io/log"
storetypes "cosmossdk.io/store/types"
upgradetypes "cosmossdk.io/x/upgrade/types"

"github.com/cosmos/cosmos-sdk/baseapp"
)

const (
legacyConsensusStoreKey = "Consensus"
consensusStoreKey = "consensus"
)

// ConsensusStoreLoader builds a store loader that safely renames the legacy
// consensus store (if present) and avoids panics when the new store already exists.
//
// If expectedStoreNames is provided, the loader will also compute adaptive store
// upgrades against the existing on-disk stores.
func ConsensusStoreLoader(
upgradeHeight int64,
baseUpgrades *storetypes.StoreUpgrades,
expectedStoreNames map[string]struct{},
logger log.Logger,
) baseapp.StoreLoader {
fallbackLoader := upgradetypes.UpgradeStoreLoader(upgradeHeight, baseUpgrades)

return func(ms storetypes.CommitMultiStore) error {
if upgradeHeight != ms.LastCommitID().Version+1 {
return baseapp.DefaultStoreLoader(ms)
}

existingStoreNames, err := loadExistingStoreNames(ms)
if err != nil {
logger.Error("Failed to load existing stores; falling back to standard upgrade loader", "error", err)
return fallbackLoader(ms)
}

effective := computeConsensusStoreUpgrades(baseUpgrades, expectedStoreNames, existingStoreNames, logger)
if len(effective.Added) == 0 && len(effective.Deleted) == 0 && len(effective.Renamed) == 0 {
logger.Info("No store upgrades required; loading latest version", "height", upgradeHeight)
return baseapp.DefaultStoreLoader(ms)
}

logger.Info(
"Applying store upgrades",
"height", upgradeHeight,
"added", effective.Added,
"deleted", effective.Deleted,
"renamed", formatStoreRenames(effective.Renamed),
)

return ms.LoadLatestVersionAndUpgrade(&effective)
}
}

func computeConsensusStoreUpgrades(
baseUpgrades *storetypes.StoreUpgrades,
expectedStoreNames map[string]struct{},
existingStoreNames map[string]struct{},
logger log.Logger,
) storetypes.StoreUpgrades {
var effective storetypes.StoreUpgrades
if expectedStoreNames != nil {
effective = computeAdaptiveStoreUpgrades(baseUpgrades, expectedStoreNames, existingStoreNames)
effective.Renamed = filterStoreRenames(effective.Renamed, existingStoreNames)
} else {
effective = cloneStoreUpgrades(baseUpgrades)
effective.Added = filterStoreAdds(effective.Added, existingStoreNames)
effective.Deleted = filterStoreDeletes(effective.Deleted, existingStoreNames)
effective.Renamed = filterStoreRenames(effective.Renamed, existingStoreNames)
}

hasLegacy := storeExists(existingStoreNames, legacyConsensusStoreKey)
hasNew := storeExists(existingStoreNames, consensusStoreKey)

switch {
case hasLegacy && !hasNew:
effective.Added = removeStoreName(effective.Added, consensusStoreKey)
effective.Deleted = removeStoreName(effective.Deleted, legacyConsensusStoreKey)
effective.Renamed = append(effective.Renamed, storetypes.StoreRename{
OldKey: legacyConsensusStoreKey,
NewKey: consensusStoreKey,
})
case !hasLegacy && !hasNew:
if expectedStoreNames == nil {
effective.Added = append(effective.Added, consensusStoreKey)
}
case hasLegacy && hasNew:
effective.Deleted = removeStoreName(effective.Deleted, legacyConsensusStoreKey)
logger.Info("Both legacy and new consensus stores exist; skipping rename", "old", legacyConsensusStoreKey, "new", consensusStoreKey)
}

effective.Added = uniqueSortedStores(effective.Added)
effective.Deleted = uniqueSortedStores(effective.Deleted)

return effective
}

func cloneStoreUpgrades(base *storetypes.StoreUpgrades) storetypes.StoreUpgrades {
if base == nil {
return storetypes.StoreUpgrades{}
}
return storetypes.StoreUpgrades{
Added: append([]string(nil), base.Added...),
Deleted: append([]string(nil), base.Deleted...),
Renamed: append([]storetypes.StoreRename(nil), base.Renamed...),
}
}

func filterStoreAdds(added []string, existing map[string]struct{}) []string {
if len(added) == 0 {
return nil
}
out := make([]string, 0, len(added))
for _, name := range added {
if !storeExists(existing, name) {
out = append(out, name)
}
}
return out
}

func filterStoreDeletes(deleted []string, existing map[string]struct{}) []string {
if len(deleted) == 0 {
return nil
}
out := make([]string, 0, len(deleted))
for _, name := range deleted {
if storeExists(existing, name) {
out = append(out, name)
}
}
return out
}

func filterStoreRenames(renames []storetypes.StoreRename, existing map[string]struct{}) []storetypes.StoreRename {
if len(renames) == 0 {
return nil
}
out := make([]storetypes.StoreRename, 0, len(renames))
for _, rename := range renames {
if storeExists(existing, rename.OldKey) && !storeExists(existing, rename.NewKey) {
out = append(out, rename)
}
}
return out
}

func storeExists(existing map[string]struct{}, name string) bool {
if existing == nil {
return false
}
_, ok := existing[name]
return ok
}

func removeStoreName(names []string, target string) []string {
if len(names) == 0 {
return nil
}
out := names[:0]
for _, name := range names {
if name != target {
out = append(out, name)
}
}
return out
}

func uniqueSortedStores(names []string) []string {
if len(names) == 0 {
return nil
}
set := make(map[string]struct{}, len(names))
for _, name := range names {
set[name] = struct{}{}
}
out := make([]string, 0, len(set))
for name := range set {
out = append(out, name)
}
sort.Strings(out)
return out
}
55 changes: 55 additions & 0 deletions app/upgrades/consensus_store_loader_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package upgrades

import (
"testing"

"cosmossdk.io/log"
storetypes "cosmossdk.io/store/types"
"github.com/stretchr/testify/require"
)

func TestComputeConsensusStoreUpgrades_RenameWhenLegacyOnly(t *testing.T) {
expected := setOf("consensus")
existing := setOf("Consensus", "crisis")
base := &storetypes.StoreUpgrades{
Deleted: []string{"crisis"},
}

effective := computeConsensusStoreUpgrades(base, expected, existing, log.NewNopLogger())

require.Len(t, effective.Renamed, 1)
require.Equal(t, storetypes.StoreRename{OldKey: "Consensus", NewKey: "consensus"}, effective.Renamed[0])
require.NotContains(t, effective.Added, "consensus")
require.NotContains(t, effective.Deleted, "Consensus")
require.Contains(t, effective.Deleted, "crisis")
}

func TestComputeConsensusStoreUpgrades_NoRenameWhenNewExists(t *testing.T) {
expected := setOf("consensus")
existing := setOf("consensus")

effective := computeConsensusStoreUpgrades(nil, expected, existing, log.NewNopLogger())

require.Empty(t, effective.Renamed)
require.Empty(t, effective.Added)
require.Empty(t, effective.Deleted)
}

func TestComputeConsensusStoreUpgrades_AddsConsensusWhenMissingNonAdaptive(t *testing.T) {
existing := map[string]struct{}{}

effective := computeConsensusStoreUpgrades(nil, nil, existing, log.NewNopLogger())

require.Contains(t, effective.Added, "consensus")
require.Empty(t, effective.Renamed)
}

func TestComputeConsensusStoreUpgrades_NoRenameWhenBothExist(t *testing.T) {
expected := setOf("consensus")
existing := setOf("Consensus", "consensus")

effective := computeConsensusStoreUpgrades(nil, expected, existing, log.NewNopLogger())

require.Empty(t, effective.Renamed)
require.NotContains(t, effective.Deleted, "Consensus")
}
Loading