From: Jeffrey Czyz Date: Wed, 12 Jun 2024 21:54:19 +0000 (-0500) Subject: Blinded payments to unannounced introduction node X-Git-Tag: v0.0.124-beta~74^2~6 X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=commitdiff_plain;h=3bf84204e3a4c6772d231fa3b85f90a79e5b4871;p=rust-lightning 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. --- 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() {