fix(01-19): embed route hints in invoice creation for private channels
Some checks failed
Demo images / Build & push demo images (push) Has been cancelled
Some checks failed
Demo images / Build & push demo images (push) Has been cancelled
handle_lnd_createinvoice posted to LND's /v1/invoices with only value/memo, so LND defaulted private to false and returned invoices with empty route_hints. Any node whose only channels are private/unannounced was unpayable through the wallet UI's Receive flow. Diagnosed on archy-x250-mad2, whose only channel (to Olympus by ZEUS) is private with ~40.8k sats usable inbound; three wallet-UI invoices never received an HTLC. Audited every other invoice-creation call site and found a second one with the identical omission: create_invoice, the seller-side/peer-file paid- content flow (content.rs -> handler for paid downloads). Same bug, same fix, wider blast radius than the one-node report suggested -- paid-file sales were unreceivable on private-channel nodes too. Extracted both call sites' invoice_body construction into one shared build_invoice_request_body() that sets private: true unconditionally, and added a unit test pinning that field so neither site can silently drift back to false. private:true is harmless on nodes with public channels -- LND still prefers a direct public route and the hint is just an unused alternate path.
This commit is contained in:
parent
050a87d2dd
commit
e5c38866ca
@ -327,10 +327,10 @@ impl RpcHandler {
|
||||
}
|
||||
|
||||
let (client, macaroon_hex) = self.lnd_client().await?;
|
||||
let invoice_body = serde_json::json!({
|
||||
"value": amount_sats.to_string(),
|
||||
"memo": memo,
|
||||
});
|
||||
// See `build_invoice_request_body` for why `private: true` is
|
||||
// unconditional — this is the seller-side/peer-file invoice path,
|
||||
// the twin of the bug fixed in `handle_lnd_createinvoice` below.
|
||||
let invoice_body = build_invoice_request_body(amount_sats, memo);
|
||||
// LND's REST endpoint can briefly drop/reset connections under load
|
||||
// (swap pressure, just-restarted, TLS handshake races), which used to
|
||||
// hard-fail the buy-file invoice with an opaque 503. Retry on a
|
||||
@ -551,10 +551,10 @@ impl RpcHandler {
|
||||
|
||||
let (client, macaroon_hex) = self.lnd_client().await?;
|
||||
|
||||
let invoice_body = serde_json::json!({
|
||||
"value": amount_sats.to_string(),
|
||||
"memo": memo,
|
||||
});
|
||||
// See `build_invoice_request_body` for why `private: true` is
|
||||
// unconditional. This is the wallet UI's Receive flow / the
|
||||
// `lnd.createinvoice` RPC — the bug diagnosed on archy-x250-mad2.
|
||||
let invoice_body = build_invoice_request_body(amount_sats, memo);
|
||||
|
||||
let resp = match client
|
||||
.post(format!("{LND_REST_BASE_URL}/v1/invoices"))
|
||||
@ -1105,10 +1105,39 @@ fn classify_lnd_address_error(message: &str) -> &'static str {
|
||||
}
|
||||
}
|
||||
|
||||
/// Build the LND `/v1/invoices` POST body used to mint a Lightning invoice.
|
||||
///
|
||||
/// `private: true` is unconditional and must stay that way: it tells LND to
|
||||
/// embed a route hint for every private/unannounced channel this node
|
||||
/// holds, which is the only way a payment can find its way in over such a
|
||||
/// channel. It is harmless when the node also has public channels — LND
|
||||
/// still routes directly over a public channel when it can, and the hint is
|
||||
/// simply an unused alternate path.
|
||||
///
|
||||
/// Shared by both invoice-creation call sites (`create_invoice`, the
|
||||
/// seller-side/peer-file flow, and `handle_lnd_createinvoice`, the wallet
|
||||
/// UI's Receive flow / `lnd.createinvoice` RPC) so a single test pins the
|
||||
/// field for both and neither can silently drift back to `false`.
|
||||
fn build_invoice_request_body(amount_sats: i64, memo: &str) -> serde_json::Value {
|
||||
serde_json::json!({
|
||||
"value": amount_sats.to_string(),
|
||||
"memo": memo,
|
||||
"private": true,
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn invoice_request_body_always_sets_private_true() {
|
||||
let body = build_invoice_request_body(1_234, "test memo");
|
||||
assert_eq!(body["private"], serde_json::json!(true));
|
||||
assert_eq!(body["value"], serde_json::json!("1234"));
|
||||
assert_eq!(body["memo"], serde_json::json!("test memo"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lnd_error_message_prefers_message_field() {
|
||||
let body = serde_json::json!({
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user