Synopsis
In-place store migrations let modules ship breaking state changes during a chain upgrade. This page covers how an upgrade runs, setting up
x/upgrade, writing and registering migrations, running them in an upgrade handler, and a worked example.How an upgrade works
An upgrade is scheduled on-chain, usually through governance, and runs in this order:- Someone submits a governance proposal containing a
MsgSoftwareUpgradewhosePlannames the upgrade (matching the name passed toSetUpgradeHandler) and sets a target height. - Validators vote. If the proposal passes,
x/upgraderecords the plan. - At the plan height every node halts and writes
upgrade-info.jsonto its home directory. - The node operator, or Cosmovisor, starts the new binary. During the next block’s
PreBlock,x/upgradesees the due plan and runs the registered upgrade handler, which callsRunMigrations. - The chain continues on the new binary with migrated state.
Plan is the on-chain upgrade record: a name and a target height. The VersionMap is a map of module name to consensus version, stored by x/upgrade, recording the version each module’s state was last migrated to. The sections below cover each piece. The Cosmovisor guide covers the node-operator side.
Consensus version
Successful upgrades of existing modules require eachAppModule to implement the function ConsensusVersion() uint64.
- The versions must be hard-coded by the module developer.
- The initial version must be set to 1.
RunMigrations compares these against the VersionMap to decide which migrations to run.
Set up x/upgrade
The rest of this page assumes the app hasx/upgrade wired in. Create the UpgradeKeeper before the module manager so the upgrade module can be registered, register the module, and run its PreBlocker. See the full wiring in the cosmos/example app.
PreBlocker method runs the module manager’s PreBlock, which is where x/upgrade detects a due plan and executes its handler:
InitChainer:
Registering migrations
To register the functionality that takes place during a module upgrade, register the migrations in theConfigurator using its RegisterMigration method, from the AppModule’s RegisterServices method.
Register migrations in increasing order, one per source version, up to the target consensus version. For example, to migrate to version 3 of a module, register migrations for versions 1 and 2:
Migrator that wraps the keeper. The next section writes one.
Writing migration scripts
A migration reads the module’s existing state and rewrites it into the new layout. Place migration functions in the module’skeeper package, or in a versioned migrations/ directory for larger modules (for example x/bank/migrations/v2).
The following Migrator moves the counter module from consensus version 1 to 2. The breaking change is a re-denomination: every stored count is multiplied by 10. Migrate1to2 reads the current value, transforms it, and writes it back, treating an unset value as a no-op rather than an error:
RegisterMigration(types.ModuleName, 1, m.Migrate1to2) as shown in the previous section.
For larger modules, keep the transformation in a versioned package so the Migrator method stays a thin wrapper. The bank module follows this pattern:
Running migrations in the app
Once modules have registered their migrations, the app runs them inside anUpgradeHandler. The upgrade handler type is:
As of Cosmos SDK v0.54,
x/upgrade is part of the main SDK module. Import it from github.com/cosmos/cosmos-sdk/x/upgrade, not the standalone cosmossdk.io/x/upgrade module, which is not compatible with v0.54.VersionMap stored by x/upgrade (reflecting the consensus versions from the previous binary), performs any additional upgrade logic, and must return the updated VersionMap from RunMigrations. Register the handler in app.go:
RunMigrations iterates over all registered modules in order, checks each module’s version in the VersionMap, and runs all registered migration scripts for modules whose consensus version has increased. The updated VersionMap is returned to the upgrade keeper, which persists it in the x/upgrade store.
Order of migrations
By default, migrations run in alphabetical order by module name, with one exception:x/auth runs last due to state dependencies with other modules (see cosmos/cosmos-sdk#10591). To change the order, call app.ModuleManager.SetOrderMigrations(module1, module2, ...) in app.go. The function panics if any registered module is omitted.
Adding new modules during an upgrade
New modules are recognized because they have no entry in thex/upgrade VersionMap store. RunMigrations calls InitGenesis for them automatically.
If you need to add stores for a new module, configure the store loader before the upgrade runs. The loader reads upgrade-info.json at startup and adds the store at the upgrade height:
LoadLatestVersion runs, so the new store is mounted when the store loads.
To skip InitGenesis for a new module (for example, if you are manually initializing state in the handler), set its version in fromVM before calling RunMigrations:
Overwriting genesis functions
The SDK provides modules that app developers can import, and those modules often already have anInitGenesis function. If you want to run a custom genesis function for one of those modules during an upgrade instead of the default one, you must both call your custom function in the handler AND manually set that module’s consensus version in fromVM. Without the second step, RunMigrations will run the module’s existing InitGenesis even though you already initialized it.
Counter module upgrade example
The counter module is built step by step in the example chain tutorial, and its source lives in the cosmos/example repository. This example continues from that module: it bumps the counter to consensus version 2 and runs the migration shown earlier during an upgrade namedmy-plan.
-
Increment the module’s
ConsensusVersionso the SDK detects that its state layout changed: -
In
RegisterServices, wrap the keeper in aMigratorand register the version 1 to 2 migration: -
In
app.go, register a handler for the plan that callsRunMigrations: -
Schedule the
my-planupgrade through governance. When the chain reaches the plan height it halts, the node operator starts the new binary, and the handler runs the counter migration duringPreBlock. See How an upgrade works.