End-to-end runbook for shipping a new runtime to the Heisenberg (internal) and Planck testnets, and smoke-testing them with the chain exercise suite.
Runtime upgrades on this chain are governance-only — pallet-sudo was removed, so
there is no sudo.setCode shortcut. An upgrade is a Tech-Referenda proposal on the
fast_upgrade track that, once passed, executes system.authorize_upgrade(code_hash);
the WASM itself is then delivered via the permissionless quantus runtime apply
(system.apply_authorized_upgrade). No node restart is required.
This flow needs a runtime that has the
fast_upgradetrack andFastUpgradeorigin. To upgrade an older live runtime that predates them, use the matching older CLI (which submitssystem.set_codeon the Root track).
Polkadot-JS Apps and the standard
@polkadot/apicannot sign for this chain (Dilithium / ML-DSA signatures). Every step below uses the Quantus CLI (quantus), built from https://github.com/Quantus-Network/quantus-cli.
See also: RUNTIME_UPGRADE_VIA_GOVERNANCE.md (the
generic single-network procedure) and RUNTIME_SURFACE.md (why
sudo is gone and how the Tech track is configured).
Do not increment spec_version or the crate version in runtime/Cargo.toml
in feature or security PRs. A consensus-critical change (new verifier rules,
changed weights, new dispatchables) still merges at the current
spec_version. Reviewers asking for a bump on that PR: the bump is not
missing.
spec_versionis incremented by the Quantus - Release Proposal workflow (.github/workflows/quantus-release-proposal.yml) whenis_runtime_upgradeis set. That PR updatesVERSIONinruntime/src/lib.rsandruntime/Cargo.toml, and is the binary + wasm that get released together. A node uses its native runtime only whenspec_name,spec_version, andauthoring_versionall match the on-chain Wasm, so amainbinary with an unreleased verifier does not silently replace the live runtime.transaction_versionis not touched by CI. Bump it only when the signed extrinsic encoding changes (theTxExtensionset or payload layout). A change to how signatures are produced or verified does not require it.
# 1. Endpoints + signing wallets (see tables below)
export HEISENBERG_WS="wss://a1-heisenberg.quantus.com"
export PLANCK_WS="wss://a1-planck.quantus.com"
# 2. Get the runtime wasm (from the GitHub release, or the srtool CI artifact)
export WASM=/path/to/quantus_runtime.compact.compressed.wasm
# 3. Heisenberg: submit -> deposit -> vote (all 3 members) -> wait -> apply -> verify
# 4. Run the exercise suite: quantus exercise --skip governance --node-url $HEISENBERG_WS
# 5. Planck: repeat the governance flow with the planck walletsFull step-by-step is below. Each upgrade is a multi-step governance flow whose
referendum index is only known after you submit — capture it from the list step.
The chain-spec (node/src/chain-specs/{heisenberg,planck}.json) only defines p2p boot
nodes, not RPC URLs — but each network also exposes the Substrate RPC over wss://
(TLS, port 443 — no port number needed).
| Testnet | RPC endpoint (--node-url) |
Backup |
|---|---|---|
| Heisenberg | wss://a1-heisenberg.quantus.com |
wss://a2-heisenberg.quantus.com |
| Planck | wss://a1-planck.quantus.com |
wss://a2-planck.quantus.com |
These are secure
wss://endpoints, so all commands — includingquantus system --runtime, whose stricter "chainHead" client refuses a remote plainws://withInsecureUrl— work directly against them.
Both testnets have a 3-member Tech Collective. The fast_upgrade track needs
≥ 80 % approval and ≥ 80 % support, so all 3 members must vote aye. Confirm the
live membership any time with:
quantus tech-collective list-members --node-url <endpoint>| Testnet | Local wallet names (all present in your keystore) | Submit --from |
|---|---|---|
| Heisenberg | crystal_alice, crystal_bob, crystal_charlie |
crystal_alice |
| Planck | planck-tc-1 (= s1), planck-tc-2 (= s2), s3 |
planck-tc-1 |
If any wallet is password-protected, add -p/--password or --password-file <file> to
every signing command (runtime update, place-decision-deposit, vote).
quantusCLI on yourPATH(quantus --version).- The Tech Collective wallets above in your keystore (
quantus wallet list). - The new runtime wasm (see next section).
- Each submit/vote wallet needs a little balance to pay fees + the decision deposit (the deposit is refundable after the referendum ends).
The canonical artifact is the compressed runtime from the release:
quantus-runtime-v<specVersion>.compact.compressed.wasm, built by srtool in CI.
Normal path — download from the published GitHub release:
cd /path/to/chain
gh release download <release-tag> --pattern '*.compact.compressed.wasm' --dir ./release-wasm
export WASM="$(find ./release-wasm -name '*.compact.compressed.wasm' | head -1)"If the release didn't publish (fallback — pull the srtool CI artifact):
The Release Tag & Publish workflow builds the runtime (srtool) before it builds the
node binaries and publishes the GitHub release. If a later matrix job fails (e.g. the
Windows node build), the release — and its wasm asset — is skipped, but the srtool
wasm still exists as a workflow artifact named runtime.
# Find the run (or use the run id from `gh run list --workflow=quantus-release.yml`)
gh run download <run-id> -n runtime -D ./ci-wasm
export WASM="$(find ./ci-wasm -name '*.compact.compressed.wasm' | head -1)"
echo "Using wasm: $WASM"Workflow artifacts expire (~90 days); release assets are permanent. If you hit the fallback, also fix + re-publish the release (
gh run rerun <run-id> --failed).
Or build it locally from the tagged commit:
git checkout <release-tag>
cargo build --release -p quantus-runtime # or: cargo build --release
export WASM="$(pwd)/target/release/wbuild/quantus-runtime/quantus_runtime.compact.compressed.wasm"export HEISENBERG_WS="wss://a1-heisenberg.quantus.com"
export HEISENBERG_TC="crystal_alice"
# a) Diff the new wasm against the live runtime (confirms it's actually an upgrade and
# prints both spec_versions)
quantus runtime compare --wasm-file "$WASM" --node-url "$HEISENBERG_WS"
# b) Confirm the collective membership (who you'll need aye votes from)
quantus tech-collective list-members --node-url "$HEISENBERG_WS"
# c) Submit the proposal: notes the authorize_upgrade(code_hash) preimage, then opens
# the referendum on the fast_upgrade track. Add --force to skip the interactive confirm.
quantus runtime update \
--wasm-file "$WASM" \
--from "$HEISENBERG_TC" \
--node-url "$HEISENBERG_WS"
# d) Find the new referendum index
quantus tech-referenda list --node-url "$HEISENBERG_WS"
export HREF=<referendum_index_from_list>
# e) Place the decision deposit (moves it Preparing -> Deciding; refundable later)
quantus tech-referenda place-decision-deposit \
--index "$HREF" \
--from "$HEISENBERG_TC" \
--node-url "$HEISENBERG_WS"
# f) Vote aye with all three members (fast_upgrade needs 80%/80% — all 3 of 3)
quantus tech-collective vote --referendum-index "$HREF" --vote aye --from crystal_alice --node-url "$HEISENBERG_WS"
quantus tech-collective vote --referendum-index "$HREF" --vote aye --from crystal_bob --node-url "$HEISENBERG_WS"
quantus tech-collective vote --referendum-index "$HREF" --vote aye --from crystal_charlie --node-url "$HEISENBERG_WS"
# g) Monitor until it passes -> confirms -> enacts (authorize_upgrade executes)
quantus tech-referenda status --index "$HREF" --node-url "$HEISENBERG_WS"
# h) Apply the authorized wasm (permissionless — any funded wallet)
quantus runtime apply --wasm-file "$WASM" --from "$HEISENBERG_TC" --node-url "$HEISENBERG_WS"
# i) Verify the new runtime is live (compare should now show equal versions)
quantus runtime compare --wasm-file "$WASM" --node-url "$HEISENBERG_WS"⏱️ Timing. The
fast_upgradetrack has 10-minute prepare, confirm, and enactment windows (~30 minutes end to end plus voting time). If a testnet runs withfast-governance, it enacts in minutes. Check the live periods withquantus tech-referenda config --node-url "$HEISENBERG_WS".
quantus exercise runs the chain exercise suite against a live node (balances,
reversible, multisig, preimage, negative, fuzz, wormhole, …). It uses ephemeral
accounts plus the crystal_alice/bob/charlie dev accounts.
# Full suite. Skip 'governance' on a non-fast-governance node (that phase needs one).
# The 'upgrade' phase is already off by default.
quantus exercise --skip governance --node-url "$HEISENBERG_WS"
# Minimal subset:
quantus exercise --phases reads,balances --node-url "$HEISENBERG_WS"
# Machine-readable report / fail on first error:
quantus exercise --skip governance --json --fail-fast --node-url "$HEISENBERG_WS"
# After Planck is upgraded, run it there too:
quantus exercise --skip governance --node-url "$PLANCK_WS"Available phases: reads, balances, reversible, multisig, preimage, governance, negative, fuzz, wormhole, upgrade (default = all except upgrade).
In-repo shell smoke test (single Alice→Bob transfer + tx-pool watch), as an alternative minimal check:
# terminal 1 — watch the pool
node scripts/testing/test_txwatch.mjs
# terminal 2 — submit a transfer (NODE_URL overrides the ws://127.0.0.1:9944 default)
NODE_URL="$HEISENBERG_WS" ./scripts/testing/submit_transfer.sh 5Identical flow, pointed at Planck, using the Planck wallets.
export PLANCK_WS="wss://a1-planck.quantus.com"
# a) diff vs live
quantus runtime compare --wasm-file "$WASM" --node-url "$PLANCK_WS"
# b) confirm members
quantus tech-collective list-members --node-url "$PLANCK_WS"
# c) submit proposal
quantus runtime update --wasm-file "$WASM" --from planck-tc-1 --node-url "$PLANCK_WS"
# d) find the referendum index
quantus tech-referenda list --node-url "$PLANCK_WS"
export PREF=<referendum_index_from_list>
# e) place decision deposit
quantus tech-referenda place-decision-deposit --index "$PREF" --from planck-tc-1 --node-url "$PLANCK_WS"
# f) vote aye with all three members (all 3 of 3 required)
quantus tech-collective vote --referendum-index "$PREF" --vote aye --from planck-tc-1 --node-url "$PLANCK_WS"
quantus tech-collective vote --referendum-index "$PREF" --vote aye --from planck-tc-2 --node-url "$PLANCK_WS"
quantus tech-collective vote --referendum-index "$PREF" --vote aye --from s3 --node-url "$PLANCK_WS"
# g) monitor to enactment
quantus tech-referenda status --index "$PREF" --node-url "$PLANCK_WS"
# h) apply the authorized wasm
quantus runtime apply --wasm-file "$WASM" --from planck-tc-1 --node-url "$PLANCK_WS"
# i) verify new version live
quantus runtime compare --wasm-file "$WASM" --node-url "$PLANCK_WS"| Symptom | Cause / fix |
|---|---|
| Referendum never leaves "Preparing" | You skipped the decision deposit (Step e). Place it, then voting/deciding starts. |
| Referendum rejected / not enough support | fast_upgrade needs ≥ 80 % approval / 80 % support → all 3 members must vote aye. |
| Referendum enacted but version unchanged | Enactment only authorizes the hash — run quantus runtime apply with the same wasm. |
| No wasm asset on the GitHub release | The release publish job failed after srtool. Pull the runtime workflow artifact (Step 0 fallback) and re-run the release. |
| Wallet prompts for a password | Add -p <password> or --password-file <file> to the signing command. |
| Action | Command |
|---|---|
| On-chain runtime version | quantus runtime compare --wasm-file <wasm> --node-url <ws> |
| List collective members | quantus tech-collective list-members --node-url <ws> |
| Submit upgrade (authorize referendum) | quantus runtime update --wasm-file <wasm> --from <member> --node-url <ws> |
| Apply authorized wasm (after enactment) | quantus runtime apply --wasm-file <wasm> --from <wallet> --node-url <ws> |
| List referenda | quantus tech-referenda list --node-url <ws> |
| Referendum detail | quantus tech-referenda get --index <i> --node-url <ws> |
| Place decision deposit | quantus tech-referenda place-decision-deposit --index <i> --from <wallet> --node-url <ws> |
| Vote | quantus tech-collective vote --referendum-index <i> --vote aye --from <member> --node-url <ws> |
| Voting status / tally | quantus tech-referenda status --index <i> --node-url <ws> |
| Track config (periods, deposits) | quantus tech-referenda config --node-url <ws> |
| Run the test suite | quantus exercise --skip governance --node-url <ws> |
| Refund deposits (after it ends) | quantus tech-referenda refund-decision-deposit --index <i> --from <wallet> --node-url <ws> |