Skip to content

feat(cashu): restore/monitor in-flight locked escrows — Track A TA-3#831

Open
grunch wants to merge 1 commit into
feat/cashu-ta2-take-flowfrom
feat/cashu-ta3-restore-monitor
Open

feat(cashu): restore/monitor in-flight locked escrows — Track A TA-3#831
grunch wants to merge 1 commit into
feat/cashu-ta2-take-flowfrom
feat/cashu-ta3-restore-monitor

Conversation

@grunch

@grunch grunch commented Jul 20, 2026

Copy link
Copy Markdown
Member

Stacked on #830 (TA-2) → #829 (TA-1) → #828 (CF-5). Review/merge the stack in order; the base retargets as each lands. The diff shown is TA-3 only.

What & why

On boot in Cashu mode, re-hydrate in-flight locked escrows from the DB — the Cashu analogue of the Lightning path's find_held_invoices resubscribe — so a restart never loses sight of an escrow that is locked but not yet released (docs/cashu/02-track-a-lock.md §6/§10). This is the optional, recommended monitor PR of Track A.

Changes

  • CashuClient::check_token_unspent: whether every proof of a stored escrow token is still unspent at the mint (NUT-07), fail-closed on a short state reply. Reuses the Y-derivation the lock path already uses; it does not re-run the full verify_escrow_token acceptance check (the token is immutable and was validated at lock — this only answers "is it still live?").
  • cashu_restore::restore_cashu_escrows: list find_locked_cashu_orders, and for each re-check the token, logging a warning for any escrow that is spent/pending (redeemed or double-spent while the daemon was down). Best-effort, log-only — it never mutates order state (that belongs to the release/cancel/dispute tracks) and never blocks boot on a mint hiccup.
  • Wired into the main.rs Cashu boot branch after the mint connects, before the scheduler starts.

Tests

  • Restore over an empty pool finds nothing and never contacts the mint (offline).
  • The mint-backed spent/live detection is exercised by the CF-3 integration harness.

Checklist

  • cargo fmt --check
  • cargo clippy --all-targets --all-features -- -D warnings
  • cargo test — 1028 passed
  • Off-by-default: no behaviour change when Cashu is disabled

Depends on #830 (TA-2), #829 (TA-1), #828 (CF-5). Refs: docs/cashu/02-track-a-lock.md (TA-3).


🧪 Manual testing — step by step

Prereqs: the Cashu-mode mostrod setup from CF-5 (#828) — mint up (docker compose -f docker-compose.cashu.yml up -d), [cashu] enabled = true. This branch is stacked on TA-2 (contains CF-5 + TA-1 + TA-2 + TA-3). Run with RUST_LOG=mostro=info.

1. Clean start — nothing locked

  1. With an empty (or no-locked-escrow) DB, start mostrod in Cashu mode.
  2. Expected, during boot (after the mint connects, before the scheduler starts):
    cashu restore: no in-flight locked escrows
    
    ✔️

2. Re-hydrate a locked escrow across a restart

  1. Get an order into the locked state — either run the end-to-end lock on the full stack (take + AddCashuEscrow), or seed a row directly:
    UPDATE orders
    SET cashu_mint_url = 'http://127.0.0.1:3338',
        cashu_escrow_token = '<a real token minted on the test mint>',
        cashu_escrow_locked_at = strftime('%s','now'),
        status = 'active'
    WHERE id = '<order-id>';
  2. Restart mostrod. Expected at boot:
    cashu restore: re-hydrating 1 in-flight locked escrow(s)
    cashu restore: order <id> escrow is live (status active)
    
    ✔️
  3. Now spend/redeem that token out-of-band at the mint, then restart again. Expected — the monitor flags it:
    cashu restore: order <id> escrow token is spent/pending at the mint (status active) — needs attention
    
    The restore never mutates order state — it only logs. ✔️

3. Unit test

cargo test --bin mostrod cashu_restore

Expected: the empty-pool restore path is a clean no-op that never contacts the mint. ✔️

@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

🗂️ Base branches to auto review (1)
  • develop

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 80697009-1704-44b0-a290-f17f5a733f3e

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/cashu-ta3-restore-monitor

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.

@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: d39115ca60

ℹ️ 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/cashu_restore.rs
continue;
};

