How Consensus Version Upgrades and Module Migrations Work in Akash
Consensus version upgrades migrate core Tendermint parameters from the legacy x/params module to the dedicated x/consensus store, while module migrations transform stored data between versions via registered handlers that execute automatically at the upgrade height.
The akash-network/node repository implements a robust on-chain upgrade mechanism built on the Cosmos SDK Upgrade module. Understanding how consensus version upgrades and module migrations work is essential for validators and developers maintaining the Akash blockchain. These mechanisms ensure seamless transitions between protocol versions without requiring manual state exports or hard forks.
Consensus Version Upgrades in Akash
Consensus version upgrades represent major structural changes to how the network stores and manages core Tendermint parameters. These upgrades modify the blockchain's underlying storage layout and module configuration.
Migrating from x/params to x/consensus
Prior to Akash v1.0.0, Tendermint consensus parameters—such as max_gas and block_time—were stored in the generic x/params sub-store. The consensus version upgrade creates a dedicated x/consensus module and migrates these parameters into an isolated store. This aligns Akash with the upstream Cosmos SDK v0.53.x consensus model.
The migration is performed by baseapp.MigrateParams, which transfers values from the legacy subspace to the new consensus params store:
// upgrades/software/v1.0.0/upgrade.go
baseAppLegacySS := up.Keepers.Cosmos.Params.Subspace(baseapp.Paramspace)
...
err := baseapp.MigrateParams(sctx, baseAppLegacySS,
up.Keepers.Cosmos.ConsensusParams.ParamsStore)
Store Upgrades and the v1.0.0 Implementation
Alongside parameter migration, the upgrade modifies the multistore layout through the StoreUpgrades struct. In upgrades/software/v1.0.0/upgrade.go, the StoreLoader adds the new consensus store while removing deprecated modules:
// upgrades/software/v1.0.0/upgrade.go
return &storetypes.StoreUpgrades{
Added: []string{consensustypes.ModuleName}, // "consensus"
Deleted: []string{ "agov", "astaking", crisistypes.ModuleName },
}
The upgrade also integrates the new module into the application by adding it to the module manager in app/modules.go and instantiating its keeper in app/types/app.go. Once the block at the upgrade height is committed, the chain's consensus version reflects the new parameters, and the old sub-store is permanently removed.
Module Migrations
While consensus upgrades handle global parameter storage, module migrations evolve individual Akash modules (such as market or deployment) from one storage version to another. Each migration is a handler that transforms stored data and updates the module's version in the module manager.
Registration Flow and Migration Handlers
Module migrations follow a declarative registration pattern centered in upgrades/types/types.go. The process involves four distinct steps:
- Define the migration – Implement the
Migrationinterface, which exposes ansdkmodule.MigrationHandler. - Register the migration – Call
utypes.RegisterMigrationwith the module name, target version, and constructor function. - Register the upgrade – Associate the migrations with a specific upgrade name using
utypes.RegisterUpgrade. - Wire into the app – During
AkashApp.registerUpgradeHandlers()inapp/upgrades.go, iterate all registered migrations and register them with the SDK's configurator.
For example, the market module migration for v1.2.0 is registered in upgrades/software/v1.2.0/init.go:
// upgrades/software/v1.2.0/init.go
utypes.RegisterMigration(mv1.ModuleName, 7, newMarketMigration)
utypes.RegisterUpgrade(UpgradeName, initUpgrade)
The global migrations map in upgrades/types/types.go stores these registrations, keyed by module name and version. During application initialization, app/upgrades.go wires them into the module configurator:
// app/upgrades.go
utypes.IterateMigrations(func(module string, version uint64, initfn utypes.NewMigrationFn) {
migrator := initfn(utypes.NewMigrator(app.cdc, app.GetKey(module)))
app.Configurator.RegisterMigration(module, version, migrator.GetHandler())
})
Anatomy of a Migration Handler
A migration handler receives the module's KV store and the application's codec. It performs three critical operations:
- Read legacy data structures using
store.Getorstore.Iterator - Transform the data into the new schema
- Write the transformed data back to the store, deleting obsolete keys when necessary
The handler executes only when the stored module version is less than the target version specified during registration. Upon successful completion, the module's version is automatically bumped in the VersionMap, preventing re-execution.
Example: Market Module Migration (v1.2.0)
The following simplified example from upgrades/software/v1.2.0/market.go demonstrates renaming a storage key:
// upgrades/software/v1.2.0/market.go (simplified)
func newMarketMigration(migrator utypes.Migrator) utypes.Migration {
return marketMigration{migrator}
}
type marketMigration struct{ utypes.Migrator }
func (m marketMigration) GetHandler() sdkmodule.MigrationHandler {
return func(ctx sdk.Context) error {
store := m.StoreKey().PrefixStore(ctx.KVStore())
// Rename key "bids" → "order_bids"
oldKey := []byte("bids")
newKey := []byte("order_bids")
bz := store.Get(oldKey)
if bz != nil {
store.Set(newKey, bz)
store.Delete(oldKey)
}
return nil
}
}
Adding a New Module Migration
To implement a new migration for an upcoming Akash release:
- Create a migration file under
upgrades/software/<next-version>/(e.g.,upgrades/software/v1.3.0/my_module.go) - Implement the
NewMigrationFnconstructor andMigrationHandlerlogic - Register it in the version's
init.go:
// upgrades/software/v1.3.0/init.go
utypes.RegisterMigration(myModule.ModuleName, 4, newMyModuleMigration)
- Bump the module's version in its
AppModuledefinition if necessary, ensuring the configurator recognizes the version delta.
The Upgrade Lifecycle: From Proposal to Execution
The complete upgrade workflow orchestrates both consensus version changes and module migrations through the following sequence:
- Plan creation – A
SoftwareUpgradeProposalis submitted on-chain, specifying a target block height and upgrade name (e.g.,v1.0.0,v1.2.0). - Handler registration – When nodes restart,
AkashApp.registerUpgradeHandlers()reads the upgrade plan from disk and callsSetUpgradeHandlerto register the appropriate handler. - Store loading – If the upgrade requires storage changes (like adding the
consensusmodule),SetStoreLoaderapplies theStoreUpgradesconfiguration. - Execution at height – When the network reaches the specified block height, the
x/upgrademodule triggers the registered handler. This executes consensus parameter migrations and invokes all registered module migrations via the configurator. - Post-upgrade operation – The chain continues with the new store layout and updated module versions. Future upgrades repeat this pattern by adding new directories under
upgrades/software/.
Summary
- Consensus version upgrades migrate Tendermint core parameters from
x/paramsto the dedicatedx/consensusmodule, requiring store additions and deletions defined inupgrades/software/v1.0.0/upgrade.go. - Module migrations use the
RegisterMigrationpattern inupgrades/types/types.goto transform module-specific data, with handlers executing automatically based on version comparisons in theVersionMap. - The upgrade orchestration occurs in
app/upgrades.go, which reads on-disk plans and wires handlers into the Cosmos SDK's upgrade module. - Adding new migrations requires only creating a handler implementation and registering it in the appropriate
upgrades/software/<version>/init.gofile.
Frequently Asked Questions
What is the difference between a consensus version upgrade and a module migration?
A consensus version upgrade modifies global blockchain parameters and storage structures—specifically moving Tendermint consensus params from x/params to x/consensus—while a module migration transforms data within a specific module (like market or deployment) from one schema version to another. Consensus upgrades affect the entire chain's configuration, whereas module migrations are scoped to individual storage namespaces.
How do I register a new module migration for an upcoming Akash upgrade?
Create a migration file in upgrades/software/<target-version>/ that implements the NewMigrationFn type and returns a Migration with your transformation logic. Import this in the version's init.go and call utypes.RegisterMigration(moduleName, targetVersion, yourConstructor). Ensure the module's version constant in its source code matches the target version so the configurator triggers the migration.
What happens if a module migration fails during an upgrade?
If a migration handler returns an error, the upgrade panics and the chain halts at the upgrade height. This safety mechanism prevents the chain from continuing with inconsistent state. Operators must investigate the failure, potentially patch the migration logic, and coordinate a restart from the last valid height before the upgrade plan was executed.
Where are the consensus parameters stored after the v1.0.0 upgrade?
After the v1.0.0 upgrade executes, consensus parameters reside in the x/consensus module's dedicated store space, accessible via the ConsensusParams keeper defined in app/types/app.go. The legacy storage in x/params is removed from the multistore, and the application no longer depends on the deprecated params subspace for Tendermint configuration.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →