Skip to content

feat(chalg): underwriter-fault challenge — bonded, council-adjudicated slashing (WIRE-297) - #549

Open
heifner wants to merge 1 commit into
masterfrom
feat/underwriter-challenge
Open

feat(chalg): underwriter-fault challenge — bonded, council-adjudicated slashing (WIRE-297)#549
heifner wants to merge 1 commit into
masterfrom
feat/underwriter-challenge

Conversation

@heifner

@heifner heifner commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Security property #4 ("the bond is slashable") was unenforced: no production path could slash an UNDERWRITER-type operator, and the collateral-lock window ended in an unconditional release. This adds the missing trigger, mirroring the batch-operator envelope dispute everywhere the problem allows.

sysio.chalg — the envelope dispute's sibling

  • openuwchal(challenger, uwreq_id, underwriter, reason, detail) — permissionless filing against a CONFIRMED commitment, time-gated to its collateral-lock window. The challenger escrows a WIRE bond = the WIRE value of the challenged locks, each leg priced through its OWN reserve's live books (token_to_wire; u128-summed). One challenge EVER per (uwreq, underwriter) — a council verdict is final per commitment; other commitments are independently challengeable.
  • uwchalbond — read-only quote sharing ONE formula helper with the charge, so quote and escrow can never drift (the split_wire_fee principle).
  • voteuwchal — record-only Tier-1 ballot (electorate + quorum snapshotted at open via snapshot_t1_electorate, now shared with opendispute). Three-way: UPHOLD / REJECT_REFUND / REJECT_FORFEIT — forfeiture to the wrongly-challenged underwriter happens ONLY by an explicit majority of the rejectors (tie favours refund); an honest mistake refunds.
  • chkuwchal — permissionless tally, chkdispute-shaped. UPHELD: slashop, then uwrit::sweeplocks (inline actions run depth-first in send order, so releaselock takes its deferred-slash branch — locked collateral debited, outbound SLASH attestations queued). REJECTED: freelocks; the locks live out their window. Past the deadline with no quorum: LAPSED — bond refunded, nobody punished. Deliberately NO after-deadline relaxed tally (a challenge must be voted inside the window) and NO epoch pause (an unresolved challenge is survivable; the chain keeps advancing).

sysio.uwrit — the lock hold

  • lock_entry.challenge_id (ABI change): a held lock is NOT released at expiry; chklocks skips it and pokes chkuwchal instead — the epoch tick is the challenge system's only cadence (possible precisely because the chain is not paused; envelope disputes need a standalone crank because theirs is).
  • holdlocks / freelocks / sweeplocks (auth sysio.chalg): hold is the authoritative liveness gate for a filing (a stale open aborts whole, escrow included); free/sweep are soft past auth — they run inline from the epoch tick, where an abort would stall advancement chain-wide.
  • The COMPLETED-flip / evidence-clear / retention tail is extracted to finalize_settled_uwreqs, shared by chklocks and sweeplocks.

The evidence-retention prerequisite flagged on WIRE-297 needed no work: CONFIRMED uwreqs carry expires_at_epoch == 0 and pruneuwreqs skips them, so the stored SwapRequest / source_tx_id / UIC bytes already survive the whole lock window; the deadline is only stamped when the locks leave.

Tests

sysio.dispatch_tests (reusing its CONFIRMED-uwreq machinery): escrow/hold/uniqueness; every filing guard incl. whole-transaction rollback; ballot guards; uphold end-to-end (SLASHED, locked collateral debited via the deferred-slash branch, bond returned, uwreq COMPLETED, chalg custody zero); reject-forfeit (bond to the underwriter, collateral byte-identical after the healthy release); reject-tie → refund; the epoch-tick skip/poke/lapse/second-sweep sequence; and chalg-only auth on the lock trio.

contracts_unit_test: 592 cases, No errors detected. ABI delta: chalg + uwrit only; other wasm churn is ABI-identical rebuilds from the full build.

Validated end-to-end on a live cluster by the new flow-underwriter-slashing (154/154 Report steps green): both verdicts exercised on the real pipeline — REJECTED_FORFEIT paid the bond to the underwriter inline; UPHELD slashed the winner, swept the locks, and refunded the challenger, with sysio.chalg ending at zero custody.

Companion PRs (merge this first): wire-libraries-ts and wire-tools-ts links to follow in comments — merge order sysio → libraries → tools.

