Cert-Prep · Interchain Dev · Saturday · Week 4 / Cycle 2 · 2026-07-11

The Cosmos SDK x/upgrade Module —
the Software Upgrade Proposal, the Upgrade Handler, and Store Migrations at the Upgrade Height

Governance decides that a chain will change. The x/upgrade module is where that decision becomes a deterministic, chain-wide event with a name, a height, and a migration every node runs in lockstep.

Lesson class Cert-Prep — Interchain Dev (Cosmos)
Ops pair δ-Chain — Automated Chain-Upgrade Orchestration
Dev pair Bash — Cosmovisor Staging Pipeline
Prior arc 2026-06-27 x/gov Module · 2026-07-04 State Export & Genesis Migration
tome_refs [] — knowledge gap (Cosmos-SDK literature absent from tome corpus; acquisition target logged)
Coined terms the on-chain schema fence · the store migration at height
Word count ~2,500

Section I§I — Frame

The 2026-06-27 cert lesson on the x/gov module ended at a specific moment: a software-upgrade proposal passes the tally and the change is ratified by the validator set. That lesson stopped there, because what the passed proposal does is a different module's job. Today covers that module. The x/upgrade module is the on-chain machinery that turns a governance decision into a coordinated chain halt, a binary swap, and a schema migration that every node executes at the same block height.

For the Interchain Dev credential this is core territory: the difference between the governance of an upgrade and the execution of one, what an upgrade handler is and when it runs, and why store migrations must be deterministic. Today's Ops and Dev lessons covered the operational half; this covers the on-chain half that makes those operations meaningful.

Knowledge gap (honesty note) This lesson ships with tome_refs: []. The vault's 44-tome corpus has no Cosmos-SDK or Interchain reference literature, so there is no book-tier source to ground citations against. The material is grounded in the Cosmos SDK's own module documentation and the arc of prior Interchain cert lessons. The gap is logged as a tome-acquisition target.

Section II§II — Domain Foundations: what x/upgrade stores and when it acts

The x/upgrade module has a small surface and one job: hold a plan, and act on it at a height. The plan carries a name, a target height, and optional info (a JSON string that by convention tells operators where to find the new binary). There is at most one active plan at a time; scheduling a new upgrade sets or replaces it.

The plan enters the store through governance. A MsgSoftwareUpgrade message, wrapped in a proposal, is what the validator set votes on. When the proposal passes, the module's handler writes the plan into the upgrade store. From that moment the chain is armed: it will halt at the plan height whether or not any operator has staged a binary. The upgrade is a consensus commitment, not an operational suggestion.

The module acts in the BeginBlocker, before any transactions. At block H it compares H against the plan height. If H is below the plan height, nothing happens. If H equals the plan height and a matching handler is registered, it runs the handler and clears the plan. If H equals the plan height and no matching handler is registered, it panics with UPGRADE REQUIRED.

Coined term I call this third case the on-chain schema fence. The old binary cannot cross into block H because it does not know how, and it refuses to guess. A binary without the registered handler for the plan name is the wrong binary for the post-upgrade chain, so the module stops it rather than let it produce a divergent state.

Section III§III — Cert-A Flavor: the upgrade handler and its registration

The upgrade handler is a named function the application registers with the upgrade keeper. Registration ties a handler to a plan name: when the chain upgrades to v2.1.0, the module looks for a handler registered under the exact string v2.1.0.

First, the handler name and the plan name must match exactly. A binary registering a handler named v2.1.0 crosses cleanly if the plan is named v2.1.0, and halts with UPGRADE REQUIRED if the plan is named anything else. The handler name is the assertion "this binary knows how to become the chain after this named upgrade."

Second, the handler runs exactly once, in-consensus, at the upgrade height. It is not a startup script. It executes inside the BeginBlocker at block H, so its effects are part of the app-hash every validator must agree on. If the handler read the wall clock, hit the network, or iterated a Go map in unspecified order, validators would compute different post-upgrade states and the chain would fail to finalize H. Determinism is the difference between an upgrade and a chain split.

Section IV§IV — Cert-B Flavor: store migrations and the module version map

Store migrations change a chain's data schema without corrupting state. Each module carries a consensus version, a monotonic integer. When a module's stored layout changes between releases, the module bumps its consensus version and registers a migration function that transforms state from the old version to the new.

