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 under HaltSigningSOL.
  • 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

Merge request reports

Loading
Loading