Draft: [POST-3.20 ONLY] XMR defense-in-depth hardening
⚠️ Status: this MR is now a source branch, not a merge candidateThis branch is being kept only as the source for a series of smaller, focused MRs. Each follow-up will be independently reviewable, will state its own risk and activation requirements, and will link back here.
Focused MR series (expected - may change)
Flags (original series context; current launch priority is in the Call column):
K: XMR keygen scopeE: XMR economic-activity scopeC: common correctness/security hardeningP: optional performance or simplification workD: deferred or decision-gated; not currently a launch requirementLA: issue or suggestion raised by Least Authority; provenance only, not severity or launch priorityPRIV: confidential security work; MR remain private until disclosure is approved
Current launch calls and consensus-impact labels follow the XMR launch overview. Status and C&A were checked on 2026-09-24. C&A shows unresolved discussion threads, then approvals received/required (for example 0, 0/1). Consensus labels describe each MR's own changes; inherited stack impact is shown separately. Private details remain private.
| ID / MR | Focus | Scope | Call | Consensus impact (own) | Status | C&A |
|---|---|---|---|---|---|---|
| RDY · !5104 (merged) | Shared simulation readiness, startup tooling and profile configuration | Non-breaking — Bifrost/test setup | Open · CI |
0, 2/2 | ||
| S00 · !5026 Target: S03A |
FROST phase retention for keygen/signing, complete-round transcript votes, activation gates and recovery; includes the common reward-accounting change and the local S29 recovery prerequisite | Flags: K, LA |
Breaking — reward accounting (direct) | Open · CI |
0, 0/1 | |
| S01 · !5029 (merged) | Replay-safe TSS keysign-failure voters and keeper write errors | Flags: C |
Breaking — voter/slash state | Merged 2026-09-10 · CI |
0, 3/2 | |
| S04A · !5034 Target: S00 |
Residual FROST verification batching; the signing phase-retention fix is already owned by S00 | Flags: E, LA |
Non-breaking (own) Inherited: breaking S00 |
Open · CI test-mocknet-scripts) · dependency blocked |
0, 0/1 | |
| S02 · !5038 Target: S04A |
Exact FROST encoded-frame validation and safe asynchronous queue ownership; defensive scope beyond launch | Flags: K |
Non-breaking (own) Inherited: breaking S00 via S04A |
Open · CI test-mocknet-scripts) · dependency blocked |
0, 0/1 | |
| S03A · !5045 Target: RDY |
FROST stream ownership, monotonic party narrowing, and LA Suggestion 2 DKG/result retries | Flags: K, E, LA |
Non-breaking — Bifrost transport | Open · CI |
0, 1/1 | |
| Atomic StreamMgr release during concurrent stream additions | Flags: C, PRIV |
Not yet verified (private) | Merged 2026-09-10 · CI |
0, 3/2 | ||
| S05A · !5047 Target: S00 |
Bifrost XMR scanner activation controls | Flags: K, E |
Non-breaking (own) Inherited: breaking S00 |
Open · CI test-mocknet-scripts) · dependency blocked |
0, 0/1 | |
| S05B · !5046 Target: RDY |
Verify one deterministic eligible-set XMR monovault generation | Flags: K |
Non-breaking — test/Bifrost changes | Open · CI |
0, 0/1 | |
| S06 · !5052 Target: RDY |
XMR signer/sidecar startup contract and daemon-derived network identity | mainnetFlags: K, E |
Non-breaking — Bifrost signer contract | Open · CI |
0, 0/1 | |
| S07 · !5053 Target: S06 |
Deployment contracts, authentication and production signer-image acceptance | Flags: K, E |
Non-breaking — Bifrost/deployment | Open · CI |
0, 0/1 | |
| S08 · !5054 Target: S06 |
Scanner birthday, rewind, network, and reorg-height behavior | Flags: E |
Non-breaking — Bifrost scanner | Open · CI |
0, 1/1 | |
| S08c · !5112 | LA-07: 730-block churn rewind, 72-block replay batches by default, reorg reconciliation before replay, duplicate-observation amount protection and restart-safe recovery | Flags: E, LA |
Non-breaking — Bifrost recovery | Open · CI |
0, 1/2 | |
| S08d | Extended automatic scanner recovery beyond S08c's declared bounds; separate deferred work | Flags: D |
Not yet verified (no MR) | No MR | — | |
| S09 · !5066 Target: RDY |
Durable local errata admission/retry and cleanup independent of peer transport | Flags: E |
Non-breaking — Bifrost observation handling | Open · CI |
2, 0/1 | |
| S10 · !5065 Target: S09 |
Canonical mined-height identity, zero-height recovery and outbound observation credit | Flags: E |
Breaking — observation identity | Open · CI |
1, 0/1 | |
| S11 · !5067 Target: S10 |
Exact spent-reference matching and guarded errata ordering; S29 owns the core recovery, leaving the remaining memoless-policy work outside launch scope | Flags: E |
Not yet verified (own query surface) Inherited: breaking S10 |
Open · CI |
2, 0/1 | |
| S12 · !5068 Target: S10 |
Terminal errata outcomes, durable receipts, retry across churn and early durable reversal/broadcast fencing | Flags: E |
Breaking — persisted errata outcomes Inherited: breaking S10 |
Open · CI |
1, 0/1 | |
| S13 · !5075 Target: S12 |
Prefer the narrow fix isolating unavailable XMR quarantine recovery from other chains' startup; preserve durable restoration and retry | Flags: E |
Non-breaking (own) Inherited: breaking S12/S10 |
Open · CI |
0, 1/1 | |
| S14 · !5107 | State-sync admission, framing, per-item chunking and uneven/zero-transaction snapshot coverage | Flags: C |
Non-breaking — Bifrost state sync | Open · CI |
1, 0/2 | |
| S15 · !5108 Target: S14 |
Cancellation-aware state processing and THORNode queries | Flags: C |
Non-breaking — Bifrost cancellation | Open · CI |
1, 0/1 | |
| S16 | Work moved into S14 | Flags: C |
Covered by S14; no separate change | No MR needed | — | |
| LA-02 - private review scope | Flags: C, E, LA, PRIV |
Breaking (previously recorded private scope) | Merged 2026-09-14 · CI |
0, 3/2 | ||
| S18 · !5037 (merged) | LA Suggestion 3: require a valid refund address when the source chain cannot recover the sender | Flags: C, E, LA |
Breaking — contract-callable swap quotes | Merged 2026-09-15 · CI |
0, 3/2 | |
| LA-03/04 - private review scope - combined verification with S17 | Flags: C, E, LA, PRIV |
Not yet verified (private) | Merged 2026-09-17 · CI |
0, 3/2 | ||
| S20A · !5111 | LA-05 part 1: reject unsupported XMR destinations in swap/limit and contract-callable quotes; safely isolate unsigned local attempts while preserving signed/recovery evidence | Flags: E, LA |
Breaking — contract-callable swap quotes | Open · CI |
0, 0/2 | |
| S20 | LA-05 part 2: consistent external-payment address policy across quotes, THORName, consensus, refunds and signing | Flags: E, LA |
Not yet verified (no MR) | No MR | — | |
| S21 | LA-06: executable limit-quote memos and preservation of valid explicit XMR refunds despite unrelated parsing failures | Flags: C, E, LA |
Not yet verified (no MR) | No MR | — | |
| LA-08: private review scope | Flags: E, LA, PRIV |
Not yet verified (no MR) | No MR | — | ||
| LA-09: private review scope | Flags: E, LA, PRIV |
Not yet verified (no MR) | No MR | — | ||
| S24 | Late-outbound errata ordering (former S12d) | Flags: D |
Not yet verified (no MR) | Deferred; reachability and design assessment required; based on S12 if promoted | — | |
| Common correctness follow-up - private review scope | Flags: D, C, PRIV |
Not yet verified (no MR) | No MR | — | ||
| S27 · !5109 | LA-11: Preserve reorg history during vault catch-up | Flags: E, LA |
Non-breaking — local scanner journal | Open · CI |
0, 1/2 | |
| LA-12–14: private review scope; MR opened privately | Flags: C, E, LA, PRIV |
Not yet verified (private) | Open · CI |
1, 0/2 | ||
| S29 · !5121 (merged) | Non-consensus-breaking outbound and spent-reference recovery, authenticated migration replay and unsigned-outbound lifetime; extracted from S00 and independent of RDY/S03A | Flags: E |
Non-breaking — outbound and spent-reference recovery | Open · CI |
0, 1/2 | |
| !5120 (merged) | Catch-up import finality: THORChain-sourced imports may finalize; already included in !5055 (merged), with no separate inclusion decision | Included in !5055 (merged); no separate inclusion decision Flags: E |
Non-breaking — Bifrost catch-up finality | Merged 2026-09-23 · CI |
1, 1/1 |
S16 was absorbed into S14; it has no separate MR or dependency. S26 is listed only in the deferred table.
The list is expected to evolve: fresh review may split, combine, simplify, or drop an item. Lowercase suffixes identify sub-items or follow-ups - they do not automatically imply separate MRs. LA Issue 6’s two halves remain combined in S21. LA Issue 7 is a new S08 follow-up, not the deferred ceremony-overlap issue.
Deferred, decision-gated or rejected work
| Item | Focus | Disposition |
|---|---|---|
| S03C | Ceremony-scoped cancellation and ownership when an abnormally long keygen overlaps its replacement | D — Promote only with reproduction or operational evidence; not an S00 launch blocker |
| S04B / S02b | Additional bounded outbound workers and ordinary/terminal scheduling | D, P — Require traffic evidence and safe expiry of stale ceremony work; no speculative multi-vault fairness |
| S02c / P14 | Aggregate reader/stream admission for shared TSS ingress | D, P — Removed from S02. Measure common ECDSA/EdDSA traffic before imposing shared limits |
| S02d | Priority/fairness scheduler and shared cross-topic ceremony budgets | D — Do not port the larger scheduler/refcount machinery without demonstrated need |
| S05C | Additional THORNode-side XMR keygen operator gate | Rejected — Duplicates existing Bifrost controls under the agreed rollout |
| S05D | Independent churn clock for XMR-only vault activation | Rejected after audit — Accepted contained delay; no production correction planned |
| S06b | Periodic runtime signer-contract revalidation | Policy-gated — Required if hot signer replacement is supported; unnecessary if replacement requires restart |
| S10b | Renewed economic processing of the same TxID across mining generations | D — Policy/design verification first; not included in current S10 |
| S24 (former S12d) | Unknown errata followed by a late original outbound observation | D — No safe small fix identified. Current scanner holds remain; normal producer reachability is unproven |
| S13c | Full asynchronous/coalescing reconciliation worker | Deferred — Current S13 uses one owned block-time maintenance loop; promote additional machinery only if evidence shows it is needed |
| State-sync capacity/indexing | Raise attestation capacity from 128 to 1,024 and add indexes | D, P — Reconsider when validator growth or measurements justify it; baseline retention bounds remain in S14 |
| S12 review: inherited common accounting finding | Pre-existing common-chain behavior identified during ordering review | Out of scope for this follow-up — Evidence retained; not fixed and not added as a series launch gate |
| S26 | Bounded retries for incomplete startup state synchronization | D, C — Brief prepared; implementation deferred. Based on S14, integrating S15 cancellation |
| P15 | State-sync ACK/idle stall protection | D — Measure after S14’s timeout correction and S26 retries; preserve legitimate processing time |
| P16 | Allocation-safe state-sync decoding | D, C — Separate design review against final S14 bounds; coordinate with S19 |
The list is expected to evolve: fresh review may split, combine, simplify or drop an item. Deferral records a scope decision, not a claim that the underlying issue is fixed.
Intended order
| Track | Order / current scope |
|---|---|
| FROST | develop -> RDY -> S03A -> S00 -> S04A -> S02; S04A/S02 remain LATER |
| Scanner controls | S00 -> S05A (LATER) |
| Deployment/scanner hardening | RDY -> S06 -> S07 / S08; S07 and S08 are siblings (all LATER) |
| State sync | develop -> S14 -> S15; independent of RDY; S16 is absorbed into S14 (LATER) |
| XMR spent-reference recovery | RDY -> S09 -> S10 -> S11 (LATER); S29 owns the required core recovery |
| XMR terminal errata/readiness | RDY -> S09 -> S10 -> S12 -> S13; S11 and S12 are siblings; only the narrow S13 startup fix is DECIDE |
| Launch scanner/payout recovery | S08c, S27 and S29 each target develop; !5055 (merged) is merged and includes !5120 (merged) |
| Independent common hardening | S01, S03B, S17 and S19 are merged; private release/activation qualifications still apply |
| Quote/address/refund correctness | S18 is merged; S20A targets develop (NEED); remaining S20/S21 work is LATER with no MRs |
| Topology verification | RDY -> S05B: audit/tests, no production consensus change (LATER) |
| Signer companions | Serai !70 (merged), !71 (merged) and !73 (merged) are merged; include required signer changes in the deployed image, with coordinated sidecar rollout for !73 (merged) |
Independent tracks can proceed concurrently. These are current stack relationships, not additional launch requirements. S08c owns LA-07 churn recovery; S08d is separate deferred work.
Deferred items are outside this sequence. S05C/S05D are abandoned.
Related MRs and signer companions
| MR | Disposition | Coordination | Consensus impact (own) | Status | C&A |
|---|---|---|---|---|---|
| !5050 | Leave independent | Protobuf ownership was settled separately; keep this MR independent of the S10/S17 series work. | Not yet verified | Open · CI |
0, 1/2 |
| !5055 (merged) | Exact signed-withdrawal recovery; already includes merged !5120 (merged), with no separate inclusion decision. | Non-breaking — Bifrost recovery | Merged 2026-09-24 · CI |
0, 3/2 | |
| !5056 | Accounting/receipt design remains outside launch scope. Reuse selected work only after review; do not assume the remaining behavior is covered. | Breaking — on-chain receipt/accounting state | Draft · CI lint, unit-tests) · |
0, 0/2 | |
| !5057 | Optional cleanup paired with Serai !72 (merged); not required for launch. | Non-breaking | Draft · CI test-regression, test-simulation) · |
0, 0/2 | |
| !4919 | S00 owns the accepted Bifrost gate; explicitly retain or reject remaining scheduling, classification and test changes before closure | Breaking — on-chain keygen/churn policy | Open · CI |
0, 1/2 | |
| !4957 | Close only after source-coverage mapping | Assign every useful remaining fix/test to a retained owner and account for unresolved discussions | Not yet verified | Draft · CI |
5, 0/2 |
| Serai !70 | Ensure the deployed immutable signer image contains these contracts; mainnet deployment is tracked in node-launcher. | Non-breaking — signer only | Merged 2026-09-06 · CI |
0, 1/0 | |
| Serai !71 | Include the locked-deposit filter in the deployed signer release; independent of optional cleanup. | Non-breaking — signer only | Merged 2026-09-22 · CI |
0, 1/0 | |
| Serai !72 | Optional signer cleanup paired with !5057; preserve supported recovery callers. | Non-breaking | Draft · CI |
0, 0/0 | |
| serai !73 | Include migration fee recalculation in the signer release. Deploy all signer sidecars together before signing resumes; mixed versions can select different transaction plans. | Non-breaking — signer transaction construction | Merged 2026-09-24 · CI test:modular-frost, test:xmr-frost-signer) |
0, 0/0 |
Rollout assumptions
- No previous XMR FROST ceremony exists.
- No ceremony may be active or resumed during deployment of S00.
- No mixed-version FROST ceremony is supported.
HaltMonovaultKeygenremains active until all intended participants run the same S00-capable Bifrost build.
POST-3.20 FOLLOW-UP — EXPECTED v3.21 SCHEDULED UPGRADE
Do not merge, cherry-pick, or release this as part of THORNode 3.20.
This MR contains consensus-visible changes and must become active through acoordinated scheduled application upgrade, not an ordinary rolling THORNoderelease. Merging this MR does not authorize XMR economic activation.
Summary
This MR hardens the XMR integration following the review of the post-3.20 implementation. It combines the merge-scoped findings from the open Huginn discussions, note 3688953712, and independent implementation reviews.
The main outcomes are:
- replay-safe TSS keysign-failure voters
- bounded and cancellation-safe FROST P2P processing
- durable, height-aware, repeatable XMR errata handling
- bounded and cancellation-aware attestation state synchronization
- safe XMR startup, signing, and scanner activation controls
- stronger THORNode/Serai signer compatibility and network-identity checks
Security and correctness invariants
After this MR:
- a completed or migrated TSS keysign-failure voter cannot replay slashing, jailing, or vault-freeze side effects
- each XMR errata generation is identified by TxID plus canonical mined height
- a transaction can be orphaned, re-mined, and orphaned again without repeating an already completed economic reversal
- every committed XMR errata reaches a terminal
REVERTEDorNO_APPLICABLE_OBSERVATIONoutcome - local quarantine completes before a reverted tombstone becomes terminal
- unresolved errata reconciliation is bounded and does not block observation delivery
- FROST ingress, retained frames, active writers, pending broadcasts, terminal results, and deferred work are bounded
- cancellation preserves terminal FROST results and waits for old writers before allowing replacement work
- attestation state synchronization has an absolute deadline, bounded framing, bounded nested attestations, and bounded concurrent inbound streams
- one unavailable XMR client does not prevent healthy non-XMR clients from starting or signing
- persisted XMR TxOuts remain durable but are not processed until Observer has successfully started XMR
- signer network, schema, capability, and daemon identity mismatches fail XMR closed without disabling unrelated chains
Changes
TSS voter replay safety
- Add an explicit persisted schema version to
TssKeysignFailVoter. - Interpret pre-versioned voters as sealed/completed after the scheduled upgrade.
- Persist lazy normalization through centralized keeper access.
- Reject unsupported future schema versions deterministically.
- Keep post-upgrade voter completion one-shot and replay-safe.
- Preserve normalized behavior through typed keeper iteration.
This is chain-agnostic and applies to ECDSA, EdDSA, and FROST keysign failures, not only XMR.
FROST and P2P lifecycle hardening
- Bound FROST payloads, bytes, concurrent readers, active broadcasts, scheduler backlog, terminal-result ingress, and deferred work.
- Preserve terminal results during all-terminal cancellation.
- Keep terminal broadcasts progressing under ordinary-ingress saturation.
- Reject oversized frames before allocating or reading their complete bodies.
- Remove the delivery-lock deadlock during FROST party narrowing.
- Revalidate queued messages after peer narrowing.
- Serialize StreamMgr release/removal without holding locks during network I/O.
- Fully close successful FROST streams and release their tracking state.
- Preserve valid work during cancellation while preventing old writers from racing replacement ceremonies.
XMR errata and spent-reference correctness
- Use canonical mined height at every XMR tombstone decision.
- Include mined height in the signed errata identity and consensus voter key.
- Support repeated errata generations for the same TxID.
- Preserve monotonic TxID-wide economic reversal once an observation is reverted.
- Persist terminal errata outcomes:
REVERTEDwhen an applicable observation was reversed;NO_APPLICABLE_OBSERVATIONwhen quorum committed without an applicable observation.
- Keep quarantine ahead of terminal persistence.
- Move reconciliation off the delivery critical path.
- Bound unresolved-tombstone reconciliation without replaying every historical completed tombstone at startup.
- Route periodic spent-reference catch-up through the same errata guard.
- Bound the attestation batcher's XMR barrier hold to one batch.
- Allow cleanup writers to progress between XMR reconciliation attempts.
- Preserve and recover exact XMR wire memos during spent-reference imports.
- Give exact populated
OutHashcandidates precedence over unhashed plans.
Legacy recovery for incomplete records created by the former two-step XMR errata outbox is intentionally not included. This waiver is valid only for a clean first activation where no database from an older enabled-XMR deployment is reused.
Attestation state-sync hardening
- Admit inbound state-sync streams before reading their bodies.
- Accept only active validator peers.
- Allow at most one active receive per peer and a bounded global number of concurrent receives.
- Read only the fixed five-byte initial control frame before admission.
- Apply one absolute transfer deadline to request, receive, and synchronous processing.
- Bound:
- advertised batches,
- records per category and batch,
- total retained records per transfer,
- attestations nested inside one record.
- Require exact sequential batch numbers and exactly one end marker after the declared batch count.
- Reject skipped, repeated, extra, oversized, and early-ended batches.
- Process batches synchronously instead of spawning one goroutine per batch.
- Propagate cancellation into THORNode queries and shared response-cache locks.
- Preserve every state category when snapshot sizes are uneven.
The existing state-sync wire framing remains unchanged.
XMR startup and activation controls
- Add
HaltScannerXMR, a Bifrost-only scanner pause which does not feedKeeper.IsChainHaltedand therefore does not block validator churn or Monovault keygen. - Apply the same scanner-only pause to periodic spent-reference catch-up.
- Fail XMR scanning closed when the scanner-halt value is unavailable.
- Defer only XMR when unresolved local XMR recovery prevents that client from starting.
- Gate XMR signing on Observer's actual XMR-started state.
- Retain deferred XMR TxOuts durably for later processing.
- Leave healthy non-XMR observation and signing paths available.
- Retain the independent
HaltMonovaultKeygencontrol.
Mocknet supplies explicit test-only signer credentials and enables the XMR scanner so simulation coverage can exercise real XMR observation. Production profiles remain operator-controlled.
Signer compatibility and deployment contract
The companion implementation is Serai MR !70, currently based on Serai commit 6844c8179b822309c174c25bd03bcdc0847bf6b1.
THORNode now:
- verifies the signer protocol, API version, state schema, source revision, and required capabilities,
- verifies Monero nettype and genesis hash derived from the signer's connected daemon rather than trusting only a deployment label,
- revalidates compatibility before DKG, keysign, and ambiguous combine retries,
- shares concurrent compatibility refreshes and uses a short success cache,
- requires explicit bearer credentials outside mocknet,
- binds signer, eBifrost, and Monero RPC publications to loopback in the shipped production-like Compose profiles,
- checks all shipped Compose profiles against the expected immutable image.
The currently pinned image:
registry.gitlab.com/thorchain/devops/serai/xmr-frost-signer:mr70-6844c817-20260824-post15@sha256:ba4d243d6f129dda8ca127f1f95e71344b97c6dca02efb3ca9e453ecf1572978
is a temporary Draft integration artifact. It must be replaced by a reviewed release image before this MR leaves Draft or is used on production stagenet or chainnet.
Version and rollout boundary
The intended activation boundary is the scheduled v3.21 application upgrade.
Rollout model
v3.20 prerequisite
v3.20 remains keygen-only:
- XMR economic activity stays disabled
- only churn and Monovault FROST keygen may run after the participating fleet is homogeneous
- scanning, inbound observation, trading, LP activity, outbound assignment, keysign/broadcast, spent-reference processing, and solvency remain disabled.
Review guide
| Area | Primary paths |
|---|---|
| TSS voter consensus state | x/thorchain/handler_tss_keysign.go, keeper and voter types |
| FROST transport and lifecycle | bifrost/p2p, bifrost/tss/go-tss/tss |
| XMR errata and spent references | bifrost/observer, x/thorchain/handler_errata_* |
| State synchronization | bifrost/observer/attestation_state_* |
| XMR startup and signing | bifrost/pkg/chainclients/monero, bifrost/signer |
| Sidecar compatibility | bifrost/pkg/chainclients/monero/sidecar |
| Deployment contract | build/docker/docker-compose*.yml, Compose contract script |