Skip to content

Runtime Upgrades

Intermediate

Introduction

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:

polkadot-omni-node --chain ./chain_spec.json --dev

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:

custom-pallet/src/lib.rs
#[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:

cargo build --release

Now, you can test this new function in pallets/custom-pallet/src/tests.rs

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:

cargo test --package custom-pallet

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:

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:

Cargo.toml
[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:

cd /path/to/your/parachain-template

Build the new runtime:

cargo build --release

Verify that you have the proper WASM builds by executing:

ls -la target/release/wbuild/parachain-template-runtime/

If you can see the following elements, it means that you are ready to submit the runtime upgrade to your running chain:

ls -la target/release/wbuild/parachain-template-runtime/
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).

  1. Open Polkadot.js Apps and connect to your node
  2. Click on the Developer and select the Extrinsics option in the dropdown

  3. Prepare the sudo call:

    1. Select the sudo pallet
    2. Select the sudo(call) extrinsic from the list

  4. In the sudo call:

    1. Select the system call
    2. Select setCode extrinsic from the list

  5. 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

  6. 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

  1. In Polkadot.js Apps, navigate to the Chain State section

    1. Click the Developer dropdown
    2. Click the Chain State option

  2. Query runtime spec version

    1. Select the System pallet
    2. Select the lastRuntimeUpgrade() query

  3. Click the + button to query the current runtime version

  4. Verify that the specVersion matches your new runtime (should be 2 if you followed the example)

Test New Functionality

  1. Navigate to Developer > Extrinsics
  2. Select your custom pallet from the dropdown
  3. 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.

    Get Started

Last update: July 8, 2025
| Created: July 8, 2025