-
Notifications
You must be signed in to change notification settings - Fork 378
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
Optional compact blinded path creation #3080
Changes from all commits
defc540
5326171
411b9b4
8012c2b
15fa0d8
7db6616
c17a026
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 |
---|---|---|
|
@@ -1554,8 +1554,9 @@ where | |
/// # | ||
/// # fn example<T: AChannelManager>(channel_manager: T) -> Result<(), Bolt12SemanticError> { | ||
/// # let channel_manager = channel_manager.get_cm(); | ||
/// # let absolute_expiry = None; | ||
/// let offer = channel_manager | ||
/// .create_offer_builder()? | ||
/// .create_offer_builder(absolute_expiry)? | ||
/// # ; | ||
/// # // Needed for compiling for c_bindings | ||
/// # let builder: lightning::offers::offer::OfferBuilder<_, _> = offer.into(); | ||
|
@@ -2287,6 +2288,19 @@ const MAX_UNFUNDED_CHANNEL_PEERS: usize = 50; | |
/// many peers we reject new (inbound) connections. | ||
const MAX_NO_CHANNEL_PEERS: usize = 250; | ||
|
||
/// The maximum expiration from the current time where an [`Offer`] or [`Refund`] is considered | ||
/// short-lived, while anything with a greater expiration is considered long-lived. | ||
/// | ||
/// Using [`ChannelManager::create_offer_builder`] or [`ChannelManager::create_refund_builder`], | ||
/// will included a [`BlindedPath`] created using: | ||
/// - [`MessageRouter::create_compact_blinded_paths`] when short-lived, and | ||
/// - [`MessageRouter::create_blinded_paths`] when long-lived. | ||
/// | ||
/// Using compact [`BlindedPath`]s may provide better privacy as the [`MessageRouter`] could select | ||
/// more hops. However, since they use short channel ids instead of pubkeys, they are more likely to | ||
/// become invalid over time as channels are closed. Thus, they are only suitable for short-term use. | ||
pub const MAX_SHORT_LIVED_RELATIVE_EXPIRY: Duration = Duration::from_secs(60 * 60 * 24); | ||
|
||
/// Used by [`ChannelManager::list_recent_payments`] to express the status of recent payments. | ||
/// These include payments that have yet to find a successful path, or have unresolved HTLCs. | ||
#[derive(Debug, PartialEq)] | ||
|
@@ -8240,16 +8254,15 @@ where | |
|
||
macro_rules! create_offer_builder { ($self: ident, $builder: ty) => { | ||
/// Creates an [`OfferBuilder`] such that the [`Offer`] it builds is recognized by the | ||
/// [`ChannelManager`] when handling [`InvoiceRequest`] messages for the offer. The offer will | ||
/// not have an expiration unless otherwise set on the builder. | ||
/// [`ChannelManager`] when handling [`InvoiceRequest`] messages for the offer. The offer's | ||
/// expiration will be `absolute_expiry` if `Some`, otherwise it will not expire. | ||
/// | ||
/// # Privacy | ||
/// | ||
/// Uses [`MessageRouter::create_blinded_paths`] to construct a [`BlindedPath`] for the offer. | ||
/// However, if one is not found, uses a one-hop [`BlindedPath`] with | ||
/// [`ChannelManager::get_our_node_id`] as the introduction node instead. In the latter case, | ||
/// the node must be announced, otherwise, there is no way to find a path to the introduction in | ||
/// order to send the [`InvoiceRequest`]. | ||
/// Uses [`MessageRouter`] to construct a [`BlindedPath`] for the offer based on the given | ||
/// `absolute_expiry` according to [`MAX_SHORT_LIVED_RELATIVE_EXPIRY`]. See those docs for | ||
/// privacy implications as well as those of the parameterized [`Router`], which implements | ||
/// [`MessageRouter`]. | ||
/// | ||
/// Also, uses a derived signing pubkey in the offer for recipient privacy. | ||
/// | ||
|
@@ -8264,19 +8277,27 @@ 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) -> Result<$builder, Bolt12SemanticError> { | ||
pub fn create_offer_builder( | ||
&$self, absolute_expiry: Option<Duration> | ||
) -> Result<$builder, Bolt12SemanticError> { | ||
let node_id = $self.get_our_node_id(); | ||
let expanded_key = &$self.inbound_payment_key; | ||
let entropy = &*$self.entropy_source; | ||
let secp_ctx = &$self.secp_ctx; | ||
|
||
let path = $self.create_blinded_path().map_err(|_| Bolt12SemanticError::MissingPaths)?; | ||
let path = $self.create_blinded_path_using_absolute_expiry(absolute_expiry) | ||
.map_err(|_| Bolt12SemanticError::MissingPaths)?; | ||
let builder = OfferBuilder::deriving_signing_pubkey( | ||
node_id, expanded_key, entropy, secp_ctx | ||
) | ||
.chain_hash($self.chain_hash) | ||
.path(path); | ||
|
||
let builder = match absolute_expiry { | ||
None => builder, | ||
Some(absolute_expiry) => builder.absolute_expiry(absolute_expiry), | ||
}; | ||
|
||
Ok(builder.into()) | ||
} | ||
} } | ||
|
@@ -8304,11 +8325,10 @@ macro_rules! create_refund_builder { ($self: ident, $builder: ty) => { | |
/// | ||
/// # Privacy | ||
/// | ||
/// Uses [`MessageRouter::create_blinded_paths`] to construct a [`BlindedPath`] for the refund. | ||
/// However, if one is not found, uses a one-hop [`BlindedPath`] with | ||
/// [`ChannelManager::get_our_node_id`] as the introduction node instead. In the latter case, | ||
/// the node must be announced, otherwise, there is no way to find a path to the introduction in | ||
/// order to send the [`Bolt12Invoice`]. | ||
/// Uses [`MessageRouter`] to construct a [`BlindedPath`] for the refund based on the given | ||
/// `absolute_expiry` according to [`MAX_SHORT_LIVED_RELATIVE_EXPIRY`]. See those docs for | ||
/// privacy implications as well as those of the parameterized [`Router`], which implements | ||
/// [`MessageRouter`]. | ||
/// | ||
/// Also, uses a derived payer id in the refund for payer privacy. | ||
/// | ||
|
@@ -8337,7 +8357,8 @@ macro_rules! create_refund_builder { ($self: ident, $builder: ty) => { | |
let entropy = &*$self.entropy_source; | ||
let secp_ctx = &$self.secp_ctx; | ||
|
||
let path = $self.create_blinded_path().map_err(|_| Bolt12SemanticError::MissingPaths)?; | ||
let path = $self.create_blinded_path_using_absolute_expiry(Some(absolute_expiry)) | ||
.map_err(|_| Bolt12SemanticError::MissingPaths)?; | ||
let builder = RefundBuilder::deriving_payer_id( | ||
node_id, expanded_key, entropy, secp_ctx, amount_msats, payment_id | ||
)? | ||
|
@@ -8406,10 +8427,9 @@ where | |
/// | ||
/// # Privacy | ||
/// | ||
/// Uses a one-hop [`BlindedPath`] for the reply path with [`ChannelManager::get_our_node_id`] | ||
/// as the introduction node and a derived payer id for payer privacy. As such, currently, the | ||
/// node must be announced. Otherwise, there is no way to find a path to the introduction node | ||
/// in order to send the [`Bolt12Invoice`]. | ||
/// For payer privacy, uses a derived payer id and uses [`MessageRouter::create_blinded_paths`] | ||
/// to construct a [`BlindedPath`] for the reply path. For further privacy implications, see the | ||
/// docs of the parameterized [`Router`], which implements [`MessageRouter`]. | ||
/// | ||
/// # Limitations | ||
/// | ||
|
@@ -8686,6 +8706,38 @@ 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. | ||
/// | ||
/// 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( | ||
&self, absolute_expiry: Option<Duration> | ||
) -> Result<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() | ||
} else { | ||
self.create_blinded_path() | ||
} | ||
} | ||
|
||
pub(super) fn duration_since_epoch(&self) -> Duration { | ||
#[cfg(not(feature = "std"))] | ||
let now = Duration::from_secs( | ||
self.highest_seen_timestamp.load(Ordering::Acquire) as u64 | ||
); | ||
#[cfg(feature = "std")] | ||
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. Until we upgrade to rust-bitcoin 0.32, we should avoid assuming we can access time even with std :(. 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. Hmmm... we are already doing so in 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. Ah, I'd missed that, we need to fix that (or mutiny is gonna be mad...). 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. Is there anything blocking us from upgrading now? 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. Just someone doing the work, I think. @tnull should chime in though, it may be that BDK is on 0.31 and we want to ship an 0.31 release first so that we can sync with the existing BDK code before jumping to 0.32. 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 discussed that with the BDK team in the meeting last week. BDK is close to finishing the upgrade and it will land ~next release, at the very least before the feature that keeps LDK Node from upgrading (data model backwards compat). So we're free to (and in fact should ASAP) upgrade to 0.32. I can see to prioritize it by the end of the week. Would this be sufficient to have it land it first, so that we don't have to deal with the things mentioned above here? 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. Yeah, preferably so. I'm not sure what our alternative is, though I don't think we're in any big rush to land this. Presumably we'll want it for the next release but waiting on 0.32 shouldn't hold up the review at least. 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. 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. Alright, now tracking here: #3100 Will see to pick it up soon if nobody is already working on it. |
||
let now = std::time::SystemTime::now() | ||
.duration_since(std::time::SystemTime::UNIX_EPOCH) | ||
.expect("SystemTime::now() should come after SystemTime::UNIX_EPOCH"); | ||
|
||
now | ||
} | ||
|
||
/// Creates a blinded path by delegating to [`MessageRouter::create_blinded_paths`]. | ||
/// | ||
/// Errors if the `MessageRouter` errors or returns an empty `Vec`. | ||
|
@@ -8696,6 +8748,27 @@ where | |
let peers = self.per_peer_state.read().unwrap() | ||
.iter() | ||
.map(|(node_id, peer_state)| (node_id, peer_state.lock().unwrap())) | ||
.filter(|(_, peer)| peer.is_connected) | ||
.filter(|(_, peer)| peer.latest_features.supports_onion_messages()) | ||
.map(|(node_id, _)| *node_id) | ||
.collect::<Vec<_>>(); | ||
|
||
self.router | ||
.create_blinded_paths(recipient, peers, secp_ctx) | ||
.and_then(|paths| paths.into_iter().next().ok_or(())) | ||
} | ||
|
||
/// Creates a blinded path by delegating to [`MessageRouter::create_compact_blinded_paths`]. | ||
/// | ||
/// Errors if the `MessageRouter` errors or returns an empty `Vec`. | ||
fn create_compact_blinded_path(&self) -> Result<BlindedPath, ()> { | ||
let recipient = self.get_our_node_id(); | ||
let secp_ctx = &self.secp_ctx; | ||
|
||
let peers = self.per_peer_state.read().unwrap() | ||
.iter() | ||
.map(|(node_id, peer_state)| (node_id, peer_state.lock().unwrap())) | ||
.filter(|(_, peer)| peer.is_connected) | ||
.filter(|(_, peer)| peer.latest_features.supports_onion_messages()) | ||
.map(|(node_id, peer)| ForwardNode { | ||
node_id: *node_id, | ||
|
@@ -8708,7 +8781,7 @@ where | |
.collect::<Vec<_>>(); | ||
|
||
self.router | ||
.create_blinded_paths(recipient, peers, secp_ctx) | ||
.create_compact_blinded_paths(recipient, peers, secp_ctx) | ||
.and_then(|paths| paths.into_iter().next().ok_or(())) | ||
} | ||
|
||
|
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.
nit: