Skip to content

Commit

Permalink
Merge pull request #3087 from shaavan/reply_path_diversity
Browse files Browse the repository at this point in the history
Allow blinded path diversification by expanding `create_blinded_paths`
  • Loading branch information
TheBlueMatt committed Jul 16, 2024
2 parents dee3ba7 + 957b337 commit 6ed398d
Show file tree
Hide file tree
Showing 2 changed files with 240 additions and 55 deletions.
105 changes: 63 additions & 42 deletions lightning/src/ln/channelmanager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
Expand Down Expand Up @@ -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
)?
Expand All @@ -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<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>,
Expand Down Expand Up @@ -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);

Expand All @@ -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);
Expand Down Expand Up @@ -8919,26 +8932,32 @@ where
)?;
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)
Expand Down Expand Up @@ -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<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)
}
}

Expand All @@ -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<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;

Expand All @@ -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<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;

Expand All @@ -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
Expand Down
Loading

0 comments on commit 6ed398d

Please sign in to comment.