Runtime Upgrades¶
Introduction¶
One of the defining features of Polkadot SDK-based blockchains is the ability to perform forkless runtime upgrades. Unlike traditional blockchains, which require hard forks and node coordination for upgrades, Polkadot networks enable seamless updates without network disruption.
Forkless upgrades are achieved through WebAssembly (Wasm) runtimes stored on-chain, which can be securely swapped and upgraded as part of the blockchain's state. By leveraging decentralized consensus, runtime updates can be happen trustlessly, ensuring continuous improvement and evolution without halting operations.
This guide explains how Polkadot's runtime versioning, Wasm deployment, and storage migrations enable these upgrades, ensuring the blockchain evolves smoothly and securely. You'll also learn how different upgrade processes apply to solo chains and parachains, depending on the network setup.
How Runtime Upgrades Work¶
In FRAME, the system
pallet uses the set_code
extrinsic to update the Wasm code for the runtime. This method allows solo chains to upgrade without disruption.
For parachains, upgrades are more complex. Parachains must first call authorize_upgrade
, followed by apply_authorized_upgrade
, to ensure the relay chain approves and applies the changes. Additionally, changes to current functionality that impact storage often require a storage migration.
Runtime Versioning¶
The executor is the component that selects the runtime execution environment to communicate with. Although you can override the default execution strategies for custom scenarios, in most cases, the executor selects the appropriate binary to use by evaluating and comparing key parameters from the native and Wasm runtime binaries.
The runtime includes a runtime version struct to provide the needed parameter information to the executor process. A sample runtime version struct might look as follows:
pub const VERSION: RuntimeVersion = RuntimeVersion {
spec_name: create_runtime_str!("node-template"),
impl_name: create_runtime_str!("node-template"),
authoring_version: 1,
spec_version: 1,
impl_version: 1,
apis: RUNTIME_API_VERSIONS,
transaction_version: 1,
};
The struct provides the following parameter information to the executor:
spec_name
- the identifier for the different runtimesimpl_name
- the name of the implementation of the spec. Serves only to differentiate code of different implementation teamsauthoring_version
- the version of the authorship interface. An authoring node won't attempt to author blocks unless this is equal to its native runtimespec_version
- the version of the runtime specification. A full node won't attempt to use its native runtime in substitute for the on-chain Wasm runtime unless thespec_name
,spec_version
, andauthoring_version
are all the same between the Wasm and native binaries. Updates to thespec_version
can be automated as a CI process, as is done for the Polkadot network. This parameter is typically incremented when there's an update to thetransaction_version
impl_version
- the version of the implementation of the specification. Nodes can ignore this. It is only used to indicate that the code is different. As long as theauthoring_version
and thespec_version
are the same, the code might have changed, but the native and Wasm binaries do the same thing. In general, only non-logic-breaking optimizations would result in a change of theimpl_version
transaction_version
- the version of the interface for handling transactions. This parameter can be useful to synchronize firmware updates for hardware wallets or other signing devices to verify that runtime transactions are valid and safe to sign. This number must be incremented if there is a change in the index of the pallets in theconstruct_runtime!
macro or if there are any changes to dispatchable functions, such as the number of parameters or parameter types. Iftransaction_version
is updated, then thespec_version
must also be updatedapis
- a list of supported runtime APIs along with their versions
The executor follows the same consensus-driven logic for both the native runtime and the Wasm runtime before deciding which to execute. Because runtime versioning is a manual process, there is a risk that the executor could make incorrect decisions if the runtime version is misrepresented or incorrectly defined.
Accessing the Runtime Version¶
The runtime version can be accessed through the state.getRuntimeVersion
RPC endpoint, which accepts an optional block identifier. It can also be accessed through the runtime metadata to understand the APIs the runtime exposes and how to interact with them.
The runtime metadata should only change when the chain's runtime spec_version
changes.
Storage Migrations¶
Storage migrations are custom, one-time functions that allow you to update storage to adapt to changes in the runtime.
For example, if a runtime upgrade changes the data type used to represent user balances from an unsigned integer to a signed integer, the storage migration would read the existing value as an unsigned integer and write back an updated value that has been converted to a signed integer.
If you don't make changes to how data is stored when needed, the runtime can't properly interpret the storage values to include in the runtime state and is likely to lead to undefined behavior.
Storage Migrations with FRAME¶
FRAME storage migrations are implemented using the OnRuntimeUpgrade
trait. The OnRuntimeUpgrade
trait specifies a single function, on_runtime_upgrade
, that allows you to specify logic to run immediately after a runtime upgrade but before any on_initialize
functions or transactions are executed.
For further details about this process, see the Storage Migrations page.
Ordering Migrations¶
By default, FRAME orders the execution of on_runtime_upgrade
functions based on the order in which the pallets appear in the construct_runtime!
macro. The functions run in reverse order for upgrades, starting with the last pallet executed first. You can impose a custom order if needed.
FRAME storage migrations run in this order:
- Custom
on_runtime_upgrade
functions if using a custom order - System
frame_system::on_runtime_upgrade
functions - All
on_runtime_upgrade
functions defined in the runtime starting with the last pallet in theconstruct_runtime!
macro
| Created: October 18, 2024