Skip to content

Commit 6ed398d

Browse files
authored
Merge pull request #3087 from shaavan/reply_path_diversity
Allow blinded path diversification by expanding `create_blinded_paths`
2 parents dee3ba7 + 957b337 commit 6ed398d

File tree

2 files changed

+240
-55
lines changed

2 files changed

+240
-55
lines changed

lightning/src/ln/channelmanager.rs

+63-42
Original file line numberDiff line numberDiff line change
@@ -8621,8 +8621,10 @@ macro_rules! create_offer_builder { ($self: ident, $builder: ty) => {
86218621
let entropy = &*$self.entropy_source;
86228622
let secp_ctx = &$self.secp_ctx;
86238623

8624-
let path = $self.create_blinded_path_using_absolute_expiry(OffersContext::Unknown {}, absolute_expiry)
8624+
let path = $self.create_blinded_paths_using_absolute_expiry(OffersContext::Unknown {}, absolute_expiry)
8625+
.and_then(|paths| paths.into_iter().next().ok_or(()))
86258626
.map_err(|_| Bolt12SemanticError::MissingPaths)?;
8627+
86268628
let builder = OfferBuilder::deriving_signing_pubkey(
86278629
node_id, expanded_key, entropy, secp_ctx
86288630
)
@@ -8694,8 +8696,10 @@ macro_rules! create_refund_builder { ($self: ident, $builder: ty) => {
86948696
let secp_ctx = &$self.secp_ctx;
86958697

86968698
let context = OffersContext::OutboundPayment { payment_id };
8697-
let path = $self.create_blinded_path_using_absolute_expiry(context, Some(absolute_expiry))
8699+
let path = $self.create_blinded_paths_using_absolute_expiry(context, Some(absolute_expiry))
8700+
.and_then(|paths| paths.into_iter().next().ok_or(()))
86988701
.map_err(|_| Bolt12SemanticError::MissingPaths)?;
8702+
86998703
let builder = RefundBuilder::deriving_payer_id(
87008704
node_id, expanded_key, entropy, secp_ctx, amount_msats, payment_id
87018705
)?
@@ -8716,6 +8720,13 @@ macro_rules! create_refund_builder { ($self: ident, $builder: ty) => {
87168720
}
87178721
} }
87188722

8723+
/// Defines the maximum number of [`OffersMessage`] including different reply paths to be sent
8724+
/// along different paths.
8725+
/// Sending multiple requests increases the chances of successful delivery in case some
8726+
/// paths are unavailable. However, only one invoice for a given [`PaymentId`] will be paid,
8727+
/// even if multiple invoices are received.
8728+
const OFFERS_MESSAGE_REQUEST_LIMIT: usize = 10;
8729+
87198730
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>
87208731
where
87218732
M::Target: chain::Watch<<SP::Target as SignerProvider>::EcdsaSigner>,
@@ -8819,7 +8830,7 @@ where
88198830
let invoice_request = builder.build_and_sign()?;
88208831

88218832
let context = OffersContext::OutboundPayment { payment_id };
8822-
let reply_path = self.create_blinded_path(context).map_err(|_| Bolt12SemanticError::MissingPaths)?;
8833+
let reply_paths = self.create_blinded_paths(context).map_err(|_| Bolt12SemanticError::MissingPaths)?;
88238834

88248835
let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self);
88258836

@@ -8832,25 +8843,27 @@ where
88328843

88338844
let mut pending_offers_messages = self.pending_offers_messages.lock().unwrap();
88348845
if !offer.paths().is_empty() {
8835-
// Send as many invoice requests as there are paths in the offer (with an upper bound).
8836-
// Using only one path could result in a failure if the path no longer exists. But only
8837-
// one invoice for a given payment id will be paid, even if more than one is received.
8838-
const REQUEST_LIMIT: usize = 10;
8839-
for path in offer.paths().into_iter().take(REQUEST_LIMIT) {
8846+
reply_paths
8847+
.iter()
8848+
.flat_map(|reply_path| offer.paths().iter().map(move |path| (path, reply_path)))
8849+
.take(OFFERS_MESSAGE_REQUEST_LIMIT)
8850+
.for_each(|(path, reply_path)| {
8851+
let message = new_pending_onion_message(
8852+
OffersMessage::InvoiceRequest(invoice_request.clone()),
8853+
Destination::BlindedPath(path.clone()),
8854+
Some(reply_path.clone()),
8855+
);
8856+
pending_offers_messages.push(message);
8857+
});
8858+
} else if let Some(signing_pubkey) = offer.signing_pubkey() {
8859+
for reply_path in reply_paths {
88408860
let message = new_pending_onion_message(
88418861
OffersMessage::InvoiceRequest(invoice_request.clone()),
8842-
Destination::BlindedPath(path.clone()),
8843-
Some(reply_path.clone()),
8862+
Destination::Node(signing_pubkey),
8863+
Some(reply_path),
88448864
);
88458865
pending_offers_messages.push(message);
88468866
}
8847-
} else if let Some(signing_pubkey) = offer.signing_pubkey() {
8848-
let message = new_pending_onion_message(
8849-
OffersMessage::InvoiceRequest(invoice_request),
8850-
Destination::Node(signing_pubkey),
8851-
Some(reply_path),
8852-
);
8853-
pending_offers_messages.push(message);
88548867
} else {
88558868
debug_assert!(false);
88568869
return Err(Bolt12SemanticError::MissingSigningPubkey);
@@ -8919,26 +8932,32 @@ where
89198932
)?;
89208933
let builder: InvoiceBuilder<DerivedSigningPubkey> = builder.into();
89218934
let invoice = builder.allow_mpp().build_and_sign(secp_ctx)?;
8922-
let reply_path = self.create_blinded_path(OffersContext::Unknown {})
8935+
let reply_paths = self.create_blinded_paths(OffersContext::Unknown {})
89238936
.map_err(|_| Bolt12SemanticError::MissingPaths)?;
89248937

89258938
let mut pending_offers_messages = self.pending_offers_messages.lock().unwrap();
89268939
if refund.paths().is_empty() {
8927-
let message = new_pending_onion_message(
8928-
OffersMessage::Invoice(invoice.clone()),
8929-
Destination::Node(refund.payer_id()),
8930-
Some(reply_path),
8931-
);
8932-
pending_offers_messages.push(message);
8933-
} else {
8934-
for path in refund.paths() {
8940+
for reply_path in reply_paths {
89358941
let message = new_pending_onion_message(
89368942
OffersMessage::Invoice(invoice.clone()),
8937-
Destination::BlindedPath(path.clone()),
8938-
Some(reply_path.clone()),
8943+
Destination::Node(refund.payer_id()),
8944+
Some(reply_path),
89398945
);
89408946
pending_offers_messages.push(message);
89418947
}
8948+
} else {
8949+
reply_paths
8950+
.iter()
8951+
.flat_map(|reply_path| refund.paths().iter().map(move |path| (path, reply_path)))
8952+
.take(OFFERS_MESSAGE_REQUEST_LIMIT)
8953+
.for_each(|(path, reply_path)| {
8954+
let message = new_pending_onion_message(
8955+
OffersMessage::Invoice(invoice.clone()),
8956+
Destination::BlindedPath(path.clone()),
8957+
Some(reply_path.clone()),
8958+
);
8959+
pending_offers_messages.push(message);
8960+
});
89428961
}
89438962

89448963
Ok(invoice)
@@ -9045,22 +9064,22 @@ where
90459064
inbound_payment::get_payment_preimage(payment_hash, payment_secret, &self.inbound_payment_key)
90469065
}
90479066

9048-
/// Creates a blinded path by delegating to [`MessageRouter`] based on the path's intended
9049-
/// lifetime.
9067+
/// Creates a collection of blinded paths by delegating to [`MessageRouter`] based on
9068+
/// the path's intended lifetime.
90509069
///
90519070
/// Whether or not the path is compact depends on whether the path is short-lived or long-lived,
90529071
/// respectively, based on the given `absolute_expiry` as seconds since the Unix epoch. See
90539072
/// [`MAX_SHORT_LIVED_RELATIVE_EXPIRY`].
9054-
fn create_blinded_path_using_absolute_expiry(
9073+
fn create_blinded_paths_using_absolute_expiry(
90559074
&self, context: OffersContext, absolute_expiry: Option<Duration>,
9056-
) -> Result<BlindedPath, ()> {
9075+
) -> Result<Vec<BlindedPath>, ()> {
90579076
let now = self.duration_since_epoch();
90589077
let max_short_lived_absolute_expiry = now.saturating_add(MAX_SHORT_LIVED_RELATIVE_EXPIRY);
90599078

90609079
if absolute_expiry.unwrap_or(Duration::MAX) <= max_short_lived_absolute_expiry {
9061-
self.create_compact_blinded_path(context)
9080+
self.create_compact_blinded_paths(context)
90629081
} else {
9063-
self.create_blinded_path(context)
9082+
self.create_blinded_paths(context)
90649083
}
90659084
}
90669085

@@ -9077,10 +9096,11 @@ where
90779096
now
90789097
}
90799098

9080-
/// Creates a blinded path by delegating to [`MessageRouter::create_blinded_paths`].
9099+
/// Creates a collection of blinded paths by delegating to
9100+
/// [`MessageRouter::create_blinded_paths`].
90819101
///
9082-
/// Errors if the `MessageRouter` errors or returns an empty `Vec`.
9083-
fn create_blinded_path(&self, context: OffersContext) -> Result<BlindedPath, ()> {
9102+
/// Errors if the `MessageRouter` errors.
9103+
fn create_blinded_paths(&self, context: OffersContext) -> Result<Vec<BlindedPath>, ()> {
90849104
let recipient = self.get_our_node_id();
90859105
let secp_ctx = &self.secp_ctx;
90869106

@@ -9094,13 +9114,14 @@ where
90949114

90959115
self.router
90969116
.create_blinded_paths(recipient, MessageContext::Offers(context), peers, secp_ctx)
9097-
.and_then(|paths| paths.into_iter().next().ok_or(()))
9117+
.and_then(|paths| (!paths.is_empty()).then(|| paths).ok_or(()))
90989118
}
90999119

9100-
/// Creates a blinded path by delegating to [`MessageRouter::create_compact_blinded_paths`].
9120+
/// Creates a collection of blinded paths by delegating to
9121+
/// [`MessageRouter::create_compact_blinded_paths`].
91019122
///
9102-
/// Errors if the `MessageRouter` errors or returns an empty `Vec`.
9103-
fn create_compact_blinded_path(&self, context: OffersContext) -> Result<BlindedPath, ()> {
9123+
/// Errors if the `MessageRouter` errors.
9124+
fn create_compact_blinded_paths(&self, context: OffersContext) -> Result<Vec<BlindedPath>, ()> {
91049125
let recipient = self.get_our_node_id();
91059126
let secp_ctx = &self.secp_ctx;
91069127

@@ -9121,7 +9142,7 @@ where
91219142

91229143
self.router
91239144
.create_compact_blinded_paths(recipient, MessageContext::Offers(context), peers, secp_ctx)
9124-
.and_then(|paths| paths.into_iter().next().ok_or(()))
9145+
.and_then(|paths| (!paths.is_empty()).then(|| paths).ok_or(()))
91259146
}
91269147

91279148
/// Creates multi-hop blinded payment paths for the given `amount_msats` by delegating to

0 commit comments

Comments
 (0)