While reviewing a testnet upgrade, I found two genesis files that passed validation but produced different hashes after startup. The fault came from a module default value, not from transactions or balances. Since then, I have viewed migration as preserving semantics, not merely changing formats.

The operator documentation for Babylon places migrate between the source genesis and the target binary. The command changes the format, schema, and default parameters, then moves the old app state into a new structure so it can be read consistently. These three layers change, but btcstaking, checkpointing, and finality must not generate additional history.

The process contains 7 steps, with the stop point illustrated at block height 2000000. Babylon stops the chain, exports the final state, creates a backup, runs migrate, validates genesis, replaces the file, and updates the binary. Validators must remain at the same height, because a difference of one block leaves their exported states without a common starting point.

Before conversion, state hashes from validators are compared against a baseline hash. Babylon then checks 3 modules, btcstaking, checkpointing, and finality, compares chain id, genesis time, and app state, then loads genesis through dry run. Successful validation confirms the structure, but does not prove that application invariants remain intact.

I picture the process as replacing the reader on an electricity meter. The encoding can change, but the accumulated reading must not return to zero or jump by one unit. Genesis is the display, while state is the amount of electricity already consumed.

I want to see testnet runs using data close to production, rehearsed rollback procedures, and published downtime. Babylon has a reasonable control process, but an exit code of 0 is not enough to call migration successful. An upgrade changes how the network reads data, not what the network has already agreed upon.
@BabylonLabs_io $BABY #baby