# How Akash Network Upgrades Handle State Migration Between Versions

> Akash Network upgrades smoothly migrate chain state between versions using a Cosmos SDK pipeline. Learn how store modifications, module migrations, and custom handlers ensure atomic transformations at block heights.

- Repository: [Akash Network/node](https://github.com/akash-network/node)
- Tags: internals
- Published: 2026-02-24

---

**Akash Network upgrades use a Cosmos SDK-based migration pipeline that combines store modifications, registered module migrations, and custom upgrade handlers to atomically transform chain state at predetermined block heights.**

The `akash-network/node` repository implements a robust state migration system built atop the Cosmos SDK's `x/upgrade` module. When the network transitions between versions, it must preserve existing data while restructuring the underlying key-value stores and module schemas. This article examines the exact mechanisms—from software upgrade proposals to migration execution—that ensure seamless state transitions across protocol versions.

## The Cosmos SDK Foundation

Akash Network's upgrade architecture extends the Cosmos SDK's upgrade module to manage consensus-breaking changes. Every version requiring state changes introduces a **software upgrade proposal** that triggers a coordinated migration pipeline when the chain reaches a specific block height.

The system guarantees that binaries running before the upgrade can continue reading the old store format, while new binaries create updated stores and execute registered migrations atomically. This dual-version compatibility prevents chain splits during coordinated upgrades.

## The Upgrade Pipeline: From Proposal to Execution

### Upgrade Definition and Registration

Each release requiring state changes implements the `IUpgrade` interface within a dedicated Go package under `upgrades/software/<version>/`. The upgrade registration process begins at node startup when `upgrades/types` builds a mapping of upgrade names to initializer functions via `RegisterUpgrade`.

For example, in the v1.0.0 release, the initializer returns an object satisfying `IUpgrade` that encapsulates both store changes and migration logic:

```go
// Located in upgrades/software/v1.0.0/upgrade.go
type upgrade struct {
    *upgrades.Upgrade
}

func (up *upgrade) UpgradeHandler() upgradetypes.UpgradeHandler {
    return func(ctx sdk.Context, plan upgradetypes.Plan, fromVM module.VersionMap) (module.VersionMap, error) {
        toVM, err := up.MM.RunMigrations(ctx, up.Configurator, fromVM)
        // Post-migration logic executes here
        return toVM, err
    }
}

```

### Store Modifications with StoreLoader

The `StoreLoader()` method defines structural changes to the multistore, returning a `storetypes.StoreUpgrades` that specifies which module stores to add, delete, or rename. This runs before any data migrations, ensuring the physical storage layout matches the new binary's expectations.

In [`upgrades/software/v1.0.0/upgrade.go`](https://github.com/akash-network/node/blob/main/upgrades/software/v1.0.0/upgrade.go), the v1.0.0 upgrade adds the consensus store while removing legacy modules:

```go
func (up *upgrade) StoreLoader() *storetypes.StoreUpgrades {
    return &storetypes.StoreUpgrades{
        Added: []string{consensustypes.ModuleName},
        Deleted: []string{
            "agov", "astaking", crisistypes.ModuleName,
        },
    }
}

```

### Module Migrations and Registration

Individual modules register migration functions that transform their specific KV-store data from old schemas to new ones. These registrations occur in the upgrade's [`init.go`](https://github.com/akash-network/node/blob/main/init.go) file using `utypes.RegisterMigration`, which associates a module name, target version, and handler function.

From [`upgrades/software/v1.0.0/init.go`](https://github.com/akash-network/node/blob/main/upgrades/software/v1.0.0/init.go):

```go
func init() {
    utypes.RegisterUpgrade(v1_0_0.UpgradeName, v1_0_0.initUpgrade)

    // Register market module migration to version 6
    utypes.RegisterMigration(mv1.ModuleName, 6, newMarketMigration)
}

```

The [`app/upgrades.go`](https://github.com/akash-network/node/blob/main/app/upgrades.go) file then binds these registrations to the Cosmos SDK's configurator:

```go
if err := app.Configurator.RegisterMigration(module, version, migrator.GetHandler()); err != nil {
    panic(err)
}

```

### Execution at Upgrade Height

When the chain reaches the designated upgrade height, the `UpgradeHandler` invokes the **module migrator** (`app.MM.RunMigrations`), which iterates over all registered migrations and executes them sequentially within a single atomic transaction:

```go
// From upgrades/software/v1.2.0/upgrade.go
toVM, err := up.MM.RunMigrations(ctx, up.Configurator, fromVM)
if err != nil {
    return nil, err
}

```

After generic migrations complete, upgrades may execute custom logic. The v1.1.0 upgrade demonstrates this pattern by closing overdrawn escrow accounts after the standard migrations finish:

```go
// Post-migration logic in v1.1.0
accounts := up.Keepers.Akash.Escrow().AccountsWithBalance(ctx)
for _, account := range accounts {
    if account.Balance.IsNegative() {
        up.Keepers.Akash.Escrow().CloseAccount(ctx, account.ID)
    }
}

```

## Height-Based Patches for Hotfixes

For critical fixes that do not require full software upgrades, the system supports `RegisterHeightPatch`. This mechanism injects one-time logic that executes at a specific block height without requiring a coordinated binary upgrade, providing a surgical intervention capability for urgent consensus issues.

This functionality is defined in [`upgrades/types/types.go`](https://github.com/akash-network/node/blob/main/upgrades/types/types.go) and allows validators to deploy patches that modify state at predetermined heights while remaining compatible with the existing software version.

## Code Example: Implementing a New Migration

To add a migration for a hypothetical v1.3.0 release that introduces a new staking module, developers create two files:

```go
// upgrades/software/v1.3.0/init.go
func init() {
    utypes.RegisterUpgrade(v1_3_0.UpgradeName, v1_3_0.initUpgrade)

    // Register migration for staking module, version 8
    utypes.RegisterMigration(stakingtypes.ModuleName, 8, newStakingMigration)
}

```

```go
// upgrades/software/v1.3.0/upgrade.go
func (up *upgrade) StoreLoader() *storetypes.StoreUpgrades {
    return &storetypes.StoreUpgrades{
        Added:   []string{stakingtypes.ModuleName},
        Deleted: []string{},
    }
}

func newStakingMigration(ctx sdk.Context, akashKeepers akashkeepers.AkashKeepers) error {
    // Transform legacy validator data into new format
    // Migrate delegation records
    return nil
}

```

When the upgrade plan `"v1.3.0"` reaches its target height, the SDK automatically creates the new staking store, executes `newStakingMigration` to transform legacy data, and commits all changes atomically.

## Summary

- **StoreLoader** (`StoreLoader()`) defines physical store additions and deletions before data migration begins.
- **Migration Registration** (`utypes.RegisterMigration`) pairs module-specific transformation functions with target versions.
- **Atomic Execution** (`app.MM.RunMigrations`) processes all registered migrations within a single block transaction.
- **Custom Handlers** allow post-migration logic for complex state transitions beyond schema changes.
- **Height Patches** provide emergency state intervention without full software upgrades.

## Frequently Asked Questions

### What triggers an Akash Network state migration?

A `SoftwareUpgradeProposal` submitted through governance triggers the migration when the chain reaches the specified block height. The proposal contains the upgrade name (matching the directory in `upgrades/software/`), target height, and optional binary download metadata parsed by [`util/cli/upgrade_info.go`](https://github.com/akash-network/node/blob/main/util/cli/upgrade_info.go).

### How does the network ensure old binaries can read state during an upgrade?

The upgrade architecture maintains backward compatibility by ensuring old binaries continue operating on the existing store format until the upgrade height. The new binary, deployed before the target height, contains both the old and new store definitions, allowing it to read legacy data and write migrated data atomically when `RunMigrations` executes.

### Can migrations be executed without adding or removing stores?

Yes. Not all upgrades require store changes. If an upgrade only modifies data schemas within existing module stores, the `StoreLoader()` can return an empty `storetypes.StoreUpgrades` or nil, while still registering migration functions that transform the existing KV data through `utypes.RegisterMigration`.

### What happens if a migration fails during execution?

If any registered migration returns an error during `app.MM.RunMigrations`, the entire upgrade handler fails, causing the block to be rejected. This atomic guarantee ensures the chain never persists partially migrated state, preventing data corruption and consensus failures.