Skip to content

feat(cashu): C0 — upgrade mostro-core to 0.14 and pin the Cashu wire form - #229

Merged
grunch merged 3 commits into
mainfrom
feat/cashu-c0-mostro-core-014
Jul 24, 2026
Merged

feat(cashu): C0 — upgrade mostro-core to 0.14 and pin the Cashu wire form#229
grunch merged 3 commits into
mainfrom
feat/cashu-c0-mostro-core-014

Conversation

@grunch

@grunch grunch commented Jul 24, 2026

Copy link
Copy Markdown
Member

Phase C0 of docs/cashu/README.md. First of the Cashu series.

Depends on: nothing. Blocks: C4, C5. Parallel with: C1, C2.

What this is

The spec's smallest, most boring phase on purpose: "No new features. Pure dependency PR — trivially reviewable, and it de-risks every later phase." Nothing Cashu-related is reachable at runtime after this merges.

mostro-core 0.13.1 → 0.14.1 compiles with zero source changes, on native and wasm32. The suite passes unmodified (118 tests, +8 new).

What 0.14 brings is the frozen protocol surface: Action::{AddCashuEscrow, CashuEscrowLocked, CashuPmSignature}, Payload::{CashuLockProof, CashuSignatures}, and the CantDoReason variants (InvalidCashuToken, CashuMintUnavailable, InvalidMintUrl, CashuEscrowNotLocked, CashuSignatureMissing).

Wire form: verified, not assumed

C0's other deliverable was to "verify and document the exact serde wire form" — §2's JSON was explicitly marked "casing to be confirmed". It is now confirmed, and pinned as tests in rust/src/mostro/cashu_wire.rs rather than left as prose, so an upstream rename fails our suite instead of a live trade:

Pinned Value
Action casing rename_all = "kebab-case"add-cashu-escrow, cashu-escrow-locked, cashu-pm-signature
Payload discriminator rename_all = "snake_case"cashu_lock_proof, cashu_signatures
CashuLockProof fields token, mint_url, buyer_pubkey, seller_pubkey, mostro_pubkey, fee_token
fee_token absent skip_serializing_if = "Option::is_none" — omitted, not null, so a no-fee node produces the pre-0.14 form byte-for-byte
MessageKind::verify AddCashuEscrow needs an order id and a CashuLockProof; CashuPmSignature rejects an empty signature vector

§2's example turned out to be accurate. The doc is updated to say so and to point at the tests.

Finding: the escrow request, resolved

Risk #1 asked how the "Mostro → seller: escrow request" reaches the client. It is not in mostro-core 0.14.1 — no payload variant, and the Cashu fields (cashu_mint_url, cashu_escrow_token, cashu_escrow_locked_at) sit on the daemon-internal Order, not on SmallOrder, which is what payloads carry.

I first concluded from that it was unpinned and C5's seller side was blocked. That was wrong — I had not yet read the daemon source the spec points at. It reuses existing types, which is exactly why nothing was added to mostro-core. Per daemon branch feat/cashu-ta2-take-flow (show_cashu_escrow_request, src/util.rs):

direction action payload
→ seller WaitingSellerToPay Payload::Order(SmallOrder)status = WaitingPayment, both trade pubkeys set, buyer_invoice = None
→ buyer WaitingSellerToPay none

mint_url, P_M and locktime are deliberately absent from the request: the client takes them from the 38385 info tags (C1) and the known Mostro pubkey.

So C5 is not blocked, and it classifies by payload shape exactly as §4.4 planned — Lightning sends the seller PayInvoice + PaymentRequest, Cashu sends WaitingSellerToPay + Order. Risk #1 is marked resolved in the doc, with one caveat recorded: that daemon branch has diverged from its main, so the shape should be re-confirmed when Track A merges.

The test survives under a truer name (escrow_request_rides_on_an_unmodified_small_order): SmallOrder staying free of Cashu fields is the assumption that makes the classification safe, and it still fails loudly if upstream changes shape.

