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
)
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
)?
}
} }
+/// 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<M: Deref, T: Deref, ES: Deref, NS: Deref, SP: Deref, F: Deref, R: Deref, L: Deref> ChannelManager<M, T, ES, NS, SP, F, R, L>
where
M::Target: chain::Watch<<SP::Target as SignerProvider>::EcdsaSigner>,
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);
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);
)?;
let builder: InvoiceBuilder<DerivedSigningPubkey> = 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)
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<Duration>,
- ) -> Result<BlindedPath, ()> {
+ ) -> Result<Vec<BlindedPath>, ()> {
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)
}
}
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<BlindedPath, ()> {
+ /// Errors if the `MessageRouter` errors.
+ fn create_blinded_paths(&self, context: OffersContext) -> Result<Vec<BlindedPath>, ()> {
let recipient = self.get_our_node_id();
let secp_ctx = &self.secp_ctx;
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<BlindedPath, ()> {
+ /// Errors if the `MessageRouter` errors.
+ fn create_compact_blinded_paths(&self, context: OffersContext) -> Result<Vec<BlindedPath>, ()> {
let recipient = self.get_our_node_id();
let secp_ctx = &self.secp_ctx;
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