From 15fb61eeb05583569699aaf87b19ca17e1664808 Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Fri, 1 Aug 2025 15:01:31 -0400 Subject: [PATCH 01/12] Remove APIs to create static invoice/async offers Now that we've implemented the static invoice server protocol, these methods are not intended to ever be called by users. If someone did want to use these apis, they could use the equivalent methods on the OffersMessageFlow. --- lightning/src/ln/async_payments_tests.rs | 91 +++++++++++++++++------- lightning/src/ln/channelmanager.rs | 74 ++----------------- 2 files changed, 73 insertions(+), 92 deletions(-) diff --git a/lightning/src/ln/async_payments_tests.rs b/lightning/src/ln/async_payments_tests.rs index 7ee48225f78..54815f46f08 100644 --- a/lightning/src/ln/async_payments_tests.rs +++ b/lightning/src/ln/async_payments_tests.rs @@ -17,6 +17,7 @@ use crate::events::{ use crate::ln::blinded_payment_tests::{fail_blinded_htlc_backwards, get_blinded_route_parameters}; use crate::ln::channelmanager::{PaymentId, RecipientOnionFields}; use crate::ln::functional_test_utils::*; +use crate::ln::inbound_payment; use crate::ln::msgs; use crate::ln::msgs::{ BaseMessageHandler, ChannelMessageHandler, MessageSendEvent, OnionMessageHandler, @@ -36,8 +37,11 @@ use crate::offers::flow::{ }; use crate::offers::invoice_request::InvoiceRequest; use crate::offers::nonce::Nonce; -use crate::offers::offer::Offer; -use crate::offers::static_invoice::StaticInvoice; +use crate::offers::offer::{Amount, Offer}; +use crate::offers::static_invoice::{ + StaticInvoice, StaticInvoiceBuilder, + DEFAULT_RELATIVE_EXPIRY as STATIC_INVOICE_DEFAULT_RELATIVE_EXPIRY, +}; use crate::onion_message::async_payments::{AsyncPaymentsMessage, AsyncPaymentsMessageHandler}; use crate::onion_message::messenger::{ Destination, MessageRouter, MessageSendInstructions, PeeledOnion, @@ -240,10 +244,50 @@ fn pass_async_payments_oms( (held_htlc_available_om_1_2, release_held_htlc) } +fn create_static_invoice_builder<'a>( + recipient: &Node, offer: &'a Offer, offer_nonce: Nonce, relative_expiry: Option, +) -> StaticInvoiceBuilder<'a> { + let entropy = recipient.keys_manager; + let amount_msat = offer.amount().and_then(|amount| match amount { + Amount::Bitcoin { amount_msats } => Some(amount_msats), + Amount::Currency { .. } => None, + }); + + let relative_expiry = relative_expiry.unwrap_or(STATIC_INVOICE_DEFAULT_RELATIVE_EXPIRY); + let relative_expiry_secs: u32 = relative_expiry.as_secs().try_into().unwrap_or(u32::MAX); + + let created_at = recipient.node.duration_since_epoch(); + let payment_secret = inbound_payment::create_for_spontaneous_payment( + &recipient.keys_manager.get_inbound_payment_key(), + amount_msat, + relative_expiry_secs, + created_at.as_secs(), + None, + ) + .unwrap(); + + recipient + .node + .flow + .create_static_invoice_builder( + &recipient.router, + entropy, + offer, + offer_nonce, + payment_secret, + relative_expiry_secs, + recipient.node.list_usable_channels(), + recipient.node.test_get_peers_for_blinded_path(), + ) + .unwrap() +} + fn create_static_invoice( always_online_counterparty: &Node, recipient: &Node, relative_expiry: Option, secp_ctx: &Secp256k1, ) -> (Offer, StaticInvoice) { + let entropy_source = recipient.keys_manager; + let blinded_paths_to_always_online_node = always_online_counterparty .message_router .create_blinded_paths( @@ -256,15 +300,14 @@ fn create_static_invoice( .unwrap(); let (offer_builder, offer_nonce) = recipient .node - .create_async_receive_offer_builder(blinded_paths_to_always_online_node) + .flow + .create_async_receive_offer_builder(entropy_source, blinded_paths_to_always_online_node) .unwrap(); let offer = offer_builder.build().unwrap(); - let static_invoice = recipient - .node - .create_static_invoice_builder(&offer, offer_nonce, relative_expiry) - .unwrap() - .build_and_sign(&secp_ctx) - .unwrap(); + let static_invoice = + create_static_invoice_builder(recipient, &offer, offer_nonce, relative_expiry) + .build_and_sign(&secp_ctx) + .unwrap(); (offer, static_invoice) } @@ -377,6 +420,7 @@ fn static_invoice_unknown_required_features() { let node_cfgs = create_node_cfgs(3, &chanmon_cfgs); let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, None]); let nodes = create_network(3, &node_cfgs, &node_chanmgrs); + let entropy_source = nodes[2].keys_manager; create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0); create_unannounced_chan_between_nodes_with_value(&nodes, 1, 2, 1_000_000, 0); @@ -393,16 +437,15 @@ fn static_invoice_unknown_required_features() { .unwrap(); let (offer_builder, nonce) = nodes[2] .node - .create_async_receive_offer_builder(blinded_paths_to_always_online_node) + .flow + .create_async_receive_offer_builder(entropy_source, blinded_paths_to_always_online_node) .unwrap(); let offer = offer_builder.build().unwrap(); - let static_invoice_unknown_req_features = nodes[2] - .node - .create_static_invoice_builder(&offer, nonce, None) - .unwrap() - .features_unchecked(Bolt12InvoiceFeatures::unknown()) - .build_and_sign(&secp_ctx) - .unwrap(); + let static_invoice_unknown_req_features = + create_static_invoice_builder(&nodes[2], &offer, nonce, None) + .features_unchecked(Bolt12InvoiceFeatures::unknown()) + .build_and_sign(&secp_ctx) + .unwrap(); // Initiate payment to the offer corresponding to the manually-constructed invoice that has // unknown required features. @@ -1073,6 +1116,7 @@ fn invalid_async_receive_with_retry( create_node_chanmgrs(3, &node_cfgs, &[None, Some(allow_priv_chan_fwds_cfg), None]); let nodes = create_network(3, &node_cfgs, &node_chanmgrs); + let entropy_source = nodes[2].keys_manager; create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0); create_unannounced_chan_between_nodes_with_value(&nodes, 1, 2, 1_000_000, 0); @@ -1102,7 +1146,8 @@ fn invalid_async_receive_with_retry( .unwrap(); let (offer_builder, offer_nonce) = nodes[2] .node - .create_async_receive_offer_builder(blinded_paths_to_always_online_node) + .flow + .create_async_receive_offer_builder(entropy_source, blinded_paths_to_always_online_node) .unwrap(); let offer = offer_builder.build().unwrap(); let amt_msat = 5000; @@ -1112,12 +1157,10 @@ fn invalid_async_receive_with_retry( // use the same nodes to avoid complicating the test with a bunch of extra nodes. let mut static_invoice_paths = Vec::new(); for _ in 0..3 { - let static_inv_for_path = nodes[2] - .node - .create_static_invoice_builder(&offer, offer_nonce, None) - .unwrap() - .build_and_sign(&secp_ctx) - .unwrap(); + let static_inv_for_path = + create_static_invoice_builder(&nodes[2], &offer, offer_nonce, None) + .build_and_sign(&secp_ctx) + .unwrap(); static_invoice_paths.push(static_inv_for_path.payment_paths()[0].clone()); } nodes[2].router.expect_blinded_payment_paths(static_invoice_paths); diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 8fa34d7c349..fa584dda468 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -138,12 +138,7 @@ use crate::util::wakers::{Future, Notifier}; use crate::blinded_path::payment::BlindedPaymentPath; #[cfg(async_payments)] use { - crate::blinded_path::message::BlindedMessagePath, - crate::offers::offer::Amount, - crate::offers::static_invoice::{ - StaticInvoice, StaticInvoiceBuilder, - DEFAULT_RELATIVE_EXPIRY as STATIC_INVOICE_DEFAULT_RELATIVE_EXPIRY, - }, + crate::blinded_path::message::BlindedMessagePath, crate::offers::static_invoice::StaticInvoice, }; #[cfg(feature = "dnssec")] @@ -11900,68 +11895,6 @@ where Ok(offer) } - /// Create an offer for receiving async payments as an often-offline recipient. - /// - /// Instead of using this method, it is preferable to call - /// [`Self::set_paths_to_static_invoice_server`] and retrieve the automatically built offer via - /// [`Self::get_async_receive_offer`]. - /// - /// If you want to build the [`StaticInvoice`] manually using this method instead, you MUST: - /// 1. Provide at least 1 [`BlindedMessagePath`] terminating at an always-online node that will - /// serve the [`StaticInvoice`] created from this offer on our behalf. - /// 2. Use [`Self::create_static_invoice_builder`] to create a [`StaticInvoice`] from this - /// [`Offer`] plus the returned [`Nonce`], and provide the static invoice to the - /// aforementioned always-online node. - #[cfg(async_payments)] - pub fn create_async_receive_offer_builder( - &self, message_paths_to_always_online_node: Vec, - ) -> Result<(OfferBuilder, Nonce), Bolt12SemanticError> { - let entropy = &*self.entropy_source; - self.flow.create_async_receive_offer_builder(entropy, message_paths_to_always_online_node) - } - - /// Creates a [`StaticInvoiceBuilder`] from the corresponding [`Offer`] and [`Nonce`] that were - /// created via [`Self::create_async_receive_offer_builder`]. If `relative_expiry` is unset, the - /// invoice's expiry will default to [`STATIC_INVOICE_DEFAULT_RELATIVE_EXPIRY`]. - /// - /// Instead of using this method to manually build the invoice, it is preferable to set - /// [`Self::set_paths_to_static_invoice_server`] and retrieve the automatically built offer via - /// [`Self::get_async_receive_offer`]. - #[cfg(async_payments)] - pub fn create_static_invoice_builder<'a>( - &self, offer: &'a Offer, offer_nonce: Nonce, relative_expiry: Option, - ) -> Result, Bolt12SemanticError> { - let entropy = &*self.entropy_source; - let amount_msat = offer.amount().and_then(|amount| match amount { - Amount::Bitcoin { amount_msats } => Some(amount_msats), - Amount::Currency { .. } => None, - }); - - let relative_expiry = relative_expiry.unwrap_or(STATIC_INVOICE_DEFAULT_RELATIVE_EXPIRY); - let relative_expiry_secs: u32 = relative_expiry.as_secs().try_into().unwrap_or(u32::MAX); - - let created_at = self.duration_since_epoch(); - let payment_secret = inbound_payment::create_for_spontaneous_payment( - &self.inbound_payment_key, - amount_msat, - relative_expiry_secs, - created_at.as_secs(), - None, - ) - .map_err(|()| Bolt12SemanticError::InvalidAmount)?; - - self.flow.create_static_invoice_builder( - &self.router, - entropy, - offer, - offer_nonce, - payment_secret, - relative_expiry_secs, - self.list_usable_channels(), - self.get_peers_for_blinded_path(), - ) - } - /// Sets the [`BlindedMessagePath`]s that we will use as an async recipient to interactively build /// [`Offer`]s with a static invoice server, so the server can serve [`StaticInvoice`]s to payers /// on our behalf when we're offline. @@ -12413,6 +12346,11 @@ where .collect::>() } + #[cfg(all(test, async_payments))] + pub(super) fn test_get_peers_for_blinded_path(&self) -> Vec { + self.get_peers_for_blinded_path() + } + #[cfg(all(test, async_payments))] /// Creates multi-hop blinded payment paths for the given `amount_msats` by delegating to /// [`Router::create_blinded_payment_paths`]. From e78774080003b8d7b34cbfd552f7eaf4bb92e1a4 Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Fri, 1 Aug 2025 15:54:08 -0400 Subject: [PATCH 02/12] Expose async offer cache API to OffersMessageFlow users We want the OffersMessageFlow to be usable by non-ChannelManager users, so expose the required methods to enable that. Also a few corresponding docs tweaks. --- .../src/offers/async_receive_offer_cache.rs | 4 +- lightning/src/offers/flow.rs | 39 +++++++++++-------- lightning/src/offers/mod.rs | 2 +- 3 files changed, 26 insertions(+), 19 deletions(-) diff --git a/lightning/src/offers/async_receive_offer_cache.rs b/lightning/src/offers/async_receive_offer_cache.rs index 3a7857ffd0f..d8a45cbc483 100644 --- a/lightning/src/offers/async_receive_offer_cache.rs +++ b/lightning/src/offers/async_receive_offer_cache.rs @@ -153,7 +153,7 @@ impl AsyncReceiveOfferCache { /// /// [`StaticInvoice`]: crate::offers::static_invoice::StaticInvoice #[cfg(async_payments)] - pub fn set_paths_to_static_invoice_server( + pub(crate) fn set_paths_to_static_invoice_server( &mut self, paths_to_static_invoice_server: Vec, ) -> Result<(), ()> { if paths_to_static_invoice_server.is_empty() { @@ -211,7 +211,7 @@ impl AsyncReceiveOfferCache { /// // We need to re-persist the cache if a fresh offer was just marked as used to ensure we continue // to keep this offer's invoice updated and don't replace it with the server. - pub fn get_async_receive_offer( + pub(crate) fn get_async_receive_offer( &mut self, duration_since_epoch: Duration, ) -> Result<(Offer, bool), ()> { self.prune_expired_offers(duration_since_epoch, false); diff --git a/lightning/src/offers/flow.rs b/lightning/src/offers/flow.rs index cd3f95e0841..dde7d58a0b9 100644 --- a/lightning/src/offers/flow.rs +++ b/lightning/src/offers/flow.rs @@ -153,8 +153,9 @@ where /// If we are an async recipient, on startup we'll interactively build offers and static invoices /// with an always-online node that will serve static invoices on our behalf. Once the offer is /// built and the static invoice is confirmed as persisted by the server, the underlying - /// [`AsyncReceiveOfferCache`] should be persisted so we remember the offers we've built. - pub(crate) fn with_async_payments_offers_cache( + /// [`AsyncReceiveOfferCache`] should be persisted using + /// [`Self::writeable_async_receive_offer_cache`] so we remember the offers we've built. + pub fn with_async_payments_offers_cache( mut self, async_receive_offer_cache: AsyncReceiveOfferCache, ) -> Self { self.paths_to_static_invoice_server = @@ -170,7 +171,7 @@ where /// This method only needs to be called once when the server first takes on the recipient as a /// client, or when the paths change, e.g. if the paths are set to expire at a particular time. #[cfg(async_payments)] - pub(crate) fn set_paths_to_static_invoice_server( + pub fn set_paths_to_static_invoice_server( &self, paths_to_static_invoice_server: Vec, ) -> Result<(), ()> { // Store the paths in the async receive cache so they are persisted with the cache, but also @@ -286,7 +287,7 @@ where /// /// Errors if blinded path creation fails or the provided `recipient_id` is larger than 1KiB. #[cfg(async_payments)] - pub(crate) fn blinded_paths_for_async_recipient( + pub fn blinded_paths_for_async_recipient( &self, recipient_id: Vec, relative_expiry: Option, peers: Vec, ) -> Result, ()> { @@ -1113,9 +1114,11 @@ where Ok(()) } - /// Forwards a [`StaticInvoice`] over the provided `responder`. + /// Forwards a [`StaticInvoice`] over the provided [`Responder`] in response to an + /// [`InvoiceRequest`] that we as a static invoice server received on behalf of an often-offline + /// recipient. #[cfg(async_payments)] - pub(crate) fn enqueue_static_invoice( + pub fn enqueue_static_invoice( &self, invoice: StaticInvoice, responder: Responder, ) -> Result<(), Bolt12SemanticError> { let duration_since_epoch = self.duration_since_epoch(); @@ -1226,8 +1229,11 @@ where /// Retrieve an [`Offer`] for receiving async payments as an often-offline recipient. Will only /// return an offer if [`Self::set_paths_to_static_invoice_server`] was called and we succeeded in /// interactively building a [`StaticInvoice`] with the static invoice server. + /// + /// Returns the requested offer as well as a bool indicating whether the cache needs to be + /// persisted using [`Self::writeable_async_receive_offer_cache`]. #[cfg(async_payments)] - pub(crate) fn get_async_receive_offer(&self) -> Result<(Offer, bool), ()> { + pub fn get_async_receive_offer(&self) -> Result<(Offer, bool), ()> { let mut cache = self.async_receive_offer_cache.lock().unwrap(); cache.get_async_receive_offer(self.duration_since_epoch()) } @@ -1249,7 +1255,7 @@ where /// /// Errors if we failed to create blinded reply paths when sending an [`OfferPathsRequest`] message. #[cfg(async_payments)] - pub(crate) fn check_refresh_async_receive_offer_cache( + pub fn check_refresh_async_receive_offer_cache( &self, peers: Vec, usable_channels: Vec, entropy: ES, router: R, timer_tick_occurred: bool, ) -> Result<(), ()> @@ -1385,7 +1391,7 @@ where /// wants us (the static invoice server) to serve [`StaticInvoice`]s to payers on their behalf. /// Sends out [`OfferPaths`] onion messages in response. #[cfg(async_payments)] - pub(crate) fn handle_offer_paths_request( + pub fn handle_offer_paths_request( &self, context: AsyncPaymentsContext, peers: Vec, entropy_source: ES, ) -> Option<(OfferPaths, MessageContext)> where @@ -1448,7 +1454,7 @@ where /// Returns `None` if we have enough offers cached already, verification of `message` fails, or we /// fail to create blinded paths. #[cfg(async_payments)] - pub(crate) fn handle_offer_paths( + pub fn handle_offer_paths( &self, message: OfferPaths, context: AsyncPaymentsContext, responder: Responder, peers: Vec, usable_channels: Vec, entropy: ES, router: R, @@ -1599,7 +1605,7 @@ where /// the static invoice from the database. /// /// Errors if the [`ServeStaticInvoice::invoice`] is expired or larger than - /// [`MAX_STATIC_INVOICE_SIZE_BYTES`], or if blinded path verification fails. + /// [`MAX_STATIC_INVOICE_SIZE_BYTES`]. /// /// [`ServeStaticInvoice::invoice`]: crate::onion_message::async_payments::ServeStaticInvoice::invoice #[cfg(async_payments)] @@ -1641,17 +1647,18 @@ where } /// Handles an incoming [`StaticInvoicePersisted`] onion message from the static invoice server. - /// Returns a bool indicating whether the async receive offer cache needs to be re-persisted. + /// Returns a bool indicating whether the async receive offer cache needs to be re-persisted using + /// [`Self::writeable_async_receive_offer_cache`]. /// /// [`StaticInvoicePersisted`]: crate::onion_message::async_payments::StaticInvoicePersisted #[cfg(async_payments)] - pub(crate) fn handle_static_invoice_persisted(&self, context: AsyncPaymentsContext) -> bool { + pub fn handle_static_invoice_persisted(&self, context: AsyncPaymentsContext) -> bool { let mut cache = self.async_receive_offer_cache.lock().unwrap(); cache.static_invoice_persisted(context, self.duration_since_epoch()) } - /// Get the `AsyncReceiveOfferCache` for persistence. - pub(crate) fn writeable_async_receive_offer_cache(&self) -> impl Writeable + '_ { - &self.async_receive_offer_cache + /// Get the [`AsyncReceiveOfferCache`] for persistence. + pub fn writeable_async_receive_offer_cache(&self) -> impl Writeable + '_ { + self.async_receive_offer_cache.encode() } } diff --git a/lightning/src/offers/mod.rs b/lightning/src/offers/mod.rs index b603deecd60..5b5cf6cdc78 100644 --- a/lightning/src/offers/mod.rs +++ b/lightning/src/offers/mod.rs @@ -16,7 +16,7 @@ pub mod offer; pub mod flow; -pub(crate) mod async_receive_offer_cache; +pub mod async_receive_offer_cache; pub mod invoice; pub mod invoice_error; mod invoice_macros; From 3691e8f2452ee561e7a22ec58afb942866ba5809 Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Mon, 11 Aug 2025 13:55:34 -0400 Subject: [PATCH 03/12] Update API for writeable async offer cache to be bytes The previous API wouldn't work for language bindings. --- lightning/src/ln/channelmanager.rs | 4 ++-- lightning/src/offers/flow.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index fa584dda468..923632a3362 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -130,7 +130,7 @@ use crate::util::logger::{Level, Logger, WithContext}; use crate::util::scid_utils::fake_scid; use crate::util::ser::{ BigSize, FixedLengthReader, LengthReadable, MaybeReadable, Readable, ReadableArgs, VecWriter, - Writeable, Writer, + WithoutLength, Writeable, Writer, }; use crate::util::wakers::{Future, Notifier}; @@ -15248,7 +15248,7 @@ where (15, self.inbound_payment_id_secret, required), (17, in_flight_monitor_updates, option), (19, peer_storage_dir, optional_vec), - (21, self.flow.writeable_async_receive_offer_cache(), required), + (21, WithoutLength(&self.flow.writeable_async_receive_offer_cache()), required), }); Ok(()) diff --git a/lightning/src/offers/flow.rs b/lightning/src/offers/flow.rs index dde7d58a0b9..6bc9f3c3766 100644 --- a/lightning/src/offers/flow.rs +++ b/lightning/src/offers/flow.rs @@ -1657,8 +1657,8 @@ where cache.static_invoice_persisted(context, self.duration_since_epoch()) } - /// Get the [`AsyncReceiveOfferCache`] for persistence. - pub fn writeable_async_receive_offer_cache(&self) -> impl Writeable + '_ { + /// Get the encoded [`AsyncReceiveOfferCache`] for persistence. + pub fn writeable_async_receive_offer_cache(&self) -> Vec { self.async_receive_offer_cache.encode() } } From 7833e535d1cf9ea1ae6b74bba5c768a4328d8298 Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Fri, 1 Aug 2025 16:32:17 -0400 Subject: [PATCH 04/12] Static invoice server: fix incorrect comment The comment stated we need peers to be connected in order to send certain onion messages, but we actually need those peers available in order to create reply paths. --- lightning/src/ln/channelmanager.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 923632a3362..8846f1ced32 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -12853,7 +12853,7 @@ where // While we usually refresh the AsyncReceiveOfferCache on a timer, we also want to start // interactively building offers as soon as we can after startup. We can't start building offers - // until we have some peer connection(s) to send onion messages over, so as a minor optimization + // until we have some peer connection(s) to receive onion messages over, so as a minor optimization // refresh the cache when a peer connects. #[cfg(async_payments)] self.check_refresh_async_receive_offer_cache(false); From 5eb8a0f1d7d5689010503e6d4ba787bc2c2f1c6c Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Fri, 1 Aug 2025 16:33:08 -0400 Subject: [PATCH 05/12] De-dedup paths between flow and async offer cache We were previously duplicating paths_to_static_invoice_server between the OffersMessageFlow and the AsyncReceiveOfferCache, even though the paths were only used in one place in the flow. De-duplicate that to remove the risk of the structs getting out-of-sync. --- .../src/offers/async_receive_offer_cache.rs | 4 +-- lightning/src/offers/flow.rs | 31 ++++++------------- 2 files changed, 11 insertions(+), 24 deletions(-) diff --git a/lightning/src/offers/async_receive_offer_cache.rs b/lightning/src/offers/async_receive_offer_cache.rs index d8a45cbc483..94ff3adacc4 100644 --- a/lightning/src/offers/async_receive_offer_cache.rs +++ b/lightning/src/offers/async_receive_offer_cache.rs @@ -143,8 +143,8 @@ impl AsyncReceiveOfferCache { } } - pub(super) fn paths_to_static_invoice_server(&self) -> Vec { - self.paths_to_static_invoice_server.clone() + pub(super) fn paths_to_static_invoice_server(&self) -> &[BlindedMessagePath] { + &self.paths_to_static_invoice_server[..] } /// Sets the [`BlindedMessagePath`]s that we will use as an async recipient to interactively build diff --git a/lightning/src/offers/flow.rs b/lightning/src/offers/flow.rs index 6bc9f3c3766..57baac9a20f 100644 --- a/lightning/src/offers/flow.rs +++ b/lightning/src/offers/flow.rs @@ -104,9 +104,6 @@ where pending_async_payments_messages: Mutex>, async_receive_offer_cache: Mutex, - /// Blinded paths used to request offer paths from the static invoice server, if we are an async - /// recipient. - paths_to_static_invoice_server: Mutex>, #[cfg(feature = "dnssec")] pub(crate) hrn_resolver: OMNameResolver, @@ -146,7 +143,6 @@ where pending_dns_onion_messages: Mutex::new(Vec::new()), async_receive_offer_cache: Mutex::new(AsyncReceiveOfferCache::new()), - paths_to_static_invoice_server: Mutex::new(Vec::new()), } } @@ -158,8 +154,6 @@ where pub fn with_async_payments_offers_cache( mut self, async_receive_offer_cache: AsyncReceiveOfferCache, ) -> Self { - self.paths_to_static_invoice_server = - Mutex::new(async_receive_offer_cache.paths_to_static_invoice_server()); self.async_receive_offer_cache = Mutex::new(async_receive_offer_cache); self } @@ -174,15 +168,9 @@ where pub fn set_paths_to_static_invoice_server( &self, paths_to_static_invoice_server: Vec, ) -> Result<(), ()> { - // Store the paths in the async receive cache so they are persisted with the cache, but also - // store them in-memory in the `OffersMessageFlow` so the flow has access to them when building - // onion messages to send to the static invoice server, without introducing undesirable lock - // dependencies with the cache. - *self.paths_to_static_invoice_server.lock().unwrap() = - paths_to_static_invoice_server.clone(); - let mut cache = self.async_receive_offer_cache.lock().unwrap(); - cache.set_paths_to_static_invoice_server(paths_to_static_invoice_server) + cache.set_paths_to_static_invoice_server(paths_to_static_invoice_server.clone())?; + Ok(()) } /// Gets the node_id held by this [`OffersMessageFlow`]` @@ -1264,7 +1252,8 @@ where R::Target: Router, { // Terminate early if this node does not intend to receive async payments. - if self.paths_to_static_invoice_server.lock().unwrap().is_empty() { + let mut cache = self.async_receive_offer_cache.lock().unwrap(); + if cache.paths_to_static_invoice_server().is_empty() { return Ok(()); } @@ -1272,11 +1261,8 @@ where // Update the cache to remove expired offers, and check to see whether we need new offers to be // interactively built with the static invoice server. - let needs_new_offers = self - .async_receive_offer_cache - .lock() - .unwrap() - .prune_expired_offers(duration_since_epoch, timer_tick_occurred); + let needs_new_offers = + cache.prune_expired_offers(duration_since_epoch, timer_tick_occurred); // If we need new offers, send out offer paths request messages to the static invoice server. if needs_new_offers { @@ -1292,18 +1278,19 @@ where }; // We can't fail past this point, so indicate to the cache that we've requested new offers. - self.async_receive_offer_cache.lock().unwrap().new_offers_requested(); + cache.new_offers_requested(); let mut pending_async_payments_messages = self.pending_async_payments_messages.lock().unwrap(); let message = AsyncPaymentsMessage::OfferPathsRequest(OfferPathsRequest {}); enqueue_onion_message_with_reply_paths( message, - &self.paths_to_static_invoice_server.lock().unwrap()[..], + cache.paths_to_static_invoice_server(), reply_paths, &mut pending_async_payments_messages, ); } + core::mem::drop(cache); if timer_tick_occurred { self.check_refresh_static_invoices( From 7878a00e1ea3bbb7b3d569d03be24bd3c36546f2 Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Fri, 1 Aug 2025 17:11:41 -0400 Subject: [PATCH 06/12] check_refresh_static_invoices: remove duration param Makes the next commit a cleaner code move since the parameter will want to be removed then. --- lightning/src/offers/flow.rs | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/lightning/src/offers/flow.rs b/lightning/src/offers/flow.rs index 57baac9a20f..66bd58214e0 100644 --- a/lightning/src/offers/flow.rs +++ b/lightning/src/offers/flow.rs @@ -1293,13 +1293,7 @@ where core::mem::drop(cache); if timer_tick_occurred { - self.check_refresh_static_invoices( - peers, - usable_channels, - duration_since_epoch, - entropy, - router, - ); + self.check_refresh_static_invoices(peers, usable_channels, entropy, router); } Ok(()) @@ -1309,12 +1303,13 @@ where /// server, based on the offers provided by the cache. #[cfg(async_payments)] fn check_refresh_static_invoices( - &self, peers: Vec, usable_channels: Vec, - duration_since_epoch: Duration, entropy: ES, router: R, + &self, peers: Vec, usable_channels: Vec, entropy: ES, + router: R, ) where ES::Target: EntropySource, R::Target: Router, { + let duration_since_epoch = self.duration_since_epoch(); let mut serve_static_invoice_msgs = Vec::new(); { let cache = self.async_receive_offer_cache.lock().unwrap(); From 2d478dc8156ce7bb8d86748addbe1a97f689fbc7 Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Fri, 1 Aug 2025 17:14:30 -0400 Subject: [PATCH 07/12] Refresh async offers on set_inv_server_paths On startup, we want the async recipient to be set up with async receive offers as soon as possible. Therefore, attempt to send out initial offer_paths_requests as soon as the recipient is configured with blinded paths to connect to the static invoice server. --- lightning/src/ln/async_payments_tests.rs | 47 ++++++++++++- lightning/src/ln/channelmanager.rs | 3 +- lightning/src/offers/flow.rs | 84 +++++++++++++++--------- 3 files changed, 101 insertions(+), 33 deletions(-) diff --git a/lightning/src/ln/async_payments_tests.rs b/lightning/src/ln/async_payments_tests.rs index 54815f46f08..0e412a285f5 100644 --- a/lightning/src/ln/async_payments_tests.rs +++ b/lightning/src/ln/async_payments_tests.rs @@ -330,6 +330,38 @@ fn extract_payment_preimage(event: &Event) -> PaymentPreimage { } } +fn expect_offer_paths_requests(recipient: &Node, next_hop_nodes: &[&Node]) { + // We want to check that the async recipient has enqueued at least one `OfferPathsRequest` and no + // other message types. Check this by iterating through all their outbound onion messages, peeling + // multiple times if the messages are forwarded through other nodes. + let per_msg_recipient_msgs = recipient.onion_messenger.release_pending_msgs(); + let mut pk_to_msg = Vec::new(); + for (pk, msgs) in per_msg_recipient_msgs { + for msg in msgs { + pk_to_msg.push((pk, msg)); + } + } + let mut num_offer_paths_reqs: u8 = 0; + while let Some((pk, msg)) = pk_to_msg.pop() { + let node = next_hop_nodes.iter().find(|node| node.node.get_our_node_id() == pk).unwrap(); + let peeled_msg = node.onion_messenger.peel_onion_message(&msg).unwrap(); + match peeled_msg { + PeeledOnion::AsyncPayments(AsyncPaymentsMessage::OfferPathsRequest(_), _, _) => { + num_offer_paths_reqs += 1; + }, + PeeledOnion::Forward(next_hop, msg) => { + let next_pk = match next_hop { + crate::blinded_path::message::NextMessageHop::NodeId(pk) => pk, + _ => panic!(), + }; + pk_to_msg.push((next_pk, msg)); + }, + _ => panic!("Unexpected message"), + } + } + assert!(num_offer_paths_reqs > 0); +} + fn advance_time_by(duration: Duration, node: &Node) { let target_time = (node.node.duration_since_epoch() + duration).as_secs() as u32; let block = create_dummy_block(node.best_block_hash(), target_time, Vec::new()); @@ -512,6 +544,7 @@ fn ignore_unexpected_static_invoice() { let inv_server_paths = nodes[1].node.blinded_paths_for_async_recipient(recipient_id.clone(), None).unwrap(); nodes[2].node.set_paths_to_static_invoice_server(inv_server_paths).unwrap(); + expect_offer_paths_requests(&nodes[2], &[&nodes[0], &nodes[1]]); // Initiate payment to the sender's intended offer. let valid_static_invoice = @@ -609,6 +642,7 @@ fn async_receive_flow_success() { let inv_server_paths = nodes[1].node.blinded_paths_for_async_recipient(recipient_id.clone(), None).unwrap(); nodes[2].node.set_paths_to_static_invoice_server(inv_server_paths).unwrap(); + expect_offer_paths_requests(&nodes[2], &[&nodes[0], &nodes[1]]); let invoice_flow_res = pass_static_invoice_server_messages(&nodes[1], &nodes[2], recipient_id.clone()); @@ -671,6 +705,7 @@ fn expired_static_invoice_fail() { let inv_server_paths = nodes[1].node.blinded_paths_for_async_recipient(recipient_id.clone(), None).unwrap(); nodes[2].node.set_paths_to_static_invoice_server(inv_server_paths).unwrap(); + expect_offer_paths_requests(&nodes[2], &[&nodes[0], &nodes[1]]); let static_invoice = pass_static_invoice_server_messages(&nodes[1], &nodes[2], recipient_id.clone()).invoice; @@ -746,6 +781,7 @@ fn timeout_unreleased_payment() { let inv_server_paths = server.node.blinded_paths_for_async_recipient(recipient_id.clone(), None).unwrap(); recipient.node.set_paths_to_static_invoice_server(inv_server_paths).unwrap(); + expect_offer_paths_requests(&nodes[2], &[&nodes[0], &nodes[1]]); let static_invoice = pass_static_invoice_server_messages(server, recipient, recipient_id.clone()).invoice; @@ -831,6 +867,7 @@ fn async_receive_mpp() { let inv_server_paths = nodes[1].node.blinded_paths_for_async_recipient(recipient_id.clone(), None).unwrap(); nodes[3].node.set_paths_to_static_invoice_server(inv_server_paths).unwrap(); + expect_offer_paths_requests(&nodes[3], &[&nodes[0], &nodes[1], &nodes[2]]); let static_invoice = pass_static_invoice_server_messages(&nodes[1], &nodes[3], recipient_id.clone()).invoice; @@ -924,6 +961,7 @@ fn amount_doesnt_match_invreq() { let inv_server_paths = nodes[1].node.blinded_paths_for_async_recipient(recipient_id.clone(), None).unwrap(); nodes[3].node.set_paths_to_static_invoice_server(inv_server_paths).unwrap(); + expect_offer_paths_requests(&nodes[3], &[&nodes[0], &nodes[1], &nodes[2]]); let static_invoice = pass_static_invoice_server_messages(&nodes[1], &nodes[3], recipient_id.clone()).invoice; @@ -1124,6 +1162,7 @@ fn invalid_async_receive_with_retry( let inv_server_paths = nodes[1].node.blinded_paths_for_async_recipient(recipient_id.clone(), None).unwrap(); nodes[2].node.set_paths_to_static_invoice_server(inv_server_paths).unwrap(); + expect_offer_paths_requests(&nodes[2], &[&nodes[0], &nodes[1]]); // Set the random bytes so we can predict the offer nonce. let hardcoded_random_bytes = [42; 32]; @@ -1251,6 +1290,7 @@ fn expired_static_invoice_message_path() { let inv_server_paths = nodes[1].node.blinded_paths_for_async_recipient(recipient_id.clone(), None).unwrap(); nodes[2].node.set_paths_to_static_invoice_server(inv_server_paths).unwrap(); + expect_offer_paths_requests(&nodes[2], &[&nodes[0], &nodes[1]]); let static_invoice = pass_static_invoice_server_messages(&nodes[1], &nodes[2], recipient_id.clone()).invoice; @@ -1315,6 +1355,7 @@ fn expired_static_invoice_payment_path() { let inv_server_paths = nodes[1].node.blinded_paths_for_async_recipient(recipient_id.clone(), None).unwrap(); nodes[2].node.set_paths_to_static_invoice_server(inv_server_paths).unwrap(); + expect_offer_paths_requests(&nodes[2], &[&nodes[0], &nodes[1]]); // Make sure all nodes are at the same block height in preparation for CLTV timeout things. let node_max_height = @@ -1576,10 +1617,12 @@ fn limit_offer_paths_requests() { let inv_server_paths = server.node.blinded_paths_for_async_recipient(recipient_id, None).unwrap(); recipient.node.set_paths_to_static_invoice_server(inv_server_paths).unwrap(); + expect_offer_paths_requests(&nodes[1], &[&nodes[0]]); // Up to TEST_MAX_UPDATE_ATTEMPTS offer_paths_requests are allowed to be sent out before the async // recipient should give up. - for _ in 0..TEST_MAX_UPDATE_ATTEMPTS { + // Subtract 1 because we sent the first request when invoice server paths were set above. + for _ in 0..TEST_MAX_UPDATE_ATTEMPTS - 1 { recipient.node.test_check_refresh_async_receive_offers(); let offer_paths_req = recipient .onion_messenger @@ -1744,6 +1787,7 @@ fn refresh_static_invoices() { let inv_server_paths = server.node.blinded_paths_for_async_recipient(recipient_id.clone(), None).unwrap(); recipient.node.set_paths_to_static_invoice_server(inv_server_paths).unwrap(); + expect_offer_paths_requests(&nodes[2], &[&nodes[0], &nodes[1]]); // Set up the recipient to have one offer and an invoice with the static invoice server. let flow_res = pass_static_invoice_server_messages(server, recipient, recipient_id.clone()); @@ -2136,6 +2180,7 @@ fn invoice_server_is_not_channel_peer() { let inv_server_paths = invoice_server.node.blinded_paths_for_async_recipient(recipient_id.clone(), None).unwrap(); recipient.node.set_paths_to_static_invoice_server(inv_server_paths).unwrap(); + expect_offer_paths_requests(&nodes[2], &[&nodes[0], &nodes[1], &nodes[3]]); let invoice = pass_static_invoice_server_messages(invoice_server, recipient, recipient_id.clone()) .invoice; diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 8846f1ced32..b2ee350edee 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -11905,7 +11905,8 @@ where pub fn set_paths_to_static_invoice_server( &self, paths_to_static_invoice_server: Vec, ) -> Result<(), ()> { - self.flow.set_paths_to_static_invoice_server(paths_to_static_invoice_server)?; + let peers = self.get_peers_for_blinded_path(); + self.flow.set_paths_to_static_invoice_server(paths_to_static_invoice_server, peers)?; let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self); Ok(()) diff --git a/lightning/src/offers/flow.rs b/lightning/src/offers/flow.rs index 66bd58214e0..a80ba4b1759 100644 --- a/lightning/src/offers/flow.rs +++ b/lightning/src/offers/flow.rs @@ -162,14 +162,24 @@ where /// [`Offer`]s with a static invoice server, so the server can serve [`StaticInvoice`]s to payers /// on our behalf when we're offline. /// + /// This method will also send out messages initiating async offer creation to the static invoice + /// server, if any peers are connected. + /// /// This method only needs to be called once when the server first takes on the recipient as a /// client, or when the paths change, e.g. if the paths are set to expire at a particular time. #[cfg(async_payments)] pub fn set_paths_to_static_invoice_server( &self, paths_to_static_invoice_server: Vec, + peers: Vec, ) -> Result<(), ()> { let mut cache = self.async_receive_offer_cache.lock().unwrap(); cache.set_paths_to_static_invoice_server(paths_to_static_invoice_server.clone())?; + core::mem::drop(cache); + + // We'll only fail here if no peers are connected yet for us to create reply paths to outbound + // offer_paths_requests, so ignore the error. + let _ = self.check_refresh_async_offers(peers, false); + Ok(()) } @@ -1252,49 +1262,61 @@ where R::Target: Router, { // Terminate early if this node does not intend to receive async payments. - let mut cache = self.async_receive_offer_cache.lock().unwrap(); - if cache.paths_to_static_invoice_server().is_empty() { - return Ok(()); + { + let cache = self.async_receive_offer_cache.lock().unwrap(); + if cache.paths_to_static_invoice_server().is_empty() { + return Ok(()); + } } + self.check_refresh_async_offers(peers.clone(), timer_tick_occurred)?; + + if timer_tick_occurred { + self.check_refresh_static_invoices(peers, usable_channels, entropy, router); + } + + Ok(()) + } + + #[cfg(async_payments)] + fn check_refresh_async_offers( + &self, peers: Vec, timer_tick_occurred: bool, + ) -> Result<(), ()> { let duration_since_epoch = self.duration_since_epoch(); + let mut cache = self.async_receive_offer_cache.lock().unwrap(); // Update the cache to remove expired offers, and check to see whether we need new offers to be // interactively built with the static invoice server. let needs_new_offers = cache.prune_expired_offers(duration_since_epoch, timer_tick_occurred); + if !needs_new_offers { + return Ok(()); + } // If we need new offers, send out offer paths request messages to the static invoice server. - if needs_new_offers { - let context = MessageContext::AsyncPayments(AsyncPaymentsContext::OfferPaths { - path_absolute_expiry: duration_since_epoch - .saturating_add(TEMP_REPLY_PATH_RELATIVE_EXPIRY), - }); - let reply_paths = match self.create_blinded_paths(peers.clone(), context) { - Ok(paths) => paths, - Err(()) => { - return Err(()); - }, - }; - - // We can't fail past this point, so indicate to the cache that we've requested new offers. - cache.new_offers_requested(); + let context = MessageContext::AsyncPayments(AsyncPaymentsContext::OfferPaths { + path_absolute_expiry: duration_since_epoch + .saturating_add(TEMP_REPLY_PATH_RELATIVE_EXPIRY), + }); + let reply_paths = match self.create_blinded_paths(peers, context) { + Ok(paths) => paths, + Err(()) => { + return Err(()); + }, + }; - let mut pending_async_payments_messages = - self.pending_async_payments_messages.lock().unwrap(); - let message = AsyncPaymentsMessage::OfferPathsRequest(OfferPathsRequest {}); - enqueue_onion_message_with_reply_paths( - message, - cache.paths_to_static_invoice_server(), - reply_paths, - &mut pending_async_payments_messages, - ); - } - core::mem::drop(cache); + // We can't fail past this point, so indicate to the cache that we've requested new offers. + cache.new_offers_requested(); - if timer_tick_occurred { - self.check_refresh_static_invoices(peers, usable_channels, entropy, router); - } + let mut pending_async_payments_messages = + self.pending_async_payments_messages.lock().unwrap(); + let message = AsyncPaymentsMessage::OfferPathsRequest(OfferPathsRequest {}); + enqueue_onion_message_with_reply_paths( + message, + cache.paths_to_static_invoice_server(), + reply_paths, + &mut pending_async_payments_messages, + ); Ok(()) } From 0506fab03492aa667367529ee5ee95f60631dd3d Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Thu, 7 Aug 2025 16:43:28 -0400 Subject: [PATCH 08/12] Replace unused async offers based on creation time Previously, we were refreshing unused async receive offers based on when the corresponding invoice was confirmed as persisted. But the time when the invoice is persisted doesn't really tell us when the offer became stale, since messages may be delayed. Given we want to always have the freshest possible offer, we'd like to replace an unused offer with a new one every few hours based on the offer's age, regardless of when it was persisted by the server, which we now do. --- .../src/offers/async_receive_offer_cache.rs | 58 ++++++++----------- 1 file changed, 23 insertions(+), 35 deletions(-) diff --git a/lightning/src/offers/async_receive_offer_cache.rs b/lightning/src/offers/async_receive_offer_cache.rs index 94ff3adacc4..67eaf05100a 100644 --- a/lightning/src/offers/async_receive_offer_cache.rs +++ b/lightning/src/offers/async_receive_offer_cache.rs @@ -35,12 +35,7 @@ enum OfferStatus { /// have a maximally fresh offer. We always want to have at least 1 offer in this state, /// preferably a few so we can respond to user requests for new offers without returning the same /// one multiple times. Returning a new offer each time is better for privacy. - Ready { - /// If this offer's invoice has been persisted for some time, it's safe to replace to ensure we - /// always have the freshest possible offer available when the user goes to pull an offer from - /// the cache. - invoice_confirmed_persisted_at: Duration, - }, + Ready, /// This offer's invoice is not yet confirmed as persisted by the static invoice server, so it is /// not yet ready to receive payments. Pending, @@ -49,6 +44,9 @@ enum OfferStatus { #[derive(Clone)] struct AsyncReceiveOffer { offer: Offer, + /// The time as duration since the Unix epoch at which this offer was created. Useful when + /// refreshing unused offers. + created_at: Duration, /// Whether this offer is used, ready for use, or pending invoice persistence with the static /// invoice server. status: OfferStatus, @@ -63,9 +61,7 @@ struct AsyncReceiveOffer { impl_writeable_tlv_based_enum!(OfferStatus, (0, Used) => {}, - (1, Ready) => { - (0, invoice_confirmed_persisted_at, required), - }, + (1, Ready) => {}, (2, Pending) => {}, ); @@ -74,6 +70,7 @@ impl_writeable_tlv_based!(AsyncReceiveOffer, { (2, offer_nonce, required), (4, status, required), (6, update_static_invoice_path, required), + (8, created_at, required), }); /// If we are an often-offline recipient, we'll want to interactively build offers and static @@ -179,9 +176,9 @@ const MAX_CACHED_OFFERS_TARGET: usize = 10; #[cfg(async_payments)] const MAX_UPDATE_ATTEMPTS: u8 = 3; -// If we have an offer that is replaceable and its invoice was confirmed as persisted more than 2 -// hours ago, we can go ahead and refresh it because we always want to have the freshest offer -// possible when a user goes to retrieve a cached offer. +// If we have an offer that is replaceable and is more than 2 hours old, we can go ahead and refresh +// it because we always want to have the freshest offer possible when a user goes to retrieve a +// cached offer. // // We avoid replacing unused offers too quickly -- this prevents the case where we send multiple // invoices from different offers competing for the same slot to the server, messages are received @@ -216,14 +213,11 @@ impl AsyncReceiveOfferCache { ) -> Result<(Offer, bool), ()> { self.prune_expired_offers(duration_since_epoch, false); - // Find the freshest unused offer, where "freshness" is based on when the invoice was confirmed - // persisted by the server. See `OfferStatus::Ready`. + // Find the freshest unused offer. See `OfferStatus::Ready`. let newest_unused_offer_opt = self - .unused_offers() - .max_by(|(_, _, persisted_at_a), (_, _, persisted_at_b)| { - persisted_at_a.cmp(&persisted_at_b) - }) - .map(|(idx, offer, _)| (idx, offer.offer.clone())); + .unused_ready_offers() + .max_by(|(_, offer_a), (_, offer_b)| offer_a.created_at.cmp(&offer_b.created_at)) + .map(|(idx, offer)| (idx, offer.offer.clone())); if let Some((idx, newest_ready_offer)) = newest_unused_offer_opt { self.offers[idx].as_mut().map(|offer| offer.status = OfferStatus::Used); return Ok((newest_ready_offer, true)); @@ -316,6 +310,7 @@ impl AsyncReceiveOfferCache { Some(offer_opt) => { *offer_opt = Some(AsyncReceiveOffer { offer, + created_at: duration_since_epoch, offer_nonce, status: OfferStatus::Pending, update_static_invoice_path, @@ -365,15 +360,11 @@ impl AsyncReceiveOfferCache { // Filter for unused offers where longer than OFFER_REFRESH_THRESHOLD time has passed since they // were last updated, so they are stale enough to warrant replacement. let awhile_ago = duration_since_epoch.saturating_sub(OFFER_REFRESH_THRESHOLD); - self.unused_offers() - .filter(|(_, _, invoice_confirmed_persisted_at)| { - *invoice_confirmed_persisted_at < awhile_ago - }) + self.unused_ready_offers() + .filter(|(_, offer)| offer.created_at < awhile_ago) // Get the stalest offer and return its index - .min_by(|(_, _, persisted_at_a), (_, _, persisted_at_b)| { - persisted_at_a.cmp(&persisted_at_b) - }) - .map(|(idx, _, _)| idx) + .min_by(|(_, offer_a), (_, offer_b)| offer_a.created_at.cmp(&offer_b.created_at)) + .map(|(idx, _)| idx) } /// Returns an iterator over (offer_idx, offer) @@ -387,13 +378,11 @@ impl AsyncReceiveOfferCache { }) } - /// Returns an iterator over (offer_idx, offer, invoice_confirmed_persisted_at) - /// where all returned offers are [`OfferStatus::Ready`] - fn unused_offers(&self) -> impl Iterator { + /// Returns an iterator over (offer_idx, offer) where all returned offers are + /// [`OfferStatus::Ready`] + fn unused_ready_offers(&self) -> impl Iterator { self.offers_with_idx().filter_map(|(idx, offer)| match offer.status { - OfferStatus::Ready { invoice_confirmed_persisted_at } => { - Some((idx, offer, invoice_confirmed_persisted_at)) - }, + OfferStatus::Ready => Some((idx, offer)), _ => None, }) } @@ -464,8 +453,7 @@ impl AsyncReceiveOfferCache { return false; } - offer.status = - OfferStatus::Ready { invoice_confirmed_persisted_at: duration_since_epoch }; + offer.status = OfferStatus::Ready; return true; } From 37ab3d3ade9b6d6e45be7d13a050f306c116f159 Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Thu, 7 Aug 2025 18:11:34 -0400 Subject: [PATCH 09/12] Async recipient: track static invoice creation time Start tracking invoice creation time in cached async offers. This field will be used in the next commit to start only updating static invoices for Used offers every few hours instead of once a minute. We also remove the blinded path context StaticInvoicePersisted::path_absolute_expiry field here, replacing it with the new invoice_created_at field. We don't actually want to terminate early if the reply path a bit stale like we did before, since we want to use the invoice_created_at field regardless to drive a faster refresh of the invoice. --- lightning/src/blinded_path/message.rs | 9 +-- lightning/src/ln/async_payments_tests.rs | 48 ----------- .../src/offers/async_receive_offer_cache.rs | 80 +++++++++++-------- lightning/src/offers/flow.rs | 11 +-- 4 files changed, 52 insertions(+), 96 deletions(-) diff --git a/lightning/src/blinded_path/message.rs b/lightning/src/blinded_path/message.rs index 7db5dc00b05..142fe99fd86 100644 --- a/lightning/src/blinded_path/message.rs +++ b/lightning/src/blinded_path/message.rs @@ -506,10 +506,9 @@ pub enum AsyncPaymentsContext { /// [`StaticInvoice`]: crate::offers::static_invoice::StaticInvoice /// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest offer_id: OfferId, - /// The time as duration since the Unix epoch at which this path expires and messages sent over - /// it should be ignored. If we receive confirmation of an invoice over this path after its - /// expiry, it may be outdated and a new invoice update should be sent instead. - path_absolute_expiry: core::time::Duration, + /// The time as duration since the Unix epoch at which the invoice corresponding to this path + /// was created. Useful to know when an invoice needs replacement. + invoice_created_at: core::time::Duration, }, /// Context contained within the reply [`BlindedMessagePath`] we put in outbound /// [`HeldHtlcAvailable`] messages, provided back to us in corresponding [`ReleaseHeldHtlc`] @@ -577,7 +576,7 @@ impl_writeable_tlv_based_enum!(AsyncPaymentsContext, }, (3, StaticInvoicePersisted) => { (0, offer_id, required), - (2, path_absolute_expiry, required), + (2, invoice_created_at, required), }, (4, OfferPathsRequest) => { (0, recipient_id, required), diff --git a/lightning/src/ln/async_payments_tests.rs b/lightning/src/ln/async_payments_tests.rs index 0e412a285f5..4da31452ec5 100644 --- a/lightning/src/ln/async_payments_tests.rs +++ b/lightning/src/ln/async_payments_tests.rs @@ -1554,54 +1554,6 @@ fn ignore_expired_offer_paths_message() { .is_none()); } -#[cfg_attr(feature = "std", ignore)] -#[test] -fn ignore_expired_invoice_persisted_message() { - // If the recipient receives a static_invoice_persisted message over an expired reply path, it - // should be ignored. - let chanmon_cfgs = create_chanmon_cfgs(2); - let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); - let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); - let nodes = create_network(2, &node_cfgs, &node_chanmgrs); - create_unannounced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0); - let server = &nodes[0]; - let recipient = &nodes[1]; - - let recipient_id = vec![42; 32]; - let inv_server_paths = - server.node.blinded_paths_for_async_recipient(recipient_id, None).unwrap(); - recipient.node.set_paths_to_static_invoice_server(inv_server_paths).unwrap(); - - // Exchange messages until we can extract the final static_invoice_persisted OM. - recipient.node.timer_tick_occurred(); - let serve_static_invoice = invoice_flow_up_to_send_serve_static_invoice(server, recipient).1; - server - .onion_messenger - .handle_onion_message(recipient.node.get_our_node_id(), &serve_static_invoice); - let mut events = server.node.get_and_clear_pending_events(); - assert_eq!(events.len(), 1); - let ack_path = match events.pop().unwrap() { - Event::PersistStaticInvoice { invoice_persisted_path, .. } => invoice_persisted_path, - _ => panic!(), - }; - - server.node.static_invoice_persisted(ack_path); - let invoice_persisted = server - .onion_messenger - .next_onion_message_for_peer(recipient.node.get_our_node_id()) - .unwrap(); - assert!(matches!( - recipient.onion_messenger.peel_onion_message(&invoice_persisted).unwrap(), - PeeledOnion::AsyncPayments(AsyncPaymentsMessage::StaticInvoicePersisted(_), _, _) - )); - - advance_time_by(TEST_TEMP_REPLY_PATH_RELATIVE_EXPIRY + Duration::from_secs(1), recipient); - recipient - .onion_messenger - .handle_onion_message(server.node.get_our_node_id(), &invoice_persisted); - assert!(recipient.node.get_async_receive_offer().is_err()); -} - #[test] fn limit_offer_paths_requests() { // Limit the number of offer_paths_requests sent to the server if they aren't responding. diff --git a/lightning/src/offers/async_receive_offer_cache.rs b/lightning/src/offers/async_receive_offer_cache.rs index 67eaf05100a..7c572967016 100644 --- a/lightning/src/offers/async_receive_offer_cache.rs +++ b/lightning/src/offers/async_receive_offer_cache.rs @@ -30,12 +30,20 @@ use crate::blinded_path::message::AsyncPaymentsContext; enum OfferStatus { /// This offer has been returned to the user from the cache, so it needs to be stored until it /// expires and its invoice needs to be kept updated. - Used, + Used { + /// The creation time of the invoice that was last confirmed as persisted by the server. Useful + /// to know when the invoice needs refreshing. + invoice_created_at: Duration, + }, /// This offer has not yet been returned to the user, and is safe to replace to ensure we always /// have a maximally fresh offer. We always want to have at least 1 offer in this state, /// preferably a few so we can respond to user requests for new offers without returning the same /// one multiple times. Returning a new offer each time is better for privacy. - Ready, + Ready { + /// The creation time of the invoice that was last confirmed as persisted by the server. Useful + /// to know when the invoice needs refreshing. + invoice_created_at: Duration, + }, /// This offer's invoice is not yet confirmed as persisted by the static invoice server, so it is /// not yet ready to receive payments. Pending, @@ -60,8 +68,12 @@ struct AsyncReceiveOffer { } impl_writeable_tlv_based_enum!(OfferStatus, - (0, Used) => {}, - (1, Ready) => {}, + (0, Used) => { + (0, invoice_created_at, required), + }, + (1, Ready) => { + (0, invoice_created_at, required), + }, (2, Pending) => {}, ); @@ -216,16 +228,18 @@ impl AsyncReceiveOfferCache { // Find the freshest unused offer. See `OfferStatus::Ready`. let newest_unused_offer_opt = self .unused_ready_offers() - .max_by(|(_, offer_a), (_, offer_b)| offer_a.created_at.cmp(&offer_b.created_at)) - .map(|(idx, offer)| (idx, offer.offer.clone())); - if let Some((idx, newest_ready_offer)) = newest_unused_offer_opt { - self.offers[idx].as_mut().map(|offer| offer.status = OfferStatus::Used); + .max_by(|(_, offer_a, _), (_, offer_b, _)| offer_a.created_at.cmp(&offer_b.created_at)) + .map(|(idx, offer, invoice_created_at)| (idx, offer.offer.clone(), invoice_created_at)); + if let Some((idx, newest_ready_offer, invoice_created_at)) = newest_unused_offer_opt { + self.offers[idx] + .as_mut() + .map(|offer| offer.status = OfferStatus::Used { invoice_created_at }); return Ok((newest_ready_offer, true)); } // If no unused offers are available, return the used offer with the latest absolute expiry self.offers_with_idx() - .filter(|(_, offer)| matches!(offer.status, OfferStatus::Used)) + .filter(|(_, offer)| matches!(offer.status, OfferStatus::Used { .. })) .max_by(|a, b| { let abs_expiry_a = a.1.offer.absolute_expiry().unwrap_or(Duration::MAX); let abs_expiry_b = b.1.offer.absolute_expiry().unwrap_or(Duration::MAX); @@ -338,9 +352,9 @@ impl AsyncReceiveOfferCache { } // If all of our offers are already used or pending, then none are available to be replaced - let no_replaceable_offers = self - .offers_with_idx() - .all(|(_, offer)| matches!(offer.status, OfferStatus::Used | OfferStatus::Pending)); + let no_replaceable_offers = self.offers_with_idx().all(|(_, offer)| { + matches!(offer.status, OfferStatus::Used { .. } | OfferStatus::Pending) + }); if no_replaceable_offers { return None; } @@ -350,7 +364,7 @@ impl AsyncReceiveOfferCache { let num_payable_offers = self .offers_with_idx() .filter(|(_, offer)| { - matches!(offer.status, OfferStatus::Used | OfferStatus::Ready { .. }) + matches!(offer.status, OfferStatus::Used { .. } | OfferStatus::Ready { .. }) }) .count(); if num_payable_offers <= 1 { @@ -361,10 +375,10 @@ impl AsyncReceiveOfferCache { // were last updated, so they are stale enough to warrant replacement. let awhile_ago = duration_since_epoch.saturating_sub(OFFER_REFRESH_THRESHOLD); self.unused_ready_offers() - .filter(|(_, offer)| offer.created_at < awhile_ago) + .filter(|(_, offer, _)| offer.created_at < awhile_ago) // Get the stalest offer and return its index - .min_by(|(_, offer_a), (_, offer_b)| offer_a.created_at.cmp(&offer_b.created_at)) - .map(|(idx, _)| idx) + .min_by(|(_, offer_a, _), (_, offer_b, _)| offer_a.created_at.cmp(&offer_b.created_at)) + .map(|(idx, _, _)| idx) } /// Returns an iterator over (offer_idx, offer) @@ -378,11 +392,11 @@ impl AsyncReceiveOfferCache { }) } - /// Returns an iterator over (offer_idx, offer) where all returned offers are + /// Returns an iterator over (offer_idx, offer, invoice_created_at) where all returned offers are /// [`OfferStatus::Ready`] - fn unused_ready_offers(&self) -> impl Iterator { + fn unused_ready_offers(&self) -> impl Iterator { self.offers_with_idx().filter_map(|(idx, offer)| match offer.status { - OfferStatus::Ready => Some((idx, offer)), + OfferStatus::Ready { invoice_created_at } => Some((idx, offer, invoice_created_at)), _ => None, }) } @@ -408,7 +422,7 @@ impl AsyncReceiveOfferCache { // them a fresh invoice on each timer tick. self.offers_with_idx().filter_map(|(idx, offer)| { let needs_invoice_update = - offer.status == OfferStatus::Used || offer.status == OfferStatus::Pending; + matches!(offer.status, OfferStatus::Used { .. } | OfferStatus::Pending); if needs_invoice_update { let offer_slot = idx.try_into().unwrap_or(u16::MAX); Some(( @@ -431,15 +445,10 @@ impl AsyncReceiveOfferCache { /// is needed. /// /// [`StaticInvoicePersisted`]: crate::onion_message::async_payments::StaticInvoicePersisted - pub(super) fn static_invoice_persisted( - &mut self, context: AsyncPaymentsContext, duration_since_epoch: Duration, - ) -> bool { - let offer_id = match context { - AsyncPaymentsContext::StaticInvoicePersisted { path_absolute_expiry, offer_id } => { - if duration_since_epoch > path_absolute_expiry { - return false; - } - offer_id + pub(super) fn static_invoice_persisted(&mut self, context: AsyncPaymentsContext) -> bool { + let (invoice_created_at, offer_id) = match context { + AsyncPaymentsContext::StaticInvoicePersisted { invoice_created_at, offer_id } => { + (invoice_created_at, offer_id) }, _ => return false, }; @@ -447,13 +456,14 @@ impl AsyncReceiveOfferCache { let mut offers = self.offers.iter_mut(); let offer_entry = offers.find(|o| o.as_ref().map_or(false, |o| o.offer.id() == offer_id)); if let Some(Some(ref mut offer)) = offer_entry { - if offer.status == OfferStatus::Used { - // We succeeded in updating the invoice for a used offer, no re-persistence of the cache - // needed - return false; + match offer.status { + OfferStatus::Used { invoice_created_at: ref mut inv_created_at } + | OfferStatus::Ready { invoice_created_at: ref mut inv_created_at } => { + *inv_created_at = core::cmp::min(invoice_created_at, *inv_created_at); + }, + OfferStatus::Pending => offer.status = OfferStatus::Ready { invoice_created_at }, } - offer.status = OfferStatus::Ready; return true; } @@ -465,7 +475,7 @@ impl AsyncReceiveOfferCache { self.offers_with_idx() .filter_map(|(_, offer)| { if matches!(offer.status, OfferStatus::Ready { .. }) - || matches!(offer.status, OfferStatus::Used) + || matches!(offer.status, OfferStatus::Used { .. }) { Some(offer.offer.clone()) } else { diff --git a/lightning/src/offers/flow.rs b/lightning/src/offers/flow.rs index a80ba4b1759..d576e2ccc9d 100644 --- a/lightning/src/offers/flow.rs +++ b/lightning/src/offers/flow.rs @@ -1331,7 +1331,6 @@ where ES::Target: EntropySource, R::Target: Router, { - let duration_since_epoch = self.duration_since_epoch(); let mut serve_static_invoice_msgs = Vec::new(); { let cache = self.async_receive_offer_cache.lock().unwrap(); @@ -1352,10 +1351,8 @@ where }; let reply_path_context = { - let path_absolute_expiry = - duration_since_epoch.saturating_add(TEMP_REPLY_PATH_RELATIVE_EXPIRY); MessageContext::AsyncPayments(AsyncPaymentsContext::StaticInvoicePersisted { - path_absolute_expiry, + invoice_created_at: invoice.created_at(), offer_id: offer.id(), }) }; @@ -1533,11 +1530,9 @@ where }; let reply_path_context = { - let path_absolute_expiry = - duration_since_epoch.saturating_add(TEMP_REPLY_PATH_RELATIVE_EXPIRY); MessageContext::AsyncPayments(AsyncPaymentsContext::StaticInvoicePersisted { offer_id, - path_absolute_expiry, + invoice_created_at: invoice.created_at(), }) }; @@ -1658,7 +1653,7 @@ where #[cfg(async_payments)] pub fn handle_static_invoice_persisted(&self, context: AsyncPaymentsContext) -> bool { let mut cache = self.async_receive_offer_cache.lock().unwrap(); - cache.static_invoice_persisted(context, self.duration_since_epoch()) + cache.static_invoice_persisted(context) } /// Get the encoded [`AsyncReceiveOfferCache`] for persistence. From be591c12ce2be7cae84a6d5b4958d9ced0fd582f Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Fri, 8 Aug 2025 12:50:54 -0400 Subject: [PATCH 10/12] Async receive: update static invoices when stale Previously, for every one of our async receive offers that are in use, we would send a fresh invoice once on every timer tick/once a minute. It'd be better to check if the invoice is actually kinda-stale before updating it to avoid bombarding the server, so we do so here using the new invoice_created field added in the previous commit. --- lightning/src/ln/async_payments_tests.rs | 65 +++++++++++++++---- .../src/offers/async_receive_offer_cache.rs | 22 +++++-- lightning/src/offers/flow.rs | 3 +- 3 files changed, 74 insertions(+), 16 deletions(-) diff --git a/lightning/src/ln/async_payments_tests.rs b/lightning/src/ln/async_payments_tests.rs index 4da31452ec5..d868eeeda80 100644 --- a/lightning/src/ln/async_payments_tests.rs +++ b/lightning/src/ln/async_payments_tests.rs @@ -28,7 +28,7 @@ use crate::ln::outbound_payment::{ PendingOutboundPayment, Retry, TEST_ASYNC_PAYMENT_TIMEOUT_RELATIVE_EXPIRY, }; use crate::offers::async_receive_offer_cache::{ - TEST_MAX_CACHED_OFFERS_TARGET, TEST_MAX_UPDATE_ATTEMPTS, + TEST_INVOICE_REFRESH_THRESHOLD, TEST_MAX_CACHED_OFFERS_TARGET, TEST_MAX_UPDATE_ATTEMPTS, TEST_MIN_OFFER_PATHS_RELATIVE_EXPIRY_SECS, TEST_OFFER_REFRESH_THRESHOLD, }; use crate::offers::flow::{ @@ -1715,11 +1715,53 @@ fn offer_cache_round_trip_ser() { assert_eq!(cached_offers_pre_ser, cached_offers_post_ser); } +#[test] +fn refresh_static_invoices_for_pending_offers() { + // Check that an invoice for an offer that is pending persistence with the server will be updated + // every timer tick. + let chanmon_cfgs = create_chanmon_cfgs(2); + let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None, None]); + let nodes = create_network(2, &node_cfgs, &node_chanmgrs); + create_unannounced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0); + let server = &nodes[0]; + let recipient = &nodes[1]; + + let recipient_id = vec![42; 32]; + let inv_server_paths = + server.node.blinded_paths_for_async_recipient(recipient_id.clone(), None).unwrap(); + recipient.node.set_paths_to_static_invoice_server(inv_server_paths).unwrap(); + expect_offer_paths_requests(&nodes[1], &[&nodes[0]]); + + // Set up the recipient to have one offer pending with the static invoice server. + invoice_flow_up_to_send_serve_static_invoice(server, recipient); + + // Every timer tick, we'll send a fresh invoice to the server. + for _ in 0..10 { + recipient.node.timer_tick_occurred(); + let pending_oms = recipient.onion_messenger.release_pending_msgs(); + pending_oms + .get(&server.node.get_our_node_id()) + .unwrap() + .iter() + .find(|msg| match server.onion_messenger.peel_onion_message(&msg).unwrap() { + PeeledOnion::AsyncPayments(AsyncPaymentsMessage::ServeStaticInvoice(_), _, _) => { + true + }, + PeeledOnion::AsyncPayments(AsyncPaymentsMessage::OfferPathsRequest(_), _, _) => { + false + }, + _ => panic!("Unexpected message"), + }) + .unwrap(); + } +} + #[cfg_attr(feature = "std", ignore)] #[test] -fn refresh_static_invoices() { - // Check that an invoice for a particular offer stored with the server will be updated once per - // timer tick. +fn refresh_static_invoices_for_used_offers() { + // Check that an invoice for a used offer stored with the server will be updated every + // INVOICE_REFRESH_THRESHOLD. let chanmon_cfgs = create_chanmon_cfgs(3); let node_cfgs = create_node_cfgs(3, &chanmon_cfgs); @@ -1744,25 +1786,26 @@ fn refresh_static_invoices() { // Set up the recipient to have one offer and an invoice with the static invoice server. let flow_res = pass_static_invoice_server_messages(server, recipient, recipient_id.clone()); let original_invoice = flow_res.invoice; - // Mark the offer as used so we'll update the invoice on timer tick. + // Mark the offer as used so we'll update the invoice after INVOICE_REFRESH_THRESHOLD. let _offer = recipient.node.get_async_receive_offer().unwrap(); // Force the server and recipient to send OMs directly to each other for testing simplicity. server.message_router.peers_override.lock().unwrap().push(recipient.node.get_our_node_id()); recipient.message_router.peers_override.lock().unwrap().push(server.node.get_our_node_id()); - assert!(recipient - .onion_messenger - .next_onion_message_for_peer(server.node.get_our_node_id()) - .is_none()); + // Prior to INVOICE_REFRESH_THRESHOLD, we won't refresh the invoice. + advance_time_by(TEST_INVOICE_REFRESH_THRESHOLD, recipient); + recipient.node.timer_tick_occurred(); + expect_offer_paths_requests(&nodes[2], &[&nodes[0], &nodes[1]]); - // Check that we'll refresh the invoice on the next timer tick. + // After INVOICE_REFRESH_THRESHOLD, we will refresh the invoice. + advance_time_by(Duration::from_secs(1), recipient); recipient.node.timer_tick_occurred(); let pending_oms = recipient.onion_messenger.release_pending_msgs(); let serve_static_invoice_om = pending_oms .get(&server.node.get_our_node_id()) .unwrap() - .into_iter() + .iter() .find(|msg| match server.onion_messenger.peel_onion_message(&msg).unwrap() { PeeledOnion::AsyncPayments(AsyncPaymentsMessage::ServeStaticInvoice(_), _, _) => true, PeeledOnion::AsyncPayments(AsyncPaymentsMessage::OfferPathsRequest(_), _, _) => false, diff --git a/lightning/src/offers/async_receive_offer_cache.rs b/lightning/src/offers/async_receive_offer_cache.rs index 7c572967016..346c7a8e206 100644 --- a/lightning/src/offers/async_receive_offer_cache.rs +++ b/lightning/src/offers/async_receive_offer_cache.rs @@ -199,6 +199,10 @@ const MAX_UPDATE_ATTEMPTS: u8 = 3; #[cfg(async_payments)] const OFFER_REFRESH_THRESHOLD: Duration = Duration::from_secs(2 * 60 * 60); +/// Invoices stored with the static invoice server may become stale due to outdated channel and fee +/// info, so they should be updated regularly. +const INVOICE_REFRESH_THRESHOLD: Duration = Duration::from_secs(2 * 60 * 60); + // Require offer paths that we receive to last at least 3 months. #[cfg(async_payments)] const MIN_OFFER_PATHS_RELATIVE_EXPIRY_SECS: u64 = 3 * 30 * 24 * 60 * 60; @@ -210,6 +214,8 @@ pub(crate) const TEST_MAX_UPDATE_ATTEMPTS: u8 = MAX_UPDATE_ATTEMPTS; #[cfg(all(test, async_payments))] pub(crate) const TEST_OFFER_REFRESH_THRESHOLD: Duration = OFFER_REFRESH_THRESHOLD; #[cfg(all(test, async_payments))] +pub(crate) const TEST_INVOICE_REFRESH_THRESHOLD: Duration = INVOICE_REFRESH_THRESHOLD; +#[cfg(all(test, async_payments))] pub(crate) const TEST_MIN_OFFER_PATHS_RELATIVE_EXPIRY_SECS: u64 = MIN_OFFER_PATHS_RELATIVE_EXPIRY_SECS; @@ -416,13 +422,21 @@ impl AsyncReceiveOfferCache { /// Returns an iterator over the list of cached offers where we need to send an updated invoice to /// the static invoice server. pub(super) fn offers_needing_invoice_refresh( - &self, + &self, duration_since_epoch: Duration, ) -> impl Iterator { // For any offers which are either in use or pending confirmation by the server, we should send // them a fresh invoice on each timer tick. - self.offers_with_idx().filter_map(|(idx, offer)| { - let needs_invoice_update = - matches!(offer.status, OfferStatus::Used { .. } | OfferStatus::Pending); + self.offers_with_idx().filter_map(move |(idx, offer)| { + let needs_invoice_update = match offer.status { + OfferStatus::Used { invoice_created_at } => { + invoice_created_at.saturating_add(INVOICE_REFRESH_THRESHOLD) + < duration_since_epoch + }, + OfferStatus::Pending => true, + // Don't bother updating `Ready` offers' invoices on a timer because the offers themselves + // are regularly rotated anyway. + OfferStatus::Ready { .. } => false, + }; if needs_invoice_update { let offer_slot = idx.try_into().unwrap_or(u16::MAX); Some(( diff --git a/lightning/src/offers/flow.rs b/lightning/src/offers/flow.rs index d576e2ccc9d..107c7208502 100644 --- a/lightning/src/offers/flow.rs +++ b/lightning/src/offers/flow.rs @@ -1333,8 +1333,9 @@ where { let mut serve_static_invoice_msgs = Vec::new(); { + let duration_since_epoch = self.duration_since_epoch(); let cache = self.async_receive_offer_cache.lock().unwrap(); - for offer_and_metadata in cache.offers_needing_invoice_refresh() { + for offer_and_metadata in cache.offers_needing_invoice_refresh(duration_since_epoch) { let (offer, offer_nonce, slot_number, update_static_invoice_path) = offer_and_metadata; From 3772c477b9036899fddf8d931431f181889baf34 Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Fri, 8 Aug 2025 14:04:30 -0400 Subject: [PATCH 11/12] Update static inv server OM TLVs to not conflict Previously one of the static invoice server onion message TLV types conflicted with a DNS resolver onion message type, causing test failures on cargo test --cfg=async_payments. We also bump the TLV numbers by 10k until they can be documented in a bLIP. --- lightning/src/onion_message/async_payments.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lightning/src/onion_message/async_payments.rs b/lightning/src/onion_message/async_payments.rs index 1f13e68f3d8..52badd71394 100644 --- a/lightning/src/onion_message/async_payments.rs +++ b/lightning/src/onion_message/async_payments.rs @@ -19,10 +19,10 @@ use crate::prelude::*; use crate::util::ser::{Readable, ReadableArgs, Writeable, Writer}; // TLV record types for the `onionmsg_tlv` TLV stream as defined in BOLT 4. -const OFFER_PATHS_REQ_TLV_TYPE: u64 = 65538; -const OFFER_PATHS_TLV_TYPE: u64 = 65540; -const SERVE_INVOICE_TLV_TYPE: u64 = 65542; -const INVOICE_PERSISTED_TLV_TYPE: u64 = 65544; +const OFFER_PATHS_REQ_TLV_TYPE: u64 = 75540; +const OFFER_PATHS_TLV_TYPE: u64 = 75542; +const SERVE_INVOICE_TLV_TYPE: u64 = 75544; +const INVOICE_PERSISTED_TLV_TYPE: u64 = 75546; const HELD_HTLC_AVAILABLE_TLV_TYPE: u64 = 72; const RELEASE_HELD_HTLC_TLV_TYPE: u64 = 74; From c0b8a44989d327743949882abfc5c67f392db91b Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Fri, 8 Aug 2025 14:07:05 -0400 Subject: [PATCH 12/12] Remove async payments cfg flag We were previously cfg-gating all async payments code to not have a half-working public API, but now that async receive is usable go ahead and remove the gating. Paying as an async sender is not yet supported. --- Cargo.toml | 1 - fuzz/src/full_stack.rs | 3 + lightning/src/events/mod.rs | 8 +- lightning/src/ln/channelmanager.rs | 188 +++++++----------- lightning/src/ln/inbound_payment.rs | 1 - lightning/src/ln/mod.rs | 2 +- lightning/src/ln/offers_tests.rs | 5 +- lightning/src/ln/outbound_payment.rs | 11 +- .../src/offers/async_receive_offer_cache.rs | 21 +- lightning/src/offers/flow.rs | 64 ++---- lightning/src/onion_message/messenger.rs | 29 +-- lightning/src/onion_message/offers.rs | 16 +- lightning/src/onion_message/packet.rs | 7 - lightning/src/routing/router.rs | 2 - 14 files changed, 120 insertions(+), 238 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3891b11a2b4..340e1f2444f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -66,6 +66,5 @@ check-cfg = [ "cfg(taproot)", "cfg(require_route_graph_test)", "cfg(splicing)", - "cfg(async_payments)", "cfg(simple_close)", ] diff --git a/fuzz/src/full_stack.rs b/fuzz/src/full_stack.rs index eb9d51d487d..ae9e385d5b0 100644 --- a/fuzz/src/full_stack.rs +++ b/fuzz/src/full_stack.rs @@ -1171,6 +1171,9 @@ fn two_peer_forwarding_seed() -> Vec { ext_from_hex("030120", &mut test); // init message (type 16) with static_remotekey required, no anchors/taproot, and other bits optional and mac ext_from_hex("0010 00021aaa 0008aaa210aa2a0a9aaa 01000000000000000000000000000000", &mut test); + // One feerate request on peer connection due to a list_channels call when seeing if the async + // receive offer cache needs updating + ext_from_hex("00fd", &mut test); // create outbound channel to peer 1 for 50k sat ext_from_hex( diff --git a/lightning/src/events/mod.rs b/lightning/src/events/mod.rs index 2b712073568..70a6ba271da 100644 --- a/lightning/src/events/mod.rs +++ b/lightning/src/events/mod.rs @@ -1628,7 +1628,6 @@ pub enum Event { /// /// [`ChannelManager::blinded_paths_for_async_recipient`]: crate::ln::channelmanager::ChannelManager::blinded_paths_for_async_recipient /// [`ChannelManager::set_paths_to_static_invoice_server`]: crate::ln::channelmanager::ChannelManager::set_paths_to_static_invoice_server - #[cfg(async_payments)] PersistStaticInvoice { /// The invoice that should be persisted and later provided to payers when handling a future /// [`Event::StaticInvoiceRequested`]. @@ -1645,6 +1644,8 @@ pub enum Event { /// /// When an [`Event::StaticInvoiceRequested`] comes in for the invoice, this id will be surfaced /// and can be used alongside the `invoice_id` to retrieve the invoice from the database. + /// + ///[`ChannelManager::blinded_paths_for_async_recipient`]: crate::ln::channelmanager::ChannelManager::blinded_paths_for_async_recipient recipient_id: Vec, /// A random identifier for the invoice. When an [`Event::StaticInvoiceRequested`] comes in for /// the invoice, this id will be surfaced and can be used alongside the `recipient_id` to @@ -1676,7 +1677,6 @@ pub enum Event { /// [`ChannelManager::set_paths_to_static_invoice_server`]: crate::ln::channelmanager::ChannelManager::set_paths_to_static_invoice_server /// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest /// [`ChannelManager::send_static_invoice`]: crate::ln::channelmanager::ChannelManager::send_static_invoice - #[cfg(async_payments)] StaticInvoiceRequested { /// An identifier for the recipient previously surfaced in /// [`Event::PersistStaticInvoice::recipient_id`]. Useful when paired with the `invoice_id` to @@ -2123,13 +2123,11 @@ impl Writeable for Event { (8, former_temporary_channel_id, required), }); }, - #[cfg(async_payments)] &Event::PersistStaticInvoice { .. } => { 45u8.write(writer)?; // No need to write these events because we can just restart the static invoice negotiation // on startup. }, - #[cfg(async_payments)] &Event::StaticInvoiceRequested { .. } => { 47u8.write(writer)?; // Never write StaticInvoiceRequested events as buffered onion messages aren't serialized. @@ -2711,10 +2709,8 @@ impl MaybeReadable for Event { })) }, // Note that we do not write a length-prefixed TLV for PersistStaticInvoice events. - #[cfg(async_payments)] 45u8 => Ok(None), // Note that we do not write a length-prefixed TLV for StaticInvoiceRequested events. - #[cfg(async_payments)] 47u8 => Ok(None), // Versions prior to 0.0.100 did not ignore odd types, instead returning InvalidValue. // Version 0.0.100 failed to properly ignore odd types, possibly resulting in corrupt diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index b2ee350edee..00e05aa9e9e 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -34,8 +34,9 @@ use bitcoin::{secp256k1, Sequence}; #[cfg(splicing)] use bitcoin::{ScriptBuf, TxIn, Weight}; -use crate::blinded_path::message::MessageForwardNode; -use crate::blinded_path::message::{AsyncPaymentsContext, OffersContext}; +use crate::blinded_path::message::{ + AsyncPaymentsContext, BlindedMessagePath, MessageForwardNode, OffersContext, +}; use crate::blinded_path::payment::{ AsyncBolt12OfferContext, Bolt12OfferContext, PaymentContext, UnauthenticatedReceiveTlvs, }; @@ -102,6 +103,7 @@ use crate::offers::offer::Offer; use crate::offers::parse::Bolt12SemanticError; use crate::offers::refund::Refund; use crate::offers::signer; +use crate::offers::static_invoice::StaticInvoice; use crate::onion_message::async_payments::{ AsyncPaymentsMessage, AsyncPaymentsMessageHandler, HeldHtlcAvailable, OfferPaths, OfferPathsRequest, ReleaseHeldHtlc, ServeStaticInvoice, StaticInvoicePersisted, @@ -134,12 +136,8 @@ use crate::util::ser::{ }; use crate::util::wakers::{Future, Notifier}; -#[cfg(all(test, async_payments))] +#[cfg(test)] use crate::blinded_path::payment::BlindedPaymentPath; -#[cfg(async_payments)] -use { - crate::blinded_path::message::BlindedMessagePath, crate::offers::static_invoice::StaticInvoice, -}; #[cfg(feature = "dnssec")] use { @@ -5094,7 +5092,7 @@ where ) } - #[cfg(all(test, async_payments))] + #[cfg(test)] pub(crate) fn test_modify_pending_payment(&self, payment_id: &PaymentId, mut callback: Fn) where Fn: FnMut(&mut PendingOutboundPayment), @@ -5224,7 +5222,6 @@ where ) } - #[cfg(async_payments)] fn check_refresh_async_receive_offer_cache(&self, timer_tick_occurred: bool) { let peers = self.get_peers_for_blinded_path(); let channels = self.list_usable_channels(); @@ -5248,27 +5245,24 @@ where } } - #[cfg(all(test, async_payments))] + #[cfg(test)] pub(crate) fn test_check_refresh_async_receive_offers(&self) { self.check_refresh_async_receive_offer_cache(false); } /// Should be called after handling an [`Event::PersistStaticInvoice`], where the `Responder` /// comes from [`Event::PersistStaticInvoice::invoice_persisted_path`]. - #[cfg(async_payments)] pub fn static_invoice_persisted(&self, invoice_persisted_path: Responder) { self.flow.static_invoice_persisted(invoice_persisted_path); } /// Forwards a [`StaticInvoice`] in response to an [`Event::StaticInvoiceRequested`]. - #[cfg(async_payments)] pub fn send_static_invoice( &self, invoice: StaticInvoice, responder: Responder, ) -> Result<(), Bolt12SemanticError> { self.flow.enqueue_static_invoice(invoice, responder) } - #[cfg(async_payments)] fn initiate_async_payment( &self, invoice: &StaticInvoice, payment_id: PaymentId, ) -> Result<(), Bolt12PaymentError> { @@ -5318,7 +5312,6 @@ where res } - #[cfg(async_payments)] fn send_payment_for_static_invoice( &self, payment_id: PaymentId, ) -> Result<(), Bolt12PaymentError> { @@ -7671,7 +7664,6 @@ where self.pending_outbound_payments .remove_stale_payments(duration_since_epoch, &self.pending_events); - #[cfg(async_payments)] self.check_refresh_async_receive_offer_cache(true); // Technically we don't need to do this here, but if we have holding cell entries in a @@ -11884,7 +11876,6 @@ where /// interactively building a [`StaticInvoice`] with the static invoice server. /// /// Useful for posting offers to receive payments later, such as posting an offer on a website. - #[cfg(async_payments)] pub fn get_async_receive_offer(&self) -> Result { let (offer, needs_persist) = self.flow.get_async_receive_offer()?; if needs_persist { @@ -11901,7 +11892,6 @@ where /// /// This method only needs to be called once when the server first takes on the recipient as a /// client, or when the paths change, e.g. if the paths are set to expire at a particular time. - #[cfg(async_payments)] pub fn set_paths_to_static_invoice_server( &self, paths_to_static_invoice_server: Vec, ) -> Result<(), ()> { @@ -12307,7 +12297,6 @@ where /// The provided `recipient_id` must uniquely identify the recipient, and will be surfaced later /// when the recipient provides us with a static invoice to persist and serve to payers on their /// behalf. - #[cfg(async_payments)] pub fn blinded_paths_for_async_recipient( &self, recipient_id: Vec, relative_expiry: Option, ) -> Result, ()> { @@ -12315,7 +12304,6 @@ where self.flow.blinded_paths_for_async_recipient(recipient_id, relative_expiry, peers) } - #[cfg(any(test, async_payments))] 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); @@ -12347,12 +12335,12 @@ where .collect::>() } - #[cfg(all(test, async_payments))] + #[cfg(test)] pub(super) fn test_get_peers_for_blinded_path(&self) -> Vec { self.get_peers_for_blinded_path() } - #[cfg(all(test, async_payments))] + #[cfg(test)] /// Creates multi-hop blinded payment paths for the given `amount_msats` by delegating to /// [`Router::create_blinded_payment_paths`]. pub(super) fn test_create_blinded_payment_paths( @@ -12856,7 +12844,6 @@ where // interactively building offers as soon as we can after startup. We can't start building offers // until we have some peer connection(s) to receive onion messages over, so as a minor optimization // refresh the cache when a peer connects. - #[cfg(async_payments)] self.check_refresh_async_receive_offer_cache(false); res } @@ -14128,7 +14115,6 @@ where log_trace!($logger, "Failed paying invoice: {:?}", e); InvoiceError::from_string(format!("{:?}", e)) }, - #[cfg(async_payments)] Err(Bolt12PaymentError::BlindedPathCreationFailed) => { let err_msg = "Failed to create a blinded path back to ourselves"; log_trace!($logger, "{}", err_msg); @@ -14161,7 +14147,6 @@ where Ok(InvreqResponseInstructions::SendStaticInvoice { recipient_id: _recipient_id, invoice_id: _invoice_id }) => { - #[cfg(async_payments)] self.pending_events.lock().unwrap().push_back((Event::StaticInvoiceRequested { recipient_id: _recipient_id, invoice_id: _invoice_id, reply_path: responder }, None)); @@ -14226,7 +14211,6 @@ where let res = self.send_payment_for_verified_bolt12_invoice(&invoice, payment_id); handle_pay_invoice_res!(res, invoice, logger); }, - #[cfg(async_payments)] OffersMessage::StaticInvoice(invoice) => { let payment_id = match context { Some(OffersContext::OutboundPayment { payment_id, .. }) => payment_id, @@ -14289,95 +14273,77 @@ where &self, _message: OfferPathsRequest, _context: AsyncPaymentsContext, _responder: Option, ) -> Option<(OfferPaths, ResponseInstruction)> { - #[cfg(async_payments)] - { - let peers = self.get_peers_for_blinded_path(); - let entropy = &*self.entropy_source; - let (message, reply_path_context) = - match self.flow.handle_offer_paths_request(_context, peers, entropy) { - Some(msg) => msg, - None => return None, - }; - _responder.map(|resp| (message, resp.respond_with_reply_path(reply_path_context))) - } - - #[cfg(not(async_payments))] - None + let peers = self.get_peers_for_blinded_path(); + let entropy = &*self.entropy_source; + let (message, reply_path_context) = + match self.flow.handle_offer_paths_request(_context, peers, entropy) { + Some(msg) => msg, + None => return None, + }; + _responder.map(|resp| (message, resp.respond_with_reply_path(reply_path_context))) } fn handle_offer_paths( &self, _message: OfferPaths, _context: AsyncPaymentsContext, _responder: Option, ) -> Option<(ServeStaticInvoice, ResponseInstruction)> { - #[cfg(async_payments)] - { - let responder = match _responder { - Some(responder) => responder, - None => return None, - }; - let (serve_static_invoice, reply_context) = match self.flow.handle_offer_paths( - _message, - _context, - responder.clone(), - self.get_peers_for_blinded_path(), - self.list_usable_channels(), - &*self.entropy_source, - &*self.router, - ) { - Some((msg, ctx)) => (msg, ctx), - None => return None, - }; - - // We cached a new pending offer, so persist the cache. - let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self); + let responder = match _responder { + Some(responder) => responder, + None => return None, + }; + let (serve_static_invoice, reply_context) = match self.flow.handle_offer_paths( + _message, + _context, + responder.clone(), + self.get_peers_for_blinded_path(), + self.list_usable_channels(), + &*self.entropy_source, + &*self.router, + ) { + Some((msg, ctx)) => (msg, ctx), + None => return None, + }; - let response_instructions = responder.respond_with_reply_path(reply_context); - return Some((serve_static_invoice, response_instructions)); - } + // We cached a new pending offer, so persist the cache. + let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self); - #[cfg(not(async_payments))] - return None; + let response_instructions = responder.respond_with_reply_path(reply_context); + return Some((serve_static_invoice, response_instructions)); } fn handle_serve_static_invoice( &self, _message: ServeStaticInvoice, _context: AsyncPaymentsContext, _responder: Option, ) { - #[cfg(async_payments)] - { - let responder = match _responder { - Some(resp) => resp, - None => return, - }; + let responder = match _responder { + Some(resp) => resp, + None => return, + }; - let (recipient_id, invoice_id) = - match self.flow.verify_serve_static_invoice_message(&_message, _context) { - Ok((recipient_id, inv_id)) => (recipient_id, inv_id), - Err(()) => return, - }; + let (recipient_id, invoice_id) = + match self.flow.verify_serve_static_invoice_message(&_message, _context) { + Ok((recipient_id, inv_id)) => (recipient_id, inv_id), + Err(()) => return, + }; - let mut pending_events = self.pending_events.lock().unwrap(); - pending_events.push_back(( - Event::PersistStaticInvoice { - invoice: _message.invoice, - invoice_slot: _message.invoice_slot, - recipient_id, - invoice_id, - invoice_persisted_path: responder, - }, - None, - )); - } + let mut pending_events = self.pending_events.lock().unwrap(); + pending_events.push_back(( + Event::PersistStaticInvoice { + invoice: _message.invoice, + invoice_slot: _message.invoice_slot, + recipient_id, + invoice_id, + invoice_persisted_path: responder, + }, + None, + )); } fn handle_static_invoice_persisted( &self, _message: StaticInvoicePersisted, _context: AsyncPaymentsContext, ) { - #[cfg(async_payments)] - { - let should_persist = self.flow.handle_static_invoice_persisted(_context); - if should_persist { - let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self); - } + let should_persist = self.flow.handle_static_invoice_persisted(_context); + if should_persist { + let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self); } } @@ -14385,31 +14351,23 @@ where &self, _message: HeldHtlcAvailable, _context: AsyncPaymentsContext, _responder: Option, ) -> Option<(ReleaseHeldHtlc, ResponseInstruction)> { - #[cfg(async_payments)] - { - self.flow.verify_inbound_async_payment_context(_context).ok()?; - return _responder.map(|responder| (ReleaseHeldHtlc {}, responder.respond())); - } - #[cfg(not(async_payments))] - return None; + self.flow.verify_inbound_async_payment_context(_context).ok()?; + return _responder.map(|responder| (ReleaseHeldHtlc {}, responder.respond())); } fn handle_release_held_htlc(&self, _message: ReleaseHeldHtlc, _context: AsyncPaymentsContext) { - #[cfg(async_payments)] - { - let payment_id = match _context { - AsyncPaymentsContext::OutboundPayment { payment_id } => payment_id, - _ => return, - }; + let payment_id = match _context { + AsyncPaymentsContext::OutboundPayment { payment_id } => payment_id, + _ => return, + }; - if let Err(e) = self.send_payment_for_static_invoice(payment_id) { - log_trace!( - self.logger, - "Failed to release held HTLC with payment id {}: {:?}", - payment_id, - e - ); - } + if let Err(e) = self.send_payment_for_static_invoice(payment_id) { + log_trace!( + self.logger, + "Failed to release held HTLC with payment id {}: {:?}", + payment_id, + e + ); } } diff --git a/lightning/src/ln/inbound_payment.rs b/lightning/src/ln/inbound_payment.rs index a7d45b896a9..f7b2a4a2b57 100644 --- a/lightning/src/ln/inbound_payment.rs +++ b/lightning/src/ln/inbound_payment.rs @@ -213,7 +213,6 @@ pub fn create_from_hash( Ok(construct_payment_secret(&iv_bytes, &metadata_bytes, &keys.metadata_key)) } -#[cfg(async_payments)] pub(crate) fn create_for_spontaneous_payment( keys: &ExpandedKey, min_value_msat: Option, invoice_expiry_delta_secs: u32, current_time: u64, min_final_cltv_expiry_delta: Option, diff --git a/lightning/src/ln/mod.rs b/lightning/src/ln/mod.rs index a513582cb64..3ca3b765dc2 100644 --- a/lightning/src/ln/mod.rs +++ b/lightning/src/ln/mod.rs @@ -60,7 +60,7 @@ pub use onion_utils::process_onion_failure; #[cfg(fuzzing)] pub use onion_utils::AttributionData; -#[cfg(all(test, async_payments))] +#[cfg(test)] #[allow(unused_mut)] mod async_payments_tests; #[cfg(test)] diff --git a/lightning/src/ln/offers_tests.rs b/lightning/src/ln/offers_tests.rs index ba9858be197..6c56ecc4270 100644 --- a/lightning/src/ln/offers_tests.rs +++ b/lightning/src/ln/offers_tests.rs @@ -227,7 +227,6 @@ pub(super) fn extract_invoice_request<'a, 'b, 'c>( Ok(PeeledOnion::Offers(message, _, reply_path)) => match message { OffersMessage::InvoiceRequest(invoice_request) => (invoice_request, reply_path.unwrap()), OffersMessage::Invoice(invoice) => panic!("Unexpected invoice: {:?}", invoice), - #[cfg(async_payments)] OffersMessage::StaticInvoice(invoice) => panic!("Unexpected static invoice: {:?}", invoice), OffersMessage::InvoiceError(error) => panic!("Unexpected invoice_error: {:?}", error), }, @@ -242,7 +241,6 @@ fn extract_invoice<'a, 'b, 'c>(node: &Node<'a, 'b, 'c>, message: &OnionMessage) Ok(PeeledOnion::Offers(message, _, reply_path)) => match message { OffersMessage::InvoiceRequest(invoice_request) => panic!("Unexpected invoice_request: {:?}", invoice_request), OffersMessage::Invoice(invoice) => (invoice, reply_path.unwrap()), - #[cfg(async_payments)] OffersMessage::StaticInvoice(invoice) => panic!("Unexpected static invoice: {:?}", invoice), OffersMessage::InvoiceError(error) => panic!("Unexpected invoice_error: {:?}", error), }, @@ -259,7 +257,6 @@ fn extract_invoice_error<'a, 'b, 'c>( Ok(PeeledOnion::Offers(message, _, _)) => match message { OffersMessage::InvoiceRequest(invoice_request) => panic!("Unexpected invoice_request: {:?}", invoice_request), OffersMessage::Invoice(invoice) => panic!("Unexpected invoice: {:?}", invoice), - #[cfg(async_payments)] OffersMessage::StaticInvoice(invoice) => panic!("Unexpected invoice: {:?}", invoice), OffersMessage::InvoiceError(error) => error, }, @@ -1235,7 +1232,7 @@ fn pays_bolt12_invoice_asynchronously() { let onion_message = alice.onion_messenger.next_onion_message_for_peer(bob_id).unwrap(); bob.onion_messenger.handle_onion_message(alice_id, &onion_message); - // Re-process the same onion message to ensure idempotency — + // Re-process the same onion message to ensure idempotency — // we should not generate a duplicate `InvoiceReceived` event. bob.onion_messenger.handle_onion_message(alice_id, &onion_message); diff --git a/lightning/src/ln/outbound_payment.rs b/lightning/src/ln/outbound_payment.rs index ffc3ee4ae19..476964db889 100644 --- a/lightning/src/ln/outbound_payment.rs +++ b/lightning/src/ln/outbound_payment.rs @@ -20,7 +20,7 @@ use crate::ln::channel_state::ChannelDetails; use crate::ln::channelmanager::{EventCompletionAction, HTLCSource, PaymentId}; use crate::ln::onion_utils; use crate::ln::onion_utils::{DecodedOnionFailure, HTLCFailReason}; -use crate::offers::invoice::Bolt12Invoice; +use crate::offers::invoice::{Bolt12Invoice, DerivedSigningPubkey, InvoiceBuilder}; use crate::offers::invoice_request::InvoiceRequest; use crate::offers::nonce::Nonce; use crate::offers::static_invoice::StaticInvoice; @@ -37,9 +37,6 @@ use crate::util::ser::ReadableArgs; #[cfg(feature = "std")] use crate::util::time::Instant; -#[cfg(async_payments)] -use crate::offers::invoice::{DerivedSigningPubkey, InvoiceBuilder}; - use core::fmt::{self, Display, Formatter}; use core::ops::Deref; use core::sync::atomic::{AtomicBool, Ordering}; @@ -54,12 +51,11 @@ use crate::sync::Mutex; /// [`ChannelManager::timer_tick_occurred`]: crate::ln::channelmanager::ChannelManager::timer_tick_occurred pub(crate) const IDEMPOTENCY_TIMEOUT_TICKS: u8 = 7; -#[cfg(async_payments)] /// The default relative expiration to wait for a pending outbound HTLC to a often-offline /// payee to fulfill. const ASYNC_PAYMENT_TIMEOUT_RELATIVE_EXPIRY: Duration = Duration::from_secs(60 * 60 * 24 * 7); -#[cfg(all(async_payments, test))] +#[cfg(test)] pub(crate) const TEST_ASYNC_PAYMENT_TIMEOUT_RELATIVE_EXPIRY: Duration = ASYNC_PAYMENT_TIMEOUT_RELATIVE_EXPIRY; @@ -637,7 +633,6 @@ pub enum Bolt12PaymentError { UnknownRequiredFeatures, /// The invoice was valid for the corresponding [`PaymentId`], but sending the payment failed. SendingFailed(RetryableSendFailure), - #[cfg(async_payments)] /// Failed to create a blinded path back to ourselves. /// /// We attempted to initiate payment to a [`StaticInvoice`] but failed to create a reply path for @@ -1111,7 +1106,6 @@ impl OutboundPayments { Ok(()) } - #[cfg(async_payments)] #[rustfmt::skip] pub(super) fn static_invoice_received( &self, invoice: &StaticInvoice, payment_id: PaymentId, features: Bolt12InvoiceFeatures, @@ -1201,7 +1195,6 @@ impl OutboundPayments { }; } - #[cfg(async_payments)] #[rustfmt::skip] pub(super) fn send_payment_for_static_invoice< R: Deref, ES: Deref, NS: Deref, NL: Deref, IH, SP, L: Deref diff --git a/lightning/src/offers/async_receive_offer_cache.rs b/lightning/src/offers/async_receive_offer_cache.rs index 346c7a8e206..1b1078dbb23 100644 --- a/lightning/src/offers/async_receive_offer_cache.rs +++ b/lightning/src/offers/async_receive_offer_cache.rs @@ -11,7 +11,7 @@ //! server as an async recipient. The static invoice server will serve the resulting invoices to //! payers on our behalf when we're offline. -use crate::blinded_path::message::BlindedMessagePath; +use crate::blinded_path::message::{AsyncPaymentsContext, BlindedMessagePath}; use crate::io; use crate::io::Read; use crate::ln::msgs::DecodeError; @@ -22,9 +22,6 @@ use crate::prelude::*; use crate::util::ser::{Readable, Writeable, Writer}; use core::time::Duration; -#[cfg(async_payments)] -use crate::blinded_path::message::AsyncPaymentsContext; - /// The status of this offer in the cache. #[derive(Clone, PartialEq)] enum OfferStatus { @@ -161,7 +158,6 @@ impl AsyncReceiveOfferCache { /// on our behalf when we're offline. /// /// [`StaticInvoice`]: crate::offers::static_invoice::StaticInvoice - #[cfg(async_payments)] pub(crate) fn set_paths_to_static_invoice_server( &mut self, paths_to_static_invoice_server: Vec, ) -> Result<(), ()> { @@ -181,11 +177,9 @@ impl AsyncReceiveOfferCache { // The target number of offers we want to have cached at any given time, to mitigate too much // reuse of the same offer while also limiting the amount of space our offers take up on the // server's end. -#[cfg(async_payments)] const MAX_CACHED_OFFERS_TARGET: usize = 10; // The max number of times we'll attempt to request offer paths per timer tick. -#[cfg(async_payments)] const MAX_UPDATE_ATTEMPTS: u8 = 3; // If we have an offer that is replaceable and is more than 2 hours old, we can go ahead and refresh @@ -196,7 +190,6 @@ const MAX_UPDATE_ATTEMPTS: u8 = 3; // invoices from different offers competing for the same slot to the server, messages are received // delayed or out-of-order, and we end up providing an offer to the user that the server just // deleted and replaced. -#[cfg(async_payments)] const OFFER_REFRESH_THRESHOLD: Duration = Duration::from_secs(2 * 60 * 60); /// Invoices stored with the static invoice server may become stale due to outdated channel and fee @@ -204,22 +197,20 @@ const OFFER_REFRESH_THRESHOLD: Duration = Duration::from_secs(2 * 60 * 60); const INVOICE_REFRESH_THRESHOLD: Duration = Duration::from_secs(2 * 60 * 60); // Require offer paths that we receive to last at least 3 months. -#[cfg(async_payments)] const MIN_OFFER_PATHS_RELATIVE_EXPIRY_SECS: u64 = 3 * 30 * 24 * 60 * 60; -#[cfg(all(test, async_payments))] +#[cfg(test)] pub(crate) const TEST_MAX_CACHED_OFFERS_TARGET: usize = MAX_CACHED_OFFERS_TARGET; -#[cfg(all(test, async_payments))] +#[cfg(test)] pub(crate) const TEST_MAX_UPDATE_ATTEMPTS: u8 = MAX_UPDATE_ATTEMPTS; -#[cfg(all(test, async_payments))] +#[cfg(test)] pub(crate) const TEST_OFFER_REFRESH_THRESHOLD: Duration = OFFER_REFRESH_THRESHOLD; -#[cfg(all(test, async_payments))] +#[cfg(test)] pub(crate) const TEST_INVOICE_REFRESH_THRESHOLD: Duration = INVOICE_REFRESH_THRESHOLD; -#[cfg(all(test, async_payments))] +#[cfg(test)] pub(crate) const TEST_MIN_OFFER_PATHS_RELATIVE_EXPIRY_SECS: u64 = MIN_OFFER_PATHS_RELATIVE_EXPIRY_SECS; -#[cfg(async_payments)] impl AsyncReceiveOfferCache { /// Retrieve a cached [`Offer`] for receiving async payments as an often-offline recipient, as /// well as returning a bool indicating whether the cache needs to be re-persisted. diff --git a/lightning/src/offers/flow.rs b/lightning/src/offers/flow.rs index 107c7208502..91c506eb4a9 100644 --- a/lightning/src/offers/flow.rs +++ b/lightning/src/offers/flow.rs @@ -19,11 +19,11 @@ use bitcoin::constants::ChainHash; use bitcoin::secp256k1::{self, PublicKey, Secp256k1}; use crate::blinded_path::message::{ - BlindedMessagePath, MessageContext, MessageForwardNode, OffersContext, + AsyncPaymentsContext, BlindedMessagePath, MessageContext, MessageForwardNode, OffersContext, }; use crate::blinded_path::payment::{ - BlindedPaymentPath, Bolt12OfferContext, Bolt12RefundContext, PaymentConstraints, - PaymentContext, UnauthenticatedReceiveTlvs, + AsyncBolt12OfferContext, BlindedPaymentPath, Bolt12OfferContext, Bolt12RefundContext, + PaymentConstraints, PaymentContext, UnauthenticatedReceiveTlvs, }; use crate::chain::channelmonitor::LATENCY_GRACE_PERIOD_BLOCKS; @@ -44,32 +44,26 @@ use crate::offers::invoice_request::{ InvoiceRequest, InvoiceRequestBuilder, VerifiedInvoiceRequest, }; use crate::offers::nonce::Nonce; -use crate::offers::offer::{DerivedMetadata, Offer, OfferBuilder}; +use crate::offers::offer::{Amount, DerivedMetadata, Offer, OfferBuilder}; use crate::offers::parse::Bolt12SemanticError; use crate::offers::refund::{Refund, RefundBuilder}; -use crate::onion_message::async_payments::AsyncPaymentsMessage; -use crate::onion_message::messenger::{Destination, MessageRouter, MessageSendInstructions}; +use crate::onion_message::async_payments::{ + AsyncPaymentsMessage, HeldHtlcAvailable, OfferPaths, OfferPathsRequest, ServeStaticInvoice, + StaticInvoicePersisted, +}; +use crate::onion_message::messenger::{ + Destination, MessageRouter, MessageSendInstructions, Responder, +}; use crate::onion_message::offers::OffersMessage; use crate::onion_message::packet::OnionMessageContents; use crate::routing::router::Router; use crate::sign::{EntropySource, NodeSigner, ReceiveAuthKey}; + +use crate::offers::static_invoice::{StaticInvoice, StaticInvoiceBuilder}; use crate::sync::{Mutex, RwLock}; use crate::types::payment::{PaymentHash, PaymentSecret}; use crate::util::ser::Writeable; -#[cfg(async_payments)] -use { - crate::blinded_path::message::AsyncPaymentsContext, - crate::blinded_path::payment::AsyncBolt12OfferContext, - crate::offers::offer::Amount, - crate::offers::static_invoice::{StaticInvoice, StaticInvoiceBuilder}, - crate::onion_message::async_payments::{ - HeldHtlcAvailable, OfferPaths, OfferPathsRequest, ServeStaticInvoice, - StaticInvoicePersisted, - }, - crate::onion_message::messenger::Responder, -}; - #[cfg(feature = "dnssec")] use { crate::blinded_path::message::DNSResolverContext, @@ -167,7 +161,6 @@ where /// /// This method only needs to be called once when the server first takes on the recipient as a /// client, or when the paths change, e.g. if the paths are set to expire at a particular time. - #[cfg(async_payments)] pub fn set_paths_to_static_invoice_server( &self, paths_to_static_invoice_server: Vec, peers: Vec, @@ -192,7 +185,6 @@ where self.receive_auth_key } - #[cfg(async_payments)] fn duration_since_epoch(&self) -> Duration { #[cfg(not(feature = "std"))] let now = Duration::from_secs(self.highest_seen_timestamp.load(Ordering::Acquire) as u64); @@ -242,7 +234,6 @@ where /// The maximum size of a received [`StaticInvoice`] before we'll fail verification in /// [`OffersMessageFlow::verify_serve_static_invoice_message]. -#[cfg(async_payments)] pub const MAX_STATIC_INVOICE_SIZE_BYTES: usize = 5 * 1024; /// Defines the maximum number of [`OffersMessage`] including different reply paths to be sent @@ -252,22 +243,20 @@ pub const MAX_STATIC_INVOICE_SIZE_BYTES: usize = 5 * 1024; /// even if multiple invoices are received. const OFFERS_MESSAGE_REQUEST_LIMIT: usize = 10; -#[cfg(all(async_payments, test))] +#[cfg(test)] pub(crate) const TEST_OFFERS_MESSAGE_REQUEST_LIMIT: usize = OFFERS_MESSAGE_REQUEST_LIMIT; /// The default relative expiry for reply paths where a quick response is expected and the reply /// path is single-use. -#[cfg(async_payments)] const TEMP_REPLY_PATH_RELATIVE_EXPIRY: Duration = Duration::from_secs(2 * 60 * 60); -#[cfg(all(async_payments, test))] +#[cfg(test)] pub(crate) const TEST_TEMP_REPLY_PATH_RELATIVE_EXPIRY: Duration = TEMP_REPLY_PATH_RELATIVE_EXPIRY; // Default to async receive offers and the paths used to update them lasting one year. -#[cfg(async_payments)] const DEFAULT_ASYNC_RECEIVE_OFFER_EXPIRY: Duration = Duration::from_secs(365 * 24 * 60 * 60); -#[cfg(all(async_payments, test))] +#[cfg(test)] pub(crate) const TEST_DEFAULT_ASYNC_RECEIVE_OFFER_EXPIRY: Duration = DEFAULT_ASYNC_RECEIVE_OFFER_EXPIRY; @@ -284,7 +273,6 @@ where /// [`Self::set_paths_to_static_invoice_server`]. /// /// Errors if blinded path creation fails or the provided `recipient_id` is larger than 1KiB. - #[cfg(async_payments)] pub fn blinded_paths_for_async_recipient( &self, recipient_id: Vec, relative_expiry: Option, peers: Vec, @@ -360,7 +348,7 @@ where ) } - #[cfg(all(test, async_payments))] + #[cfg(test)] /// Creates multi-hop blinded payment paths for the given `amount_msats` by delegating to /// [`Router::create_blinded_payment_paths`]. pub(crate) fn test_create_blinded_payment_paths( @@ -445,7 +433,6 @@ where let nonce = match context { None if invoice_request.metadata().is_some() => None, Some(OffersContext::InvoiceRequest { nonce }) => Some(nonce), - #[cfg(async_payments)] Some(OffersContext::StaticInvoiceRequested { recipient_id, invoice_id, @@ -507,7 +494,6 @@ where /// /// Returns `Err(())` if: /// - The inbound payment context has expired. - #[cfg(async_payments)] pub fn verify_inbound_async_payment_context( &self, context: AsyncPaymentsContext, ) -> Result<(), ()> { @@ -623,7 +609,6 @@ where /// 2. Use [`Self::create_static_invoice_builder`] to create a [`StaticInvoice`] from this /// [`Offer`] plus the returned [`Nonce`], and provide the static invoice to the /// aforementioned always-online node. - #[cfg(async_payments)] pub fn create_async_receive_offer_builder( &self, entropy_source: ES, message_paths_to_always_online_node: Vec, ) -> Result<(OfferBuilder, Nonce), Bolt12SemanticError> @@ -792,7 +777,6 @@ where /// Creates a [`StaticInvoiceBuilder`] from the corresponding [`Offer`] and [`Nonce`] that were /// created via [`Self::create_async_receive_offer_builder`]. - #[cfg(async_payments)] pub fn create_static_invoice_builder<'a, ES: Deref, R: Deref>( &self, router: &R, entropy_source: ES, offer: &'a Offer, offer_nonce: Nonce, payment_secret: PaymentSecret, relative_expiry_secs: u32, @@ -1115,7 +1099,6 @@ where /// Forwards a [`StaticInvoice`] over the provided [`Responder`] in response to an /// [`InvoiceRequest`] that we as a static invoice server received on behalf of an often-offline /// recipient. - #[cfg(async_payments)] pub fn enqueue_static_invoice( &self, invoice: StaticInvoice, responder: Responder, ) -> Result<(), Bolt12SemanticError> { @@ -1144,7 +1127,6 @@ where /// /// [`ReleaseHeldHtlc`]: crate::onion_message::async_payments::ReleaseHeldHtlc /// [`supports_onion_messages`]: crate::types::features::Features::supports_onion_messages - #[cfg(async_payments)] pub fn enqueue_held_htlc_available( &self, invoice: &StaticInvoice, payment_id: PaymentId, peers: Vec, ) -> Result<(), Bolt12SemanticError> { @@ -1230,13 +1212,12 @@ where /// /// Returns the requested offer as well as a bool indicating whether the cache needs to be /// persisted using [`Self::writeable_async_receive_offer_cache`]. - #[cfg(async_payments)] pub fn get_async_receive_offer(&self) -> Result<(Offer, bool), ()> { let mut cache = self.async_receive_offer_cache.lock().unwrap(); cache.get_async_receive_offer(self.duration_since_epoch()) } - #[cfg(all(test, async_payments))] + #[cfg(test)] pub(crate) fn test_get_async_receive_offers(&self) -> Vec { self.async_receive_offer_cache.lock().unwrap().test_get_payable_offers() } @@ -1252,7 +1233,6 @@ where /// the cache can self-regulate the number of messages sent out. /// /// Errors if we failed to create blinded reply paths when sending an [`OfferPathsRequest`] message. - #[cfg(async_payments)] pub fn check_refresh_async_receive_offer_cache( &self, peers: Vec, usable_channels: Vec, entropy: ES, router: R, timer_tick_occurred: bool, @@ -1278,7 +1258,6 @@ where Ok(()) } - #[cfg(async_payments)] fn check_refresh_async_offers( &self, peers: Vec, timer_tick_occurred: bool, ) -> Result<(), ()> { @@ -1323,7 +1302,6 @@ where /// Enqueue onion messages that will used to request invoice refresh from the static invoice /// server, based on the offers provided by the cache. - #[cfg(async_payments)] fn check_refresh_static_invoices( &self, peers: Vec, usable_channels: Vec, entropy: ES, router: R, @@ -1392,7 +1370,6 @@ where /// Handles an incoming [`OfferPathsRequest`] onion message from an often-offline recipient who /// wants us (the static invoice server) to serve [`StaticInvoice`]s to payers on their behalf. /// Sends out [`OfferPaths`] onion messages in response. - #[cfg(async_payments)] pub fn handle_offer_paths_request( &self, context: AsyncPaymentsContext, peers: Vec, entropy_source: ES, ) -> Option<(OfferPaths, MessageContext)> @@ -1455,7 +1432,6 @@ where /// /// Returns `None` if we have enough offers cached already, verification of `message` fails, or we /// fail to create blinded paths. - #[cfg(async_payments)] pub fn handle_offer_paths( &self, message: OfferPaths, context: AsyncPaymentsContext, responder: Responder, peers: Vec, usable_channels: Vec, entropy: ES, @@ -1544,7 +1520,6 @@ where /// Creates a [`StaticInvoice`] and a blinded path for the server to forward invoice requests from /// payers to our node. - #[cfg(async_payments)] fn create_static_invoice_for_server( &self, offer: &Offer, offer_nonce: Nonce, peers: Vec, usable_channels: Vec, entropy: ES, router: R, @@ -1608,7 +1583,6 @@ where /// [`MAX_STATIC_INVOICE_SIZE_BYTES`]. /// /// [`ServeStaticInvoice::invoice`]: crate::onion_message::async_payments::ServeStaticInvoice::invoice - #[cfg(async_payments)] pub fn verify_serve_static_invoice_message( &self, message: &ServeStaticInvoice, context: AsyncPaymentsContext, ) -> Result<(Vec, u128), ()> { @@ -1638,7 +1612,6 @@ where /// to payers on behalf of an often-offline recipient. This method must be called after persisting /// a [`StaticInvoice`] to confirm to the recipient that their corresponding [`Offer`] is ready to /// receive async payments. - #[cfg(async_payments)] pub fn static_invoice_persisted(&self, responder: Responder) { let mut pending_async_payments_messages = self.pending_async_payments_messages.lock().unwrap(); @@ -1651,7 +1624,6 @@ where /// [`Self::writeable_async_receive_offer_cache`]. /// /// [`StaticInvoicePersisted`]: crate::onion_message::async_payments::StaticInvoicePersisted - #[cfg(async_payments)] pub fn handle_static_invoice_persisted(&self, context: AsyncPaymentsContext) -> bool { let mut cache = self.async_receive_offer_cache.lock().unwrap(); cache.static_invoice_persisted(context) diff --git a/lightning/src/onion_message/messenger.rs b/lightning/src/onion_message/messenger.rs index 38c8cd304d9..553977dfe18 100644 --- a/lightning/src/onion_message/messenger.rs +++ b/lightning/src/onion_message/messenger.rs @@ -15,9 +15,7 @@ use bitcoin::hashes::sha256::Hash as Sha256; use bitcoin::hashes::{Hash, HashEngine}; use bitcoin::secp256k1::{self, PublicKey, Scalar, Secp256k1, SecretKey}; -#[cfg(async_payments)] -use super::async_payments::AsyncPaymentsMessage; -use super::async_payments::AsyncPaymentsMessageHandler; +use super::async_payments::{AsyncPaymentsMessage, AsyncPaymentsMessageHandler}; use super::dns_resolution::{DNSResolverMessage, DNSResolverMessageHandler}; use super::offers::{OffersMessage, OffersMessageHandler}; use super::packet::OnionMessageContents; @@ -26,11 +24,9 @@ use super::packet::{ ForwardControlTlvs, Packet, Payload, ReceiveControlTlvs, BIG_PACKET_HOP_DATA_LEN, SMALL_PACKET_HOP_DATA_LEN, }; -#[cfg(async_payments)] -use crate::blinded_path::message::AsyncPaymentsContext; use crate::blinded_path::message::{ - BlindedMessagePath, DNSResolverContext, ForwardTlvs, MessageContext, MessageForwardNode, - NextMessageHop, OffersContext, ReceiveTlvs, + AsyncPaymentsContext, BlindedMessagePath, DNSResolverContext, ForwardTlvs, MessageContext, + MessageForwardNode, NextMessageHop, OffersContext, ReceiveTlvs, }; use crate::blinded_path::utils; use crate::blinded_path::{IntroductionNode, NodeIdLookUp}; @@ -440,7 +436,6 @@ impl Responder { } /// Converts a [`Responder`] into its inner [`BlindedMessagePath`]. - #[cfg(async_payments)] pub(crate) fn into_blinded_path(self) -> BlindedMessagePath { self.reply_path } @@ -977,7 +972,6 @@ pub enum PeeledOnion { /// Received offers onion message, with decrypted contents, context, and reply path Offers(OffersMessage, Option, Option), /// Received async payments onion message, with decrypted contents, context, and reply path - #[cfg(async_payments)] AsyncPayments(AsyncPaymentsMessage, AsyncPaymentsContext, Option), /// Received DNS resolver onion message, with decrypted contents, context, and reply path DNSResolver(DNSResolverMessage, Option, Option), @@ -1181,7 +1175,6 @@ where (ParsedOnionMessageContents::Offers(msg), None) => { Ok(PeeledOnion::Offers(msg, None, reply_path)) }, - #[cfg(async_payments)] ( ParsedOnionMessageContents::AsyncPayments(msg), Some(MessageContext::AsyncPayments(ctx)), @@ -1678,15 +1671,12 @@ where ); } - #[cfg(async_payments)] - { - for (message, instructions) in self.async_payments_handler.release_pending_messages() { - let _ = self.send_onion_message_internal( - message, - instructions, - format_args!("when sending AsyncPaymentsMessage"), - ); - } + for (message, instructions) in self.async_payments_handler.release_pending_messages() { + let _ = self.send_onion_message_internal( + message, + instructions, + format_args!("when sending AsyncPaymentsMessage"), + ); } // Enqueue any initiating `DNSResolverMessage`s to send. @@ -2098,7 +2088,6 @@ where let _ = self.handle_onion_message_response(msg, instructions); } }, - #[cfg(async_payments)] Ok(PeeledOnion::AsyncPayments(message, context, reply_path)) => { log_receive!(message, reply_path.is_some()); let responder = reply_path.map(Responder::new); diff --git a/lightning/src/onion_message/offers.rs b/lightning/src/onion_message/offers.rs index c4c7774a112..06988d4db8f 100644 --- a/lightning/src/onion_message/offers.rs +++ b/lightning/src/onion_message/offers.rs @@ -16,7 +16,6 @@ use crate::offers::invoice::Bolt12Invoice; use crate::offers::invoice_error::InvoiceError; use crate::offers::invoice_request::InvoiceRequest; use crate::offers::parse::Bolt12ParseError; -#[cfg(async_payments)] use crate::offers::static_invoice::StaticInvoice; use crate::onion_message::messenger::{MessageSendInstructions, Responder, ResponseInstruction}; use crate::onion_message::packet::OnionMessageContents; @@ -30,7 +29,7 @@ use crate::prelude::*; const INVOICE_REQUEST_TLV_TYPE: u64 = 64; const INVOICE_TLV_TYPE: u64 = 66; const INVOICE_ERROR_TLV_TYPE: u64 = 68; -#[cfg(async_payments)] +// Spec'd in https://github.com/lightning/bolts/pull/1149. const STATIC_INVOICE_TLV_TYPE: u64 = 70; /// A handler for an [`OnionMessage`] containing a BOLT 12 Offers message as its payload. @@ -79,7 +78,6 @@ pub enum OffersMessage { /// [`Refund`]: crate::offers::refund::Refund Invoice(Bolt12Invoice), - #[cfg(async_payments)] /// A [`StaticInvoice`] sent in response to an [`InvoiceRequest`]. StaticInvoice(StaticInvoice), @@ -91,9 +89,10 @@ impl OffersMessage { /// Returns whether `tlv_type` corresponds to a TLV record for Offers. pub fn is_known_type(tlv_type: u64) -> bool { match tlv_type { - INVOICE_REQUEST_TLV_TYPE | INVOICE_TLV_TYPE | INVOICE_ERROR_TLV_TYPE => true, - #[cfg(async_payments)] - STATIC_INVOICE_TLV_TYPE => true, + INVOICE_REQUEST_TLV_TYPE + | INVOICE_TLV_TYPE + | INVOICE_ERROR_TLV_TYPE + | STATIC_INVOICE_TLV_TYPE => true, _ => false, } } @@ -102,7 +101,6 @@ impl OffersMessage { match tlv_type { INVOICE_REQUEST_TLV_TYPE => Ok(Self::InvoiceRequest(InvoiceRequest::try_from(bytes)?)), INVOICE_TLV_TYPE => Ok(Self::Invoice(Bolt12Invoice::try_from(bytes)?)), - #[cfg(async_payments)] STATIC_INVOICE_TLV_TYPE => Ok(Self::StaticInvoice(StaticInvoice::try_from(bytes)?)), _ => Err(Bolt12ParseError::Decode(DecodeError::InvalidValue)), } @@ -112,7 +110,6 @@ impl OffersMessage { match &self { OffersMessage::InvoiceRequest(_) => "Invoice Request", OffersMessage::Invoice(_) => "Invoice", - #[cfg(async_payments)] OffersMessage::StaticInvoice(_) => "Static Invoice", OffersMessage::InvoiceError(_) => "Invoice Error", } @@ -128,7 +125,6 @@ impl fmt::Debug for OffersMessage { OffersMessage::Invoice(message) => { write!(f, "{:?}", message.as_tlv_stream()) }, - #[cfg(async_payments)] OffersMessage::StaticInvoice(message) => { write!(f, "{:?}", message) }, @@ -144,7 +140,6 @@ impl OnionMessageContents for OffersMessage { match self { OffersMessage::InvoiceRequest(_) => INVOICE_REQUEST_TLV_TYPE, OffersMessage::Invoice(_) => INVOICE_TLV_TYPE, - #[cfg(async_payments)] OffersMessage::StaticInvoice(_) => STATIC_INVOICE_TLV_TYPE, OffersMessage::InvoiceError(_) => INVOICE_ERROR_TLV_TYPE, } @@ -164,7 +159,6 @@ impl Writeable for OffersMessage { match self { OffersMessage::InvoiceRequest(message) => message.write(w), OffersMessage::Invoice(message) => message.write(w), - #[cfg(async_payments)] OffersMessage::StaticInvoice(message) => message.write(w), OffersMessage::InvoiceError(message) => message.write(w), } diff --git a/lightning/src/onion_message/packet.rs b/lightning/src/onion_message/packet.rs index 301473fba6a..ee41ee98572 100644 --- a/lightning/src/onion_message/packet.rs +++ b/lightning/src/onion_message/packet.rs @@ -12,7 +12,6 @@ use bitcoin::secp256k1::ecdh::SharedSecret; use bitcoin::secp256k1::PublicKey; -#[cfg(async_payments)] use super::async_payments::AsyncPaymentsMessage; use super::dns_resolution::DNSResolverMessage; use super::messenger::CustomOnionMessageHandler; @@ -131,7 +130,6 @@ pub enum ParsedOnionMessageContents { /// A message related to BOLT 12 Offers. Offers(OffersMessage), /// A message related to async payments. - #[cfg(async_payments)] AsyncPayments(AsyncPaymentsMessage), /// A message requesting or providing a DNSSEC proof DNSResolver(DNSResolverMessage), @@ -146,7 +144,6 @@ impl OnionMessageContents for ParsedOnionMessageContent fn tlv_type(&self) -> u64 { match self { &ParsedOnionMessageContents::Offers(ref msg) => msg.tlv_type(), - #[cfg(async_payments)] &ParsedOnionMessageContents::AsyncPayments(ref msg) => msg.tlv_type(), &ParsedOnionMessageContents::DNSResolver(ref msg) => msg.tlv_type(), &ParsedOnionMessageContents::Custom(ref msg) => msg.tlv_type(), @@ -156,7 +153,6 @@ impl OnionMessageContents for ParsedOnionMessageContent fn msg_type(&self) -> String { match self { ParsedOnionMessageContents::Offers(ref msg) => msg.msg_type(), - #[cfg(async_payments)] ParsedOnionMessageContents::AsyncPayments(ref msg) => msg.msg_type(), ParsedOnionMessageContents::DNSResolver(ref msg) => msg.msg_type(), ParsedOnionMessageContents::Custom(ref msg) => msg.msg_type(), @@ -166,7 +162,6 @@ impl OnionMessageContents for ParsedOnionMessageContent fn msg_type(&self) -> &'static str { match self { ParsedOnionMessageContents::Offers(ref msg) => msg.msg_type(), - #[cfg(async_payments)] ParsedOnionMessageContents::AsyncPayments(ref msg) => msg.msg_type(), ParsedOnionMessageContents::DNSResolver(ref msg) => msg.msg_type(), ParsedOnionMessageContents::Custom(ref msg) => msg.msg_type(), @@ -178,7 +173,6 @@ impl Writeable for ParsedOnionMessageContents { fn write(&self, w: &mut W) -> Result<(), io::Error> { match self { ParsedOnionMessageContents::Offers(msg) => msg.write(w), - #[cfg(async_payments)] ParsedOnionMessageContents::AsyncPayments(msg) => msg.write(w), ParsedOnionMessageContents::DNSResolver(msg) => msg.write(w), ParsedOnionMessageContents::Custom(msg) => msg.write(w), @@ -293,7 +287,6 @@ impl message = Some(ParsedOnionMessageContents::Offers(msg)); Ok(true) }, - #[cfg(async_payments)] tlv_type if AsyncPaymentsMessage::is_known_type(tlv_type) => { let msg = AsyncPaymentsMessage::read(msg_reader, tlv_type)?; message = Some(ParsedOnionMessageContents::AsyncPayments(msg)); diff --git a/lightning/src/routing/router.rs b/lightning/src/routing/router.rs index d56d5747e09..77f2e204b68 100644 --- a/lightning/src/routing/router.rs +++ b/lightning/src/routing/router.rs @@ -23,7 +23,6 @@ use crate::ln::channelmanager::{PaymentId, RecipientOnionFields, MIN_FINAL_CLTV_ use crate::ln::msgs::{DecodeError, MAX_VALUE_MSAT}; use crate::ln::onion_utils; use crate::offers::invoice::Bolt12Invoice; -#[cfg(async_payments)] use crate::offers::static_invoice::StaticInvoice; use crate::routing::gossip::{ DirectedChannelInfo, EffectiveCapacity, NetworkGraph, NodeId, ReadOnlyNetworkGraph, @@ -1010,7 +1009,6 @@ impl PaymentParameters { /// Creates parameters for paying to a blinded payee from the provided invoice. Sets /// [`Payee::Blinded::route_hints`], [`Payee::Blinded::features`], and /// [`PaymentParameters::expiry_time`]. - #[cfg(async_payments)] #[rustfmt::skip] pub fn from_static_invoice(invoice: &StaticInvoice) -> Self { Self::blinded(invoice.payment_paths().to_vec())