Files
archy/.planning/phases/01-federation-mesh-hardening/01-19-SUMMARY.md
T

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>