Loading
SOL durable-nonce outbounds — mimir-gated, default off
Release-3.21.0. Consensus-breaking (x/thorchain + proto).
What's broken: SOL outbounds sign with a recent blockhash that expires ~45s after it is chosen (150 blocks at ~300ms slots). Slow keysigns miss that window, so outbounds fail and get rescheduled.
What this MR changes
- Durable nonces. Each SOL vault can use its own nonce account. Outbounds from that vault sign with the stored nonce, which does not expire.
- The nonce is agreed on-chain. It is committed only on a final, identical 2/3 vote from the vault's own address, for the vault's own seed-derived account. Slots only move forward.
- Query:
GET /thorchain/vault/{pubkey}/sol_nonce. - Once a vault has a nonce it stays durable (one-way per vault).
- One durable tx in flight per vault. Every signer reads the pending queue at the keysign height. When a vault's nonce advances, its waiting SOL outbounds are re-issued at that height right away, instead of waiting for LackSigning.
- No double pay. LackSigning keeps a durable outbound on its vault while an old copy could still land. It holds (never re-issues) an outbound whose copy landed but did not match. Memoless outbounds are linked from the recovered memo, so the hold sees them too.
- Nonce-account creation. The creation is observed and its rent + fee are booked as vault gas, so vault balances stay correct. If the creation reaches quorum before its nonce does, it is deferred, not slashed.
- Recovery.
- Bifrost refuses to sign durable txs that would fail (bad recipient, low balance, failed simulation).
- Validators can re-attest the nonce with
MsgSolNonceAttest(2/3 identical votes, ≥600 blocks old) when only failed vault txs moved it. - A dead durable copy is dropped and re-signed instead of retried.
- Recent-blockhash path (vaults without a nonce): same bytes as today by default. SOL block time 400→300ms, which also changes bifrost SOL scanner pacing. Keep-alive poll 10s→2s.
Mimirs
SOLDurableNonce(default off): lets vaults create nonce accounts.SOLNonceClear-<vault pubkey>= H: break-glass. Ignores a committed nonce with updated_height ≤ H, so the vault signs on the recent-blockhash path. Set it only after confirming the old nonce is consumed, or underHaltSigningSOL.SOLAnchorMarginSeconds(unset/≤0 → 15s, clamped 12–30): how far back the recent blockhash is picked.
Consensus reads the nonce records and SOLNonceClear, but both are inert until a nonce is committed. Keep-alive self-sends and the nonce-create tx stay on the recent-blockhash path.
Tests: unit tests for every guard (mutation-checked), full regression suite, bifrost SOL client and scanner tests.
Known gaps
- Ops runbook maintained separately. Held outbounds are terminal and need manual resolution; have the runbook in place before enabling
SOLDurableNonce. - A Ragnarok payment has no inbound link, so the landed-copy hold can't see it.
- Re-attestation gives up after 100+ txs on the nonce account (dust can force this); break-glass still works.
- Nonce records and attest voters are never pruned.
- Nonce-account rent is not reclaimed when a vault retires (~0.0015 SOL per vault per churn).
- One byzantine vote can defer a creation-shaped transfer (bounded, no theft).
- If booking the creation rent as gas fails (store error), the nonce stays committed and the rent goes unbooked (logged only).
- The landed-copy sibling check looks back ~1 day; an older match makes it hold, never re-issue.
No merge, tag or enable without Itz.
Edited by Itzamna