Node Operations & Tooling · Portaldot

Portal Canary

I test a Portaldot runtime upgrade on a copy of today's mainnet, and check every account, before the upgrade ever touches mainnet.

Venkat · portal-canary-app.vercel.app · github.com/Venkat5599/portalcanary

Portal Canary · Venkat01 / 10

The problem

An upgrade can look perfect and still destroy money.

✓ setCode accepted

The extrinsic succeeds and spec_version goes from 1002 to 1006.

✓ Blocks keep coming

Three new blocks under the new runtime, finalized, no error anywhere.

✗ Every balance survived

286.305 POT gone from 9 real accounts. Total supply still claims it exists.

I built a runtime whose migration halves every reserved balance and ran it on a copy of mainnet. Every signal the chain gives said the upgrade worked. The only thing that showed it was checking the money, account by account.

Portal Canary · Venkat02 / 10

Why Portaldot

This has to be built for Portaldot specifically.

Mainnet speaks an old format

Portaldot mainnet runs a 2021 runtime with metadata V13. Current Substrate tooling reads V14 and up, so it can't decode mainnet at all. I wrote a decoder for both V13 and V14.

No upgrade-testing API on mainnet

Mainnet exposes 13 runtime APIs and none of them is for trying an upgrade. Every upgrade is one setCode, live.

The V3 move is the big one

Mainnet against the V3 testnet: 969 changes and 111 risks. 3 are critical: two would stop block production, and setCode would refuse the third outright.

The proof lives on Portaldot

Every run is recorded on the Portaldot V3 testnet, and the results registry is a contract on the V3 EVM.

Portal Canary · Venkat03 / 10

How it works

One run: fork mainnet, upgrade the fork, check everything, record it.

01

Fork mainnet

Its latest finalized block: all 4,716 accounts, deposits, locks, stakes. Takes about 15 seconds.

02

Upgrade the fork

setCode with the candidate runtime, on a local chain that only we control.

03

Check

Every balance and the total supply, before and after. Blocks still produced. Runtime risks. 5 workflows on both sides.

04

Record

One file hashes every result file. Its hash goes on the V3 testnet.

05

Verify

The run page re-checks the record in your browser. A script re-checks every run, no key needed.

Mainnet itself is never written to. The upgrade step refuses any chain that reports itself as live.

Portal Canary · Venkat04 / 10

Proof · a run on real mainnet state

Run 971a7223bf7b: Critical on mainnet state

Critical, on mainnet block #2,747,398.

286.305 POTdestroyed by the migration
9 of 9real accounts with deposits lost half, each listed with before and after
0accounts changed when the clean runtime ran the same way

runs 971a7223bf7b, bbbf22879a6a (caught) · 4be59393add6, a98bc8ba5969 (clean)

Portal Canary · Venkat05 / 10

Proof · on-chain, checkable by anyone

You don't have to trust the page.

Each run writes one result file holding the sha256 of every other file. That hash is written to the V3 testnet. The run page checks all three links itself: the files, the result, the block.

10 of 10recorded runs verify against V3 (29 Oct 2026), re-checked by a script that needs no key or account

V3 blocks #234,189 · #237,962 · #238,224 · #239,489 · #240,062 …

On-chain record card: verified
Portal Canary · Venkat06 / 10

Proof · it runs every day

Compare mainnet to V3: 969 changes, 111 risks

Compare any two nodes. Check mainnet every morning.

  • Compare judges every runtime difference with a severity, the reason, and the values before and after. Mainnet against itself: 0 and 0.
  • A daily check runs the clean and the broken runtime on fresh mainnet state. On day one it caught a real bug in my own recording step and opened an issue; it closed it after the fix.
  • 117 tests. CI forks mainnet and requires the broken migration to be caught.
Portal Canary · Venkat07 / 10

Where it stands

Live today, on the testnet.

Dashboardportal-canary-app.vercel.app/dashboard: new runs, run pages, Compare, Doctor, Supply, Registry, On your node
Runs21 kept, 10 recorded on V3 and all 10 verified, 9 on real mainnet state (29 Oct 2026)
Registry contract0x62Eacc58CE0FA37F06F24074AFb7B3F22Df708FF · V3 EVM, chain 420420777 · 4 verdicts stored
On your nodethe workflow suite from your own browser against your own node; one wallet approval funds the whole run
EngineRust: V13 + V14 decoder, diff, risk rules; the same code runs in the browser as WebAssembly
Tests42 Rust · 33 unit · 38 browser · 4 contract · CI green
Portal Canary · Venkat08 / 10

What support makes possible

Make this part of how every Portaldot upgrade ships.

Gate runtime changes

A check on every runtime pull request: fork mainnet, upgrade, post the verdict and the account table before anyone merges.

Mainnet's exact runtime

Today the fork runs mainnet's state on the dev runtime. Next: mainnet's own runtime and its 60-second slots.

V3 upgrades

The same run for V3's own future upgrades. The V14 decoder already reads it, and contract state comes next.

Operators

Endpoint checks and alerts for the public nodes, so a broken endpoint is noticed before users notice it.

Portal Canary · Venkat09 / 10

Before the upgrade, not after.

Try a run: open the dashboard, pick a runtime, keep Mainnet state, press Start run.

portal-canary-app.vercel.app/dashboard

github.com/Venkat5599/portalcanary

Thank you. Venkat

Portal Canary · Venkat10 / 10