From 3bf84204e3a4c6772d231fa3b85f90a79e5b4871 Mon Sep 17 00:00:00 2001 From: Jeffrey Czyz Date: Wed, 12 Jun 2024 16:54:19 -0500 Subject: [PATCH] Blinded payments to unannounced introduction node When creating blinded paths for receiving onion payments, allow using the recipient's only peer as the introduction node when the recipient is unannounced. This allows for sending payments without going through an intermediary, which is useful for testing or when only connected to an LSP with an empty NetworkGraph. --- lightning/src/ln/offers_tests.rs | 24 +++++++++++++++++++++++- lightning/src/routing/router.rs | 16 +++++++++++++++- 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/lightning/src/ln/offers_tests.rs b/lightning/src/ln/offers_tests.rs index 9f4f07df8..1922ef71b 100644 --- a/lightning/src/ln/offers_tests.rs +++ b/lightning/src/ln/offers_tests.rs @@ -985,8 +985,18 @@ fn creates_offer_with_blinded_path_using_unannounced_introduction_node() { let onion_message = bob.onion_messenger.next_onion_message_for_peer(alice_id).unwrap(); alice.onion_messenger.handle_onion_message(&bob_id, &onion_message); - let (_, reply_path) = extract_invoice_request(alice, &onion_message); + let (invoice_request, reply_path) = extract_invoice_request(alice, &onion_message); + assert_ne!(invoice_request.payer_id(), bob_id); assert_eq!(reply_path.introduction_node, IntroductionNode::NodeId(alice_id)); + + let onion_message = alice.onion_messenger.next_onion_message_for_peer(bob_id).unwrap(); + + let invoice = extract_invoice(bob, &onion_message); + assert_ne!(invoice.signing_pubkey(), alice_id); + assert!(!invoice.payment_paths().is_empty()); + for (_, path) in invoice.payment_paths() { + assert_eq!(path.introduction_node, IntroductionNode::NodeId(bob_id)); + } } /// Checks that a refund can be created using an unannounced node as a blinded path's introduction @@ -1019,6 +1029,18 @@ fn creates_refund_with_blinded_path_using_unannounced_introduction_node() { assert_eq!(path.introduction_node, IntroductionNode::NodeId(alice_id)); } expect_recent_payment!(bob, RecentPaymentDetails::AwaitingInvoice, payment_id); + + let expected_invoice = alice.node.request_refund_payment(&refund).unwrap(); + + let onion_message = alice.onion_messenger.next_onion_message_for_peer(bob_id).unwrap(); + + let invoice = extract_invoice(bob, &onion_message); + assert_eq!(invoice, expected_invoice); + assert_ne!(invoice.signing_pubkey(), alice_id); + assert!(!invoice.payment_paths().is_empty()); + for (_, path) in invoice.payment_paths() { + assert_eq!(path.introduction_node, IntroductionNode::NodeId(bob_id)); + } } /// Fails creating or paying an offer when a blinded path cannot be created because no peers are diff --git a/lightning/src/routing/router.rs b/lightning/src/routing/router.rs index 9a88d9138..19931d5c5 100644 --- a/lightning/src/routing/router.rs +++ b/lightning/src/routing/router.rs @@ -100,7 +100,20 @@ impl> + Clone, L: Deref, ES: Deref, S: Deref, // recipient's node_id. const MIN_PEER_CHANNELS: usize = 3; + let has_one_peer = first_hops + .first() + .map(|details| details.counterparty.node_id) + .map(|node_id| first_hops + .iter() + .skip(1) + .all(|details| details.counterparty.node_id == node_id) + ) + .unwrap_or(false); + let network_graph = self.network_graph.deref().read_only(); + let is_recipient_announced = + network_graph.nodes().contains_key(&NodeId::from_pubkey(&recipient)); + let paths = first_hops.into_iter() .filter(|details| details.counterparty.features.supports_route_blinding()) .filter(|details| amount_msats <= details.inbound_capacity_msat) @@ -109,7 +122,8 @@ impl> + Clone, L: Deref, ES: Deref, S: Deref, .filter(|details| network_graph .node(&NodeId::from_pubkey(&details.counterparty.node_id)) .map(|node_info| node_info.channels.len() >= MIN_PEER_CHANNELS) - .unwrap_or(false) + // Allow payments directly with the only peer when unannounced. + .unwrap_or(!is_recipient_announced && has_one_peer) ) .filter_map(|details| { let short_channel_id = match details.get_inbound_payment_scid() { -- 2.39.5