From afabf7bc4412ce645c9010652438913d897f85d1 Mon Sep 17 00:00:00 2001 From: shaavan Date: Mon, 10 Jun 2024 19:15:19 +0530 Subject: [PATCH] Expand `create_blinded_path` Functionality for Enhanced Path Diversification - Previously, the `create_blinded_path` function was limited to returning a single `BlindedPath`, which restricted the usage of `blinded_paths`. - This commit extends the `create_blinded_path` function to return the entire blinded path vector generated by the `MessageRouter`'s `create_blinded_paths`. - The updated functionality is integrated across the codebase, enabling the sending of Offers Response messages, such as `InvoiceRequest` (in `pay_for_offer`) and `Invoice` (in `request_refund_payment`), utilizing multiple reply paths. --- lightning/src/ln/channelmanager.rs | 105 +++++++++++++++++------------ 1 file changed, 63 insertions(+), 42 deletions(-) diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 89f967c4f..955a15561 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -8621,8 +8621,10 @@ macro_rules! create_offer_builder { ($self: ident, $builder: ty) => { let entropy = &*$self.entropy_source; let secp_ctx = &$self.secp_ctx; - let path = $self.create_blinded_path_using_absolute_expiry(OffersContext::Unknown {}, absolute_expiry) + let path = $self.create_blinded_paths_using_absolute_expiry(OffersContext::Unknown {}, absolute_expiry) + .and_then(|paths| paths.into_iter().next().ok_or(())) .map_err(|_| Bolt12SemanticError::MissingPaths)?; + let builder = OfferBuilder::deriving_signing_pubkey( node_id, expanded_key, entropy, secp_ctx ) @@ -8694,8 +8696,10 @@ macro_rules! create_refund_builder { ($self: ident, $builder: ty) => { let secp_ctx = &$self.secp_ctx; let context = OffersContext::OutboundPayment { payment_id }; - let path = $self.create_blinded_path_using_absolute_expiry(context, Some(absolute_expiry)) + let path = $self.create_blinded_paths_using_absolute_expiry(context, Some(absolute_expiry)) + .and_then(|paths| paths.into_iter().next().ok_or(())) .map_err(|_| Bolt12SemanticError::MissingPaths)?; + let builder = RefundBuilder::deriving_payer_id( node_id, expanded_key, entropy, secp_ctx, amount_msats, payment_id )? @@ -8716,6 +8720,13 @@ macro_rules! create_refund_builder { ($self: ident, $builder: ty) => { } } } +/// Defines the maximum number of [`OffersMessage`] including different reply paths to be sent +/// along different paths. +/// Sending multiple requests increases the chances of successful delivery in case some +/// paths are unavailable. However, only one invoice for a given [`PaymentId`] will be paid, +/// even if multiple invoices are received. +const OFFERS_MESSAGE_REQUEST_LIMIT: usize = 10; + impl ChannelManager where M::Target: chain::Watch<::EcdsaSigner>, @@ -8819,7 +8830,7 @@ where let invoice_request = builder.build_and_sign()?; let context = OffersContext::OutboundPayment { payment_id }; - let reply_path = self.create_blinded_path(context).map_err(|_| Bolt12SemanticError::MissingPaths)?; + let reply_paths = self.create_blinded_paths(context).map_err(|_| Bolt12SemanticError::MissingPaths)?; let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self); @@ -8832,25 +8843,27 @@ where let mut pending_offers_messages = self.pending_offers_messages.lock().unwrap(); if !offer.paths().is_empty() { - // Send as many invoice requests as there are paths in the offer (with an upper bound). - // Using only one path could result in a failure if the path no longer exists. But only - // one invoice for a given payment id will be paid, even if more than one is received. - const REQUEST_LIMIT: usize = 10; - for path in offer.paths().into_iter().take(REQUEST_LIMIT) { + reply_paths + .iter() + .flat_map(|reply_path| offer.paths().iter().map(move |path| (path, reply_path))) + .take(OFFERS_MESSAGE_REQUEST_LIMIT) + .for_each(|(path, reply_path)| { + let message = new_pending_onion_message( + OffersMessage::InvoiceRequest(invoice_request.clone()), + Destination::BlindedPath(path.clone()), + Some(reply_path.clone()), + ); + pending_offers_messages.push(message); + }); + } else if let Some(signing_pubkey) = offer.signing_pubkey() { + for reply_path in reply_paths { let message = new_pending_onion_message( OffersMessage::InvoiceRequest(invoice_request.clone()), - Destination::BlindedPath(path.clone()), - Some(reply_path.clone()), + Destination::Node(signing_pubkey), + Some(reply_path), ); pending_offers_messages.push(message); } - } else if let Some(signing_pubkey) = offer.signing_pubkey() { - let message = new_pending_onion_message( - OffersMessage::InvoiceRequest(invoice_request), - Destination::Node(signing_pubkey), - Some(reply_path), - ); - pending_offers_messages.push(message); } else { debug_assert!(false); return Err(Bolt12SemanticError::MissingSigningPubkey); @@ -8919,26 +8932,32 @@ where )?; let builder: InvoiceBuilder = builder.into(); let invoice = builder.allow_mpp().build_and_sign(secp_ctx)?; - let reply_path = self.create_blinded_path(OffersContext::Unknown {}) + let reply_paths = self.create_blinded_paths(OffersContext::Unknown {}) .map_err(|_| Bolt12SemanticError::MissingPaths)?; let mut pending_offers_messages = self.pending_offers_messages.lock().unwrap(); if refund.paths().is_empty() { - let message = new_pending_onion_message( - OffersMessage::Invoice(invoice.clone()), - Destination::Node(refund.payer_id()), - Some(reply_path), - ); - pending_offers_messages.push(message); - } else { - for path in refund.paths() { + for reply_path in reply_paths { let message = new_pending_onion_message( OffersMessage::Invoice(invoice.clone()), - Destination::BlindedPath(path.clone()), - Some(reply_path.clone()), + Destination::Node(refund.payer_id()), + Some(reply_path), ); pending_offers_messages.push(message); } + } else { + reply_paths + .iter() + .flat_map(|reply_path| refund.paths().iter().map(move |path| (path, reply_path))) + .take(OFFERS_MESSAGE_REQUEST_LIMIT) + .for_each(|(path, reply_path)| { + let message = new_pending_onion_message( + OffersMessage::Invoice(invoice.clone()), + Destination::BlindedPath(path.clone()), + Some(reply_path.clone()), + ); + pending_offers_messages.push(message); + }); } Ok(invoice) @@ -9045,22 +9064,22 @@ where inbound_payment::get_payment_preimage(payment_hash, payment_secret, &self.inbound_payment_key) } - /// Creates a blinded path by delegating to [`MessageRouter`] based on the path's intended - /// lifetime. + /// Creates a collection of blinded paths by delegating to [`MessageRouter`] based on + /// the path's intended lifetime. /// /// Whether or not the path is compact depends on whether the path is short-lived or long-lived, /// respectively, based on the given `absolute_expiry` as seconds since the Unix epoch. See /// [`MAX_SHORT_LIVED_RELATIVE_EXPIRY`]. - fn create_blinded_path_using_absolute_expiry( + fn create_blinded_paths_using_absolute_expiry( &self, context: OffersContext, absolute_expiry: Option, - ) -> Result { + ) -> Result, ()> { let now = self.duration_since_epoch(); let max_short_lived_absolute_expiry = now.saturating_add(MAX_SHORT_LIVED_RELATIVE_EXPIRY); if absolute_expiry.unwrap_or(Duration::MAX) <= max_short_lived_absolute_expiry { - self.create_compact_blinded_path(context) + self.create_compact_blinded_paths(context) } else { - self.create_blinded_path(context) + self.create_blinded_paths(context) } } @@ -9077,10 +9096,11 @@ where now } - /// Creates a blinded path by delegating to [`MessageRouter::create_blinded_paths`]. + /// Creates a collection of blinded paths by delegating to + /// [`MessageRouter::create_blinded_paths`]. /// - /// Errors if the `MessageRouter` errors or returns an empty `Vec`. - fn create_blinded_path(&self, context: OffersContext) -> Result { + /// Errors if the `MessageRouter` errors. + fn create_blinded_paths(&self, context: OffersContext) -> Result, ()> { let recipient = self.get_our_node_id(); let secp_ctx = &self.secp_ctx; @@ -9094,13 +9114,14 @@ where self.router .create_blinded_paths(recipient, MessageContext::Offers(context), peers, secp_ctx) - .and_then(|paths| paths.into_iter().next().ok_or(())) + .and_then(|paths| (!paths.is_empty()).then(|| paths).ok_or(())) } - /// Creates a blinded path by delegating to [`MessageRouter::create_compact_blinded_paths`]. + /// Creates a collection of blinded paths by delegating to + /// [`MessageRouter::create_compact_blinded_paths`]. /// - /// Errors if the `MessageRouter` errors or returns an empty `Vec`. - fn create_compact_blinded_path(&self, context: OffersContext) -> Result { + /// Errors if the `MessageRouter` errors. + fn create_compact_blinded_paths(&self, context: OffersContext) -> Result, ()> { let recipient = self.get_our_node_id(); let secp_ctx = &self.secp_ctx; @@ -9121,7 +9142,7 @@ where self.router .create_compact_blinded_paths(recipient, MessageContext::Offers(context), peers, secp_ctx) - .and_then(|paths| paths.into_iter().next().ok_or(())) + .and_then(|paths| (!paths.is_empty()).then(|| paths).ok_or(())) } /// Creates multi-hop blinded payment paths for the given `amount_msats` by delegating to -- 2.39.5