Test plan

  • cargo build — native, clean
  • cargo check --target wasm32-unknown-unknown — clean
  • cargo test — 118 pass, 0 fail (110 pre-existing, unmodified, + 8 new wire tests)
  • cargo clippy -- -D warnings — clean
  • No rust/src/api/ change, so no frb-generate.sh run is required
  • Zero Dart-side change; Lightning behaviour byte-for-byte identical

Series plan

C0 ✅ · C1 mode detection · C2 wallet core (cdk 0.17.3) · C3 wallet UI · C4 escrow primitives · C5 Track A · C6–C8 Tracks B/C/D · C9 wasm · C10 polish

Waves 0–1 (C0–C4) are implementable today. C5+ is gated on the finding above.

Summary by CodeRabbit

  • Documentation

    • Clarified the exact Cashu escrow message wire format, including field naming, serialization, and confirmed examples.
    • Documented that Cashu escrow-request fields are not currently included in the relevant request payload.
  • Compatibility

    • Updated the Cashu integration to align with the latest mostro-core 0.14 release.
  • Validation

    • Added coverage for Cashu message serialization, required fields, signatures, and payload behavior.

…form

First phase of docs/cashu/README.md. Pure dependency PR, as specified: no
features, no behaviour change, nothing Cashu-related is reachable at runtime.

0.13.1 -> 0.14.1 compiles with zero source changes on native and wasm; the
suite passes unmodified. What 0.14 adds is the frozen Cashu protocol surface:
Action::{AddCashuEscrow, CashuEscrowLocked, CashuPmSignature},
Payload::{CashuLockProof, CashuSignatures} and five CantDoReason variants.

The phase's other deliverable was to verify the wire form rather than trust
§2's illustrative JSON. Done, and pinned as tests (rust/src/mostro/cashu_wire.rs)
so an upstream rename fails our suite instead of a live trade: Action is
kebab-case, Payload snake_case (so the discriminator is `cashu_lock_proof`),
CashuLockProof's field names are as documented, and fee_token is
skip_serializing_if=Option::is_none — a node charging no fee produces the
pre-0.14 form byte-for-byte. §2's example turned out to be accurate.

One finding changes the plan. The escrow request (Mostro -> seller after a
take) is not merely undocumented, it is absent from mostro-core 0.14.1: the
Cashu fields live on the daemon-internal Order and NOT on SmallOrder, which is
what payloads actually carry, and there is no payload variant for the request.
So C5's seller side cannot be implemented without inventing a wire format.
A test asserts this, so the day upstream adds the carrier the suite goes red
and points at the decision. Risk #1 updated from "not yet published" to
"confirmed absent", with the blast radius scoped: only C5's seller side.
@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@grunch, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 34 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 0b39c5c8-4f85-45b3-9af8-fcfa99469097

📥 Commits

Reviewing files that changed from the base of the PR and between a14732b and 40365fa.

📒 Files selected for processing (3)
  • docs/cashu/README.md
  • rust/Cargo.toml
  • rust/src/mostro/cashu_wire.rs

Walkthrough

The PR upgrades mostro-core, adds test-only Cashu wire-contract validation, and updates Cashu documentation with confirmed serialization details and the currently absent escrow-request payload shape.

Changes

Cashu wire contracts

Layer / File(s) Summary
Wire contract tests
rust/Cargo.toml, rust/src/mostro/cashu_wire.rs, rust/src/mostro/mod.rs
The mostro-core version is upgraded, and test-only coverage verifies Cashu serialization, message validation, signature requirements, and SmallOrder escrow-request fields.
Wire contract documentation
docs/cashu/README.md
Documentation records confirmed casing and fee_token serialization behavior and states that the escrow-request payload is absent upstream.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Poem

A rabbit checks each token’s trail,
With kebab names set clean and pale.
Snake_case hops through payload streams,
While missing fields remain in dreams.
The docs now match the tests’ bright tale.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the dependency upgrade and the new Cashu wire-format pinning tests/doc updates.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/cashu-c0-mostro-core-014

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.

