fix(xmr): S29 - recover outbounds and spent references from consensus
Recover XMR outbound metadata and spent references from persisted outbound consensus, including eligible late migration observations. Keep unsigned XMR outbounds available for their chain's full signing window.
Series context
This is S29 part of the S series tracked in the XMR v3.21.0 launch plan.
- Scope: XMR outbound and spent-reference recovery, migration replay, unsigned outbound lifetime, and the supporting THORNode query API.
- Depends on: no other S-series MR. This is a standalone MR targeting
develop, independent of RDY/S03A and S00 keygen activation. - Extracted from: S00(!5026), where the recovery work was originally carried alongside keygen and rollout changes.
- Related work: S11(!5067) covers exact outbound-plan recovery and errata ordering. That work remains separate; shared recovery interfaces and protobuf fields must stay aligned.
This MR is independently reviewable and does not inherit approval or verification results from !4983 (closed) or S00.
Problem
An internal XMR transfer can share its transaction ID with an inbound voter. An inbound-first lookup can therefore hide the outbound consensus needed to reconstruct source-side recovery metadata. Committed-event payloads alone are also insufficient authority for importing spent references.
When authentication arrives after scanning, the source migration observation can remain missing. Separately, unsigned XMR outbounds can expire from the signer before XMR's extended signing window has elapsed.
Changes
- Add explicit outbound-voter selection to the THORNode query and generated protobuf/OpenAPI interfaces. Preserve the existing default lookup behavior; explicit outbound selection never falls back to inbound.
- Treat committed quorum events as vault/transaction hints. Live import and periodic catch-up rebuild recovery data from committed vault references and non-reverted outbound consensus, without falling back to individual voter observations.
- Fetch live references and outbound voters outside the shared cache lock, using caller cancellation and a one-block maximum per request. Check cancellation before sidecar writes and replay enrollment. Let an already-started write complete its durable signer bookkeeping so catch-up can finish deferred replay.
- Preserve exact signed wire memos and restore internal-transfer pairing metadata. Replay eligible migration-source observations only after canonical-chain validation, retaining durable replay state until the required observations are stored.
- Build retiring-node certificates from the same filtered, confirmation-classified observations used by active nodes.
- Retain unsigned XMR outbounds for the extended signing window while preserving other-chain expiry and signed-XMR rebroadcast behavior.
Recover XMR payouts after churn and restart
- Extend historical spend-certificate recovery to payouts and refunds from Active, Retiring and Inactive vaults. Former vault members remain subject to membership, signature, quorum, planned-payment and Monero spend checks.
- Keep the original memo on newly signed XMR payouts, even when global memoless outbounds are enabled. Refuse new payouts whose original memo is missing.
- Ignore incoming outputs carrying
OUT,REFUNDorRAGNAROKmemos so that change is not counted as a deposit before the spend proof arrives. - Save late recovery observations and their block-journal updates together before completing the import. Recovery survives restarts, including when the original journal entry has already been pruned.
- Save pending producer certificates before acknowledging the scanner. Retry them through a bounded queue until THORChain confirms finalization or reversion. Calculate confirmations from the full filtered batch.
- Keep observations retryable while the node's startup status is unknown.
These additional production changes are in Bifrost and do not change THORNode consensus rules. The matching THORNode query support described below is still required.
Verification
- Full Observer, Monero and thorclient tests, both normal and
mocknet. - p2p/TSS test-package compilation and affected-package vet in both modes.
- Full Monero and focused thorclient freshness race suites with
mocknet, each repeated twice. - Test coverage for consensus selection, forged hints, visibility races, recovery bookkeeping, migration replay, locked-cache bypass, stalled HTTP reads, caller cancellation/deadlines, and completion of an already-started sidecar write.
The XMR-required cluster run passed with an explicit xmr-historical-refund stage. All 189 actors completed, including the departed-signer recovery scenario and all 13 standard inactive-vault refund tests. Related focused tests and repository lint also passed locally.
Compatibility and rollout
This includes THORNode query changes as well as Bifrost recovery changes. Deploy matching query and client support. The new outbound query field uses protobuf field 3, S11(!5067)'s include_xmr_outbound must use field 4 when its stack is rebased.
Keep the corresponding recovery changes aligned in S00(!5026) and dependent integration branches. XMR economic activity remains gated by the required series review, integration and deployment plan.
XMR memoless outbounds remain disabled regardless of the global setting. Incoming payments carrying outbound memos, including vault payouts sent directly to another vault address, are ignored: they are neither credited nor refunded.