Skip to content

Commit

Permalink
fix(cosmos): don't rerun store migrations on upgrade
Browse files Browse the repository at this point in the history
  • Loading branch information
mhofman committed Aug 31, 2024
1 parent f6011d0 commit 8738a9b
Show file tree
Hide file tree
Showing 2 changed files with 68 additions and 8 deletions.
9 changes: 7 additions & 2 deletions golang/cosmos/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -873,18 +873,23 @@ func NewAgoricApp(
app.SetBeginBlocker(app.BeginBlocker)
app.SetEndBlocker(app.EndBlocker)

for name := range upgradeNamesOfThisVersion {
for _, name := range upgradeNamesOfThisVersion {
app.UpgradeKeeper.SetUpgradeHandler(
name,
unreleasedUpgradeHandler(app, name),
)
}

// At this point we don't have a way to read from the store, so we have to
// rely on data saved by the x/upgrade module in the previous software.
upgradeInfo, err := app.UpgradeKeeper.ReadUpgradeInfoFromDisk()
if err != nil {
panic(err)
}
if upgradeNamesOfThisVersion[upgradeInfo.Name] && !app.UpgradeKeeper.IsSkipHeight(upgradeInfo.Height) {
// Store migrations can only run once, so we use a notion of "primary upgrade
// name" to trigger them. Testnets may end up upgrading from one rc to
// another, which shouldn't re-run store upgrades.
if isPrimaryUpgradeName(upgradeInfo.Name) && !app.UpgradeKeeper.IsSkipHeight(upgradeInfo.Height) {
storeUpgrades := storetypes.StoreUpgrades{
Added: []string{},
Deleted: []string{},
Expand Down
67 changes: 61 additions & 6 deletions golang/cosmos/app/upgrade.go
Original file line number Diff line number Diff line change
@@ -1,22 +1,70 @@
package gaia

import (
"fmt"

"github.com/Agoric/agoric-sdk/golang/cosmos/vm"
swingsetkeeper "github.com/Agoric/agoric-sdk/golang/cosmos/x/swingset/keeper"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/module"
upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types"
)

var upgradeNamesOfThisVersion = map[string]bool{
"UNRELEASED_BASIC": true, // no-frills
"UNRELEASED_A3P_INTEGRATION": true,
"UNRELEASED_main": true,
"UNRELEASED_devnet": true,
var upgradeNamesOfThisVersion = []string{
"UNRELEASED_BASIC", // no-frills
"UNRELEASED_A3P_INTEGRATION",
"UNRELEASED_main",
"UNRELEASED_devnet",
"UNRELEASED_REAPPLY",
}

// isUpgradeNameOfThisVersion returns whether the provided plan name is a
// known upgrade name of this software version
func isUpgradeNameOfThisVersion(name string) bool {
for _, upgradeName := range upgradeNamesOfThisVersion {
if upgradeName == name {
return true
}
}
return false
}

// validUpgradeName is an identity function that asserts the provided name
// is an upgrade name of this software version. It can be used as a sort of
// dynamic enum check.
func validUpgradeName(name string) string {
if !isUpgradeNameOfThisVersion(name) {
panic(fmt.Errorf("invalid upgrade name: %s", name))
}
return name
}

// isPrimaryUpgradeName returns wether the provided plan name is considered a
// primary for the purpose of applying store migrations for the first upgrade
// of this version.
// It is expected that only primary plan names are used for non testing chains.
func isPrimaryUpgradeName(name string) bool {
if name == "" {
// An empty upgrade name can happen if there are no upgrade in progress
return false
}
switch name {
case validUpgradeName("UNRELEASED_BASIC"),
validUpgradeName("UNRELEASED_A3P_INTEGRATION"),
validUpgradeName("UNRELEASED_main"),
validUpgradeName("UNRELEASED_devnet"):
return true
case validUpgradeName("UNRELEASED_REAPPLY"):
return false
default:
panic(fmt.Errorf("unexpected upgrade name %s", validUpgradeName(name)))
}
}

// isFirstTimeUpgradeOfThisVersion looks up in the upgrade store whether no
// upgrade plan name of this version have previously been applied.
func isFirstTimeUpgradeOfThisVersion(app *GaiaApp, ctx sdk.Context) bool {
for name := range upgradeNamesOfThisVersion {
for _, name := range upgradeNamesOfThisVersion {
if app.UpgradeKeeper.GetDoneHeight(ctx, name) != 0 {
return false
}
Expand All @@ -34,6 +82,13 @@ func unreleasedUpgradeHandler(app *GaiaApp, targetUpgrade string) func(sdk.Conte
// These CoreProposalSteps are not idempotent and should only be executed
// as part of the first upgrade using this handler on any given chain.
if isFirstTimeUpgradeOfThisVersion(app, ctx) {
// The storeUpgrades defined in app.go only execute for the primary upgrade name
// If we got here and this first upgrade of this version does not use the
// primary upgrade name, stores have not been initialized correctly.
if !isPrimaryUpgradeName(plan.Name) {
return module.VersionMap{}, fmt.Errorf("cannot run %s as first upgrade", plan.Name)
}

// Each CoreProposalStep runs sequentially, and can be constructed from
// one or more modules executing in parallel within the step.
CoreProposalSteps = []vm.CoreProposalStep{
Expand Down

0 comments on commit 8738a9b

Please sign in to comment.