Same workflows before and after an upgrade, every difference classified, one command to reproduce.




Portal Canary checks a Portaldot runtime upgrade end to end, before it ever reaches the chain.

Mainnet serves metadata V13, which modern subxt cannot decode. Canary speaks raw JSON-RPC
and SCALE, and decodes V13 and V14 alike.
Canary diffs every pallet's calls, events, storage,
constants and errors, then flags the changes that
would halt block production on a running chain.
Run the same workflows before and after an upgrade.
Freshly funded accounts, state read at pinned blocks,
every expectation evaluated on both sides.
An upgrade only counts once the new runtime
has produced three more blocks. A spec bump
alone is not proof that an upgrade worked.

Node | Runtime | spec | Size | Actions |
| clean.wasm | 1003 | 2.45 MB | ||
| ed100x.wasm | 1004 | 2.45 MB +91 B | ||
| fee10x.wasm | 1005 | 2.45 MB +2 B | ||
| source-60s.wasm | 1003 | 2.45 MB +234 B | ||
| clean.wasm | 1003 | 2.45 MB +234 B | ||
| clean.wasm | 1005 |
Constants | Status | Values | impact |
ExistentialDeposit | Changed | 1 POT → 1 00 POT | High |
impl_version: 0,
+ MILLISECS_PER_BLOCK: Moment = 6_000;
+ ExistentialDeposit: Balance =
+ 100 * DOLLARS;
+ TransactionByteFee: Balance =
+ 100 * MILLICENTS;
+ spec_version: 1005,
- MILLISECS_PER_BLOCK: Moment = 60_000;
- ExistentialDeposit: Balance =
- 1 * DOLLARS;
- TransactionByteFee: Balance =
- 10 * MILLICENTS;
- spec_version: 1001,
+ spec_version: 10011004,
transaction_version: 2,
Rule | Severity | Finding | Constant |
Fixed | Critical | BABE epoch duration cannot change on a running chain | Babe.constant.EpochDuration |
Fixed | Critical | BABE slot duration cannot change on a running chain | Babe.constant.EpochDuration |
Fixed | Critical | BABE epoch duration cannot change on a running chain | Babe.constant.ExpectedBlockTime |
Fixed | Critical | BABE slot duration cannot change on a running chain | Babe.constant.EpochDuration |
Linked | Critical | The timestamp period is tied to the BABE slot | Timestamp.constant.MinimumPeriod |
$ canary upgrade --ws ws://127.0.0.1:9945 --wasm runtimes/out/ed100x.compact.wasm
before spec_version 1002
events system.CodeUpdated, sudo.Sudid, system.ExtrinsicSuccess
after spec_version 1004
liveness ok (#9 -> #12 under the new runtime)
$ canary compare --baseline ws://127.0.0.1:9944 --candidate ws://127.0.0.1:9945
~ Balances.constant.ExistentialDeposit
baseline: T::Balance = 100000000000000
candidate: T::Balance = 10000000000000000
$ canary test --baseline ws://127.0.0.1:9944 --candidate ws://127.0.0.1:9945 --injected "ed100x"
PASS Info balances.keep_alive
REGRESSION High balances.transfer.basic
step 0 dispatch success baseline: success candidate: failed (balances.ExistentialDeposit)
delta receiver.free baseline: +100000000000000 candidate: +0
PASS Info balances.transfer.insufficient
PASS Info identity.deposit
$ canary upgrade --ws ws://127.0.0.1:9946 --wasm runtimes/out/source-60s.compact.wasm --timeout 40
after spec_version 1003
liveness HALTED: best block stuck at #85 for 40s after the upgrade (needed #88)
verdict REGRESSION Critical: runtime accepted by setCode but block production stopped
node log: panicked at 'Timestamp slot must match `CurrentSlot`'
canary attest writes a finding hash to the dev chain with system.remark, and canary verify checks it later, so a result can be confirmed without trusting whoever reported it.
See how many calls, events, storage items, constants and errors moved between two runtimes, pallet by pallet.
The compare engine is compiled to WebAssembly, about 300 KB. Your browser opens a WebSocket to each node and diffs them locally, with no server involved.
Sum every account's free and reserved balance at one finalized block and compare it with total issuance. Mainnet: delta +0 planck.
Every check is free and Apache-2.0.
For one endpoint,
checked layer by layer.
For two runtimes,
diffed pallet by pallet.
Yes, if you build or operate Portaldot runtimes and want to know an upgrade is safe before it reaches the chain. Canary checks endpoints, diffs runtimes, runs workflows on both sides of an upgrade and tells you exactly what changed.
If you only want to look at a diff without installing anything, open the Browser dashboard instead.
Yes. Mainnet runs metadata V13, which modern subxt cannot decode. Canary speaks raw JSON-RPC and SCALE and decodes V13 and V14, so doctor, compare and invariants all run against mainnet today.
Open the live compare.
In short, the CLI runs every check including real setCode upgrades on a dev chain, while the dashboard runs the compare engine as WebAssembly in your browser against live nodes.Yes. canary upgrade applies a candidate WASM to a running dev chain with sudo.sudoUncheckedWeight(system.setCode), waits for three more blocks under the new runtime, then runs the workflow corpus against it.
try-runtime tells you the migration ran. Canary tells you whether your app still works, shows the exact state that diverged and gives you one command to reproduce it. See the recorded runs:
No. The page opens a WebSocket from your browser to each node and runs the diff locally. No server is involved. A wallet is optional: it signs only when you run the workflow suite or anchor a report on a chain other than a local dev chain.
Every workflow gets one of four verdicts:
Absolutely. Canary ships a GitHub Action, and its own CI runs against a real Portaldot dev node. Start from the action.yml in the repository.
To summarize:
exit code : 1 on any regression, 2 if anything is inconclusive.canary test --baseline ws://127.0.0.1:9944 --candidate ws://127.0.0.1:9945
Note: A spec_version bump is not proof. The public dev node and mainnet both report spec_version 1002but are different runtimes. Run canary compare before trusting dev-node results (details in docs/V3_READINESS.md).
Yes, Apache-2.0. The Rust workspace, the WASM engine, the workflow corpus and the candidate runtimes with their exact source diffs are all in the repository.
Try it in the browser instead. ↓
Browser dashboardPick a baseline and a candidate endpoint and see every pallet change and upgrade hazard, computed by the same Rust engine as the CLI. Nothing to install.
