Upgrades
The enablement of forkless runtime upgrades is one of the defining characteristics of the Substrate framework for blockchain development. This capability is made possible by including the definition of the state transition function, i.e. the runtime itself, as an element in the blockchain's evolving runtime state. This allows network maintainers to leverage the blockchain's capabilities for trustless, decentralized consensus to securely make enhancements to the runtime.
In the FRAME system for runtime development, the System library defines
the set_code
call
that is used to update the definition of the runtime. The
Forkless Upgrade a Chain tutorial describes the details
of FRAME runtime upgrades and demonstrates two mechanisms for performing them. Both upgrades
demonstrated in that tutorial are strictly additive, which means that they modify the runtime by
means of extending it as opposed to updating the existing runtime state. In the event that a
runtime upgrade defines changes to existing state, it will likely be necessary to perform a "storage
migration".
Runtime versioning
In order for the executor to be able to select the appropriate runtime
execution environment, it needs to know the spec_name
, spec_version
and authoring_version
of
both the native and Wasm runtime.
The runtime provides a runtime version struct. A sample runtime version struct is shown below:
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,
};
spec_name
: The identifier for the different Substrate runtimes.impl_name
: The name of the implementation of the spec. This is of little consequence for the node and serves only to differentiate code of different implementation teams.authoring_version
: The version of the authorship interface. An authoring node will not attempt to author blocks unless this is equal to its native runtime.spec_version
: The version of the runtime specification. A full node will not attempt to use its native runtime in substitute for the on-chain Wasm runtime unless all ofspec_name
,spec_version
, andauthoring_version
are the same between Wasm and native. This would typically get bumped alongside atransaction_version
bump.impl_version
: The version of the implementation of the specification. Nodes are free to ignore this; it serves only as an indication that the code is different. As long as the other two versions are the same then while the actual code may be different, it is required to 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 extrinsics interface, useful to synchronize firmware updates for hardware wallets or for any signing device to verify runtime transactions are valid. This allows hardware wallets to know which transactions they can safely sign. This number must be bumped when there is a change in the index of the pallets in theconstruct_runtime!
macro or when any dispatchable changes, such as the number of parameters or any parameter type changes. This includes any change in pallet and dispatchable runtime parameter types. If this number is updated, then thespec_version
must also be updated. Updates to thespec_version
can be automated as a CI process, as is done for the Polkadot network.apis
is a list of supported runtime APIs along with their versions.
As mentioned above, the executor always verifies that the native runtime has the same consensus-driven logic before it chooses to execute it, independent of whether the version is higher or lower.
The runtime versioning is manually set. Thus the executor can still make inappropriate decisions if the runtime version is misrepresented.
Accessing the runtime version
The runtime version is useful for application or integration developers that are building on a FRAME
runtime. The FRAME runtime system exposes this information by way of the state.getRuntimeVersion
RPC endpoint, which accepts an optional block identifier. Most developers building on a FRAME-based
blockchain will use the runtime's metadata to understand the APIs the runtime exposes
and the requirements for interacting with these APIs. The runtime's metadata should only change
when the chain's
runtime spec_version
changes.
Forkless runtime upgrades
Traditional blockchains require a hard fork when upgrading the state transition function of their chain. This requires node operators to stop their nodes and manually upgrade to the latest executable. For distributed production networks, coordination of a hard fork upgrades can be a complex process.
The culmination of the properties listed on this page allows for Substrate-based blockchains to perform "forkless runtime upgrades". This means that the upgrade of the runtime logic can happen in real time without causing a fork in the network.
To perform a forkless runtime upgrade, Substrate uses existing runtime logic to update the Wasm
runtime stored on the blockchain to a new consensus-breaking version with new logic. This upgrade
gets pushed out to all syncing nodes on the network as a part of the consensus process. Once the
Wasm runtime is upgraded, the executor will see that the native runtime spec_name
, spec_version
,
or authoring_version
no longer matches this new Wasm runtime. As a result, it will fall back to
execute the canonical Wasm runtime instead of using the native runtime in any of the execution
processes.
Storage migrations
Storage migrations are custom, one-time functions that allow developers to rework existing storage in order to convert it to conform to updated expectations. For instance, imagine a runtime upgrade that changes the data type used to represent user balances from an unsigned integer to a signed integer - in this case, 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. Failure to perform a storage migration when needed will result in the runtime execution engine misinterpreting the storage values that represent the runtime state and lead to undefined behavior. Substrate runtime storage migrations fall into a category of storage management broadly referred to as "data migrations".
Storage migrations with FRAME
FRAME storage migrations are implemented by way of
the OnRuntimeUpgrade
trait,
which specifies a single function, on_runtime_upgrade
. This function provides a hook that allows
runtime developers to specify logic that will run immediately after a runtime upgrade but before
any extrinsics or even the on_initialize
function has executed.
Preparing for a migration
Preparing for a storage migration means understanding the changes that are defined by a runtime
upgrade. The Substrate repository uses
the D1-runtime-migration
label
to designate such changes.
Writing a migration
Each runtime migration will be different, but there are certain conventions and best practices that should be followed.
- Extract migrations into reusable functions and write tests for them.
- Include logging in migrations to assist in debugging.
- Remember that migrations are executed within the context of the upgraded runtime, which means that migration code may need to include deprecated types, as in this example.
- Use storage versions to make migrations safer by making them more declarative, as in this example.
Ordering migrations
By default, FRAME will order the execution of on_runtime_upgrade
functions according to the order
in which the pallets appear in the construct_runtime!
macro - in particular, they will run in
reverse (top-to-bottom) order. FRAME exposes a capability to inject storage migrations in a custom
order, if needed (see an
example here).
FRAME storage migrations will run in this order:
frame_system::on_runtime_upgrade
- Custom
on_runtime_upgrade
, as described above - All
on_runtime_upgrade
functions defined in the pallets included in the runtime, in the order described above
If you are running on a Substrate version after commit #bd8c1cae
, the storage migration order has been updated
to:
- Custom
on_runtime_upgrade
frame_system::on_runtime_upgrade
- All
on_runtime_upgrade
functions defined in all included pallets.
The reason is to cater for scenarios where one needs to write custom code to make
frame_system::on_runtime_upgrade
run and return successfully. Refer to the details here.
Testing migrations
It is important to test storage migrations and a number of utilities exist to assist in this process. The Substrate Debug Kit includes a Remote Externalities tool that allows storage migration unit testing to be safely performed on live chain data. The Fork Off Substrate script makes it easy to create a chain specification that can be used to bootstrap a local test chain for testing runtime upgrades and storage migrations.
Learn more
- Parity Runtime Engineer Alexander Popiak maintains a Substrate Migrations repository with lots of helpful information about Substrate runtime upgrades and storage migrations.
- Learn how to write a basic storage migration for a pallet