The upgrade handler drives this through the module version map: a record of which consensus version each module was at before the upgrade. The handler passes that map to the migration runner, which compares each module's stored version against the version the new binary declares and runs the registered migration for every gap. A module that went from consensus version 2 to 4 has its 2-to-3 and 3-to-4 migrations run in sequence.

Coined term The store migration at height: migrations run inside the handler, in-consensus, at block H, so the schema transformation is itself a deterministic consensus event. Every validator runs identical migration code against identical starting state and produces identical ending state. A migration cannot be "sort of run" or "run later"; it is bound to the exact block where the upgrade lands.

A module with no schema change has no version gap, so the runner runs nothing for it. Most upgrades migrate only one or two modules; the rest pass through untouched. The version map is what lets the runner tell the difference cheaply.

Section V§V — Worked Scenario: a two-module upgrade at height 18,500,000

Governance passed a MsgSoftwareUpgrade naming v2.1.0 at height 18,500,000. The store holds the plan; the chain is armed. Operators stage the binary via today's Bash pipeline. The binary registers a handler named v2.1.0 wiring two migrations: x/staking moved from consensus version 4 to 5 (a new validator-record field), and a new x/nftmarket module was added at version 1.

At block 18,500,000 the BeginBlocker fires. On a staged validator the v2.1.0 handler matches the plan and calls the migration runner with the version map. It sees x/staking at 4 in the map but 5 in the binary, so it runs the 4-to-5 migration and populates the new field with its default. It sees x/nftmarket absent from the map but present in the binary, so it stands up its store. Every other module shows no gap and is skipped. The handler returns, the plan clears, and the chain finalizes the block under v2.1.0.

On an unstaged validator the old binary has no v2.1.0 handler. The BeginBlocker hits the schema fence and panics with UPGRADE REQUIRED: v2.1.0. That node halts until Cosmovisor swaps a staged binary or an operator acts. The 2026-07-04 state-export cert lesson is the recovery path if the migration proves buggy on mainnet.

Section VI§VI — Connection to Today's Ops + Dev Lessons

The three lessons are one upgrade from three angles. This cert lesson is the on-chain mechanism: x/upgrade stores the plan, panics at the schema fence, and runs the handler and migrations in-consensus at height H. The Ops lesson is the operational response: Cosmovisor reads the halt and swaps the binary. The Dev lesson is the preparation: a Bash pipeline stages and checksums the binary. Governance arms the upgrade, the schema fence enforces it, Cosmovisor executes it, and the staging pipeline makes execution safe.

Section VII§VII — Practice Questions

Q1
A software-upgrade proposal for v3 at height H has passed. No operator has staged the v3 binary. What happens at height H?
AnswerThe chain still halts. Every node's BeginBlocker hits the schema fence at H and panics with UPGRADE REQUIRED: v3. The upgrade is a consensus commitment; staging affects recovery speed, not whether the halt occurs.
Q2
Why must an upgrade handler be deterministic?
AnswerIt runs in-consensus at the upgrade height, so its output is part of the app-hash. Non-deterministic behavior would make validators compute different post-upgrade states and the chain would fail to finalize the upgrade block.
Q3
A module's consensus version is 3 in the version map and 3 in the new binary. What migration runs for it?
AnswerNone. The runner sees no version gap and skips the module. Only modules whose stored version lags the binary's declared version have migrations run.
Q4
What is the relationship between the plan name and the handler name?
AnswerThey must match exactly. The module looks up a handler by the plan's name string; a binary whose handler name does not match the plan name is treated as the wrong binary and halts at the fence.
Q5
Where in the block lifecycle does the upgrade execute, and how often?
AnswerIn the BeginBlocker of the upgrade-height block, exactly once. The handler runs before transactions, clears the plan when it returns, and does not run again.

Section VIII§VIII — Closing

The x/upgrade module is the bridge between a governance vote and a running chain that has become something new. Governance arms the plan; the BeginBlocker enforces it at the schema fence; the handler runs the store migrations in-consensus so the schema change is itself a deterministic event. For the exam, hold three things: the halt is a consensus commitment that staging cannot cancel, the handler name must match the plan name exactly, and migrations run at height because a schema change to a replicated state machine must be as deterministic as the blocks around it.

🫡 ⚖️ 📜 Leo.Syri — Praetor Consulate of Imperium Luminaura · Cert-Prep / Interchain · 2026-07-11