match cashu_client.check_token_unspent(token).await {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Verify the stored mint before checking state

When an operator changes [cashu].mint_url while there are locked rows from the previous mint, this calls NUT-07 on the newly configured client without comparing the stored order.cashu_mint_url (or the token's mint URL) to cashu_client.mint_url(). Since checkstate only receives Y values and the existing verifier notes that unknown secrets can be reported Unspent, the restore log can mark an escrow from another mint as live even though this daemon is no longer connected to the mint that issued/redeems it; guard the mint URL before treating Ok(true) as live.

Useful? React with 👍 / 👎.

Comment thread src/main.rs

// Re-hydrate in-flight locked escrows after a restart (TA-3), the
// Cashu analogue of the Lightning `find_held_invoices` resubscribe.
cashu_restore::restore_cashu_escrows(get_db_pool().as_ref(), &cashu_client).await;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Don't block boot on best-effort restore checks

This call is awaited before the Cashu branch builds the AppContext, starts the scheduler, or enters run_cashu; inside restore_cashu_escrows each order is checked serially and check_state can wait up to MINT_REQUEST_TIMEOUT (10s). With a slow mint and many locked escrows, a best-effort monitor can therefore stall all event processing for N×10s on every restart, so run the checks in the background or cap the whole restore pass instead of blocking boot.

Useful? React with 👍 / 👎.

Comment thread src/cashu_restore.rs

/// Re-hydrate and re-check every in-flight locked Cashu escrow at boot.
pub async fn restore_cashu_escrows(pool: &SqlitePool, cashu_client: &CashuClient) {
let locked = match find_locked_cashu_orders(pool).await {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Filter restore to in-flight Cashu statuses

find_locked_cashu_orders is explicitly designed and tested to return terminal locked rows because cashu_escrow_locked_at is never cleared; by feeding every returned row into the spent/pending warning, a normally released escrow whose proofs are spent, or a canceled terminal order, will be logged as needing attention on every later boot. The restore path is described as monitoring in-flight escrows, so filter out terminal statuses here (or ensure close paths clear the lock) before calling the mint.

Useful? React with 👍 / 👎.

On boot in Cashu mode, re-hydrate in-flight locked escrows from the DB (the
Cashu analogue of the Lightning `find_held_invoices` resubscribe) so a restart
never loses sight of a locked-but-not-released escrow
(docs/cashu/02-track-a-lock.md §6/§10).

- `CashuClient::check_token_unspent`: whether every proof of a stored escrow
  token is still unspent at the mint (NUT-07), fail-closed on a short state
  reply. Reuses the Y-derivation the lock path already uses; does not re-run
  the full acceptance check (the token is immutable and was validated at lock).
- `cashu_restore::restore_cashu_escrows`: list `find_locked_cashu_orders`, and
  for each re-check the token against the mint, logging a warning for any
  escrow that is spent/pending (redeemed or double-spent while the daemon was
  down). Best-effort, log-only — it never mutates order state (that belongs to
  the release/cancel/dispute tracks) and never blocks boot.
- Wired into the `main.rs` Cashu boot branch after the mint connects, before
  the scheduler starts.

Test: restore over an empty pool finds nothing and never contacts the mint.
The mint-backed spent/live detection is exercised by the CF-3 integration
harness.

Depends on CF-4 + CF-5. Base: feat/cashu-ta2-take-flow
Refs: docs/cashu/02-track-a-lock.md (TA-3)
@grunch
grunch force-pushed the feat/cashu-ta2-take-flow branch from 6652b70 to a16c745 Compare July 21, 2026 01:50
@grunch
grunch force-pushed the feat/cashu-ta3-restore-monitor branch from d39115c to 456c3f4 Compare July 21, 2026 01:50
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