[feat] Solvency: unify solvency checks into a single reporter
Supersedes !4511 (closed).
Consolidates Bifrost solvency reporting across chains, inspired by MAYA’s unify-solvency-checks work (mayachain/mayanode!698). Also incorporates the TRON slashing mitigation, telemetry, and regression simulation from !5059.
Summary
- Replace the legacy per-chain solvency implementations with a shared reporter, retaining chain-specific account lookup and gas-buffer behavior.
- Dispatch scanner-triggered checks from durably scanned heights and normalize reports to canonical cadence boundaries.
- Run halted-chain recovery through a shared THORChain-block-time runner, while suppressing scanner-triggered checks during catch-up.
- Process reporter tasks sequentially per chain, bound queues and attestation calls, and cancel outstanding work during shutdown.
- Require successful checks of every relevant vault before reporting that a halted chain is fully solvent.
- Handle XMR reconciliation deferrals without blocking insolvency reports for other vaults or repeatedly querying the same cadence bucket. Register FROST vault mappings before querying the sidecar.
- Reject collection when a required gas estimate is unavailable, allowing a later trigger to retry.
- Remove unfair TRON solvency observation slashing, expose fragmentation through telemetry, and add dedicated four-validator regression coverage.
- After verifying the churn-out payout, the BondYield simulation now waits for the validator to rejoin and vault migration to finish, restoring the original validator set before subsequent stages such as TRON solvency fragmentation run.
TRON: two separate problems
Report-height and cadence fragmentation
Previously, reporting could follow a sampled chain tip rather than durable scan progress, and strict modulo-based scheduling could miss report rounds.
Scanned-height dispatch and canonical height bucketing address this scheduling problem. They align the report-height component across validators; they do not guarantee identical balances or report IDs.
Balance fragmentation and unfair slashing - #3098
TRON account queries still read latest state rather than balances pinned to the reported height. Honest validators can therefore submit different balances for the same canonical round.
This can leave nodes on minority payloads with observation penalties. Non-signers can also be penalized for a consensus that is subsequently discarded as stale.
The mitigation from !5059:
- Introduces
Chain.SupportsSolvencyObservationSlashing()as the chain-policy boundary. - Disables ordinary observation charges, consensus refunds, non-signer penalties, and late-attestation refunds for TRON.
- Retains the existing duplicate-message penalty and other chains’ slash accounting.
- Adds TRON-only metrics covering payload fragmentation, round outcomes, attestation counts, and stale-consensus discards.
This slashing-policy change does not alter consensus thresholds, bypass stale-report rejection, or refund previously accumulated slash points.
Scope and trade-offs
This addresses the unfair-slashing symptoms of #3098, not deterministic historical TRON balances. Payload fragmentation and unsuccessful report rounds can still occur.
A root-cause solution would require reliable height-specific state for both TRX and supported TRC-20 assets, or a validated historical snapshot/reconstruction mechanism. Proving completeness and consistent behavior across nodes and token contracts introduces substantial implementation and operational complexity.
The compromise removes the ordinary incentive to participate in TRON solvency observation and the observation cost of distinct payloads. Duplicate-message penalties remain, but do not prevent distinct-payload spam.
Simulation coverage
Unified reporter: single-node insolvency and recovery
The insolvency suite uses the mocknet-only memo MOCKNET_SIMULATION_ONLY_SKIP_OBSERVATION for vault-adjacent transactions.
These transactions change external balances without updating THORChain’s observed vault accounting. The scenario creates an insolvency gap, checks that reporting halts the chain, restores the balance, and checks automatic recovery.
Each chain’s actor performs two complete halt/unhalt cycles and requires a newer halt height in the second cycle, demonstrating that reporting becomes eligible again in a later cadence bucket.
Vault selection follows the chain-specific signer, including EdDSA and XMR FROST keys, and fails when that signer cannot be identified safely.
This scenario remains single-node mocknet only: its balance-changing steps require direct access to the vault signer and do not support a multi-party TSS vault.
TRON slashing: four-validator cluster
Run:
make test-simulation-tron-solvencyThis rebuilds and resets the local cluster, then runs:
seed -> bootstrap -> churn -> tron-solvency-fragmentationChurn activates the validators before the assertions run. Missing activation produces an immediate setup error.
The actor submits differing TRX/USDT payloads at the same stale height and verifies:
- Ordinary solvency observations do not change any validator’s slash points.
- The resulting stale consensus is discarded.
- Repeating an already-signed payload still incurs the duplicate penalty.
This tests on-chain slash accounting. It does not simulate historical TRON RPC accuracy or Bifrost gossip/quorum formation.
Verification
- Affected common, metrics, reporter, blockscanner, Solana, and observer package tests passed.
- Focused solvency-handler tests passed.
- Simulation actor, suite, and command-package tests passed.
- Focused race tests passed with three repetitions on the identical file tree before history linearization.
- The four-validator TRON simulation completed successfully, verifying zero ordinary slash-point deltas and the expected duplicate penalty. The cluster was not rerun after the history-only rewrite.
The complete repository test suite and single-node end-to-end insolvency suite have not been rerun for this updated head. The results above describe the verified scope.
Relationship to !5059
This branch includes the three original commits from !5059: the TRON slashing policy, telemetry, and regression simulation.
!5059 remains an independent delivery path for the existing per-chain reporters, so the mitigation does not have to wait for reporter unification.
- If !5059 merges first: this MR retains the same changes and delivers the unified reporter on top.
- If this MR merges first: !5059 can be closed as superseded once its complete changes are confirmed included.
Closes #3098 for the unfair-slashing behavior. Deterministic historical TRON balances remain outside this MR’s scope.