Skip to content

feat(bond): phase 2 — taker dispute slash#723

Open
grunch wants to merge 1 commit intomainfrom
feat/bond-phase-2-taker-dispute-slash
Open

feat(bond): phase 2 — taker dispute slash#723
grunch wants to merge 1 commit intomainfrom
feat/bond-phase-2-taker-dispute-slash

Conversation

@grunch
Copy link
Copy Markdown
Member

@grunch grunch commented Apr 29, 2026

Summary

Phase 2 of the anti-abuse bond rollout (issue #711, spec
docs/ANTI_ABUSE_BOND.md §7).
Routes a dispute outcome onto the taker's bond when the operator has
opted in.

Behaviour gate: enabled && apply_to ∈ {take, both} && slash_on_lost_dispute.

  • admin_settle_action (seller wins escrow) and admin_cancel_action
    (buyer is refunded) now call a new
    bond::apply_taker_dispute_outcome_or_warn in place of the Phase 1
    release helper. That entrypoint:
    • derives the taker side from the order kind (Sell-order taker =
      buyer; Buy-order taker = seller) and combines it with the dispute
      outcome to decide whether the taker lost;
    • gate off OR taker won: falls through to
      release_bonds_for_order_or_warn — Phase 1 behaviour preserved by
      default;
    • gate on AND taker lost: transitions the active taker parent
      bond to pending-payout / lost-dispute via a conditional UPDATE
      keyed on the active states only (Requested / Locked). Phase 3
      will settle the bond hold invoice and pay the winner — Phase 2
      deliberately performs no LND interaction here.
  • Bond lookup is via find_active_bonds_for_order filtered to
    BondRole::Taker with parent_bond_id IS NULL, so a released
    prior-taker row from supersede_prior_taker_bonds and Phase 6 child
    rows are both ignored.

Phase 2 hardening of on_bond_invoice_canceled

A bond already in pending-payout is now left untouched on an
LND-cancel event. Without this, an LND CLTV-expiry cancel (which
Phase 3 must beat with settle_hold_invoice) would silently flip the
row to released and drop the slashed claim. Phase 1 behaviour for
active states (Requested / LockedReleased) is unchanged.

Tests

cargo test --bin mostrod: 266 passed. New tests in app::bond::flow:

  • taker_lost_dispute_truth_table — 4-cell (kind, seller_won) matrix.
  • slash_on_lost_dispute_enabled_is_false_without_config — Phase 2
    inertness guarantee (no [anti_abuse_bond] block ⇒ gate off).
  • slash_taker_bond_transitions_locked_to_pending_payout — happy path.
  • slash_taker_bond_transitions_requested_to_pending_payout — defensive.
  • slash_taker_bond_skips_already_pending_payout — idempotent.
  • slash_taker_bond_skips_terminal — no-op on Released.
  • slash_taker_bond_no_bond_is_noop — feature-off-when-trade-started.
  • slash_taker_bond_picks_active_parent_among_prior_released — picks
    the active row, not the released prior-taker row.
  • slash_taker_bond_ignores_maker_bond — taker-only scope.
  • slash_taker_bond_ignores_phase6_child_rows — parent-only scope.
  • apply_dispute_outcome_releases_with_flag_off — Phase 1 contract
    preserved when the gate is off.
  • on_bond_invoice_canceled_leaves_pending_payout_untouched — Phase 2
    defense.
  • on_bond_invoice_canceled_still_releases_active_bond — Phase 1
    regression guard.

Test plan

  • cargo test --bin mostrod (266 passed)
  • cargo clippy --all-targets --all-features -- -D warnings (clean)
  • cargo fmt --check (clean)
  • Manual LND/regtest, slash flag on, Sell order: taker is buyer,
    open dispute, admin_settle → bond row should be
    pending-payout / lost-dispute, hold invoice still locked
    at LND.
  • Manual LND/regtest, slash flag on, Buy order: taker is seller,
    open dispute, admin_cancel → bond row should be
    pending-payout / lost-dispute, hold invoice still locked
    at LND.
  • Manual LND/regtest, slash flag on, taker won: bond released
    (Phase 1 behaviour).
  • Manual LND/regtest, slash flag off: bond released regardless of
    outcome (Phase 1 behaviour preserved).

Acceptance (from spec §7.3)

  • With flag off: Phase 1 behaviour preserved (release).
  • With flag on + taker lost: bond row is PendingPayout.
  • With flag on + taker won: bond released.

Compatibility

  • Database migration: none. Phase 0's schema already has all required
    columns (state, slashed_reason).
  • Default config remains enabled = false, so existing nodes see no
    behaviour change.
  • New BondState::PendingPayout rows are persisted but are not yet
    acted upon by any other subsystem; the Phase 3 PR will pick them
    up.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Admin cancellation and settlement now apply dispute outcomes to taker bond handling, routing bonds appropriately based on dispute results.
    • Added Phase 2 logic to conditionally slash and move taker bonds to pending payout when disputes are lost and slashing is enabled.

Routes the dispute outcome onto the taker's bond when the operator has
opted in. Behaviour gate: enabled && apply_to ∈ {take, both} &&
slash_on_lost_dispute.

