ADR-0016: The challenge window is two timers, and only one of them is seven days
ADR-0003 set the challenge window at seven days and wrote the policy that it is "shortened only when a validity proof, not a timer, guarantees correctness." ADR-0015 then added the air gap and recorded the…
Status: accepted (ACCEPTED AND FINAL). Dated 2026-09-04.
2026-09-04. Status: ACCEPTED AND FINAL — the owner approved this ADR and the 48-hour window on 2026-09-04, reversing the parameter they had previously affirmed, and closed the question the same day. The constant is changed in the code. Do not reopen this number without a new owner decision; one reversal was recorded in error that day and corrected.
The parameter, as it should be quoted
Dispute window (optimistic backup): 432,000 slots — 48 hours at Solana's 400 ms target. Insulated against historic halts, protected by the ChallengeOpen finality gate, and clearing non-disputed roots for rapid asset turnover through fast liquidity bridges.
Three notes so the line stays true when it is repeated:
- The stored unit is slots — DEFAULT_CHALLENGE_WINDOW_SLOTS = 432_000, and the SCC reads Clock::slot in the one place it converts the sysvar (9ebc03f, 2026-09-05). This ADR said "seconds" for most of a day; the code moved under it and the text is corrected. The number the owner quoted was the one the chain now holds.
- "Insulated against halts" is now true of the window AND the deadlines. Both count slots. A halted chain produces none, so a move deadline freezes, an honest party cannot be timed out, and the window itself cannot run out while nobody could have challenged. Under the earlier wall-clock window the worst halt on record, ~19 h, would have spent 40% of 48 hours on time no one could use; that exposure is closed rather than merely tolerable.
- ChallengeOpen is the stronger of the two guarantees. finalize refuses a CHALLENGED root outright (STATUS_CHALLENGED => ChallengeOpen) with no time-based escape, so once a challenge is open the game gates finality and the window no longer does. A halt cannot finalize a root out from under a live dispute.
- The fast-exit clearance is real but not yet underwritten. The LP path needs no protocol change, and 48 hours cuts a provider's capital lock-up from eight days to three, which is what makes the market cheaper. Today it is team-fronted; no third party underwrites it. Say "clears the way for", never "provides".
Adopted; prerequisite 1 has since landed. Slot-denominated deadlines shipped in a9edcc8, and 9ebc03f then moved the window itself to slots too — so a halt can neither time out a party nor spend the window. Condition 3, the new chain, is met: the beta chain was deployed and initialized on 2026-09-05 (protocol/onchain/DEVNET.md) with window_slots = 432000 and the game's enforcer PDA as dispute authority, and its first root is on Solana. scc_initialize fixes the window at initialization and the SCC has no setter, so the constant governs the beta chain and changes nothing already running: the alpha chain keeps its seven days.
Context
ADR-0003 set the challenge window at seven days and wrote the policy that it is "shortened only when a validity proof, not a timer, guarantees correctness." ADR-0015 then added the air gap and recorded the owner's decision at the time: add the air gap, keep the seven days.
Neither number was derived from Solana. Seven days is the Ethereum optimistic-rollup convention, adopted because it is what Arbitrum, Base and OP Mainnet use. That is a reasonable default for a chain settling to Ethereum. It is not a measurement of anything on the chain we settle to.
Two things have changed since, and both are measurable rather than rhetorical.
We can now compute what the game actually needs. The dispute is 2·ceil(log2(steps)) + 1 moves (solieum_dispute::worst_case_moves), each bounded by the response window the challenger chooses, floored at MIN_RESPONSE_WINDOW_SLOTS = 1,500 slots (600 s at the 400 ms target). For the largest trace a challenger can currently afford to dispute — 131,072 steps, the ceiling the 100,000-lamport proposer bond implies (fees::min_bond_for_steps) — that is 35 moves:
| Response window per move | Worst-case game | Fits in 7 d |
| 600 s (the floor) | 5.8 h | 28.8× over |
| 1 h | 35 h | 4.8× over |
| 4,900 s | 47.7 h | 3.5× over |
And the field moved. No chain settling to Solana ships a seven-day optimistic window. SVM rollups either prove validity and settle in minutes, or are network extensions where funds never leave Solana and there is no bridge delay at all. The one SVM chain that does carry seven days settles to Ethereum, which is the exception that states the rule. A seven-day exit is now a number a builder reads as a mistake rather than as caution, and they are not wrong to.
The actual finding
Seven days is one timer doing two jobs, and doing neither well.
ADR-0015 already named the two questions, and they are not the same question:
- Was this root challenged? Answered by the dispute game. Its duration is arithmetic, above, and it is hours.
- Was the dispute machinery itself sound? A bug in the one-step verifier or the state-tree check means a false root wins its game, or is never challenged because the tooling that would have challenged it was wrong. No amount of window answers this — only a human noticing does.
Question 2 is why ADR-0015 exists, and the air gap is the right shape for it: an interval with a hand on it, not a pause. But once the air gap exists, the window is no longer carrying question 2. It is carrying question 1 alone, and question 1 needs hours.
This is the same split OP Mainnet makes — 3 d 12 h of dispute and 3 d 12 h of finality delay — and ADR-0015 cites it approvingly while keeping our seven days in front of our air gap rather than splitting it.
Decision
Size each timer for its own job.
- Challenge window: 48 hours — 432,000 slots at the 400 ms target, replacing seven days.
- Air gap: unchanged, at ADR-0015's recommended 24 hours for mainnet.
Total worst-case exit falls from about 8 days to about 3. Quote that total, never the window alone: the user waits for both.
48 hours is chosen so that the arithmetic above still holds with room — it clears the 35-hour worst case at a one-hour response window, and it clears the 5.8-hour case at the 600 s floor by 8×. It is not the smallest defensible number; it is the smallest one that does not constrain the trace length a challenger may dispute (see Consequences).
Four inputs, and what each contributes
- Detection — minutes. solieum-node verify --watch re-derives the chain from Solana alone and alarms on divergence. This input used to be "however long until someone looks"; it is now a running process.
- Reaching Solana under censorship — seconds to minutes. Leaders rotate every four slots, so suppressing a challenger's move requires colluding with a continuous supermajority of stake. On Ethereum a concentrated builder market makes multi-hour censorship credible, and that is a real input to their seven days. It is not one to ours. This is the single largest reason the Ethereum number does not transfer.
- The game — hours. The table above.
- Solana unavailability — see below. This is the one input that argues for days rather than hours, and it is the one this ADR proposes to remove rather than to pay for.
The halt problem, and why deadlines should count slots
A per-move deadline is wall-clock (Clock::unix_timestamp). Solana has halted before — the longest full halt on record is ~19 h (2023-02-25), with 17 h 12 m in 2021 and nothing since 2024-02-06. Checked 2026-09-04; the table and its caveats are under "What must be true" below. A halt longer than one response window makes the party who owes the next move lose on timeout for a reason that is not their fault, and paying for that with window length is expensive: covering a 19-hour halt at every one of 35 moves would need a window of weeks, which is worse than the number we are trying to fix.
Count deadlines in slots, not seconds. A halted chain produces no slots, so a slot-denominated deadline pauses exactly when the chain does, and neither party can be timed out by an outage. Clock::slot is already available to the dispute program; the change is confined to how solieum-dispute stores and compares a deadline.
This is a prerequisite for the number above, not a nice-to-have. Without it, 48 hours means a Solana halt during a dispute can decide the dispute.
What this does not change
The window stays a timer, and ADR-0003's real point survives intact: a timer is not a proof. Shortening it does not move us toward validity proofs, and ADR-0002's plan — a second, independently implemented prover, withdrawals gating on agreement — is still what takes exits to minutes. This ADR only stops us paying 168 hours for something that costs 6.
Consequences
- Changing the window means a new chain. scc_initialize fixes the window at initialization and the SCC has no setter. The devnet chain keeps seven days until it is re-initialized — which it needs anyway, for the dispute authority (still the payer key) and for the ADR-0007 sparse-tree migration. Fold all three into one new chain.
- A shorter window constrains trace length, if the response window is long. The constraint is (2·ceil(log2 N) + 1)·r < W: | Window | r = 600 s | r = 1 h | |---|---|---| | 7 d | 2^503 steps | 2^83 steps | | 48 h | 2^143 steps | 8,388,608 steps | | 24 h | 2^71 steps | 2,048 steps | At 48 hours nothing realistic binds. At 24 hours a challenger who wants hour-long moves can only dispute ~2,000-step traces, which a busy block exceeds. That is why the recommendation is 48 and not 24.
- Less slack for a human to notice a machinery bug. This is the real cost and it should not be waved away. The mitigation is that this is the air gap's job, not the window's, and the air gap is unchanged at 24 hours. If that trade feels wrong, the honest response is to lengthen the air gap, not the window: the air gap has a hand on it and the window does not.
- Market makers still need fast exits. Three days is not capital mobility either. The liquidity-provider path works today with no protocol change — a user names the provider as the withdrawal's recipient, is paid on Solana immediately, and the provider finalizes later — and solieum-node verify is what lets a provider price that risk instead of trusting us. Shortening the window makes that market cheaper by cutting the capital lock-up from eight days to three.
- The site's copy changes. /markets currently gates HFT and TradFi on the validity-proof class because of the seven days. That gating stays true at three days and should stay; what changes is the number quoted everywhere else, and it must be quoted as window plus air gap.
Reversal triggers
- Multi-prover live (ADR-0002): revisit the whole shape. With validity proofs the dispute window is not shortened, it is removed.
- A Solana halt longer than 24 hours: revisit the slot-denominated deadline assumption, and the air gap alongside it.
- A verifier or state-tree bug found in production: stop, and treat the air gap as the binding constraint rather than the window.
- Any trace-length change — new one-step classes that make blocks longer in steps — re-run the table above before assuming 48 hours still clears.
What must be true before this is adopted
Status as of acceptance, 2026-09-04. The parameter is changed in the code; three of these four remain open, and the new chain (3) is the gate that holds them all — nothing carrying value is initialized until then.
- Slot-denominated deadlines in solieum-dispute and the dispute program, with the halt case tested. — done. a9edcc8 put the per-move deadlines on Clock::slot, with Runtime::execute_at so a halt is testable by moving wall time and slots apart; 9ebc03f then moved the SCC's window to slots as well (DEFAULT_CHALLENGE_WINDOW_SLOTS = 432_000), so fits_within_window compares like with like and no longer guesses a slot time. The node's finalize_due judges due-ness by the cluster's slot and refuses to finalize when it cannot read one.
- The public halt record checked against the 17–19 h figure used here. — done, 2026-09-04. The figure is confirmed and is now conservative. Full cluster halts, the ones that stopped block production and needed a coordinated restart: | Date | Duration | Cause | |---|---|---| | 2021-09-14 | 17 h 12 m | transaction flood during an IDO | | 2022-04-30 | ~7 h | NFT mint bots | | 2022-06-01 | ~4.5 h | durable-nonce runtime bug | | 2022-09-30 | ~8.5 h | duplicate-block bug | | 2023-02-25 | ~19 h | shred-forwarding service malfunction — the longest on record | | 2024-02-06 | ~5 h | LoadedPrograms bug | January 2022's often-cited ~30 hours was degraded performance and partial outage, not a halt — throughput collapsed but blocks kept coming, so it is not the case a slot-denominated deadline is sized against. No full halt since 2024-02-06: 30 consecutive months as of August 2026, roughly 913 days, with Solana's status page showing 100% uptime for June, July and August 2026. The near-miss is the more interesting datum. In August 2026 a routing failure at one infrastructure provider took 28.83% of staked SOL delinquent — 86% of the 33.34% at which Solana stops finalizing. The cluster did not halt; blocks were produced and transactions landed throughout. That is a finality stall rather than a halt, and slots keep advancing through it, so a slot-denominated deadline advances too and a challenger's move still lands. It is the right behaviour, but it is worth knowing the two failure modes are different and only one of them freezes the dispute clock. What this means for the 48 hours, precisely. A halt cannot let a root finalize out from under an open dispute: finalize refuses a CHALLENGED root outright (STATUS_CHALLENGED => ChallengeOpen in the SCC), with no time-based escape, so once a challenge is open the game gates finality and the window no longer does. The exposure is therefore only to a halt landing in the window before anyone opens a challenge, and there the arithmetic is comfortable: the worst halt on record, 19 hours, still leaves 29 of the 48, against a watchtower that detects divergence in minutes. Sources are secondary (Helius, StatusGator, Protos, CoinDesk, Solana's own 02-06-24 incident report); re-check before mainnet rather than inheriting this table.
- A new chain, carrying this window, the enforcer PDA as dispute authority, and the sparse tree. — done, 2026-09-05. The owner deferred it on 2026-09-04 to wait for the sparse tree, so that one re-initialization would carry all three rather than two; the tree landed and was wired (ef61e6d, 08d45a4), and the next day the four programs went up as the beta chain — SCC 85LdjERK…, DA 6p4uYDP4…, inbox FxiCUnQ7…, dispute game G7zcwYuX…, slots 493393268–493394141, 7.224 SOL (devnet rent had moved to 5,080 lamports per byte, under the 6,333 the table below assumed) — and the node's first run initialized the chain account (slot 493401026, DjkpxaBR…) with window_slots = 432_000 and the game's enforcer PDA GHFwhJMqyBhSQT9GXShYQuoLGWyqSHVoSvCoMBBoXhTb as dispute authority. getSettlement reads back window_slots: 432000 and disputes_enforceable: true; root 1 (the first sparse root) was committed under bond at slot 493401648 and is finalizable from slot id 7789; protocol/onchain/DEVNET.md has every account and signature. The portal 6ozbkkhD…, named in its genesis, followed the same day (deployed slot 493469174, initialized slot 493474424; the bridge pins to it now), so all five programs of the beta chain are on-chain. The alpha chain keeps its seven days; the 48 hours governs beta. "A new chain" is a redeploy of four programs, not an initialization. This was checked on 2026-09-04 before spending anything, and it is worth writing down because the phrase understates it by an order of magnitude:
- Genesis AtxMy5BY…, network solieum-testnet-beta, chain
- The chain PDA is seeds = [CHAIN_SEED] with init — one chain per program deployment, creatable once. There is no setter and no close, so the existing chain account cannot be reused or reset.
- The dispute game is compile-time bound to the SCC it was built against: EnforceVerdict derives the chain and root accounts with seeds::program = solieum_scc::ID. The deployed game can only enforce into FHkPeKq8…, so a new SCC needs a new dispute game rebuilt against it. This is also why the enforcer PDA cannot simply be pointed at a new chain.
- The DA feed ([FEED_SEED]) and inbox queue ([QUEUE_SEED]) are singletons on the same pattern. Reusing them would make the new chain derive the old chain's batches and queue entries as its own — wrong rather than untidy.
| Program | Rent | |---|---| | SCC | 2.20 SOL | | Dispute game | 2.74 | | DA | 1.76 | | Inbox | 1.76 | | Without a bridge | 8.46 | | Bridge portal | +2.47 → 10.93 |
The devnet payer held 9.79 SOL when this was costed, so the portal does not fit alongside the rest and the faucet's 2 × 5 SOL per 8 hours is the constraint on doing it in one sitting. Budget a re-deploy, not a transaction.
Redeploy checklist for the bridge front-end (owner's instruction, 2026-09-05: update pinned.ts when we redeploy). The wallet bridge at bridge/src/lib/pinned.ts pins the deployed portal and refuses to sign against anything else — that refusal is the feature, so a redeploy that skips this does not break quietly, it stops the bridge. Every field below changes with the new chain, not only the ELF hash:
- PROGRAMDATA.address — the new portal's program-data account. The portal is a new deployment, not an in-place upgrade: it is bound to the SCC by seeds::program, and the SCC is new.
- PROGRAMDATA.lastDeployedSlot, upgradeAuthority, elfLen, elfSha256 — read from the cluster after deploy; elfSha256 is the sha256 of a clean build of the committed source, and the page recomputes it over the on-chain bytes.
- PROGRAMDATA.commit — the commit those bytes are a clean build of.
- L2_NETWORK — the new chain's network name. It is solieum-testnet-alpha today; a new genesis is a new name, and a node reporting the old one is a different chain.
- The program ids in bridge/.env.production and the pinned constants (VITE_PORTAL_PROGRAM, VITE_INBOX_PROGRAM, VITE_SCC_PROGRAM) — all four are new deployments.
- Then npm run verify:encoding against the new deployment, before npm run build.
Also protocol/onchain/DEVNET.md, upgrade-devnet.sh's id table, and the explorer's network menu, which lists chains by genesis hash.
- The site's exit numbers restated as window plus air gap. — done (2026-09-04): the metric is now "Total exit ~3 days", the glossary, FAQ, bridge walkthrough and markets copy all quote window plus air gap, and every place describing the devnet chain says it still carries the seven days it was created with.