…d slashing (WIRE-297)

Security property #4 ("the bond is slashable") was unenforced: no production
path could slash an UNDERWRITER-type operator, and the collateral-lock window
ended in an unconditional release. This adds the missing trigger, mirroring
the batch-operator envelope dispute everywhere the problem allows.

sysio.chalg — the envelope dispute's sibling:
  * openuwchal(challenger, uwreq_id, underwriter, reason, detail) —
    permissionless filing against a CONFIRMED commitment, time-gated to its
    collateral-lock window. The challenger escrows a WIRE bond = the WIRE
    value of the challenged locks, each leg priced through its OWN reserve's
    live books (token_to_wire; u128-summed). One challenge EVER per
    (uwreq, underwriter) — a council verdict is final per commitment; other
    commitments are independently challengeable.
  * uwchalbond — read-only quote sharing ONE formula helper with the charge,
    so quote and escrow can never drift (the split_wire_fee principle).
  * voteuwchal — record-only Tier-1 ballot (electorate + quorum snapshotted
    at open via snapshot_t1_electorate, now shared with opendispute).
    Three-way: UPHOLD / REJECT_REFUND / REJECT_FORFEIT — forfeiture to the
    wrongly-challenged underwriter happens ONLY by an explicit majority of
    the rejectors (tie favours refund); an honest mistake refunds.
  * chkuwchal — permissionless tally, chkdispute-shaped. UPHELD: slashop,
    then uwrit::sweeplocks (inline actions run depth-first in send order, so
    releaselock takes its deferred-slash branch — locked collateral debited,
    outbound SLASH attestations queued). REJECTED: freelocks; the locks live
    out their window. Past the deadline with no quorum: LAPSED — bond
    refunded, nobody punished. Deliberately NO after-deadline relaxed tally
    (a challenge must be voted inside the window) and NO epoch pause (an
    unresolved challenge is survivable; the chain keeps advancing).

sysio.uwrit — the lock hold:
  * lock_entry.challenge_id (ABI change): a held lock is NOT released at
    expiry; chklocks skips it and pokes chkuwchal instead — the epoch tick is
    the challenge system's only cadence (possible precisely because the
    chain is not paused; envelope disputes need a standalone crank because
    theirs is).
  * holdlocks / freelocks / sweeplocks (auth sysio.chalg): hold is the
    authoritative liveness gate for a filing (a stale open aborts whole,
    escrow included); free/sweep are soft past auth — they run inline from
    the epoch tick, where an abort would stall advancement chain-wide.
  * The COMPLETED-flip / evidence-clear / retention tail is extracted to
    finalize_settled_uwreqs, shared by chklocks and sweeplocks.

The evidence-retention prerequisite flagged on WIRE-297 needed no work:
CONFIRMED uwreqs carry expires_at_epoch == 0 and pruneuwreqs skips them, so
the stored SwapRequest / source_tx_id / UIC bytes already survive the whole
lock window; the deadline is only stamped when the locks leave.

Tests (sysio.dispatch_tests, reusing its CONFIRMED-uwreq machinery): the
bond quote pinned to the shared AMM kernel over LIVE post-settlement books
(winner selection settles inline — challenge-time books have already moved);
escrow/hold/uniqueness; every filing guard incl. whole-transaction rollback;
ballot guards; uphold end-to-end (SLASHED, locked collateral debited via the
deferred-slash branch, bond returned, uwreq COMPLETED, chalg custody zero);
reject-forfeit (bond to the underwriter, collateral byte-identical after the
healthy release); reject-tie → refund; the epoch-tick skip/poke/lapse/
second-sweep sequence; and chalg-only auth on the lock trio.

contracts_unit_test: 592 cases, No errors detected. ABI delta: chalg + uwrit
only; other wasm churn is ABI-identical rebuilds from the full build.

Change-Id: I3f2c743bbca520558d959ac3deac4e5193c9bf8f
@heifner

heifner commented Aug 6, 2026

Copy link
Copy Markdown
Contributor Author

Companion PRs: Wire-Network/wire-libraries-ts#61 (regenerated SysioContractTypes) and Wire-Network/wire-tools-ts#56 (flow-underwriter-slashing, the live-cluster validation — 154/154 steps green). Merge order: this → libraries → tools.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant