From 1f3e56147fd9279fb1b71885e4f546022efb4af5 Mon Sep 17 00:00:00 2001 From: archipelago Date: Fri, 31 Jul 2026 07:56:04 -0400 Subject: [PATCH] =?UTF-8?q?docs(01):=20add=20FED-08=20=E2=80=94=20wallet?= =?UTF-8?q?=20invoices=20must=20embed=20route=20hints=20(private-channel?= =?UTF-8?q?=20receive)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Diagnosed on archy-x250-mad2: handle_lnd_createinvoice omits LND's private flag, so invoices carry route_hints: [] and are unroutable for any node whose channels are unannounced. Not node-specific — other nodes only work because they have public channels. Planned as 01-19 in wave 1 so it ships early. Co-Authored-By: Claude Opus 5 (1M context) --- .planning/REQUIREMENTS.md | 2 + .planning/ROADMAP.md | 3 +- .../01-19-PLAN.md | 135 ++++++++++++++++++ 3 files changed, 139 insertions(+), 1 deletion(-) create mode 100644 .planning/phases/01-federation-mesh-hardening/01-19-PLAN.md diff --git a/.planning/REQUIREMENTS.md b/.planning/REQUIREMENTS.md index bdbb0864..4d75cfa6 100644 --- a/.planning/REQUIREMENTS.md +++ b/.planning/REQUIREMENTS.md @@ -18,6 +18,7 @@ declared exit criteria (multinode pass + workstreams B/C/F), `.planning/codebase - [ ] **FED-04**: Mesh messaging parity — attachment send (and the rest of the mesh chat surface) behaves identically on the demo and on real nodes: the demo backend implements the same RPC surface the UI calls, transport decisions mirror the real size-based tier logic, and no demo-only modals exist - [ ] **FED-05**: Inter-node Lightning channel opening UX — the UI shows the node's shareable Lightning URI; lists trusted (federated) nodes by hostname for one-click channel opening; and lets the user browse/request channels with public nodes — using the existing design system and components, verified on the :8100 dev preview against archi-dev before deploy - [ ] **FED-06**: On-brand payment success animation — the invoice "paid" tick's circle uses the screensaver-style ring with outer EQ-segment lines (reuse `ScreensaverRing.vue`'s compact size) in place of the current success burst, applied consistently everywhere the paid tick shows +- [ ] **FED-08**: Lightning invoices created by the wallet embed route hints (LND `private` flag) so nodes whose channels are unannounced can actually receive payments — diagnosed on archy-x250-mad2 2026-07-31, where every wallet-UI invoice had `route_hints: []` and was unroutable; the bug is unconditional and affects any node without a public channel - [ ] **FED-07**: Fedimint gateway never installs with a pre-set password — gateway credentials are generated per-install via manifest-declared `generated_secrets` (or explicitly set by the user), never baked into the image/manifest; existing installs with the default password get a migration path (BLOCKER — default credentials are a security hole) ### UI Fixes (UIFIX) — user-reported blockers, added 2026-07-30 @@ -111,6 +112,7 @@ Which phases cover which requirements. Updated during roadmap creation. | FED-05 | Phase 1 | Pending | | FED-06 | Phase 1 | Pending | | FED-07 | Phase 1 | Pending | +| FED-08 | Phase 1 | Pending | | UIFIX-01 | Phase 1 | Pending | | UIFIX-02 | Phase 1 | Pending | | UIFIX-03 | Phase 1 | Pending | diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index 514e5479..6d769e44 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -35,7 +35,7 @@ signed/decentralized registry and a user installs it on their node. **Goal**: Federation and mesh are tight — a structured review of the fleet/federation and mesh code feeds fixes so node removal sticks, sync converges, and mesh messaging (including attachments) behaves identically everywhere it runs **Depends on**: Nothing (first phase) -**Requirements**: FED-01, FED-02, FED-03, FED-04, FED-05, FED-06, FED-07, UIFIX-01, UIFIX-02, UIFIX-03, UIFIX-04, UIFIX-05, UIFIX-06 +**Requirements**: FED-01, FED-02, FED-03, FED-04, FED-05, FED-06, FED-07, UIFIX-01, UIFIX-02, UIFIX-03, UIFIX-04, UIFIX-05, UIFIX-06, FED-08 **Success Criteria** (what must be TRUE): 1. A structured code review of the federation/fleet area (`core/archipelago/src/federation`, node sync, FIPS/transport dial layer) and the mesh area (`core/archipelago/src/mesh`, mesh RPC surface) produces a findings list, and every finding is fixed or explicitly deferred with a reason @@ -57,6 +57,7 @@ signed/decentralized registry and a user installs it on their node. Plans: +- [ ] 01-19-PLAN.md — URGENT wave 1: wallet invoices embed route hints so private-channel nodes can receive (FED-08) - [ ] 01-01-PLAN.md — Serialize the federation node store and make removal stick (FED-01) - [ ] 01-02-PLAN.md — Demo mesh/federation RPC parity + automated parity harness (FED-04) - [ ] 01-03-PLAN.md — On-brand paid tick: ScreensaverRing badge variant on both success surfaces (FED-06) diff --git a/.planning/phases/01-federation-mesh-hardening/01-19-PLAN.md b/.planning/phases/01-federation-mesh-hardening/01-19-PLAN.md new file mode 100644 index 00000000..6a93a7b8 --- /dev/null +++ b/.planning/phases/01-federation-mesh-hardening/01-19-PLAN.md @@ -0,0 +1,135 @@ +--- +phase: 01-federation-mesh-hardening +plan: 19 +type: execute +wave: 1 +depends_on: [] +files_modified: + - core/archipelago/src/api/rpc/lnd/wallet.rs +autonomous: false +requirements: [FED-08] + +must_haves: + truths: + - "An invoice created through the wallet UI's Receive flow embeds a route hint for every private/unannounced channel the node holds" + - "`lncli decodepayreq ` on an invoice created via the wallet UI shows a non-empty `route_hints` array containing the private channel's chan_id" + - "A real external wallet can pay an invoice created by the wallet UI on a node whose only channel is private — the HTLC arrives and the invoice reaches SETTLED" + - "Nodes with public channels are unaffected — payments still route directly over the public channel" + - "Every other invoice-creation call site in the codebase is audited for the same omission, and each is either fixed or documented as deliberately not needing route hints" + prohibitions: + - "MUST NOT change the amount, memo, expiry, or any other invoice field's existing behavior" + - "MUST NOT log, echo, or commit any macaroon, invoice preimage, or node credential" + artifacts: + - path: "core/archipelago/src/api/rpc/lnd/wallet.rs" + provides: "Invoice creation that sets LND's `private` flag so route hints are embedded" +--- + + +Fix Lightning receive on nodes whose channels are private/unannounced. + +`handle_lnd_createinvoice` posts to LND's REST `/v1/invoices` with only `value` +and `memo`. LND defaults `private` to `false`, so the returned invoice carries +`route_hints: []`. Private channels are not propagated through public gossip, so +a sender has no way to find a route — the invoice is unpayable by anyone. + +Diagnosed on `archy-x250-mad2` (2026-07-31), whose single channel to "Olympus by +ZEUS" is `private: true` with ~40.8k sats of usable inbound. Three wallet-UI +invoices (10,000 / 5,000 / 500 sats) all had empty route hints and never received +an HTLC. The one invoice that DID settle carries a memo ("Paid to Archipelago +(Order ID: ...)") that appears nowhere in this Rust source — it came from a +separate system (likely BTCPay) that builds invoices correctly. That is why +"some payments have worked" while the wallet's own Receive flow never has. + +**This is not node-specific.** The bug is unconditional; it only *manifests* +where a node lacks a public channel. Any user relying on a private channel has a +broken Receive flow today. + + + +@$HOME/.claude/gsd-core/workflows/execute-plan.md +@$HOME/.claude/gsd-core/templates/summary.md + + + + + + Task 1: Embed route hints in wallet-created invoices + One field in one JSON body; revert is a single-line change. + core/archipelago/src/api/rpc/lnd/wallet.rs + + - `core/archipelago/src/api/rpc/lnd/wallet.rs` around lines 554-557 — `handle_lnd_createinvoice`'s `invoice_body` construction + - `core/archipelago/src/api/rpc/lnd/channels.rs:271,368` — the only current uses of a `private` field (channel OPEN, not invoice creation); confirms the omission is specific to invoices + + + Add `"private": true` to `invoice_body` in `handle_lnd_createinvoice` so LND + embeds hop hints for unannounced channels: + + ```rust + let invoice_body = serde_json::json!({ + "value": amount_sats.to_string(), + "memo": memo, + "private": true, + }); + ``` + + Setting it unconditionally is correct and safe: a route hint is harmless when + the node also has public channels — LND still routes directly over a public + channel when it can, and the hint merely offers an alternate path. Add a + short comment stating why it is unconditional, so a future reader doesn't + "optimize" it back to conditional and silently reintroduce the bug. + + Then audit every OTHER invoice-creation call site for the same omission — + grep the tree for `/v1/invoices`, `addinvoice`, hold-invoice, LNURL and any + keysend-adjacent flow. Fix each that should carry route hints; for any that + deliberately should not, record the reason in the SUMMARY. Report the full + list either way. + + + cd core && cargo build --release 2>&1 | tail -5 && cargo test -p archipelago 2>&1 | tail -10 + + + - `grep -A6 'let invoice_body' core/archipelago/src/api/rpc/lnd/wallet.rs` shows `"private": true` + - `cargo build --release` succeeds + - Existing tests pass + - The SUMMARY lists every invoice-creation call site found, with fixed/not-needed and the reason + + Wallet-created invoices ask LND for route hints, and every other invoice path has been audited. + + + + Task 2: Confirm a real payment arrives on archy-x250-mad2 + + Invoice creation now sets LND's `private` flag, so invoices embed a hop hint + for the node's unannounced channel and become routable from outside. + + + 1. Deploy the patched `archipelago` binary to `archy-x250-mad2`. + 2. Create a test invoice through the **wallet UI** (not raw `lncli`) — e.g. 100 sats. + 3. `lncli decodepayreq ` → `route_hints` is populated with the + Olympus channel's `chan_id` (was `[]` before this fix). + 4. Pay it from a real external wallet. Expected: the payment succeeds. + 5. `lncli listinvoices` → that invoice shows a non-empty `htlcs` array and + `state: SETTLED`. + 6. Sanity-check a node WITH public channels (e.g. archi-dev-box): creating and + paying an invoice there still works exactly as before. + + Type "approved", or describe what you saw — which step, what happened instead. + + + + + +- `cargo build --release` and the existing test suite pass +- On archy-x250-mad2, a wallet-UI invoice decodes with populated `route_hints` and settles when paid externally +- A public-channel node is unaffected + + + +- Receiving Lightning payments works through the wallet UI on a node whose only channel is private +- No regression for nodes with public channels +- Any other invoice-creation path sharing this omission is fixed or explicitly cleared + + + +Create `.planning/phases/01-federation-mesh-hardening/01-19-SUMMARY.md`. It MUST record: the full list of invoice-creation call sites audited with each one's disposition, and the decoded `route_hints` before/after evidence from the node. +