@@ -1554,8 +1554,9 @@ where
15541554/// #
15551555/// # fn example<T: AChannelManager>(channel_manager: T) -> Result<(), Bolt12SemanticError> {
15561556/// # let channel_manager = channel_manager.get_cm();
1557+ /// # let absolute_expiry = None;
15571558/// let offer = channel_manager
1558- /// .create_offer_builder()?
1559+ /// .create_offer_builder(absolute_expiry )?
15591560/// # ;
15601561/// # // Needed for compiling for c_bindings
15611562/// # let builder: lightning::offers::offer::OfferBuilder<_, _> = offer.into();
@@ -2287,6 +2288,19 @@ const MAX_UNFUNDED_CHANNEL_PEERS: usize = 50;
22872288/// many peers we reject new (inbound) connections.
22882289const MAX_NO_CHANNEL_PEERS: usize = 250;
22892290
2291+ /// The maximum expiration from the current time where an [`Offer`] or [`Refund`] is considered
2292+ /// short-lived, while anything with a greater expiration is considered long-lived.
2293+ ///
2294+ /// Using [`ChannelManager::create_offer_builder`] or [`ChannelManager::create_refund_builder`],
2295+ /// will included a [`BlindedPath`] created using:
2296+ /// - [`MessageRouter::create_compact_blinded_paths`] when short-lived, and
2297+ /// - [`MessageRouter::create_blinded_paths`] when long-lived.
2298+ ///
2299+ /// Using compact [`BlindedPath`]s may provide better privacy as the [`MessageRouter`] could select
2300+ /// more hops. However, since they use short channel ids instead of pubkeys, they are more likely to
2301+ /// become invalid over time as channels are closed. Thus, they are only suitable for short-term use.
2302+ pub const MAX_SHORT_LIVED_RELATIVE_EXPIRY: Duration = Duration::from_secs(60 * 60 * 24);
2303+
22902304/// Used by [`ChannelManager::list_recent_payments`] to express the status of recent payments.
22912305/// These include payments that have yet to find a successful path, or have unresolved HTLCs.
22922306#[derive(Debug, PartialEq)]
@@ -8240,16 +8254,17 @@ where
82408254
82418255macro_rules! create_offer_builder { ($self: ident, $builder: ty) => {
82428256 /// Creates an [`OfferBuilder`] such that the [`Offer`] it builds is recognized by the
8243- /// [`ChannelManager`] when handling [`InvoiceRequest`] messages for the offer. The offer will
8244- /// not have an expiration unless otherwise set on the builder .
8257+ /// [`ChannelManager`] when handling [`InvoiceRequest`] messages for the offer. The offer's
8258+ /// expiration will be `absolute_expiry` if `Some`, otherwise it will not expire .
82458259 ///
82468260 /// # Privacy
82478261 ///
8248- /// Uses [`MessageRouter::create_compact_blinded_paths`] to construct a [`BlindedPath`] for the
8249- /// offer. However, if one is not found, uses a one-hop [`BlindedPath`] with
8262+ /// Uses [`MessageRouter`] to construct a [`BlindedPath`] for the offer based on the given
8263+ /// `absolute_expiry` according to [`MAX_SHORT_LIVED_RELATIVE_EXPIRY`]. See those docs for
8264+ /// privacy implications. However, if one is not found, uses a one-hop [`BlindedPath`] with
82508265 /// [`ChannelManager::get_our_node_id`] as the introduction node instead. In the latter case,
8251- /// the node must be announced, otherwise, there is no way to find a path to the introduction in
8252- /// order to send the [`InvoiceRequest`].
8266+ /// the node must be announced, otherwise, there is no way to find a path to the introduction
8267+ /// node in order to send the [`InvoiceRequest`].
82538268 ///
82548269 /// Also, uses a derived signing pubkey in the offer for recipient privacy.
82558270 ///
@@ -8264,20 +8279,27 @@ macro_rules! create_offer_builder { ($self: ident, $builder: ty) => {
82648279 ///
82658280 /// [`Offer`]: crate::offers::offer::Offer
82668281 /// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
8267- pub fn create_offer_builder(&$self) -> Result<$builder, Bolt12SemanticError> {
8282+ pub fn create_offer_builder(
8283+ &$self, absolute_expiry: Option<Duration>
8284+ ) -> Result<$builder, Bolt12SemanticError> {
82688285 let node_id = $self.get_our_node_id();
82698286 let expanded_key = &$self.inbound_payment_key;
82708287 let entropy = &*$self.entropy_source;
82718288 let secp_ctx = &$self.secp_ctx;
82728289
8273- let path = $self.create_compact_blinded_path( )
8290+ let path = $self.create_blinded_path_using_absolute_expiry(absolute_expiry )
82748291 .map_err(|_| Bolt12SemanticError::MissingPaths)?;
82758292 let builder = OfferBuilder::deriving_signing_pubkey(
82768293 node_id, expanded_key, entropy, secp_ctx
82778294 )
82788295 .chain_hash($self.chain_hash)
82798296 .path(path);
82808297
8298+ let builder = match absolute_expiry {
8299+ None => builder,
8300+ Some(absolute_expiry) => builder.absolute_expiry(absolute_expiry),
8301+ };
8302+
82818303 Ok(builder.into())
82828304 }
82838305} }
@@ -8305,11 +8327,12 @@ macro_rules! create_refund_builder { ($self: ident, $builder: ty) => {
83058327 ///
83068328 /// # Privacy
83078329 ///
8308- /// Uses [`MessageRouter::create_compact_blinded_paths`] to construct a [`BlindedPath`] for the
8309- /// refund. However, if one is not found, uses a one-hop [`BlindedPath`] with
8330+ /// Uses [`MessageRouter`] to construct a [`BlindedPath`] for the refund based on the given
8331+ /// `absolute_expiry` according to [`MAX_SHORT_LIVED_RELATIVE_EXPIRY`]. See those docs for
8332+ /// privacy implications. However, if one is not found, uses a one-hop [`BlindedPath`] with
83108333 /// [`ChannelManager::get_our_node_id`] as the introduction node instead. In the latter case,
8311- /// the node must be announced, otherwise, there is no way to find a path to the introduction in
8312- /// order to send the [`Bolt12Invoice`].
8334+ /// the node must be announced, otherwise, there is no way to find a path to the introduction
8335+ /// node in order to send the [`Bolt12Invoice`].
83138336 ///
83148337 /// Also, uses a derived payer id in the refund for payer privacy.
83158338 ///
@@ -8338,7 +8361,7 @@ macro_rules! create_refund_builder { ($self: ident, $builder: ty) => {
83388361 let entropy = &*$self.entropy_source;
83398362 let secp_ctx = &$self.secp_ctx;
83408363
8341- let path = $self.create_compact_blinded_path( )
8364+ let path = $self.create_blinded_path_using_absolute_expiry(Some(absolute_expiry) )
83428365 .map_err(|_| Bolt12SemanticError::MissingPaths)?;
83438366 let builder = RefundBuilder::deriving_payer_id(
83448367 node_id, expanded_key, entropy, secp_ctx, amount_msats, payment_id
@@ -8688,6 +8711,38 @@ where
86888711 inbound_payment::get_payment_preimage(payment_hash, payment_secret, &self.inbound_payment_key)
86898712 }
86908713
8714+ /// Creates a blinded path by delegating to [`MessageRouter`] based on the path's intended
8715+ /// lifetime.
8716+ ///
8717+ /// Whether or not the path is compact depends on whether the path is short-lived or long-lived,
8718+ /// respectively, based on the given `absolute_expiry` as seconds since the Unix epoch. See
8719+ /// [`MAX_SHORT_LIVED_RELATIVE_EXPIRY`].
8720+ fn create_blinded_path_using_absolute_expiry(
8721+ &self, absolute_expiry: Option<Duration>
8722+ ) -> Result<BlindedPath, ()> {
8723+ let now = self.duration_since_epoch();
8724+ let max_short_lived_absolute_expiry = now.saturating_add(MAX_SHORT_LIVED_RELATIVE_EXPIRY);
8725+
8726+ if absolute_expiry.unwrap_or(Duration::MAX) <= max_short_lived_absolute_expiry {
8727+ self.create_compact_blinded_path()
8728+ } else {
8729+ self.create_blinded_path()
8730+ }
8731+ }
8732+
8733+ pub(super) fn duration_since_epoch(&self) -> Duration {
8734+ #[cfg(not(feature = "std"))]
8735+ let now = Duration::from_secs(
8736+ self.highest_seen_timestamp.load(Ordering::Acquire) as u64
8737+ );
8738+ #[cfg(feature = "std")]
8739+ let now = std::time::SystemTime::now()
8740+ .duration_since(std::time::SystemTime::UNIX_EPOCH)
8741+ .expect("SystemTime::now() should come after SystemTime::UNIX_EPOCH");
8742+
8743+ now
8744+ }
8745+
86918746 /// Creates a blinded path by delegating to [`MessageRouter::create_blinded_paths`].
86928747 ///
86938748 /// Errors if the `MessageRouter` errors or returns an empty `Vec`.
0 commit comments