fix(xmr): S27 - preserve reorg history during vault catch-up

Series context

This is S27 of the focused replacement MRs for the superseded XMR MR !4983 (closed). It addresses Issue 11 from Least Authority’s XMR review.

  • Scope: Bifrost’s local XMR reorg journal.
  • Target: develop in the public thorchain/thornode repository.
  • Dependencies: standalone MR, not stacked on another series MR.
  • Related work: S08(!5054)/S09(!5066) recovery and observation cleanup, and S22 orphaned-observation handling.

Which scanner?

Two separate components are involved:

  • The Monero signer sidecar’s local scanner scans Monero blocks for registered vaults. It keeps a global scan cursor and a separate checkpoint for each vault.
  • The Bifrost XMR chain scanner requests those scans, receives the results and stores transaction IDs in Bifrost’s local reorg journal.

The partial results come from the sidecar’s local scanner. This MR fixes how the Bifrost XMR chain scanner’s store saves those results.

THORChain has only one active XMR vault. The sidecar can also scan retiring or inactive vaults for recovery.

Least Authority finding

Least Authority reported that historical catch-up for a vault newly registered with the sidecar can erase Bifrost’s reorg history for an already-scanned vault.

The sidecar intentionally skips vaults that have already scanned the requested block. Its response therefore contains only the newly registered vault’s results. Bifrost incorrectly treats that partial response as the complete transaction list for the block.

For example:

Vault A has scanned through block 999.
Its deposit in block 998 is saved in Bifrost’s reorg journal (reorgStore).
                         ↓
Keygen agrees on height 1000.
Vault B is registered to scan from 990 (10-block safety margin).
                         ↓
Catch-up reaches block 998.
A is skipped because it already scanned it. B has no transactions.
                         ↓
WITHOUT THIS MR:
The empty result erases A’s deposit from the journal.
                         ↓
A later reorg removes that deposit.
Bifrost misses the correction because the journal entry is gone.

Changes

  • Merge transaction IDs when catch-up returns results for the same block, including empty results.
  • Remove duplicates and keep the IDs in a stable order.
  • Preserve known vault-origin information. Incomplete information stays incomplete.
  • Reject conflicting height or parent information.
  • Continue replacing the journal entry when the block hash actually changes.
  • Save the receipt, merged journal and progress marker together in the existing synced database write.

The change adds one lookup of Bifrost’s existing journal entry. The sidecar’s scanning behaviour is unchanged.

Verification

The empty-result regression failed before the fix and passes afterward.

Tests cover partial results, duplicate retries, transaction origins, block replacement, failed writes, restart, lost responses and journal pruning. A Bifrost XMR chain scanner regression also proves that a later reorg still produces the expected errata and relocation handling after empty catch-up and restart.

These tests exercise the partial catch-up and recovery behaviour, they do not reproduce the entire earlier registration-failure sequence.

Compatibility and rollout

This changes only Bifrost’s local journal handling. There is no wire-format, database-schema or THORNode consensus change.

Deploy the reviewed build across the Bifrost fleet while XMR scanning is halted.

Merge request reports

Loading
Loading