104 lines
4.9 KiB
Markdown
104 lines
4.9 KiB
Markdown
---
|
|||
|
|
phase: 01-federation-mesh-hardening
|
||
|
|
plan: 19
|
||
|
|
subsystem: lightning
|
||
|
|
tags: [lnd, invoices, route-hints, private-channels, wallet]
|
||
|
|
|
||
|
|
requires:
|
||
|
|
- phase: 01-federation-mesh-hardening
|
||
|
|
provides: "The two existing LND invoice-creation call sites in core/archipelago/src/api/rpc/lnd/wallet.rs — the seller-side/peer-file flow and the wallet UI's Receive flow"
|
||
|
|
provides:
|
||
|
|
- "build_invoice_request_body(): one place where an invoice body is minted, with `private: true` unconditional so LND embeds route hints for unannounced channels"
|
||
|
|
affects: [wallet, lightning, paid-content]
|
||
|
|
|
||
|
|
tech-stack:
|
||
|
|
added: []
|
||
|
|
patterns:
|
||
|
|
- "Two call sites that must agree get one shared constructor plus one test on the constructor, rather than two near-identical literals and a hope. The duplicated json! literal is exactly how one site got fixed and the other didn't."
|
||
|
|
|
||
|
|
key-files:
|
||
|
|
created: []
|
||
|
|
modified:
|
||
|
|
- core/archipelago/src/api/rpc/lnd/wallet.rs
|
||
|
|
|
||
|
|
key-decisions:
|
||
|
|
- "`private: true` is unconditional rather than conditional on 'does this node have only private channels'. It is harmless when public channels exist — LND still routes directly over a public channel when it can, and the hint is an unused alternate path — so the conditional would add a failure mode (mis-detecting channel state) to buy nothing."
|
||
|
|
- "Both call sites route through one constructor so a single test pins the field for both, and neither can silently drift back to `false`."
|
||
|
|
|
||
|
|
requirements-completed: [FED-08]
|
||
|
|
|
||
|
|
coverage:
|
||
|
|
- id: D1
|
||
|
|
description: "An invoice minted by the wallet UI's Receive flow embeds route hints for private/unannounced channels"
|
||
|
|
requirement: "FED-08"
|
||
|
|
verification:
|
||
|
|
- kind: unit
|
||
|
|
ref: "core/archipelago/src/api/rpc/lnd/wallet.rs#invoice_request_body_always_sets_private_true"
|
||
|
|
status: pass
|
||
|
|
human_judgment: false
|
||
|
|
- id: D2
|
||
|
|
description: "The seller-side/peer-file invoice path embeds them too — the twin site does not drift"
|
||
|
|
requirement: "FED-08"
|
||
|
|
verification:
|
||
|
|
- kind: other
|
||
|
|
ref: "Both call sites (wallet.rs:330 and wallet.rs:554) call build_invoice_request_body; the json! literal exists in exactly one place"
|
||
|
|
status: pass
|
||
|
|
human_judgment: false
|
||
|
|
- id: D3
|
||
|
|
description: "A payment actually arrives over a private channel on an affected node"
|
||
|
|
requirement: "FED-08"
|
||
|
|
verification:
|
||
|
|
- kind: manual_procedural
|
||
|
|
ref: "Post-OTA checkpoint on the affected node (archy-x250-mad2) — NOT RUN; the plan forbids deploying directly to a user device, so this is verified after the release lands"
|
||
|
|
status: deferred
|
||
|
|
human_judgment: true
|
||
|
|
|
||
|
|
duration: 20min
|
||
|
|
completed: 2026-08-02
|
||
|
|
status: complete-pending-post-ota-checkpoint
|
||
|
|
---
|
||
|
|
|
||
|
|
# Phase 1 Plan 19: Invoices Embed Route Hints for Private Channels (FED-08) Summary
|
||
|
|
|
||
|
|
**A node whose inbound liquidity sits on unannounced channels could not be paid: its invoices carried no route hints, so a payer had no way to discover a path in. Both invoice-creation paths now mint their body through one constructor that sets `private: true` unconditionally.**
|
||
|
|
|
||
|
|
## Performance
|
||
|
|
|
||
|
|
- **Duration:** ~20 min (code committed 2026-07-31 as `e5c38866`; this SUMMARY 2026-08-02)
|
||
|
|
- **Tasks:** 1/2 (Task 2 is a post-OTA checkpoint on a user device — see below)
|
||
|
|
- **Files modified:** 1
|
||
|
|
|
||
|
|
## Accomplishments
|
||
|
|
|
||
|
|
- `build_invoice_request_body(amount_sats, memo)` is now the single place an LND `/v1/invoices` body is constructed, with `private: true` set unconditionally and a doc comment explaining why it must stay that way.
|
||
|
|
- Both call sites use it: the seller-side/peer-file flow (`create_invoice`) and the wallet UI's Receive flow (`handle_lnd_createinvoice`, the `lnd.createinvoice` RPC). The duplicated `json!` literal that let one site diverge from the other is gone.
|
||
|
|
- `invoice_request_body_always_sets_private_true` pins the field for both sites at once.
|
||
|
|
|
||
|
|
## Deviations from Plan
|
||
|
|
|
||
|
|
None.
|
||
|
|
|
||
|
|
**Process note:** as with 01-20, the code landed on 2026-07-31 (`e5c38866`) but no SUMMARY was written and the roadmap entry was never ticked, so the plan read as unstarted. This file closes that gap.
|
||
|
|
|
||
|
|
## Known Stubs
|
||
|
|
|
||
|
|
None.
|
||
|
|
|
||
|
|
## Threat Flags
|
||
|
|
|
||
|
|
None. `private: true` does not weaken anything: it adds a routing hint to an invoice the payer already holds. It does reveal the existence of an unannounced channel to whoever holds that specific invoice — which is inherent to being payable over that channel at all, and is the explicit intent of the requirement.
|
||
|
|
|
||
|
|
## Outstanding: post-OTA checkpoint (Task 2)
|
||
|
|
|
||
|
|
The affected node is **archy-x250-mad2, a user's device**. Commit `516c3bfa` records the standing
|
||
|
|
constraint: never deploy directly to a user device — verify post-OTA instead. So the remaining
|
||
|
|
verification is:
|
||
|
|
|
||
|
|
1. Ship the release
|
||
|
|
2. After it lands on the affected node, mint an invoice from the wallet's Receive flow
|
||
|
|
3. Confirm the BOLT11 decodes with a route hint (`r` field) for the private channel
|
||
|
|
4. Confirm a payment from outside actually settles over it
|
||
|
|
|
||
|
|
This is the one piece of FED-08 that cannot be proven from here.
|
||
|
|
</content>
|