Runtime Upgrades¶
IntermediateIntroduction¶
Upgrading the runtime of your Polkadot SDK-based blockchain is a fundamental feature that allows you to add new functionality, fix bugs, or improve performance without requiring a hard fork. Runtime upgrades are performed by submitting a special extrinsic that replaces the existing on-chain WASM runtime code. This process is trustless, transparent, and can be executed either through governance or using sudo, depending on your chain's configuration.
This tutorial will guide you through the steps to prepare, submit, and verify a runtime upgrade for your parachain or standalone Polkadot SDK-based chain. For this example, you'll continue from the state left by the previous tutorials, where you have a custom pallet integrated into your runtime.
Update the Runtime¶
In this section, you will add a new feature to your existing custom pallet and upgrade your runtime to include this new functionality.
Start Your Chain¶
Before making any changes, ensure your blockchain node is running properly:
Verify your chain is operational and note the runtime version state in Polkadot JS. For more details, check the Interact with the Node.
As you can see, the runtime version is 1
since this chain has not been upgraded. Keep this chain running in the background.
Add a New Feature¶
Now, you can extend your existing custom pallet by adding a new dispatchable function to reset the counter to zero. This provides a meaningful upgrade that demonstrates new functionality. Copy and paste the following code at the end of your lib.rs
file in your custom pallet:
#[pallet::call]
impl<T: Config> Pallet<T> {
// ... existing calls like increment, decrement, etc.
/// Reset the counter to zero.
///
/// The dispatch origin of this call must be _Root_.
///
/// Emits `CounterValueSet` event when successful.
#[pallet::call_index(3)]
#[pallet::weight(0)]
pub fn reset_counter(origin: OriginFor<T>) -> DispatchResult {
ensure_root(origin)?;
<CounterValue<T>>::put(0u32);
Self::deposit_event(Event::CounterValueSet { counter_value: 0 });
Ok(())
}
}
The reset_counter
function will be a Root-only operation that sets the counter value back to zero, regardless of its current state. This is useful for administrative purposes, such as clearing the counter after maintenance, testing, or at the start of new periods. Unlike the existing increment/decrement functions that any signed user can call, this reset function requires Root privileges, making it a controlled administrative action.
Ensure that your runtime compiles by running:
Now, you can test this new function in pallets/custom-pallet/src/tests.rs
// ... existing unit tests
...
#[test]
fn reset_counter_works() {
new_test_ext().execute_with(|| {
System::set_block_number(1);
// First increment the counter
assert_ok!(CustomPallet::increment(RuntimeOrigin::signed(1), 1));
// Ensure the event matches the increment action
System::assert_last_event(
Event::CounterIncremented {
counter_value: 1,
who: 1,
incremented_amount: 1,
}
.into(),
);
// Reset should work with root origin
assert_ok!(CustomPallet::reset_counter(RuntimeOrigin::root()));
// Check that the event was emitted
System::assert_last_event(Event::CounterValueSet { counter_value: 0 }.into());
});
}
#[test]
fn reset_counter_fails_without_root() {
new_test_ext().execute_with(|| {
System::set_block_number(1);
// Should fail with non-root origin
assert_noop!(
CustomPallet::reset_counter(RuntimeOrigin::signed(1)),
sp_runtime::DispatchError::BadOrigin
);
});
}
Ensure that your tests pass by running:
Update Runtime Configuration¶
Since you've only added new functionality without changing existing APIs, minimal runtime changes are needed. However, verify that your runtime configuration is still compatible. If you've added new configuration parameters to your pallet, update them accordingly in the runtime/configs/mod.rs
.
Bump the Runtime Version¶
This is a critical step - you must increment the runtime version numbers to signal that an upgrade has occurred. In runtime/src/lib.rs
:
#[sp_version::runtime_version]
pub const VERSION: RuntimeVersion = RuntimeVersion {
spec_name: alloc::borrow::Cow::Borrowed("parachain-template-runtime"),
impl_name: alloc::borrow::Cow::Borrowed("parachain-template-runtime"),
authoring_version: 1,
spec_version: 2, // <-- increment this (was 1)
impl_version: 0,
apis: apis::RUNTIME_API_VERSIONS,
transaction_version: 1,
system_version: 1,
};
Also update runtime/Cargo.toml
version:
[package]
name = "parachain-template-runtime"
description = "A parachain runtime template built with Substrate and Cumulus, part of Polkadot Sdk."
version = "0.2.0" # <-- increment this version
# ... rest of your Cargo.toml
For more information about runtime versioning, check the Runtime Upgrades guide.
Build the New Runtime¶
Navigate to your project root:
Build the new runtime:
Verify that you have the proper WASM builds by executing:
If you can see the following elements, it means that you are ready to submit the runtime upgrade to your running chain:
parachain_template_runtime.wasm parachain_template_runtime.compact.wasm parachain_template_runtime.compact.compressed.wasm
Submit the Runtime Upgrade¶
You can submit a runtime upgrade using the Sudo pallet (for development chains) or via on-chain governance (for production chains).
- Open Polkadot.js Apps and connect to your node
-
Click on the Developer and select the Extrinsics option in the dropdown
-
Prepare the sudo call:
- Select the sudo pallet
-
Select the sudo(call) extrinsic from the list
-
In the sudo call:
- Select the system call
-
Select setCode extrinsic from the list
-
For the
code
parameter, click file upload and select your WASM runtime file:- Use
parachain_template_runtime.compact.compressed.wasm
if available (smaller file) -
Otherwise, use
parachain_template_runtime.wasm
- Use
-
Click Submit Transaction and sign the transaction with the sudo key
Using Governance (Production)
For production chains with governance enabled, you must follow the on-chain democratic process. This involves submitting a preimage of the new runtime code (using the Democracy pallet), referencing the preimage hash in a proposal, and then following your chain's governance process (such as voting and council approval) until the proposal passes and is enacted. This ensures that runtime upgrades are transparent and subject to community oversight.
Verify the Upgrade¶
After the runtime upgrade extrinsic is included in a block, verify that the upgrade was successful.
Check Runtime Version¶
-
In Polkadot.js Apps, navigate to the Chain State section
- Click the Developer dropdown
-
Click the Chain State option
-
Query runtime spec version
- Select the System pallet
-
Select the lastRuntimeUpgrade() query
-
Click the + button to query the current runtime version
-
Verify that the
specVersion
matches your new runtime (should be2
if you followed the example)
Test New Functionality¶
- Navigate to Developer > Extrinsics
- Select your custom pallet from the dropdown
-
You should now see the new
resetCounter
function available
Now, you can test the new functionality:
- First, increment the counter using your existing function
- Then use the new reset function (note: you'll need sudo/root privileges)
- Verify the counter value is reset to 0
Where to Go Next¶
-
Tutorial Deploy on Paseo TestNet
Deploy your Polkadot SDK blockchain on Paseo! Follow this step-by-step guide for a seamless journey to a successful TestNet deployment.
| Created: July 8, 2025