feat(bond): anti-abuse bond phase 0 foundation#712
Conversation
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>
|
Warning Rate limit exceeded
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 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 configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (6)
WalkthroughThis 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
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related issues
Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
💡 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".
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
AndreaDiazCorreia
left a comment
There was a problem hiding this comment.
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.
- 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?
- 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.
- 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
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
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_satsset up Phase 6 (range-order) proportional slashing without needing a future breaking migration, and retainingpreimage+hashmatches the "Mostro holds preimage" design choice. FK onorder_idis correct; deliberately skipping a self-referential FK onparent_bond_idis reasonable and well-commented.One optional thought for later phases, not blocking:
find_bonds_by_state(per the enriched summary) filters bystateand orders bycreated_at. At real traffic volumes a composite(state, created_at)index would let the scheduler'sPendingPayoutsweep 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 renamingamount_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 (asbase_amount_satsis). It forces every caller and the template comment to re-explain the unit. A name likeamount_pct(orpct_of_order) would make the formulamax(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
📒 Files selected for processing (16)
docs/ANTI_ABUSE_BOND.mdmigrations/20260423120000_anti_abuse_bond.sqlsettings.tpl.tomlsrc/app.rssrc/app/bond/db.rssrc/app/bond/math.rssrc/app/bond/mod.rssrc/app/bond/model.rssrc/app/bond/types.rssrc/app/context.rssrc/app/dev_fee.rssrc/app/rate_user.rssrc/config/mod.rssrc/config/settings.rssrc/config/types.rssrc/config/wizard.rs
- 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.
Adds the inert data and configuration layer for the anti-abuse bond feature described in docs/ANTI_ABUSE_BOND.md (issue #711):
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
Documentation