feat(cashu): F3 — EscrowBackend trait + Lightning impl + Cashu stub#760
Conversation
Introduce the `EscrowBackend` seam between the order-action handlers and the escrow mechanism (Lightning today, Cashu opt-in later), per docs/CASHU_ESCROW_ARCHITECTURE.md. Thin primitive-wrapper design: the trait keeps today's hold-invoice primitives, the handlers receive `&mut dyn EscrowBackend`, and the dispatcher coerces its concrete `LndConnector` at the call site (no AppContext wiring yet). - src/escrow.rs: `EscrowBackend` trait (create/settle/cancel hold invoice), `impl EscrowBackend for LndConnector` (behaviour-preserving pass-through), and a `CashuBackend` stub (`unimplemented!()`) for the Cashu feature tracks. - Route the release/settle path through `&mut dyn EscrowBackend` (`settle_seller_hold_invoice` in util.rs, `release_action` in release.rs). - Register `mod escrow` in main.rs. Scope notes: `take_*`/`add_invoice` build their own connector inside `show_hold_invoice` (lock seam is Track A); `admin_*` stay on concrete `LndConnector` because they feed the LN-only bond subsystem (Track D); `cancel.rs` keeps its existing `CancelLightning` trait. Behaviour-preserving: cargo fmt clean, clippy clean, 373 tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. 🗂️ Base branches to auto review (1)
Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
WalkthroughThis PR introduces an ChangesEscrow Backend Abstraction
Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 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 |
|
@codex review |
|
Codex Review: Didn't find any major issues. Breezy! ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
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.
|
There was a problem hiding this comment.
🧹 Nitpick comments (1)
src/escrow.rs (1)
39-43: 🏗️ Heavy liftConsider abstracting the Lightning-specific return type in future iterations.
The
create_hold_invoicemethod returnsAddHoldInvoiceRespfromfedimint-tonic-lnd, which is Lightning-specific. This means any alternative backend (like Cashu) would need to produce Lightning invoice responses, creating a leaky abstraction. For milestone F3 with stubbed Cashu methods this is acceptable, but Track A (Cashu lock) will likely need to revisit this design—consider introducing a backend-agnostic envelope type or splitting the trait into backend-specific variants.🤖 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/escrow.rs` around lines 39 - 43, The trait method create_hold_invoice currently returns the Lightning-specific type AddHoldInvoiceResp, which leaks LND into the trait; refactor by introducing a backend-agnostic envelope/result type (e.g., HoldInvoiceResult or CreateHoldInvoiceResponse) used as the return for create_hold_invoice, or split the trait into backend-specific variants (e.g., LightningEscrow with create_hold_invoice returning AddHoldInvoiceResp and CashuEscrow with a Cashu-specific response); update signatures that reference create_hold_invoice to use the new abstract type and implement adapters that convert AddHoldInvoiceResp into the envelope so non-Lightning backends can implement the trait without depending on fedimint-tonic-lnd.
🤖 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/escrow.rs`:
- Around line 39-43: The trait method create_hold_invoice currently returns the
Lightning-specific type AddHoldInvoiceResp, which leaks LND into the trait;
refactor by introducing a backend-agnostic envelope/result type (e.g.,
HoldInvoiceResult or CreateHoldInvoiceResponse) used as the return for
create_hold_invoice, or split the trait into backend-specific variants (e.g.,
LightningEscrow with create_hold_invoice returning AddHoldInvoiceResp and
CashuEscrow with a Cashu-specific response); update signatures that reference
create_hold_invoice to use the new abstract type and implement adapters that
convert AddHoldInvoiceResp into the envelope so non-Lightning backends can
implement the trait without depending on fedimint-tonic-lnd.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 322c62c4-8c18-49e5-bcb4-9c232876c0cf
📒 Files selected for processing (4)
src/app/release.rssrc/escrow.rssrc/main.rssrc/util.rs
There was a problem hiding this comment.
Code Review Summary
Verdict: Approved
Strict pass on the current F3 scope. The diff is small and behavior-preserving: release_action and settle_seller_hold_invoice now accept the escrow trait, the LndConnector implementation delegates to the existing inherent methods, and no active runtime path instantiates the Cashu stub.
Looks Good
- The PR keeps Lightning behavior unchanged for the touched release/admin-settle path.
- The trait implementation explicitly delegates via
LndConnector::..., avoiding accidental recursion through the trait method names. settle_hold_invoice/cancel_hold_invoicediscard the LND response only after?, preserving error propagation.cargo fmt --all -- --checkpasses locally.
Non-blocking notes
- The current
EscrowBackendis still Lightning-shaped (AddHoldInvoiceResp, preimage/hash, hold-invoice method names). That is acceptable for the declared thin-primitive F3 scope, but the next Cashu tracks should not treat this as the final semantic backend API. - The
CashuBackendunimplemented!()methods are fine only because nothing wires it into runtime yet. The config-mode PR must fail closed until real implementations replace these stubs.
I could not run the full cargo test / cargo clippy suite in this runner because the filesystem is still effectively full, but source inspection plus cargo fmt --check found no blocking issue.
Address review: the EscrowBackend::create_hold_invoice trait method returned LND's AddHoldInvoiceResp, leaking fedimint-tonic-lnd into the escrow contract. Introduce a small `HoldInvoice` envelope and convert inside the Lightning impl so a non-Lightning backend can implement the trait without that dependency. Contained to src/escrow.rs; the method has no trait callers, so nothing else changes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
I reviewed the current head commit 723e4b83849ffd585958cc51d73d991aed349cfe.
The EscrowBackend trait is the right direction, but this PR still lands the abstraction only partially applied.
Right now:
release_actionandsettle_seller_hold_invoicewere moved to&mut dyn EscrowBackend, which is good,- but other escrow-opening / escrow-driving paths still instantiate
LndConnectordirectly (src/app/bond/flow.rs, helper paths insrc/util.rs, scheduler/runtime paths, etc.).
That leaves the seam inconsistent: some code goes through the backend abstraction, other escrow-relevant code still hardcodes Lightning directly. For a foundation PR, that is exactly the kind of half-switched state that makes later Cashu work harder to review and easier to miss in follow-up patches.
Requested changes
Please either:
- finish threading the escrow abstraction through the remaining escrow-related paths that still construct / call
LndConnectordirectly, or - narrow the stated scope so this PR only introduces the trait and updates the specific call sites that are explicitly in scope, without also carrying the broader Cashu schema preparation.
As written, the abstraction boundary is not yet consistently enforced, so I don't think it is ready to merge as the foundation seam.
|
Thanks for the review. I want to clarify the intended scope, because two of the three flagged paths are deliberate, documented carve-outs rather than half-switched state.
The bond is a separate primitive from trade escrow (LN-only, not subject to the Cashu 2-of-3 multisig design), and invoice subscription / payout are Lightning transport concerns, not escrow operations. Threading them through You are right about one path, though: I am keeping this PR scoped as the behavior-preserving F3 foundation (trait +
So: keep the foundation PR scoped to the trait plus the genuinely-escrow call sites, leave the documented LN-only carve-outs as-is, and close the one real gap in its own follow-up. |
job_cancel_orders returns locked trade funds to the seller via LndConnector::cancel_hold_invoice directly, bypassing the EscrowBackend seam (Lightning-only). Capture routing it through cooperative_cancel as an Integration & Hardening follow-up so Cashu timeout refunds are not missed. Distinct from Track C, which owns only cancel.rs's user path. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
F3 ·
EscrowBackendtrait + Lightning impl + Cashu stubFoundation milestone F3 from
docs/CASHU_ESCROW_ARCHITECTURE.md. Adds the escrow-backend seam so the order-action handlers stop calling LND directly, and stubs the Cashu backend for the parallel feature tracks. Opt-in feature is still off — Lightning behaviour is unchanged.Design (as agreed)
EscrowBackendkeeps today's hold-invoice primitives (create_hold_invoice,settle_hold_invoice,cancel_hold_invoice) rather than semanticlock/release/...names.&mut dyn EscrowBackend; the dispatcher keeps its concreteLndConnectorand relies on unsized coercion at the call site. AppContext-carried backend is deferred.Changes
src/escrow.rs(new):EscrowBackendtrait;impl EscrowBackend for LndConnector(behaviour-preserving pass-through to the inherent methods);CashuBackendstub withunimplemented!()bodies for the lock/release/cancel tracks.src/util.rs:settle_seller_hold_invoicenow takes&mut dyn EscrowBackend.src/app/release.rs:release_actionnow takes&mut dyn EscrowBackend(keepsLndConnectorfor the buyer-payoutsend_payment, which is not an escrow primitive).src/main.rs: registermod escrow.Scope notes / deliberate deviations
take_buy/take_sell/add_invoicedon't thread a connector — they build their own insideshow_hold_invoice, so the lock seam belongs to Track A.admin_settle/admin_cancelstay on concreteLndConnectorbecause they feed the LN-only bond subsystem (bond::apply_bond_resolution), which is mutually exclusive with Cashu (design decision Remove 11th parameter #5). Dispute routing is Track D.cancel.rsretains its existingCancelLightningtrait; folding it (and bond'sSettleLightning) intoEscrowBackendis left as future cleanup.Verification
cargo fmt --all— cleancargo clippy --bin mostrod --all-targets— cleancargo test --bin mostrod— 373 passed, 0 failed (behaviour-preserving)🤖 Generated with Claude Code
Summary by CodeRabbit