-
Notifications
You must be signed in to change notification settings - Fork 367
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
Enable Creation of Offers and Refunds Without Blinded Path #3246
base: main
Are you sure you want to change the base?
Changes from all commits
b9645d8
fab8816
e297444
ffb13c3
44e9789
863dcd6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -223,6 +223,34 @@ pub enum PendingHTLCRouting { | |
}, | ||
} | ||
|
||
/// Represents the types of [`BlindedMessagePath`] that can be created. | ||
pub enum BlindedPathType { | ||
/// A compact version of [`BlindedMessagePath`]. | ||
/// | ||
/// Compact blinded paths use a [`ShortChannelId`] instead of a [`NodeId`] to represent forward nodes. | ||
/// This approach drastically reduces the size of each individual forward packet but requires | ||
/// knowledge of the local channel graph to find the corresponding channel for each node. | ||
/// | ||
/// Compact blinded paths are especially useful when size is a constraint, such as when offers | ||
/// and refund information must be represented in QR code form. | ||
/// | ||
/// [`ShortChannelId`]: crate::blinded_path::message::NextMessageHop::ShortChannelId | ||
/// [`NodeId`]: crate::blinded_path::message::NextMessageHop::NodeId | ||
Compact, | ||
|
||
/// A full-length version of [`BlindedMessagePath`]. | ||
/// | ||
/// Unlike compact blinded paths, full-length paths use each individual forward node's [`NodeId`] for representation. | ||
/// This increases the size of each forward packet as more space is required for representation. | ||
/// However, it does not require knowledge of the local channel graph to create the path. | ||
/// | ||
/// Full-length blinded paths are useful when onion messages are communicated over the wire and size constraints are not an issue, | ||
/// such as when sending a response to an onion message. | ||
/// | ||
/// [`NodeId`]: crate::blinded_path::message::NextMessageHop::NodeId | ||
Full, | ||
} | ||
|
||
/// Information used to forward or fail this HTLC that is being forwarded within a blinded path. | ||
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] | ||
pub struct BlindedForward { | ||
|
@@ -1852,14 +1880,14 @@ where | |
/// | ||
/// ``` | ||
/// # use lightning::events::{Event, EventsProvider, PaymentPurpose}; | ||
/// # use lightning::ln::channelmanager::AChannelManager; | ||
/// # use lightning::ln::channelmanager::{AChannelManager, BlindedPathType}; | ||
/// # use lightning::offers::parse::Bolt12SemanticError; | ||
/// # | ||
/// # fn example<T: AChannelManager>(channel_manager: T) -> Result<(), Bolt12SemanticError> { | ||
/// # let channel_manager = channel_manager.get_cm(); | ||
/// # let absolute_expiry = None; | ||
/// # let blinded_path = BlindedPathType::Full; | ||
/// let offer = channel_manager | ||
/// .create_offer_builder(absolute_expiry)? | ||
/// .create_offer_builder(Some(blinded_path))? | ||
/// # ; | ||
/// # // Needed for compiling for c_bindings | ||
/// # let builder: lightning::offers::offer::OfferBuilder<_, _> = offer.into(); | ||
|
@@ -1955,7 +1983,7 @@ where | |
/// ``` | ||
/// # use core::time::Duration; | ||
/// # use lightning::events::{Event, EventsProvider}; | ||
/// # use lightning::ln::channelmanager::{AChannelManager, PaymentId, RecentPaymentDetails, Retry}; | ||
/// # use lightning::ln::channelmanager::{AChannelManager, BlindedPathType, PaymentId, RecentPaymentDetails, Retry}; | ||
/// # use lightning::offers::parse::Bolt12SemanticError; | ||
/// # | ||
/// # fn example<T: AChannelManager>( | ||
|
@@ -1964,9 +1992,10 @@ where | |
/// # ) -> Result<(), Bolt12SemanticError> { | ||
/// # let channel_manager = channel_manager.get_cm(); | ||
/// let payment_id = PaymentId([42; 32]); | ||
/// let blinded_path = Some(BlindedPathType::Full); | ||
/// let refund = channel_manager | ||
/// .create_refund_builder( | ||
/// amount_msats, absolute_expiry, payment_id, retry, max_total_routing_fee_msat | ||
/// amount_msats, absolute_expiry, payment_id, retry, max_total_routing_fee_msat, blinded_path | ||
/// )? | ||
/// # ; | ||
/// # // Needed for compiling for c_bindings | ||
|
@@ -9121,7 +9150,7 @@ macro_rules! create_offer_builder { ($self: ident, $builder: ty) => { | |
/// [`Offer`]: crate::offers::offer::Offer | ||
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest | ||
pub fn create_offer_builder( | ||
&$self, absolute_expiry: Option<Duration> | ||
&$self, blinded_path: Option<BlindedPathType>, | ||
) -> Result<$builder, Bolt12SemanticError> { | ||
let node_id = $self.get_our_node_id(); | ||
let expanded_key = &$self.inbound_payment_key; | ||
|
@@ -9130,17 +9159,21 @@ macro_rules! create_offer_builder { ($self: ident, $builder: ty) => { | |
|
||
let nonce = Nonce::from_entropy_source(entropy); | ||
let context = OffersContext::InvoiceRequest { nonce }; | ||
let path = $self.create_blinded_paths_using_absolute_expiry(context, absolute_expiry) | ||
.and_then(|paths| paths.into_iter().next().ok_or(())) | ||
|
||
let mut builder = OfferBuilder::deriving_signing_pubkey(node_id, expanded_key, nonce, secp_ctx) | ||
.chain_hash($self.chain_hash); | ||
|
||
if let Some(path_type) = blinded_path { | ||
let paths = match path_type { | ||
BlindedPathType::Compact => $self.create_compact_blinded_paths(context), | ||
BlindedPathType::Full => $self.create_blinded_paths(MessageContext::Offers(context)), | ||
} | ||
.map_err(|_| Bolt12SemanticError::MissingPaths)?; | ||
let builder = OfferBuilder::deriving_signing_pubkey(node_id, expanded_key, nonce, secp_ctx) | ||
.chain_hash($self.chain_hash) | ||
.path(path); | ||
|
||
let builder = match absolute_expiry { | ||
None => builder, | ||
Some(absolute_expiry) => builder.absolute_expiry(absolute_expiry), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don't we need to keep passing absolute expiry? Presumably this would be a part of the information communicated to the router, as well. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you be explicit about what you want the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Currently this PR isn't changing
ie focusing very much on the why, not the how in the interface. You're right that building the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this approach makes sense! Assuming the Additionally, if it’s the main decider on path compactness, it could manage the following setting: for path in &mut paths {
path.use_compact_introduction_node(&network_graph);
} This way, it can help avoid creating a compact last hop for full-length paths. To support the three cases Jeff mentioned in his comment, we could consider updating the fn create_offer_builder(..., why: Option<WhyWeWantABlindedPath>) -> Offer {
...
}
I do have a couple of things I’m curious about with this approach: → Since → Would it be useful for There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Seems like there would be some duplication with pub enum BlindedPathUse {
Persistent,
Ephemeral { expiry: Duration },
ReplyPath,
} That way were aren't tying it strictly to existing BOLT12 use cases. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Hmm, not quite visualizing this one but ISTM that we'd want There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suppose that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Validating the amount makes sense to me, but this is really what I want to get away from in the router - if we give the Can we just straight up give the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. FWIW, I'm not convinced we need to do what I suggested. Reply path construction doesn't really need much information outside what we'd already provide the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
}; | ||
builder = paths.into_iter().fold(builder, |builder, path| { | ||
builder.path(path) | ||
}); | ||
} | ||
|
||
Ok(builder.into()) | ||
} | ||
|
@@ -9194,7 +9227,7 @@ macro_rules! create_refund_builder { ($self: ident, $builder: ty) => { | |
/// [Avoiding Duplicate Payments]: #avoiding-duplicate-payments | ||
pub fn create_refund_builder( | ||
&$self, amount_msats: u64, absolute_expiry: Duration, payment_id: PaymentId, | ||
retry_strategy: Retry, max_total_routing_fee_msat: Option<u64> | ||
retry_strategy: Retry, max_total_routing_fee_msat: Option<u64>, blinded_path: Option<BlindedPathType> | ||
) -> Result<$builder, Bolt12SemanticError> { | ||
let node_id = $self.get_our_node_id(); | ||
let expanded_key = &$self.inbound_payment_key; | ||
|
@@ -9203,16 +9236,25 @@ macro_rules! create_refund_builder { ($self: ident, $builder: ty) => { | |
|
||
let nonce = Nonce::from_entropy_source(entropy); | ||
let context = OffersContext::OutboundPayment { payment_id, nonce, hmac: None }; | ||
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_signing_pubkey( | ||
node_id, expanded_key, nonce, secp_ctx, amount_msats, payment_id | ||
let mut builder = RefundBuilder::deriving_signing_pubkey( | ||
node_id, expanded_key, nonce, secp_ctx, | ||
amount_msats, payment_id, | ||
)? | ||
.chain_hash($self.chain_hash) | ||
.absolute_expiry(absolute_expiry) | ||
.path(path); | ||
.chain_hash($self.chain_hash) | ||
.absolute_expiry(absolute_expiry); | ||
|
||
if let Some(path_type) = blinded_path { | ||
let paths = match path_type { | ||
BlindedPathType::Compact => $self.create_compact_blinded_paths(context), | ||
BlindedPathType::Full => $self.create_blinded_paths(MessageContext::Offers(context)), | ||
} | ||
.map_err(|_| Bolt12SemanticError::MissingPaths)?; | ||
|
||
builder = paths.into_iter().fold(builder, |builder, path| { | ||
builder.path(path) | ||
}) | ||
} | ||
|
||
let _persistence_guard = PersistenceNotifierGuard::notify_on_drop($self); | ||
|
||
|
@@ -9596,25 +9638,7 @@ where | |
inbound_payment::get_payment_preimage(payment_hash, payment_secret, &self.inbound_payment_key) | ||
} | ||
|
||
/// 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_paths_using_absolute_expiry( | ||
&self, context: OffersContext, absolute_expiry: Option<Duration>, | ||
) -> Result<Vec<BlindedMessagePath>, ()> { | ||
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_paths(context) | ||
} else { | ||
self.create_blinded_paths(MessageContext::Offers(context)) | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
pub(super) fn duration_since_epoch(&self) -> Duration { | ||
#[cfg(not(feature = "std"))] | ||
let now = Duration::from_secs( | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we should be requesting specific types of compact paths from the
ChannelManager
. This is still theChannelManager
deciding what kind of blinded path it wants, but IMO we should be moving towards theChannelManager
communicating why it wants a blinded path and letting theMessageRouter
pick what kind of blinded path is best (number of paths, number of hops, type, etc).There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you clarify what sort of interface you want for
create_offer_builder
andcreate_refund_builder
? It's not clear to me what you're looking for that will satisfy the three use cases that I outlined in #3246 (comment). Are you just asking for a rename or something else?