Skip to content

feat(bond): anti-abuse bond phase 0 foundation#712

Merged
grunch merged 3 commits into
mainfrom
feat/anti-abuse-bond-phase-0
Apr 24, 2026
Merged

feat(bond): anti-abuse bond phase 0 foundation#712
grunch merged 3 commits into
mainfrom
feat/anti-abuse-bond-phase-0

Conversation

@grunch

@grunch grunch commented Apr 24, 2026

Copy link
Copy Markdown
Member

Adds the inert data and configuration layer for the anti-abuse bond feature described in docs/ANTI_ABUSE_BOND.md (issue #711):

  • [anti_abuse_bond] settings block, opt-in and disabled by default, with Settings::is_bond_enabled() gate.
  • bonds table + Bond sqlx model + CRUD helpers under src/app/bond/.
  • BondRole / BondState / BondSlashReason enums with parse roundtrips.
  • Pure compute_bond_amount helper: max(amount_sats * order_amount, base_amount_sats).

No trade flow, scheduler, or LND path is touched in this PR; every code path added here is behind the disabled-by-default gate. Later phases (taker lock, dispute slash, payout, timeout slash, maker bond, range orders) will hook into the same data model.

Summary by CodeRabbit

  • New Features

    • Anti-abuse bond infrastructure with database storage, configuration options, and amount calculation logic.
  • Documentation

    • Specification document detailing bond feature phases, lifecycle, database schema, configuration surface, payout workflows, and acceptance tests.

Adds the inert data and configuration layer for the anti-abuse bond
feature described in docs/ANTI_ABUSE_BOND.md (issue #711):

- [anti_abuse_bond] settings block, opt-in and disabled by default,
  with Settings::is_bond_enabled() gate.
- bonds table + Bond sqlx model + CRUD helpers under src/app/bond/.
- BondRole / BondState / BondSlashReason enums with parse roundtrips.
- Pure compute_bond_amount helper:
    max(amount_sats * order_amount, base_amount_sats).

No trade flow, scheduler, or LND path is touched in this PR; every
code path added here is behind the disabled-by-default gate. Later
phases (taker lock, dispute slash, payout, timeout slash, maker bond,
range orders) will hook into the same data model.

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

coderabbitai Bot commented Apr 24, 2026

Copy link
Copy Markdown
Contributor

Warning

Rate limit exceeded

@grunch has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 35 minutes and 0 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 35 minutes and 0 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 54d7f9fe-fd6b-478c-ba63-d2604b8fecc9

📥 Commits

Reviewing files that changed from the base of the PR and between 12a8a4f and 86c3bb4.

📒 Files selected for processing (6)
  • docs/ANTI_ABUSE_BOND.md
  • settings.tpl.toml
  • src/app/bond/db.rs
  • src/app/bond/math.rs
  • src/app/bond/model.rs
  • src/config/types.rs

Walkthrough

This pull request introduces Phase 0 infrastructure for the anti-abuse bond feature, including database schema, configuration types and settings, domain models, persistence layer with CRUD operations, bond amount computation logic, and documentation of the feature specification with eight-phase rollout plan.

Changes

Cohort / File(s) Summary
Documentation & Specification
docs/ANTI_ABUSE_BOND.md
Introduces single-source-of-truth specification for anti-abuse bond feature with final configuration surface, eight-phase rollout plan with explicit gates, database schema/models, bond lifecycle behavior, payout workflow, invariants, and acceptance test requirements.
Database Schema
migrations/20260423120000_anti_abuse_bond.sql
Creates bonds table linked to orders via order_id, capturing bond lifecycle state, hold-invoice artifacts, slashing progress, payout metrics, and timing fields with indexes for querying by order, state, and parent bond.
Configuration
settings.tpl.toml, src/config/settings.rs, src/config/types.rs, src/config/mod.rs, src/config/wizard.rs
Adds optional anti_abuse_bond configuration block with BondApplyTo enum and AntiAbuseBondSettings struct containing enabled flag, bond sizing, slashing behavior, and payout-retry parameters. Integrates into global Settings with accessor methods.
Bond Domain Module
src/app/bond/types.rs, src/app/bond/model.rs, src/app/bond/math.rs, src/app/bond/db.rs, src/app/bond/mod.rs, src/app.rs
Implements core bond functionality: string-backed enums for role/state/slash-reason, Bond database row model with constructor, pure helper for bond amount computation (percentage + floor), CRUD operations (create/find/update), and public API surface.
Test Infrastructure Updates
src/app/context.rs, src/app/dev_fee.rs, src/app/rate_user.rs
Updates test settings initialization to explicitly set anti_abuse_bond: None field for Settings struct construction across multiple test modules.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

Possibly related PRs

  • Settings refactor #481: Both PRs modify the configuration subsystem by extending src/config::types and src/config::settings, including additions to the Settings struct and re-exports.

Suggested reviewers

  • Catrya
  • mostronatorcoder

Poem

🐰 A bond so strong, now etched in schema,
With roles and states in every comma,
Configuration blooms, math sings true,
Phase zero done—the hardest part through!
Hold invoices guard what traders do. ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 68.75% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(bond): anti-abuse bond phase 0 foundation' clearly and concisely summarizes the main change: introducing the foundational Phase 0 data/configuration infrastructure for the anti-abuse bond feature as described in issue #711.
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 unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/anti-abuse-bond-phase-0

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.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 12a8a4f2db

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/app/bond/db.rs Outdated
@grunch

grunch commented Apr 24, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Apr 24, 2026

Copy link
Copy Markdown
Contributor
✅ 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.

@AndreaDiazCorreia AndreaDiazCorreia left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Hey! I did an initial pass on this with AI assistance, and a couple of things came up that I wanted to run by you. I'm not 100% sure they're actual concerns, so take them as questions rather than blockers.

  1. Should we skip serializing preimage on the Bond struct?

The Bond struct in src/app/bond/model.rs derives Serialize and the preimage field is a plain Option. Phase 0 doesn't serialize bonds anywhere external, but I was wondering. Once Phases 2/3/5 wire this up to audit events, logs, or RPC responses, isn't there a risk that a Bond ends up serialized somewhere it shouldn't, leaking the preimage? My AI flagged that anyone with the preimage could claim the HTLC before Mostro does. Wouldn't it be safer to add #[serde(skip_serializing)] on preimage (and maybe payout_invoice) now, while the surface is tiny, rather than having to remember later?

  1. The migration schema seems to jump ahead of Phase 0

The SQL already includes parent_bond_id, child_order_id, slashed_share_sats (Phase 6) and payout_routing_fee_sats (Phase 3), but the spec doc in §5.1 shows a smaller table (range_parent_id instead of parent_bond_id plus child_order_id, without the other two). I get the appeal of one migration vs. multiple ALTER TABLEs, but shouldn't the spec doc be updated to match what actually shipped? Otherwise it's going to drift even more as the later phases land.

  1. find_bond_by_order_and_role plus LIMIT 1 feels risky for Phase 6

The query at src/app/bond/db.rs:33 assumes there's at most one row per (order_id, role), but in Phase 6 a maker parent bond would have multiple rows sharing order_id and role = 'maker' (one per child). There's no UNIQUE constraint either, so LIMIT 1 becomes non deterministic. Not a bug today since nothing calls it, but shouldn't we either filter by parent_bond_id IS NULL or return Vec until Phase 6 nails down the semantics?

Again, sorry if any of this is off base. The AI surfaced it and I didn't want to just sit on it. I'll give it a proper review myself tomorrow, but wanted to leave these here in the meantime

@grunch

grunch commented Apr 24, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Apr 24, 2026

Copy link
Copy Markdown
Contributor
✅ 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.

@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.

Actionable comments posted: 5

🧹 Nitpick comments (2)
migrations/20260423120000_anti_abuse_bond.sql (1)

16-67: Schema looks solid for Phase 0.

Table structure aligns with the documented phase roadmap: parent_bond_id / child_order_id / slashed_share_sats set up Phase 6 (range-order) proportional slashing without needing a future breaking migration, and retaining preimage + hash matches the "Mostro holds preimage" design choice. FK on order_id is correct; deliberately skipping a self-referential FK on parent_bond_id is reasonable and well-commented.

One optional thought for later phases, not blocking: find_bonds_by_state (per the enriched summary) filters by state and orders by created_at. At real traffic volumes a composite (state, created_at) index would let the scheduler's PendingPayout sweep avoid a filesort. Safe to defer until Phase 3 when that query actually runs.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@migrations/20260423120000_anti_abuse_bond.sql` around lines 16 - 67, Add a
composite index to support efficient ordered scans by state and creation time:
create an index on (state, created_at) named e.g. idx_bonds_state_created_at so
the scheduler query used by find_bonds_by_state / PendingPayout can filter by
state and order by created_at without a filesort; add this index now or add a
migration to introduce idx_bonds_state_created_at before Phase 3 when
PendingPayout sweeps are enabled.
src/config/types.rs (1)

44-47: Consider renaming amount_sats — it's a percentage, not sats.

The field is documented as a percentage (0.01 = 1%) and is multiplied by an order size to produce a sat amount, yet the name reads as if it were already denominated in satoshis (as base_amount_sats is). It forces every caller and the template comment to re-explain the unit. A name like amount_pct (or pct_of_order) would make the formula max(amount_pct * order_amount, base_amount_sats) self-evident and remove a class of config-entry mistakes where operators paste a sat value into this field.

Since the template is still commented-out and no external consumers exist yet, this is the cheapest time to rename. Non-blocking.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/config/types.rs` around lines 44 - 47, Rename the confusing percentage
field amount_sats to a percentage-named identifier (e.g., amount_pct or
pct_of_order) and update its serde default helper accordingly (rename
default_bond_amount_pct → default_amount_pct); update the doc comment to show
the new unit (0.01 = 1%) and change any usages of amount_sats and
default_bond_amount_pct in code to the new names so the bond formula reads
max(amount_pct * order_amount, base_amount_sats). Ensure struct field, default
function, and all references (parsing/serialization, tests, and docs) are
updated together to avoid breakage.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/ANTI_ABUSE_BOND.md`:
- Around line 148-151: Update the stale file path in the documentation: replace
the reference to src/db/bonds.rs with the actual module path src/app/bond/db.rs
in the ANTI_ABUSE_BOND.md bullet describing the Bond model and helpers; ensure
the bullet still lists the helper functions create_bond,
find_bond_by_order_and_role, find_bonds_by_state, and update_bond_state so
readers can locate those functions under src/app/bond/db.rs.
- Around line 445-448: The fenced code block containing "share_fraction  =
child_sats / parent_max_sats" and "slash_amount    = round(parent_bond_amount *
share_fraction)" is missing a language specifier; update that fenced block in
ANTI_ABUSE_BOND.md by adding a language tag (e.g., ```text) immediately after
the opening backticks so the block complies with markdownlint MD040 and the repo
guideline.
- Around line 122-145: The SQL snippet in ANTI_ABUSE_BOND.md is out of sync with
the migration: replace or update the inline CREATE TABLE so it exactly matches
the migration (use parent_bond_id instead of range_parent_id, add
child_order_id, slashed_share_sats, payout_routing_fee_sats, and the
idx_bonds_parent index, plus any column types/defaults present in
migrations/20260423120000_anti_abuse_bond.sql), or remove the inline SQL
entirely and replace it with a clear pointer to the canonical migration (and
then update the Phase-6 "extend Bond" section to use the same column
names/fields as the Phase-0 schema in the migration).

In `@src/app/bond/db.rs`:
- Around line 27-39: The query in find_bond_by_order_and_role returns an
arbitrary row when multiple child bonds exist; make it deterministic by
restricting to parent-level bonds: change the SQL in sqlx::query_as::<_,
Bond>("SELECT * FROM bonds WHERE order_id = ? AND role = ? LIMIT 1") to include
AND parent_bond_id IS NULL (i.e. "WHERE order_id = ? AND role = ? AND
parent_bond_id IS NULL LIMIT 1"), keep the same bind order (order_id, role_str)
and error handling; alternatively, if you intend to return the earliest bond
instead, add an ORDER BY created_at ASC before LIMIT 1 — pick one approach and
update find_bond_by_order_and_role accordingly.

In `@src/app/bond/math.rs`:
- Around line 109-111: The comment on the test using cfg_with(0.007, 0) is
incorrect about rounding mode: update the comment to state that Rust's
f64::round() rounds half away from zero (so 3.5 → 4) rather than "round half to
even"; locate the test that calls compute_bond_amount(500, &cfg) and replace the
misleading phrase "round half to even" with "rounds half away from zero" (or
similar wording mentioning f64::round()) to accurately reflect the behavior.

---

Nitpick comments:
In `@migrations/20260423120000_anti_abuse_bond.sql`:
- Around line 16-67: Add a composite index to support efficient ordered scans by
state and creation time: create an index on (state, created_at) named e.g.
idx_bonds_state_created_at so the scheduler query used by find_bonds_by_state /
PendingPayout can filter by state and order by created_at without a filesort;
add this index now or add a migration to introduce idx_bonds_state_created_at
before Phase 3 when PendingPayout sweeps are enabled.

In `@src/config/types.rs`:
- Around line 44-47: Rename the confusing percentage field amount_sats to a
percentage-named identifier (e.g., amount_pct or pct_of_order) and update its
serde default helper accordingly (rename default_bond_amount_pct →
default_amount_pct); update the doc comment to show the new unit (0.01 = 1%) and
change any usages of amount_sats and default_bond_amount_pct in code to the new
names so the bond formula reads max(amount_pct * order_amount,
base_amount_sats). Ensure struct field, default function, and all references
(parsing/serialization, tests, and docs) are updated together to avoid breakage.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f7517181-4912-47e8-bf30-3299ab6a910e

📥 Commits

Reviewing files that changed from the base of the PR and between 673340d and 12a8a4f.

📒 Files selected for processing (16)
  • docs/ANTI_ABUSE_BOND.md
  • migrations/20260423120000_anti_abuse_bond.sql
  • settings.tpl.toml
  • src/app.rs
  • src/app/bond/db.rs
  • src/app/bond/math.rs
  • src/app/bond/mod.rs
  • src/app/bond/model.rs
  • src/app/bond/types.rs
  • src/app/context.rs
  • src/app/dev_fee.rs
  • src/app/rate_user.rs
  • src/config/mod.rs
  • src/config/settings.rs
  • src/config/types.rs
  • src/config/wizard.rs

Comment thread docs/ANTI_ABUSE_BOND.md
Comment thread docs/ANTI_ABUSE_BOND.md Outdated
Comment thread docs/ANTI_ABUSE_BOND.md Outdated
Comment thread src/app/bond/db.rs
Comment thread src/app/bond/math.rs Outdated
grunch added 2 commits April 24, 2026 09:18
- Restrict find_bond_by_order_and_role to parent rows via
  parent_bond_id IS NULL so Phase 6 child slash rows cannot shadow the
  parent bond in state transitions.
- Mark Bond.preimage and Bond.payout_invoice with
  #[serde(skip_serializing)] to prevent accidental leakage through
  future audit events, nostr payloads, or RPC responses.
- Rename AntiAbuseBondSettings.amount_sats to amount_pct; the field is
  a unitless fraction and shared a name with Bond::amount_sats (i64
  sats). Updated TOML template, spec doc, math helper, and tests.
- Sync spec doc (§3, §5.1) with the shipped migration schema
  (parent_bond_id, child_order_id, slashed_share_sats,
  payout_routing_fee_sats, idx_bonds_parent) and the actual module
  path src/app/bond/db.rs.
- Fix misleading "round half to even" comment; f64::round rounds half
  away from zero.
- Add language tag to fenced block for markdownlint MD040.
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.

2 participants