admin_settle_action and admin_cancel_action now call a new
bond::apply_taker_dispute_outcome_or_warn instead of the Phase 1
release helper. That entrypoint:
  - reads the order kind (Sell-order taker = buyer; Buy-order taker =
    seller) and combines it with the dispute outcome (admin_settle =
    seller wins, admin_cancel = buyer wins) to decide whether the
    taker lost;
  - with the slash gate off OR the taker won, falls through to
    release_bonds_for_order_or_warn — Phase 1 behaviour preserved by
    default;
  - with the gate on AND the taker lost, transitions the active taker
    parent bond to pending-payout / lost-dispute via a conditional
    UPDATE keyed on the active states only (Requested / Locked).
    Phase 3's payout job then settles the bond hold invoice and pays
    the winner — Phase 2 deliberately does no LND interaction here.

The lookup uses find_active_bonds_for_order filtered to BondRole::Taker
with parent_bond_id IS NULL so a Released prior-taker row from
supersede_prior_taker_bonds and (eventually) Phase 6 child rows are
both ignored.

Also a small Phase 2 hardening in on_bond_invoice_canceled: a bond
already in pending-payout is now left untouched on an LND-cancel
event. Without this, an LND CLTV-expiry cancel (which Phase 3 must
beat with settle_hold_invoice) would silently flip the row to
released and drop the slashed claim. Phase 1 behaviour for active
states (Requested / Locked → Released) is unchanged.

Tests cover:
  - taker_lost_dispute truth table for the 4 (kind, seller_won) cells;
  - the Phase 2 inertness guarantee (slash gate off without config);
  - slash_taker_bond_for_lost_dispute happy path on Locked, defensive
    Requested, idempotency on PendingPayout / Released, no-op when no
    bond row exists, picking the active row over a Released prior-
    taker row, ignoring Maker rows and Phase 6 child rows;
  - apply_taker_dispute_outcome_or_warn falls through to release with
    the gate off (Phase 1 contract);
  - on_bond_invoice_canceled leaves PendingPayout alone but still
    releases active bonds (Phase 1 regression guard).

Spec: docs/ANTI_ABUSE_BOND.md §7.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 29, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 36246e91-bd48-46bf-992a-126edca6e83c

📥 Commits

Reviewing files that changed from the base of the PR and between 54b50c8 and e30f198.

📒 Files selected for processing (4)
  • src/app/admin_cancel.rs
  • src/app/admin_settle.rs
  • src/app/bond/flow.rs
  • src/app/bond/mod.rs

Walkthrough

The PR implements taker-dispute outcome handling by replacing unconditional taker bond releases with outcome-driven processing. Admin cancel and settle actions now route taker bonds either to Phase 3 payout (when slashing is enabled) or release them, based on dispute results and configuration.

Changes

Cohort / File(s) Summary
Admin Action Handlers
src/app/admin_cancel.rs, src/app/admin_settle.rs
Replaced release_bonds_for_order_or_warn calls with apply_taker_dispute_outcome_or_warn to enable outcome-driven taker bond processing instead of unconditional release.
Bond Flow Core Logic
src/app/bond/flow.rs
Added Phase 2 taker-lost-dispute handling with new public entrypoint apply_taker_dispute_outcome_or_warn, dispute-outcome computation, configuration gating (slash_on_lost_dispute_enabled), and conditional state transitions from Requested/Locked to PendingPayout. Enhanced on_bond_invoice_canceled to preserve PendingPayout bonds during Phase 3. Includes comprehensive test suite with truth-table checks, slash transition tests, and idempotence verification.
Bond Module Exports
src/app/bond/mod.rs
Re-exported the new apply_taker_dispute_outcome_or_warn function to the module's public API surface.

Sequence Diagram

sequenceDiagram
    participant Admin as Admin Action
    participant Outcome as Dispute Outcome Logic
    participant BondFlow as Bond Flow Handler
    participant DB as Database

    Admin->>Outcome: apply_taker_dispute_outcome_or_warn(order, seller_won)
    Outcome->>Outcome: Determine taker identity & dispute result
    alt slash_on_lost_dispute enabled & taker lost
        Outcome->>BondFlow: slash_taker_bond_for_lost_dispute()
        BondFlow->>DB: UPDATE bond SET state=PendingPayout<br/>WHERE state IN (Requested, Locked)<br/>AND order_id=?
        DB-->>BondFlow: Rows updated
        BondFlow-->>Outcome: ✓ Slashed
    else
        Outcome->>BondFlow: release_bond() fallback
        BondFlow->>DB: UPDATE bond SET state=Released<br/>WHERE id=?
        DB-->>BondFlow: Row updated
        BondFlow-->>Outcome: ✓ Released
    end
    Outcome-->>Admin: Processing complete
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related issues

Possibly related PRs

Suggested reviewers

  • arkanoider
  • AndreaDiazCorreia

Poem

🐰 A taker once disputed with might,
But logic now knows wrong from right—
Phase two shall slash the bonds astray,
Phase three completes the payout's way!
No more unconditional release,
Just righteous bonds and taker's peace.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(bond): phase 2 — taker dispute slash' directly and specifically summarizes the main change: implementing Phase 2 of bond dispute handling with taker bond slashing on lost disputes.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/bond-phase-2-taker-dispute-slash

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
Review rate limit: 0/1 reviews remaining, refill in 60 minutes.

Comment @coderabbitai help to get the list of available commands and usage tips.

@grunch
Copy link
Copy Markdown
Member Author

grunch commented Apr 29, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 29, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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