fix(thorchain): gas correction must not touch an un-debited placeholder gas
Problem
EVM outbound gas over-credit → phantom vault balance → SOLVENCYHALTVAULT → ETH chain halt → churn blocked. Surfaced on stagenet 3.21 rc6; the vulnerable path (correctOutboundGas + the gas-agnostic correction + the bifrost MaxGas instant self-observation) is present on develop, so mainnet 3.21 would hit it (mainnet 3.20.3 does not — it lacks the gas-agnostic outbound correction trigger).
A broadcasting signer's instant self-observation reports the scheduled MaxGas before the tx is mined. Bifrost sends it ConfirmationRequired:0, so the observer records it final-shaped (BlockHeight == FinaliseHeight). Gas-agnostic consensus matching (EqualsConsensusIgnoreGas) then lets that MaxGas overwrite the finalized voter.Tx gas after the quorum has already debited the real gas; correctOutboundGas subsequently credits (MaxGas − real) the vault was never debited.
Confirmed on stagenet from the on-chain vote trace of one affected outbound: the real gas (10129) debited, then a phantom credit of 150000 − 10129 = 139871.
Fix (consensus)
- Finalization preserves the debited gas. When a different-gas variant finalizes over an already-executed outbound, keep the gas (and finality) the quorum debited rather than overwriting with the finalizing variant's gas. Same-gas confirmation of an outbound is unchanged — no behaviour change on any ordinary outbound.
- Block-height guard on the correction. The gas correction only acts on an observation at or above the current consensus Tx's block height (a pre-mine placeholder sits below the mined block), and advances the tracked height so a later placeholder is rejected. A genuine reorg re-inclusion sits at
>=the original height and still corrects. - The symmetric credit is kept — it is the legitimate refund when the quorum debited the higher gas. Removing it would strand funds on the common ordering (where the MaxGas observation forms consensus first and is debited).
Tests
Final-bucket tests covering the real production path, each positive-controlled (the fix was reverted to confirm each test fails for the right reason):
- incident ordering — no phantom credit;
- mirror ordering — the refund still fires;
- late MaxGas straggler — no over-debit;
- normal non-final→final same-gas — no finality flip, and a same-height different-gas reorg still corrects;
- genuine higher-block reorg finalizing first — still corrects.
Existing reorg tests unchanged and green; full x/thorchain observed-tx/outbound suite green under -race -count=3.
Notes
Shipped to stagenet as stagenet-3.21.0-rc7. This MR is the consensus fix only; the stagenet-specific one-time vault ledger reconciliation that accompanied rc7 is intentionally not included here (it is network-gated to stagenet and would be permanent dead code on mainnet).