Overview Upgrade handlers enable coordinated on-chain upgrades across all validators at specific block heights via governance proposals. They provide a mechanism for chains to perform data migrations, parameter updates, and module upgrades in a deterministic way. 
Upgrade handlers are critical for maintaining consensus during chain upgrades. All validators must run the same upgrade logic at the same height. 
When to Use Upgrade Handlers Upgrade handlers are required when: 
Breaking state changes : Modifying storage formats or data structuresModule migrations : Updating module versions or parametersProtocol upgrades : Implementing new features that require state transitionsData migrations : Moving data between different storage locations 
Basic Structure Registering Upgrade Handlers Upgrade handlers are registered in your app’s RegisterUpgradeHandlers() method: 
Show View upgrade handler registration example
// app/upgrades.go package  app import  (     " context "     upgradetypes  " cosmossdk.io/x/upgrade/types "     sdk  " github.com/cosmos/cosmos-sdk/types "     " github.com/cosmos/cosmos-sdk/types/module " ) func  ( app  * App )  RegisterUpgradeHandlers () {     app . UpgradeKeeper . SetUpgradeHandler (         "v1.0.0" ,  // upgrade name (must match governance proposal)         func ( ctx  context . Context ,  plan  upgradetypes . Plan ,  fromVM  module . VersionMap ) ( module . VersionMap ,  error ) {             sdkCtx  :=  sdk . UnwrapSDKContext ( ctx )             sdkCtx . Logger (). Info ( "Starting upgrade" ,  "name" ,  plan . Name )             // Run module migrations             migrations ,  err  :=  app . ModuleManager . RunMigrations ( ctx ,  app . configurator ,  fromVM )             if  err  !=  nil  {                 return  nil ,  err             }             // Add custom migration logic here             sdkCtx . Logger (). Info ( "Upgrade complete" ,  "name" ,  plan . Name )             return  migrations ,  nil         },     ) } Module Version Management The upgrade handler receives and returns a module.VersionMap that tracks module versions: 
// fromVM contains the module versions before the upgrade // The returned VersionMap contains the new versions after migration migrations ,  err  :=  app . ModuleManager . RunMigrations ( ctx ,  app . configurator ,  fromVM ) Organizing Upgrade Code For better maintainability, organize upgrades in separate packages: 
Directory Structure app/ ├── upgrades/ │   ├── v1_0_0/ │   │   ├── constants.go    # Upgrade name and configuration │   │   ├── handler.go      # Main upgrade handler │   │   └── migrations.go   # Migration logic │   ├── v1_1_0/ │   │   ├── constants.go │   │   ├── handler.go │   │   └── migrations.go │   └── types.go            # Shared types └── upgrades.go             # RegisterUpgradeHandlers constants.go Show View upgrade constants definition
// app/upgrades/v1_0_0/constants.go package  v1_0_0 import  (     storetypes  " cosmossdk.io/store/types "     " github.com/yourchain/app/upgrades " ) const  UpgradeName  =  "v1.0.0" var  Upgrade  =  upgrades . Upgrade {     UpgradeName :           UpgradeName ,     CreateUpgradeHandler :  CreateUpgradeHandler ,     StoreUpgrades :  storetypes . StoreUpgrades {         Added :   [] string {},  // New modules         Deleted : [] string {},  // Removed modules     }, } handler.go Show View upgrade handler implementation
// app/upgrades/v1_0_0/handler.go package  v1_0_0 import  (     " context "     storetypes  " cosmossdk.io/store/types "     upgradetypes  " cosmossdk.io/x/upgrade/types "     sdk  " github.com/cosmos/cosmos-sdk/types "     " github.com/cosmos/cosmos-sdk/types/module " ) func  CreateUpgradeHandler (     mm  * module . Manager ,     configurator  module . Configurator ,     keepers  * upgrades . UpgradeKeepers ,     storeKeys  map [ string ] * storetypes . KVStoreKey , )  upgradetypes . UpgradeHandler  {     return  func ( c  context . Context ,  plan  upgradetypes . Plan ,  vm  module . VersionMap ) ( module . VersionMap ,  error ) {         ctx  :=  sdk . UnwrapSDKContext ( c )         // Run module migrations         vm ,  err  :=  mm . RunMigrations ( c ,  configurator ,  vm )         if  err  !=  nil  {             return  nil ,  err         }         // Custom migrations         if  err  :=  runCustomMigrations ( ctx ,  keepers ,  storeKeys );  err  !=  nil  {             return  nil ,  err         }         return  vm ,  nil     } } Migration Patterns Parameter Migrations Migrating module parameters to new formats: 
func  migrateParams ( ctx  sdk . Context ,  keeper  paramskeeper . Keeper )  error  {     // Get old params     var  oldParams  v1 . Params     keeper . GetParamSet ( ctx ,  & oldParams )     // Convert to new format     newParams  :=  v2 . Params {         Field1 :  oldParams . Field1 ,         Field2 :  convertField ( oldParams . Field2 ),         // New field with default value         Field3 :  "default" ,     }     // Set new params     keeper . SetParams ( ctx ,  newParams )     return  nil } State Migrations Moving data between different storage locations: 
func  migrateState ( ctx  sdk . Context ,  storeKey  storetypes . StoreKey )  error  {     store  :=  ctx . KVStore ( storeKey )     // Iterate over old storage     iterator  :=  storetypes . KVStorePrefixIterator ( store ,  oldPrefix )     defer  iterator . Close ()     for  ;  iterator . Valid ();  iterator . Next () {         oldKey  :=  iterator . Key ()         value  :=  iterator . Value ()         // Transform key/value if needed         newKey  :=  transformKey ( oldKey )         newValue  :=  transformValue ( value )         // Write to new location         store . Set ( newKey ,  newValue )         // Delete old entry         store . Delete ( oldKey )     }     return  nil } Module Addition/Removal Adding or removing modules during upgrade: 
// In constants.go var  Upgrade  =  upgrades . Upgrade {     UpgradeName :  "v2.0.0" ,     CreateUpgradeHandler :  CreateUpgradeHandler ,     StoreUpgrades :  storetypes . StoreUpgrades {         Added :   [] string { "newmodule" },         Deleted : [] string { "oldmodule" },     }, } // In handler.go func  CreateUpgradeHandler ( ... )  upgradetypes . UpgradeHandler  {     return  func ( c  context . Context ,  plan  upgradetypes . Plan ,  vm  module . VersionMap ) ( module . VersionMap ,  error ) {         // Delete old module version         delete ( vm ,  "oldmodule" )         // Initialize new module         if  err  :=  newModuleKeeper . InitGenesis ( ctx ,  defaultGenesis );  err  !=  nil  {             return  nil ,  err         }         // Run migrations         return  mm . RunMigrations ( c ,  configurator ,  vm )     } } Best Practices Always test upgrade handlers thoroughly on testnets before mainnet deployment. 
Idempotency Make migrations idempotent when possible: 
func  migrateSomething ( ctx  sdk . Context ,  store  sdk . KVStore )  error  {     // Check if migration already done     if  store . Has ( migrationCompleteKey ) {         ctx . Logger (). Info ( "Migration already completed, skipping" )         return  nil     }     // Perform migration     // ...     // Mark as complete     store . Set ( migrationCompleteKey , [] byte { 1 })     return  nil } Error Handling Use comprehensive error handling and logging: 
func  migrate ( ctx  sdk . Context ,  keeper  Keeper )  error  {     ctx . Logger (). Info ( "Starting migration" ,  "module" ,  "mymodule" )     count  :=  0     iterator  :=  keeper . IterateAllRecords ( ctx )     defer  iterator . Close ()     for  ;  iterator . Valid ();  iterator . Next () {         if  err  :=  processRecord ( iterator . Key (),  iterator . Value ());  err  !=  nil  {             ctx . Logger (). Error ( "Failed to migrate record" ,                 "key" ,  iterator . Key (),                 "error" ,  err ,             )             return  fmt . Errorf ( "migration failed at record  %d :  %w " ,  count ,  err )         }         count ++         // Log progress for long migrations         if  count % 1000  ==  0  {             ctx . Logger (). Info ( "Migration progress" ,  "processed" ,  count )         }     }     ctx . Logger (). Info ( "Migration complete" ,  "total_migrated" ,  count )     return  nil } Testing Create comprehensive tests for upgrade handlers: 
func  TestUpgradeHandler ( t  * testing . T ) {     app  :=  setupApp ( t )     ctx  :=  app . NewContext ( false ,  tmproto . Header { Height :  1 })     // Setup pre-upgrade state     setupOldState ( t ,  ctx ,  app )     // Run upgrade handler     _ ,  err  :=  v1_0_0 . CreateUpgradeHandler (         app . ModuleManager ,         app . configurator ,         & upgrades . UpgradeKeepers {             // ... keepers         },         app . keys ,     )( ctx ,  upgradetypes . Plan { Name :  "v1.0.0" },  app . ModuleManager . GetVersionMap ())     require . NoError ( t ,  err )     // Verify post-upgrade state     verifyNewState ( t ,  ctx ,  app ) } Upgrade Process Create Upgrade Proposal Submit a governance proposal with the upgrade details: 
mantrachaind  tx  gov  submit-proposal  software-upgrade  v1.0.0  \     --title  "Upgrade to v1.0.0"  \     --description  "Upgrade description"  \     --upgrade-height  1000000  \     --from  validator  \     --deposit  10000000stake Vote on Proposal Validators and delegators vote on the upgrade: 
mantrachaind  tx  gov  vote  1  yes  --from  validator Prepare Binary Build and distribute the new binary with the upgrade handler: 
# Build new binary make  build # Test upgrade on local network ./scripts/test-upgrade.sh # Distribute to validators # Use Cosmovisor for automated upgrades Monitor Upgrade Watch logs during the upgrade height: 
# Monitor upgrade logs tail  -f  ~/.mantrachaind/logs/upgrade.log # Verify upgrade success mantrachaind  query  upgrade  applied  v1.0.0 Cosmos EVM Specific Migrations For Cosmos EVM chains, specific migrations include: 
ERC20 Precompiles Migration Fee Market Parameters : Updating EIP-1559 parametersCustom Precompiles : Registering new precompiled contractsEVM State : Migrating account balances or contract storage 
Troubleshooting Consensus Failure Symptom:  Chain halts with consensus failure at upgrade heightCauses: 
Validators running different binary versions 
Upgrade handler not registered 
Non-deterministic migration logic 
 
Solution: 
Ensure all validators have the same binary 
Verify upgrade handler is registered 
Review migration logic for non-determinism 
 
Upgrade Panic Symptom:  Node panics during upgradeCauses: 
Unhandled error in migration 
Missing required state 
Invalid type assertions 
 
Solution: 
Add comprehensive error handling 
Validate state before migration 
Use safe type conversions 
 
State Corruption Symptom:  Invalid state after upgradeCauses: 
Partial migration completion 
Incorrect data transformation 
Missing cleanup of old data 
 
Solution: 
Make migrations atomic 
Thoroughly test transformations 
Ensure old data is properly cleaned up 
 
References