TSS: make keysign-failure voters replay-safe
Summary
This is S01 of the focused follow-ups split from !4983 (closed). The complete series plan is tracked there.
This is common THORNode consensus work and is not specific to XMR.
Problem
After a TSS keysign-failure voter reaches quorum, THORNode clears its signers and last_round_count fields but historically did not persist terminality. The same signers could replay the failure, rebuild quorum, and repeat event emission, observation accounting, vault freezing, blamed-node slashing, and jail creation or extension.
Changes
- Add an explicit
TssKeysignFailVoterStateenum distinguishing legacy, pending, and completed voters. - Create missing voters explicitly as pending, and persist completed state only after all required terminal effects succeed.
- Treat persisted state-zero legacy voters and completed voters as terminal replay barriers with no replay side effects or normalization writes.
- Reject attempts to persist legacy or unsupported future states.
- Return and propagate voter serialization and storage errors instead of panicking or continuing without durable state.
- Make observation charges and refunds, blamed-node slash points, jail writes, vault freeze state, and the completed replay barrier transaction-atomic. The replay barrier is written last.
- Preserve slash telemetry caller attribution on direct, error-propagating keeper paths.
- Add legacy-state compatibility, keeper-reinstantiation, restart/replay, exact-quorum, atomic rollback, accounting, telemetry, serialization-error, and write-failure tests.
There is no module or store migration, voter walker, Genesis voter transport, Bifrost change, or XMR-specific branch in this MR.
Keysign-failure voters are intentionally omitted from Genesis. Their store prefix has no pruning lifecycle, so exporting every historical legacy record would make Genesis state grow without bound. Runtime KV records remain intact and continue to suppress duplicate reports during normal operation and in-place binary upgrades.
Relation to XMR
XMR FROST keysign failures use this common voter and handler, including terminal FROSTKEYSIGNSHARE and FROSTKEYSIGNRESULT rounds. Early XMR activation is likely to exercise failure and retry paths heavily, but this fix protects every supported chain using the handler.
Consensus and rollout boundary
This changes how persisted state-zero voter records are interpreted and must ship at a scheduled THORNode upgrade, never as a rolling patch. At activation, every persisted legacy voter becomes a terminal replay barrier. No upgrade-time scan, preflight, or rewriting migration is required.
A coordinated Genesis export/import restart intentionally omits the voter prefix. The accepted residual risk is limited to the finite set of in-flight signing attempts that can reproduce a pre-export voter ID after the restart. Rescheduling at a new TxOut height produces a new voter ID and restores normal processing.
The upgrade is one-way. A pre-!5029 binary does not understand or preserve the explicit voter state and is therefore not a safe rollback target after activation. Any rollback build must retain the new voter-state semantics; no Genesis voter field is required.