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
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.
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.
How it works
One run: fork mainnet, upgrade the fork, check everything, record it.
Fork mainnet
Its latest finalized block: all 4,716 accounts, deposits, locks, stakes. Takes about 15 seconds.
Upgrade the fork
setCode with the candidate runtime, on a local chain that only we control.
Check
Every balance and the total supply, before and after. Blocks still produced. Runtime risks. 5 workflows on both sides.
Record
One file hashes every result file. Its hash goes on the V3 testnet.
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.
Proof · a run on real mainnet state

Critical, on mainnet block #2,747,398.
runs 971a7223bf7b, bbbf22879a6a (caught) · 4be59393add6, a98bc8ba5969 (clean)
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.
V3 blocks #234,189 · #237,962 · #238,224 · #239,489 · #240,062 …

Proof · it runs every day

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.
Where it stands
Live today, on the testnet.
| Dashboard | portal-canary-app.vercel.app/dashboard: new runs, run pages, Compare, Doctor, Supply, Registry, On your node |
| Runs | 21 kept, 10 recorded on V3 and all 10 verified, 9 on real mainnet state (29 Oct 2026) |
| Registry contract | 0x62Eacc58CE0FA37F06F24074AFb7B3F22Df708FF · V3 EVM, chain 420420777 · 4 verdicts stored |
| On your node | the workflow suite from your own browser against your own node; one wallet approval funds the whole run |
| Engine | Rust: V13 + V14 decoder, diff, risk rules; the same code runs in the browser as WebAssembly |
| Tests | 42 Rust · 33 unit · 38 browser · 4 contract · CI green |
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.
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