Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow blinded path diversification by expanding create_blinded_paths #3087

Merged
merged 3 commits into from
Jul 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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(()))
shaavan marked this conversation as resolved.
Show resolved Hide resolved
.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(()))
shaavan marked this conversation as resolved.
Show resolved Hide resolved
.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(()))
shaavan marked this conversation as resolved.
Show resolved Hide resolved
.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
Loading