Skip to content

feat(bond): notify slashed party on dispute slash (#768)#779

Merged
grunch merged 4 commits into
mainfrom
bond-dm-loser
Jun 16, 2026
Merged

feat(bond): notify slashed party on dispute slash (#768)#779
grunch merged 4 commits into
mainfrom
bond-dm-loser

Conversation

@Catrya

@Catrya Catrya commented Jun 16, 2026

Copy link
Copy Markdown
Member

fix #768
Dispute slashes now send Action::BondSlashed to each slashed party, the same notice the timeout path already sends. Previously a settle/cancel resolution produced the same order message whether or not a bond was slashed, so the loser had no signal they forfeited a bond.

How

apply_bond_resolution returns the confirmed-slashed bond rows; the admin settle/cancel handlers send one best-effort notice per row. Confirmation uses the durable slashed_reason witness (or a freshly-inserted range slice
child), so a transient settle failure and an idempotent admin retry both yield no notice — never untruthful, winner never re-notified. Amount is the full bond, or the slice's proportional share for range maker bonds.

No protocol/schema change.

How to test manually

  1. Enable bonds: [anti_abuse_bond] enabled = true, apply_to = "both".
  2. Create and take an order so both parties lock a bond.
  3. Open a dispute, have a solver resolve it with admin-settle / admin-cancel carrying a BondResolution that slashes one side.
  4. Confirm in the logs the slashed party receives bond-slashed with the forfeited amount, right after the admin-settled / admin-canceled message — while the non-slashed party gets no such notice. grep bond-slashed logs.txt should show one message to the loser only.

Commits

  • refactor: generalize slash confirmation witness
  • feat: notify slashed party on dispute slash
  • test: cover the forfeiture notice
  • docs: document Phase 2.5

Summary by CodeRabbit

Release Notes

  • New Features

    • Bond-slashed notifications are now dispatched to affected parties when solver-directed disputes result in confirmed slash outcomes
    • Notifications are withheld on transient failures and idempotent retries to ensure accuracy
  • Documentation

    • Updated anti-abuse bond rollout plan with Phase 2.5 specification, covering dispute-slash notification behavior, proportional slashing for range makers, and expanded test scenarios

Catrya added 4 commits June 16, 2026 09:49
  - Extract slash_reason_recorded(pool, bond_id, expected) from timeout_slash_confirmed
  - timeout_slash_confirmed now delegates to it keyed on BondSlashReason::Timeout
  - Pure refactor, no behavior change
  - apply_bond_resolution returns the confirmed-slashed bond rows
  - non-range/taker rows confirmed via slash_reason_recorded(LostDispute)
  - range maker rows returned only when the slice child row is newly inserted
  - admin_settle/admin_cancel send a best-effort BondSlashed notice per row
  - transient settle failures and idempotent retries yield no row, so a notice
    is never untruthful and a winner is never re-notified
  - apply_bond_resolution returns one confirmed row per slashed side
  - both-sides slash returns both rows; null payload returns none
  - transient settle failure returns no row (notice never untruthful)
  - non-range and range retries return no row (winner never re-notified)
  - range slash returns the child slice row with the slice amount
@coderabbitai

coderabbitai Bot commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

apply_bond_resolution return type changes from Result<(), MostroError> to Result<Vec<Bond>, MostroError>, accumulating only durably confirmed slashed bond rows. admin_settle_action and admin_cancel_action are updated to iterate those rows and call notify_bond_slashed per party. A new slash_reason_recorded helper is extracted and timeout_slash_confirmed becomes a wrapper over it. Spec and tests are added.

Changes

Phase 2.5: Dispute-slash BondSlashed forfeiture notice

Layer / File(s) Summary
apply_bond_resolution return contract and slash_reason_recorded helper
src/app/bond/slash.rs
Return type changes to Result<Vec<Bond>, MostroError>; notify_rows accumulator collects range-maker child rows on actual insertion and non-range rows only on durable slashed_reason confirmation; new slash_reason_recorded generic helper is extracted and timeout_slash_confirmed becomes a thin wrapper.
BondSlashed notification wiring in admin_settle and admin_cancel
src/app/admin_settle.rs, src/app/admin_cancel.rs
Both action handlers switch to match on apply_bond_resolution; on Ok(slashed_rows) they iterate and call bond::notify_bond_slashed best-effort per confirmed row; on Err they emit a tracing::warn!.
Tests for apply_bond_resolution return contract
src/app/bond/slash.rs
New test cases validate: single taker slash returns one row, dual slash returns two rows, null/no-slash returns empty, transient settle failure returns empty, idempotent retry returns empty, range-maker dispute slash returns child slice row with proportional amount.
Spec: Phase 2.5 entry and updated daemon/test descriptions
docs/ANTI_ABUSE_BOND.md
Phase 2.5 table row added; status section references #768; Phase 2 daemon-behaviour section gains the BondSlashed notification step; test section gains cases for confirmed/absent notice and range-maker proportional slashing.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

  • #768 – Notify the slashed user when their bond is slashed in a dispute: This PR directly implements the feature described in #768 — sending Action::BondSlashed to each party whose bond is confirmed slashed in a dispute resolution, mirroring the existing timeout-slash notice.
  • Feature: Anti-Abuse Bond — Phased Implementation for Taker and Maker Flows #711: The changes extend the confirmed-slash notification mechanics that are part of the anti-abuse bond payment flow and state management design discussed in #711.

Possibly related PRs

  • MostroP2P/mostro#737: Introduced the Phase 2 BondResolution/apply_bond_resolution API in slash.rs that this PR directly extends with the return-value contract.
  • MostroP2P/mostro#744: Modifies slash.rs around timeout/dispute slash confirmation and Action::BondSlashed emission via notify_bond_slashed, the same pattern this PR extends to admin cancel/settle paths.

Suggested reviewers

  • AndreaDiazCorreia
  • arkanoider
  • BraCR10
  • mostronatorcoder

Poem

🐇 A slash once hidden now speaks loud and clear,
The bond is forfeited — the notice is here!
Confirmed by a witness durable and true,
No phantom alerts, only facts that came through.
Idempotent retries won't shout "slashed again,"
The rabbit ensures only real cuts bring pain. 🔔

🚥 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 clearly and concisely describes the main change: adding bond slash notifications in dispute scenarios, which is the primary objective of this PR.
Linked Issues check ✅ Passed The PR fully implements all requirements from issue #768: sending explicit BondSlashed notices on dispute slash, only when slashes occur, for slashed parties, with best-effort delivery and proportional amounts for range makers.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing dispute slash notifications as specified in issue #768; no unrelated modifications are present.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ 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 bond-dm-loser

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

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/app/bond/slash.rs (1)

350-354: 💤 Low value

The cloned target may carry stale state fields.

At line 353, target.clone() copies the bond as it was before slash_one ran. The CAS inside slash_one updates state, slashed_reason, slashed_at, and node_share_sats in the database, but those changes are not reflected in the returned Bond. If the caller (admin handlers) or downstream code relies on the returned row's state or slashed_at for logging or the SmallOrder payload, it will see the pre-slash Locked state instead of PendingPayout.

Currently notify_bond_slashed only uses pubkey, amount_sats, and order_id from the bond, so this is benign today. However, consider either:

  1. Re-fetching the bond after confirmation (like the range path does via find_slice_slash_child)
  2. Documenting that the returned row's metadata fields may be stale

This is low-risk since the notice only needs amount_sats and pubkey, but it's a potential footgun for future maintainers.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/bond/slash.rs` around lines 350 - 354, The `target` variable cloned
into `notify_rows` at line 353 contains stale state because `slash_one` updates
the database fields (state, slashed_reason, slashed_at, node_share_sats) but the
in-memory `target` is not refreshed. To fix this, after the
`slash_reason_recorded` check passes and before pushing to `notify_rows`,
re-fetch the bond from the database (similar to how `find_slice_slash_child`
works in the range path) to ensure the cloned bond reflects the updated state.
Alternatively, if re-fetching is not feasible, add a comment documenting that
the bond's metadata fields are stale and will not reflect the post-slash state,
cautioning future maintainers that relying on state, slashed_at, or
node_share_sats fields could be a footgun.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/app/bond/slash.rs`:
- Around line 350-354: The `target` variable cloned into `notify_rows` at line
353 contains stale state because `slash_one` updates the database fields (state,
slashed_reason, slashed_at, node_share_sats) but the in-memory `target` is not
refreshed. To fix this, after the `slash_reason_recorded` check passes and
before pushing to `notify_rows`, re-fetch the bond from the database (similar to
how `find_slice_slash_child` works in the range path) to ensure the cloned bond
reflects the updated state. Alternatively, if re-fetching is not feasible, add a
comment documenting that the bond's metadata fields are stale and will not
reflect the post-slash state, cautioning future maintainers that relying on
state, slashed_at, or node_share_sats fields could be a footgun.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 733d547b-4aa3-4633-a3e2-01ba47f1b382

📥 Commits

Reviewing files that changed from the base of the PR and between 5b07be2 and d7bd709.

📒 Files selected for processing (4)
  • docs/ANTI_ABUSE_BOND.md
  • src/app/admin_cancel.rs
  • src/app/admin_settle.rs
  • src/app/bond/slash.rs

@mostronatorcoder mostronatorcoder Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I reviewed the current head and I do not see a blocking issue.

What I checked closely:

  • The notification is sent only for slashes that are actually confirmed, not merely intended. For non-range rows that confirmation is keyed off the durable slashed_reason witness; for range maker rows it is keyed off a newly inserted slice child row.
  • That avoids the two failure modes I would care most about here: sending an untruthful BondSlashed notice after a transient settle failure, and re-notifying on an idempotent admin retry.
  • The range path also looks correct: the returned row is the slice child with the proportional amount, not the full parent bond, so the notice payload matches what was actually forfeited on that slice.
  • The admin handlers keep the notice best-effort and post-confirmation, which is the right failure boundary for this kind of user signal.

The added tests cover the important edge cases well: one-sided slash, both-sided slash, null payload, transient settle failure, non-range retry, range slash, and range retry.

Approved on the current revision.

@grunch grunch merged commit 68a125e into main Jun 16, 2026
8 checks passed
@grunch grunch deleted the bond-dm-loser branch June 16, 2026 17:20
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.

Notify the slashed user when their bond is slashed in a dispute

2 participants