150 lines
7.4 KiB
Markdown
150 lines
7.4 KiB
Markdown
---
|
|||
|
|
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 <invoice>` 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"
|
||
|
|
---
|
||
|
|
|
||
|
|
<objective>
|
||
|
|
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.
|
||
|
|
</objective>
|
||
|
|
|
||
|
|
<execution_context>
|
||
|
|
@$HOME/.claude/gsd-core/workflows/execute-plan.md
|
||
|
|
@$HOME/.claude/gsd-core/templates/summary.md
|
||
|
|
</execution_context>
|
||
|
|
|
||
|
|
<tasks>
|
||
|
|
|
||
|
|
<task type="auto">
|
||
|
|
<name>Task 1: Embed route hints in wallet-created invoices</name>
|
||
|
|
<reversibility rating="reversible">One field in one JSON body; revert is a single-line change.</reversibility>
|
||
|
|
<files>core/archipelago/src/api/rpc/lnd/wallet.rs</files>
|
||
|
|
<read_first>
|
||
|
|
- `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
|
||
|
|
</read_first>
|
||
|
|
<action>
|
||
|
|
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.
|
||
|
|
</action>
|
||
|
|
<verify>
|
||
|
|
<automated>cd core && cargo build --release 2>&1 | tail -5 && cargo test -p archipelago 2>&1 | tail -10</automated>
|
||
|
|
</verify>
|
||
|
|
<acceptance_criteria>
|
||
|
|
- `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
|
||
|
|
</acceptance_criteria>
|
||
|
|
<done>Wallet-created invoices ask LND for route hints, and every other invoice path has been audited.</done>
|
||
|
|
</task>
|
||
|
|
|
||
|
|
<task type="checkpoint:human-verify" gate="blocking">
|
||
|
|
<name>Task 2: Confirm the fix, then hand off to post-OTA verification</name>
|
||
|
|
<what-built>
|
||
|
|
Invoice creation now sets LND's `private` flag at both call sites, so
|
||
|
|
invoices embed a hop hint for the node's unannounced channel and become
|
||
|
|
routable from outside.
|
||
|
|
</what-built>
|
||
|
|
<constraint priority="highest">
|
||
|
|
**`archy-x250-mad2` is a USER'S device holding real funds. Never deploy to
|
||
|
|
it, SSH to it, or run any command against it.** This fix reaches it only via
|
||
|
|
the normal OTA release, after the release is confirmed. Any verification
|
||
|
|
involving that node is performed by its owner after the release lands —
|
||
|
|
never by us, and never as a direct deploy.
|
||
|
|
</constraint>
|
||
|
|
<how-to-verify>
|
||
|
|
Verifiable now, without touching any user device:
|
||
|
|
1. Tests assert the request body sent to LND's `/v1/invoices` carries
|
||
|
|
`"private": true` for BOTH `handle_lnd_createinvoice` (wallet Receive)
|
||
|
|
and `create_invoice` (paid-content/peer-files seller flow).
|
||
|
|
2. On a node under our control with only PUBLIC channels, creating and paying
|
||
|
|
an invoice still behaves exactly as before — no regression. Note the
|
||
|
|
limitation honestly: a public-channel node shows empty route hints even
|
||
|
|
when the fix is correct, so this checks non-regression only.
|
||
|
|
|
||
|
|
Post-OTA-release, performed by the device owner:
|
||
|
|
3. Create an invoice through the **wallet UI** (not raw `lncli`) — e.g. 100 sats.
|
||
|
|
4. `lncli decodepayreq <invoice>` → `route_hints` populated with the Olympus
|
||
|
|
channel's `chan_id` (was `[]` before this fix).
|
||
|
|
5. Pay it from a real external wallet; `lncli listinvoices` shows a non-empty
|
||
|
|
`htlcs` array and `state: SETTLED`.
|
||
|
|
</how-to-verify>
|
||
|
|
<resume-signal>Type "approved", or describe what you saw — which step, what happened instead.</resume-signal>
|
||
|
|
</task>
|
||
|
|
|
||
|
|
</tasks>
|
||
|
|
|
||
|
|
<verification>
|
||
|
|
- `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
|
||
|
|
</verification>
|
||
|
|
|
||
|
|
<success_criteria>
|
||
|
|
- 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
|
||
|
|
</success_criteria>
|
||
|
|
|
||
|
|
<output>
|
||
|
|
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.
|
||
|
|
</output>
|