…ter all

My first pass concluded the escrow request had no wire form and that C5's
seller side was blocked. That was wrong, and the correction matters because
it unblocks a phase.

It is pinned — in the daemon branch feat/cashu-ta2-take-flow, not in
mostro-core, because it needs no new types: show_cashu_escrow_request
(src/util.rs) sends the seller Action::WaitingSellerToPay carrying
Payload::Order(SmallOrder) with status WaitingPayment, both trade pubkeys and
buyer_invoice = None, and sends the buyer the same action with no payload.
mint_url, P_M and locktime are absent from the request by design: the client
reads them from the 38385 info tags (C1) and the known Mostro pubkey.

Absence from mostro-core was the observation; "therefore unpinned" was the
faulty inference — I had not read the daemon source the spec pointed at.

So C5 classifies by payload shape exactly as §4.4 planned: Lightning sends the
seller PayInvoice + PaymentRequest, Cashu sends WaitingSellerToPay + Order.
The test survives with a truer name — SmallOrder carrying no Cashu fields is
now the pinned *assumption* behind that classification rather than evidence of
a gap, and it still fails loudly if upstream changes shape.

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

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

Inline comments:
In `@rust/Cargo.toml`:
- Line 19: Update the mostro-core dependency declaration in Cargo.toml from
version 0.14 to 0.14.1, preserving the existing dependency configuration while
enforcing the tested minimum version.

In `@rust/src/mostro/cashu_wire.rs`:
- Around line 228-265: Update the test
escrow_request_shape_is_still_unpinned_upstream to state and detect only Cashu
fields added to SmallOrder; remove any claim that it detects a new Payload
variant. In docs/cashu/README.md at line 572, require an explicit Payload API
review when upgrading dependencies instead of claiming the test suite detects
new request variants.
- Around line 125-157: Update rust/src/mostro/cashu_wire.rs lines 125-157 in
add_cashu_escrow_message_matches_the_documented_example to serialize an Order
containing the MessageKind, trade index, and version, then assert the complete
order envelope including order.version; update docs/cashu/README.md lines 96-102
only if needed to keep its documented field-for-field example aligned with the
test.
🪄 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 Plus

Run ID: 70033c53-47e7-4ae7-89c5-6e750a831e67

📥 Commits

Reviewing files that changed from the base of the PR and between 71061fb and a14732b.

⛔ Files ignored due to path filters (1)
  • rust/Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (4)
  • docs/cashu/README.md
  • rust/Cargo.toml
  • rust/src/mostro/cashu_wire.rs
  • rust/src/mostro/mod.rs

Comment thread rust/Cargo.toml Outdated
Comment thread rust/src/mostro/cashu_wire.rs
Comment thread rust/src/mostro/cashu_wire.rs Outdated
The wire-form test claimed to match docs/cashu/README.md §2 "field for
field" but serialized a bare MessageKind, so it pinned neither the `order`
envelope Message adds nor the `version` stamp MessageKind::new applies —
both of which the daemon parses. Asserting them surfaced a stale example:
PROTOCOL_VER is 2 since the transport-v2 migration, not the 1 the doc
carried over from protocol v1.

- Cargo.toml: 0.14 → 0.14.1, the version the wire form was confirmed
  against and the one docs/cashu/README.md §2 names. The releases differ
  only in transport.rs (protocol-v1 deprecation), so Cargo.lock is
  unchanged; this aligns the declaration with the tested minimum.
- cashu_wire.rs: serialize Message::Order(..) and assert the whole
  envelope, version included.
- README §2: "version": 1 → 2.

Verified: cargo test --lib (118 passed), cargo clippy -- -D warnings clean.
@grunch
grunch merged commit 4fc9453 into main Jul 24, 2026
3 of 4 checks passed
@grunch
grunch deleted the feat/cashu-c0-mostro-core-014 branch July 24, 2026 21:04
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