From 4174d39d5278925b6742f7e2ae60e63ac7583b08 Mon Sep 17 00:00:00 2001 From: shaavan Date: Tue, 12 Nov 2024 19:29:21 +0530 Subject: [PATCH 01/30] Introduce `message_received` in Offer/OnionMessageHandler To decouple offers and onion message-related code from `ChannelManager`, this commit introduces the `message_received` function in `Offer/OnionMessageHandler`. Currently, the function focuses on handling the retry logic for `InvoiceRequest` messages. Moving this responsibility ensures a cleaner separation of concerns and sets the foundation for managing offers/onion messages directly within the appropriate handler. --- lightning/src/ln/msgs.rs | 11 +++++++++++ lightning/src/onion_message/offers.rs | 8 ++++++++ 2 files changed, 19 insertions(+) diff --git a/lightning/src/ln/msgs.rs b/lightning/src/ln/msgs.rs index 659ec65f6cf..d28c748323c 100644 --- a/lightning/src/ln/msgs.rs +++ b/lightning/src/ln/msgs.rs @@ -1729,6 +1729,17 @@ pub trait OnionMessageHandler { /// /// Note that this method is called before [`Self::peer_connected`]. fn provided_init_features(&self, their_node_id: PublicKey) -> InitFeatures; + + /// Indicates that a message was received from any peer for any handler. + /// + /// This function delegates to the underlying [`OffersMessageHandler::message_received`]. + /// Refer to its documentation for more details on the behavior and implementation. + /// + /// **Note:** Since this function is called frequently, it should be implemented + /// with efficiency in mind to minimize performance overhead. + /// + /// [`OffersMessageHandler::message_received`]: crate::onion_message::offers::OffersMessageHandler::message_received + fn message_received(&self) {} } #[derive(Clone, Debug, PartialEq, Eq)] diff --git a/lightning/src/onion_message/offers.rs b/lightning/src/onion_message/offers.rs index f93c0854ea5..21034843190 100644 --- a/lightning/src/onion_message/offers.rs +++ b/lightning/src/onion_message/offers.rs @@ -47,6 +47,14 @@ pub trait OffersMessageHandler { &self, message: OffersMessage, context: Option, responder: Option, ) -> Option<(OffersMessage, ResponseInstruction)>; + /// Indicates that a message was received from any peer for any handler. + /// Called before the message is passed to the appropriate handler. + /// Useful for indicating that a network connection is active. + /// + /// Note: Since this function is called frequently, it should be as + /// efficient as possible for its intended purpose. + fn message_received(&self) {} + /// Releases any [`OffersMessage`]s that need to be sent. /// /// Typically, this is used for messages initiating a payment flow rather than in response to From c2f2e0875cb5cdeb41c599dccb1459b8c399b90e Mon Sep 17 00:00:00 2001 From: shaavan Date: Tue, 12 Nov 2024 19:31:06 +0530 Subject: [PATCH 02/30] Move `message_received` out of `ChannelMessageHandler` Since `ChannelMessageHandler`'s `message_received` function was solely used for handling invoice request retries. This function has been removed from `ChannelMessageHandler`, and the relevant code has been migrated to `OffersMessageHandler`'s `message_received`. This ensures invoice request retries are now handled in the appropriate context. --- lightning-net-tokio/src/lib.rs | 1 - lightning/src/ln/channelmanager.rs | 66 ++++++++++++------------ lightning/src/ln/msgs.rs | 8 --- lightning/src/ln/offers_tests.rs | 4 +- lightning/src/ln/peer_handler.rs | 4 +- lightning/src/onion_message/messenger.rs | 4 ++ lightning/src/util/test_utils.rs | 2 - 7 files changed, 40 insertions(+), 49 deletions(-) diff --git a/lightning-net-tokio/src/lib.rs b/lightning-net-tokio/src/lib.rs index 944033102c6..a76a651866e 100644 --- a/lightning-net-tokio/src/lib.rs +++ b/lightning-net-tokio/src/lib.rs @@ -786,7 +786,6 @@ mod tests { fn get_chain_hashes(&self) -> Option> { Some(vec![ChainHash::using_genesis_block(Network::Testnet)]) } - fn message_received(&self) {} } impl MessageSendEventsProvider for MsgHandler { fn get_and_clear_pending_msg_events(&self) -> Vec { diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index b170a97f9a5..14f580111ca 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -11943,39 +11943,6 @@ where NotifyOption::SkipPersistHandleEvents }); } - - fn message_received(&self) { - for (payment_id, retryable_invoice_request) in self - .pending_outbound_payments - .release_invoice_requests_awaiting_invoice() - { - let RetryableInvoiceRequest { invoice_request, nonce } = retryable_invoice_request; - let hmac = payment_id.hmac_for_offer_payment(nonce, &self.inbound_payment_key); - let context = MessageContext::Offers(OffersContext::OutboundPayment { - payment_id, - nonce, - hmac: Some(hmac) - }); - match self.create_blinded_paths(context) { - Ok(reply_paths) => match self.enqueue_invoice_request(invoice_request, reply_paths) { - Ok(_) => {} - Err(_) => { - log_warn!(self.logger, - "Retry failed for an invoice request with payment_id: {}", - payment_id - ); - } - }, - Err(_) => { - log_warn!(self.logger, - "Retry failed for an invoice request with payment_id: {}. \ - Reason: router could not find a blinded path to include as the reply path", - payment_id - ); - } - } - } - } } impl @@ -12205,6 +12172,39 @@ where } } + fn message_received(&self) { + for (payment_id, retryable_invoice_request) in self + .pending_outbound_payments + .release_invoice_requests_awaiting_invoice() + { + let RetryableInvoiceRequest { invoice_request, nonce } = retryable_invoice_request; + let hmac = payment_id.hmac_for_offer_payment(nonce, &self.inbound_payment_key); + let context = MessageContext::Offers(OffersContext::OutboundPayment { + payment_id, + nonce, + hmac: Some(hmac) + }); + match self.create_blinded_paths(context) { + Ok(reply_paths) => match self.enqueue_invoice_request(invoice_request, reply_paths) { + Ok(_) => {} + Err(_) => { + log_warn!(self.logger, + "Retry failed for an invoice request with payment_id: {}", + payment_id + ); + } + }, + Err(_) => { + log_warn!(self.logger, + "Retry failed for an invoice request with payment_id: {}. \ + Reason: router could not find a blinded path to include as the reply path", + payment_id + ); + } + } + } + } + fn release_pending_messages(&self) -> Vec<(OffersMessage, MessageSendInstructions)> { core::mem::take(&mut self.pending_offers_messages.lock().unwrap()) } diff --git a/lightning/src/ln/msgs.rs b/lightning/src/ln/msgs.rs index d28c748323c..4b0220f68fc 100644 --- a/lightning/src/ln/msgs.rs +++ b/lightning/src/ln/msgs.rs @@ -1607,14 +1607,6 @@ pub trait ChannelMessageHandler : MessageSendEventsProvider { /// If it's `None`, then no particular network chain hash compatibility will be enforced when /// connecting to peers. fn get_chain_hashes(&self) -> Option>; - - /// Indicates that a message was received from any peer for any handler. - /// Called before the message is passed to the appropriate handler. - /// Useful for indicating that a network connection is active. - /// - /// Note: Since this function is called frequently, it should be as - /// efficient as possible for its intended purpose. - fn message_received(&self); } /// A trait to describe an object which can receive routing messages. diff --git a/lightning/src/ln/offers_tests.rs b/lightning/src/ln/offers_tests.rs index 6455a60b139..2e091221788 100644 --- a/lightning/src/ln/offers_tests.rs +++ b/lightning/src/ln/offers_tests.rs @@ -1101,7 +1101,7 @@ fn creates_and_pays_for_offer_with_retry() { // Simulate a scenario where the original onion_message is lost before reaching Alice. // Use handle_message_received to regenerate the message. - bob.node.message_received(); + bob.onion_messenger.message_received(); let onion_message = bob.onion_messenger.next_onion_message_for_peer(alice_id).unwrap(); alice.onion_messenger.handle_onion_message(bob_id, &onion_message); @@ -1124,7 +1124,7 @@ fn creates_and_pays_for_offer_with_retry() { // Expect no more OffersMessage to be enqueued by this point, even after calling // handle_message_received. - bob.node.message_received(); + bob.onion_messenger.message_received(); assert!(bob.onion_messenger.next_onion_message_for_peer(alice_id).is_none()); diff --git a/lightning/src/ln/peer_handler.rs b/lightning/src/ln/peer_handler.rs index f561ef18edc..ef73e100fad 100644 --- a/lightning/src/ln/peer_handler.rs +++ b/lightning/src/ln/peer_handler.rs @@ -398,8 +398,6 @@ impl ChannelMessageHandler for ErroringMessageHandler { fn handle_tx_abort(&self, their_node_id: PublicKey, msg: &msgs::TxAbort) { ErroringMessageHandler::push_error(self, their_node_id, msg.channel_id); } - - fn message_received(&self) {} } impl Deref for ErroringMessageHandler { @@ -1638,7 +1636,7 @@ impl Date: Wed, 4 Dec 2024 17:41:11 +0530 Subject: [PATCH 03/30] Remove async BOLT12 handling support This commit temporarily removes support for async BOLT12 message handling to enable a smoother transition in the upcoming refactor. The current implementation of async handling is abrupt, as it requires delving into the synchronous case and generating an event mid-flow based on the `manual_handling` flag. This approach adds unnecessary complexity and coupling. A later commit will introduce a new struct, `OffersMessageFlow`, designed to handle and create offer messages. This new struct will support async handling in a more structured way by allowing users to implement a parameterized trait for asynchronous message handling. Removing the existing async support now ensures a cleaner and more seamless migration of offer-related code from `ChannelManager` to `OffersMessageFlow`. --- lightning/src/events/mod.rs | 64 +--------------------- lightning/src/ln/channelmanager.rs | 8 --- lightning/src/ln/offers_tests.rs | 87 ++---------------------------- lightning/src/util/config.rs | 21 ++++---- 4 files changed, 16 insertions(+), 164 deletions(-) diff --git a/lightning/src/events/mod.rs b/lightning/src/events/mod.rs index 5bc446f9724..852207ff88e 100644 --- a/lightning/src/events/mod.rs +++ b/lightning/src/events/mod.rs @@ -18,7 +18,6 @@ pub mod bump_transaction; pub use bump_transaction::BumpTransactionEvent; -use crate::blinded_path::message::OffersContext; use crate::blinded_path::payment::{Bolt12OfferContext, Bolt12RefundContext, PaymentContext, PaymentContextRef}; use crate::chain::transaction; use crate::ln::channelmanager::{InterceptId, PaymentId, RecipientOnionFields}; @@ -27,8 +26,6 @@ use crate::types::features::ChannelTypeFeatures; use crate::ln::msgs; use crate::ln::types::ChannelId; use crate::types::payment::{PaymentPreimage, PaymentHash, PaymentSecret}; -use crate::offers::invoice::Bolt12Invoice; -use crate::onion_message::messenger::Responder; use crate::routing::gossip::NetworkUpdate; use crate::routing::router::{BlindedTail, Path, RouteHop, RouteParameters}; use crate::sign::SpendableOutputDescriptor; @@ -580,6 +577,8 @@ pub enum PaymentFailureReason { /// An invoice was received that required unknown features. UnknownRequiredFeatures, /// A [`Bolt12Invoice`] was not received in a reasonable amount of time. + /// + /// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice InvoiceRequestExpired, /// An [`InvoiceRequest`] for the payment was rejected by the recipient. /// @@ -867,39 +866,6 @@ pub enum Event { /// Sockets for connecting to the node. addresses: Vec, }, - /// Indicates a [`Bolt12Invoice`] in response to an [`InvoiceRequest`] or a [`Refund`] was - /// received. - /// - /// This event will only be generated if [`UserConfig::manually_handle_bolt12_invoices`] is set. - /// Use [`ChannelManager::send_payment_for_bolt12_invoice`] to pay the invoice or - /// [`ChannelManager::abandon_payment`] to abandon the associated payment. See those docs for - /// further details. - /// - /// # Failure Behavior and Persistence - /// This event will eventually be replayed after failures-to-handle (i.e., the event handler - /// returning `Err(ReplayEvent ())`) and will be persisted across restarts. - /// - /// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest - /// [`Refund`]: crate::offers::refund::Refund - /// [`UserConfig::manually_handle_bolt12_invoices`]: crate::util::config::UserConfig::manually_handle_bolt12_invoices - /// [`ChannelManager::send_payment_for_bolt12_invoice`]: crate::ln::channelmanager::ChannelManager::send_payment_for_bolt12_invoice - /// [`ChannelManager::abandon_payment`]: crate::ln::channelmanager::ChannelManager::abandon_payment - InvoiceReceived { - /// The `payment_id` associated with payment for the invoice. - payment_id: PaymentId, - /// The invoice to pay. - invoice: Bolt12Invoice, - /// The context of the [`BlindedMessagePath`] used to send the invoice. - /// - /// [`BlindedMessagePath`]: crate::blinded_path::message::BlindedMessagePath - context: Option, - /// A responder for replying with an [`InvoiceError`] if needed. - /// - /// `None` if the invoice wasn't sent with a reply path. - /// - /// [`InvoiceError`]: crate::offers::invoice_error::InvoiceError - responder: Option, - }, /// Indicates an outbound payment we made succeeded (i.e. it made it all the way to its target /// and we got back the payment preimage for it). /// @@ -1803,15 +1769,6 @@ impl Writeable for Event { (0, peer_node_id, required), }); }, - &Event::InvoiceReceived { ref payment_id, ref invoice, ref context, ref responder } => { - 41u8.write(writer)?; - write_tlv_fields!(writer, { - (0, payment_id, required), - (2, invoice, required), - (4, context, option), - (6, responder, option), - }); - }, &Event::FundingTxBroadcastSafe { ref channel_id, ref user_channel_id, ref funding_txo, ref counterparty_node_id, ref former_temporary_channel_id} => { 43u8.write(writer)?; write_tlv_fields!(writer, { @@ -2293,23 +2250,6 @@ impl MaybeReadable for Event { }; f() }, - 41u8 => { - let mut f = || { - _init_and_read_len_prefixed_tlv_fields!(reader, { - (0, payment_id, required), - (2, invoice, required), - (4, context, option), - (6, responder, option), - }); - Ok(Some(Event::InvoiceReceived { - payment_id: payment_id.0.unwrap(), - invoice: invoice.0.unwrap(), - context, - responder, - })) - }; - f() - }, 43u8 => { let mut channel_id = RequiredWrapper(None); let mut user_channel_id = RequiredWrapper(None); diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 14f580111ca..0d85e63608b 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -12117,14 +12117,6 @@ where &self.logger, None, None, Some(invoice.payment_hash()), ); - if self.default_configuration.manually_handle_bolt12_invoices { - let event = Event::InvoiceReceived { - payment_id, invoice, context, responder, - }; - self.pending_events.lock().unwrap().push_back((event, None)); - return None; - } - let res = self.send_payment_for_verified_bolt12_invoice(&invoice, payment_id); handle_pay_invoice_res!(res, invoice, logger); }, diff --git a/lightning/src/ln/offers_tests.rs b/lightning/src/ln/offers_tests.rs index 2e091221788..ba9c42d366a 100644 --- a/lightning/src/ln/offers_tests.rs +++ b/lightning/src/ln/offers_tests.rs @@ -48,11 +48,10 @@ use crate::blinded_path::message::BlindedMessagePath; use crate::blinded_path::payment::{Bolt12OfferContext, Bolt12RefundContext, PaymentContext}; use crate::blinded_path::message::{MessageContext, OffersContext}; use crate::events::{ClosureReason, Event, MessageSendEventsProvider, PaymentFailureReason, PaymentPurpose}; -use crate::ln::channelmanager::{Bolt12PaymentError, MAX_SHORT_LIVED_RELATIVE_EXPIRY, PaymentId, RecentPaymentDetails, Retry, self}; +use crate::ln::channelmanager::{MAX_SHORT_LIVED_RELATIVE_EXPIRY, PaymentId, RecentPaymentDetails, Retry, self}; use crate::types::features::Bolt12InvoiceFeatures; use crate::ln::functional_test_utils::*; use crate::ln::msgs::{ChannelMessageHandler, Init, NodeAnnouncement, OnionMessage, OnionMessageHandler, RoutingMessageHandler, SocketAddress, UnsignedGossipMessage, UnsignedNodeAnnouncement}; -use crate::ln::outbound_payment::IDEMPOTENCY_TIMEOUT_TICKS; use crate::offers::invoice::Bolt12Invoice; use crate::offers::invoice_error::InvoiceError; use crate::offers::invoice_request::{InvoiceRequest, InvoiceRequestFields}; @@ -1143,87 +1142,11 @@ fn creates_and_pays_for_offer_with_retry() { /// Checks that a deferred invoice can be paid asynchronously from an Event::InvoiceReceived. #[test] +#[ignore] fn pays_bolt12_invoice_asynchronously() { - let mut manually_pay_cfg = test_default_channel_config(); - manually_pay_cfg.manually_handle_bolt12_invoices = true; - - 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, Some(manually_pay_cfg)]); - let nodes = create_network(2, &node_cfgs, &node_chanmgrs); - - create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 10_000_000, 1_000_000_000); - - let alice = &nodes[0]; - let alice_id = alice.node.get_our_node_id(); - let bob = &nodes[1]; - let bob_id = bob.node.get_our_node_id(); - - let offer = alice.node - .create_offer_builder(None).unwrap() - .amount_msats(10_000_000) - .build().unwrap(); - - let payment_id = PaymentId([1; 32]); - bob.node.pay_for_offer(&offer, None, None, None, payment_id, Retry::Attempts(0), None).unwrap(); - expect_recent_payment!(bob, RecentPaymentDetails::AwaitingInvoice, payment_id); - - let onion_message = bob.onion_messenger.next_onion_message_for_peer(alice_id).unwrap(); - alice.onion_messenger.handle_onion_message(bob_id, &onion_message); - - let (invoice_request, _) = extract_invoice_request(alice, &onion_message); - let payment_context = PaymentContext::Bolt12Offer(Bolt12OfferContext { - offer_id: offer.id(), - invoice_request: InvoiceRequestFields { - payer_signing_pubkey: invoice_request.payer_signing_pubkey(), - quantity: None, - payer_note_truncated: None, - human_readable_name: None, - }, - }); - - let onion_message = alice.onion_messenger.next_onion_message_for_peer(bob_id).unwrap(); - bob.onion_messenger.handle_onion_message(alice_id, &onion_message); - - let (invoice, context) = match get_event!(bob, Event::InvoiceReceived) { - Event::InvoiceReceived { payment_id: actual_payment_id, invoice, context, .. } => { - assert_eq!(actual_payment_id, payment_id); - (invoice, context) - }, - _ => panic!("No Event::InvoiceReceived"), - }; - assert_eq!(invoice.amount_msats(), 10_000_000); - assert_ne!(invoice.signing_pubkey(), alice_id); - assert!(!invoice.payment_paths().is_empty()); - for path in invoice.payment_paths() { - assert_eq!(path.introduction_node(), &IntroductionNode::NodeId(alice_id)); - } - - assert!(bob.node.send_payment_for_bolt12_invoice(&invoice, context.as_ref()).is_ok()); - assert_eq!( - bob.node.send_payment_for_bolt12_invoice(&invoice, context.as_ref()), - Err(Bolt12PaymentError::DuplicateInvoice), - ); - - route_bolt12_payment(bob, &[alice], &invoice); - expect_recent_payment!(bob, RecentPaymentDetails::Pending, payment_id); - - claim_bolt12_payment(bob, &[alice], payment_context); - expect_recent_payment!(bob, RecentPaymentDetails::Fulfilled, payment_id); - - assert_eq!( - bob.node.send_payment_for_bolt12_invoice(&invoice, context.as_ref()), - Err(Bolt12PaymentError::DuplicateInvoice), - ); - - for _ in 0..=IDEMPOTENCY_TIMEOUT_TICKS { - bob.node.timer_tick_occurred(); - } - - assert_eq!( - bob.node.send_payment_for_bolt12_invoice(&invoice, context.as_ref()), - Err(Bolt12PaymentError::UnexpectedInvoice), - ); + // This test is temporarily disabled as manually_handle_bolt12_invoices and InvoiceReceived + // are no longer present. + todo!("Update this test when the relevant functionality is reintroduced."); } /// Checks that an offer can be created using an unannounced node as a blinded path's introduction diff --git a/lightning/src/util/config.rs b/lightning/src/util/config.rs index 745e13ae6c3..c1fafac15d4 100644 --- a/lightning/src/util/config.rs +++ b/lightning/src/util/config.rs @@ -844,20 +844,17 @@ pub struct UserConfig { /// [`ChannelManager::get_intercept_scid`]: crate::ln::channelmanager::ChannelManager::get_intercept_scid /// [`Event::HTLCIntercepted`]: crate::events::Event::HTLCIntercepted pub accept_intercept_htlcs: bool, - /// If this is set to `true`, the user needs to manually pay [`Bolt12Invoice`]s when received. + /// If this is set to `false`, when receiving a keysend payment we'll fail it if it has multiple + /// parts. If this is set to `true`, we'll accept the payment. /// - /// When set to `true`, [`Event::InvoiceReceived`] will be generated for each received - /// [`Bolt12Invoice`] instead of being automatically paid after verification. Use - /// [`ChannelManager::send_payment_for_bolt12_invoice`] to pay the invoice or - /// [`ChannelManager::abandon_payment`] to abandon the associated payment. + /// Setting this to `true` will break backwards compatibility upon downgrading to an LDK + /// version prior to 0.0.116 while receiving an MPP keysend. If we have already received an MPP + /// keysend, downgrading will cause us to fail to deserialize [`ChannelManager`]. /// /// Default value: `false` /// - /// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice - /// [`Event::InvoiceReceived`]: crate::events::Event::InvoiceReceived - /// [`ChannelManager::send_payment_for_bolt12_invoice`]: crate::ln::channelmanager::ChannelManager::send_payment_for_bolt12_invoice - /// [`ChannelManager::abandon_payment`]: crate::ln::channelmanager::ChannelManager::abandon_payment - pub manually_handle_bolt12_invoices: bool, + /// [`ChannelManager`]: crate::ln::channelmanager::ChannelManager + pub accept_mpp_keysend: bool, } impl Default for UserConfig { @@ -870,7 +867,7 @@ impl Default for UserConfig { accept_inbound_channels: true, manually_accept_inbound_channels: false, accept_intercept_htlcs: false, - manually_handle_bolt12_invoices: false, + accept_mpp_keysend: false, } } } @@ -889,7 +886,7 @@ impl Readable for UserConfig { accept_inbound_channels: Readable::read(reader)?, manually_accept_inbound_channels: Readable::read(reader)?, accept_intercept_htlcs: Readable::read(reader)?, - manually_handle_bolt12_invoices: Readable::read(reader)?, + accept_mpp_keysend: Readable::read(reader)?, }) } } From d7c47af2e38f2a3a1649726151634a704f299f02 Mon Sep 17 00:00:00 2001 From: shaavan Date: Fri, 15 Nov 2024 17:56:06 +0530 Subject: [PATCH 04/30] Introduce `OffersMessageFlow` This commit introduces a new struct, `OffersMessageFlow`, to extract all offers message-related code out of `ChannelManager`. By moving this logic into a dedicated struct, it creates a foundation for separating responsibilities and sets up a base for further code restructuring in subsequent commits. --- lightning/src/offers/flow.rs | 61 ++++++++++++++++++++++++++++++++++++ lightning/src/offers/mod.rs | 1 + 2 files changed, 62 insertions(+) create mode 100644 lightning/src/offers/flow.rs diff --git a/lightning/src/offers/flow.rs b/lightning/src/offers/flow.rs new file mode 100644 index 00000000000..02471d9545f --- /dev/null +++ b/lightning/src/offers/flow.rs @@ -0,0 +1,61 @@ +// This file is Copyright its original authors, visible in version control +// history. +// +// This file is licensed under the Apache License, Version 2.0 or the MIT license +// , at your option. +// You may not use this file except in accordance with one or both of these +// licenses. + +//! Provides data structures and functions for creating and managing Offers messages, +//! facilitating communication, and handling Bolt12 invoice payments. + +use crate::prelude::*; +use core::ops::Deref; + +use crate::util::logger::Logger; + +/// Facilitates the handling, communication, and management of Offers messages within a Lightning +/// node, enabling the creation, verification, and resolution of BOLT 12 invoices and related +/// payment flows. +/// +/// The `OffersMessageFlow` struct integrates several components to manage the lifecycle of Offers +/// messages, ensuring robust communication and payment handling: +/// - EntropySource to provide cryptographic randomness essential for Offers message handling. +/// - [`Logger`] for detailed operational logging of Offers-related activity. +/// - OffersMessageCommons for core operations shared across Offers messages, such as metadata +/// verification and signature handling. +/// - MessageRouter for routing Offers messages to their appropriate destinations within the +/// Lightning network. +/// - Manages OffersMessage for creating and processing Offers-related messages. +/// - Handles [`DNSResolverMessage`] for resolving human-readable names in Offers messages +/// (when the `dnssec` feature is enabled). +/// +/// Key Features: +/// - Supports creating BOLT 12 Offers, invoice requests, and refunds. +/// - Integrates with the Lightning node's broader message and payment infrastructure. +/// - Handles cryptographic operations and message validation to ensure compliance with BOLT 12. +/// - Supports DNS resolution for human-readable names (when enabled with `dnssec` feature). +/// +/// This struct is essential for enabling BOLT12 payment workflows in the Lightning network, +/// providing the foundational mechanisms for Offers and related message exchanges. +/// +/// [`DNSResolverMessage`]: crate::onion_message::dns_resolution::DNSResolverMessage +pub struct OffersMessageFlow +where + L::Target: Logger, +{ + /// The Logger for use in the OffersMessageFlow and which may be used to log + /// information during deserialization. + pub logger: L, +} + +impl OffersMessageFlow +where + L::Target: Logger, +{ + /// Creates a new [`OffersMessageFlow`] + pub fn new(logger: L) -> Self { + Self { logger } + } +} diff --git a/lightning/src/offers/mod.rs b/lightning/src/offers/mod.rs index e4fe7d789db..ef34f15e0b7 100644 --- a/lightning/src/offers/mod.rs +++ b/lightning/src/offers/mod.rs @@ -12,6 +12,7 @@ //! //! Offers are a flexible protocol for Lightning payments. +pub mod flow; #[macro_use] pub mod offer; From 7f7a1fe2cbb98b219b0d1b80c1e9e59b9cce9bc9 Mon Sep 17 00:00:00 2001 From: shaavan Date: Fri, 15 Nov 2024 18:45:45 +0530 Subject: [PATCH 05/30] Introduce `OffersMessageCommons` Trait and Implementation A new trait, `OffersMessageCommons`, is introduced to encapsulate functions that are commonly used by both BOLT12-related functionality and other parts of `ChannelManager`. This enables a clean transition of BOLT12 code to `OffersMessageFlow` by moving shared functions into the new trait, ensuring they remain accessible to both `ChannelManager` and the refactored BOLT12 code. --- fuzz/src/chanmon_consistency.rs | 1 + lightning/src/events/mod.rs | 8 +- lightning/src/ln/async_signer_tests.rs | 1 + lightning/src/ln/bolt11_payment.rs | 1 + lightning/src/ln/chanmon_update_fail_tests.rs | 2 +- lightning/src/ln/channelmanager.rs | 329 +++++++++--------- lightning/src/ln/functional_test_utils.rs | 2 +- lightning/src/ln/functional_tests.rs | 3 +- lightning/src/ln/inbound_payment.rs | 4 +- lightning/src/ln/invoice_utils.rs | 11 +- lightning/src/ln/mod.rs | 2 +- lightning/src/ln/monitor_tests.rs | 2 +- lightning/src/ln/onion_route_tests.rs | 2 +- lightning/src/ln/outbound_payment.rs | 2 +- lightning/src/ln/payment_tests.rs | 3 +- lightning/src/ln/priv_short_conf_tests.rs | 2 +- lightning/src/ln/shutdown_tests.rs | 2 +- lightning/src/offers/flow.rs | 119 +++++++ 18 files changed, 313 insertions(+), 183 deletions(-) diff --git a/fuzz/src/chanmon_consistency.rs b/fuzz/src/chanmon_consistency.rs index ca3f0028f3a..e9a6c919e6d 100644 --- a/fuzz/src/chanmon_consistency.rs +++ b/fuzz/src/chanmon_consistency.rs @@ -56,6 +56,7 @@ use lightning::ln::msgs::{ }; use lightning::ln::script::ShutdownScript; use lightning::ln::types::ChannelId; +use lightning::offers::flow::OffersMessageCommons; use lightning::offers::invoice::UnsignedBolt12Invoice; use lightning::onion_message::messenger::{Destination, MessageRouter, OnionMessagePath}; use lightning::routing::router::{ diff --git a/lightning/src/events/mod.rs b/lightning/src/events/mod.rs index 852207ff88e..e2bbfb0cc4f 100644 --- a/lightning/src/events/mod.rs +++ b/lightning/src/events/mod.rs @@ -85,22 +85,22 @@ pub enum PaymentPurpose { /// A payment for a BOLT 11 invoice. Bolt11InvoicePayment { /// The preimage to the payment_hash, if the payment hash (and secret) were fetched via - /// [`ChannelManager::create_inbound_payment`]. When handling [`Event::PaymentClaimable`], + /// [`OffersMessageCommons::create_inbound_payment`]. When handling [`Event::PaymentClaimable`], /// this can be passed directly to [`ChannelManager::claim_funds`] to claim the payment. No /// action is needed when seen in [`Event::PaymentClaimed`]. /// - /// [`ChannelManager::create_inbound_payment`]: crate::ln::channelmanager::ChannelManager::create_inbound_payment + /// [`OffersMessageCommons::create_inbound_payment`]: crate::offers::flow::OffersMessageCommons::create_inbound_payment /// [`ChannelManager::claim_funds`]: crate::ln::channelmanager::ChannelManager::claim_funds payment_preimage: Option, /// The "payment secret". This authenticates the sender to the recipient, preventing a /// number of deanonymization attacks during the routing process. /// It is provided here for your reference, however its accuracy is enforced directly by /// [`ChannelManager`] using the values you previously provided to - /// [`ChannelManager::create_inbound_payment`] or + /// [`OffersMessageCommons::create_inbound_payment`] or /// [`ChannelManager::create_inbound_payment_for_hash`]. /// /// [`ChannelManager`]: crate::ln::channelmanager::ChannelManager - /// [`ChannelManager::create_inbound_payment`]: crate::ln::channelmanager::ChannelManager::create_inbound_payment + /// [`OffersMessageCommons::create_inbound_payment`]: crate::offers::flow::OffersMessageCommons::create_inbound_payment /// [`ChannelManager::create_inbound_payment_for_hash`]: crate::ln::channelmanager::ChannelManager::create_inbound_payment_for_hash payment_secret: PaymentSecret, }, diff --git a/lightning/src/ln/async_signer_tests.rs b/lightning/src/ln/async_signer_tests.rs index bfac345a4c6..72a646e1a1b 100644 --- a/lightning/src/ln/async_signer_tests.rs +++ b/lightning/src/ln/async_signer_tests.rs @@ -26,6 +26,7 @@ use crate::ln::channel_state::{ChannelDetails, ChannelShutdownState}; use crate::ln::channelmanager::{PaymentId, RAACommitmentOrder, RecipientOnionFields}; use crate::ln::msgs::ChannelMessageHandler; use crate::ln::{functional_test_utils::*, msgs}; +use crate::offers::flow::OffersMessageCommons; use crate::sign::ecdsa::EcdsaChannelSigner; use crate::sign::SignerProvider; use crate::util::test_channel_signer::SignerOp; diff --git a/lightning/src/ln/bolt11_payment.rs b/lightning/src/ln/bolt11_payment.rs index 232183cdb70..8f6296093cf 100644 --- a/lightning/src/ln/bolt11_payment.rs +++ b/lightning/src/ln/bolt11_payment.rs @@ -87,6 +87,7 @@ fn params_from_invoice( #[cfg(test)] mod tests { use super::*; + use crate::offers::flow::OffersMessageCommons; use crate::routing::router::Payee; use crate::types::payment::PaymentSecret; use bitcoin::hashes::sha256::Hash as Sha256; diff --git a/lightning/src/ln/chanmon_update_fail_tests.rs b/lightning/src/ln/chanmon_update_fail_tests.rs index dc67b198149..673eb669acf 100644 --- a/lightning/src/ln/chanmon_update_fail_tests.rs +++ b/lightning/src/ln/chanmon_update_fail_tests.rs @@ -19,7 +19,7 @@ use crate::chain::channelmonitor::{ANTI_REORG_DELAY, ChannelMonitor}; use crate::chain::transaction::OutPoint; use crate::chain::{ChannelMonitorUpdateStatus, Listen, Watch}; use crate::events::{Event, MessageSendEvent, MessageSendEventsProvider, PaymentPurpose, ClosureReason, HTLCDestination}; -use crate::ln::channelmanager::{RAACommitmentOrder, PaymentSendFailure, PaymentId, RecipientOnionFields}; +use crate::ln::channelmanager::{PaymentId, PaymentSendFailure, RAACommitmentOrder, RecipientOnionFields}; use crate::ln::channel::{AnnouncementSigsState, ChannelPhase}; use crate::ln::msgs; use crate::ln::types::ChannelId; diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 0d85e63608b..6c870e9870e 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -28,7 +28,7 @@ use bitcoin::hashes::hmac::Hmac; use bitcoin::hashes::sha256::Hash as Sha256; use bitcoin::hash_types::{BlockHash, Txid}; -use bitcoin::secp256k1::{SecretKey,PublicKey}; +use bitcoin::secp256k1::{schnorr, PublicKey, SecretKey}; use bitcoin::secp256k1::Secp256k1; use bitcoin::{secp256k1, Sequence, Weight}; @@ -47,6 +47,7 @@ use crate::events::{self, Event, EventHandler, EventsProvider, InboundChannelFun // construct one themselves. use crate::ln::inbound_payment; use crate::ln::types::ChannelId; +use crate::offers::flow::OffersMessageCommons; use crate::types::payment::{PaymentHash, PaymentPreimage, PaymentSecret}; use crate::ln::channel::{self, Channel, ChannelPhase, ChannelError, ChannelUpdateStatus, ShutdownResult, UpdateFulfillCommitFetch, OutboundV1Channel, InboundV1Channel, WithChannelContext, InboundV2Channel, InteractivelyFunded as _}; use crate::ln::channel_state::ChannelDetails; @@ -119,7 +120,7 @@ use core::{cmp, mem}; use core::borrow::Borrow; use core::cell::RefCell; use crate::io::Read; -use crate::sync::{Arc, Mutex, RwLock, RwLockReadGuard, FairRwLock, LockTestExt, LockHeldState}; +use crate::sync::{Arc, FairRwLock, LockHeldState, LockTestExt, Mutex, MutexGuard, RwLock, RwLockReadGuard}; use core::sync::atomic::{AtomicUsize, AtomicBool, Ordering}; use core::time::Duration; use core::ops::Deref; @@ -1143,8 +1144,15 @@ impl_writeable_tlv_based_enum_upgradable!(MonitorUpdateCompletionAction, }, ); +/// Represents an action that needs to be performed after a user handles a specific event. +/// These actions ensure that the channel state changes associated with an event are properly +/// persisted and handled to maintain consistency and safety. #[derive(Clone, Debug, PartialEq, Eq)] pub(crate) enum EventCompletionAction { + /// Indicates that an `RAA` (RevokeAndACK) channel monitor update should be released. + /// This is typically used to ensure that changes associated with the counterparty's + /// revoked commitment state are correctly applied to the channel monitor only after + /// the user has processed the event. ReleaseRAAChannelMonitorUpdate { counterparty_node_id: PublicKey, channel_funding_outpoint: OutPoint, @@ -4749,36 +4757,6 @@ where } } - fn verify_bolt12_invoice( - &self, invoice: &Bolt12Invoice, context: Option<&OffersContext>, - ) -> Result { - let secp_ctx = &self.secp_ctx; - let expanded_key = &self.inbound_payment_key; - - match context { - None if invoice.is_for_refund_without_paths() => { - invoice.verify_using_metadata(expanded_key, secp_ctx) - }, - Some(&OffersContext::OutboundPayment { payment_id, nonce, .. }) => { - invoice.verify_using_payer_data(payment_id, nonce, expanded_key, secp_ctx) - }, - _ => Err(()), - } - } - - fn send_payment_for_verified_bolt12_invoice(&self, invoice: &Bolt12Invoice, payment_id: PaymentId) -> Result<(), Bolt12PaymentError> { - let best_block_height = self.best_block.read().unwrap().height; - let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self); - let features = self.bolt12_invoice_features(); - self.pending_outbound_payments - .send_payment_for_bolt12_invoice( - invoice, payment_id, &self.router, self.list_usable_channels(), features, - || self.compute_inflight_htlcs(), &self.entropy_source, &self.node_signer, &self, - &self.secp_ctx, best_block_height, &self.logger, &self.pending_events, - |args| self.send_payment_along_path(args) - ) - } - #[cfg(async_payments)] fn initiate_async_payment( &self, invoice: &StaticInvoice, payment_id: PaymentId @@ -4894,11 +4872,6 @@ where self.abandon_payment_with_reason(payment_id, PaymentFailureReason::UserAbandoned) } - fn abandon_payment_with_reason(&self, payment_id: PaymentId, reason: PaymentFailureReason) { - let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self); - self.pending_outbound_payments.abandon_payment(payment_id, reason, &self.pending_events); - } - /// Send a spontaneous payment, which is a payment that does not require the recipient to have /// generated an invoice. Optionally, you may specify the preimage. If you do choose to specify /// the preimage, it must be a cryptographically secure random value that no intermediate node @@ -9984,6 +9957,162 @@ macro_rules! create_refund_builder { ($self: ident, $builder: ty) => { } } } +impl OffersMessageCommons for ChannelManager +where + M::Target: chain::Watch<::EcdsaSigner>, + T::Target: BroadcasterInterface, + ES::Target: EntropySource, + NS::Target: NodeSigner, + SP::Target: SignerProvider, + F::Target: FeeEstimator, + R::Target: Router, + MR::Target: MessageRouter, + L::Target: Logger, +{ + fn get_pending_offers_messages(&self) -> MutexGuard<'_, Vec<(OffersMessage, MessageSendInstructions)>> { + self.pending_offers_messages.lock().expect("Mutex is locked by other thread.") + } + + fn sign_bolt12_invoice( + &self, invoice: &UnsignedBolt12Invoice, + ) -> Result { + self.node_signer.sign_bolt12_invoice(invoice) + } + + fn create_inbound_payment(&self, min_value_msat: Option, invoice_expiry_delta_secs: u32, + min_final_cltv_expiry_delta: Option) -> Result<(PaymentHash, PaymentSecret), ()> { + inbound_payment::create(&self.inbound_payment_key, min_value_msat, invoice_expiry_delta_secs, + &self.entropy_source, self.highest_seen_timestamp.load(Ordering::Acquire) as u64, + min_final_cltv_expiry_delta) + } + + /// Creates multi-hop blinded payment paths for the given `amount_msats` by delegating to + /// [`Router::create_blinded_payment_paths`]. + fn create_blinded_payment_paths( + &self, amount_msats: u64, payment_secret: PaymentSecret, payment_context: PaymentContext + ) -> Result, ()> { + let expanded_key = &self.inbound_payment_key; + let entropy = &*self.entropy_source; + let secp_ctx = &self.secp_ctx; + + let first_hops = self.list_usable_channels(); + let payee_node_id = self.get_our_node_id(); + let max_cltv_expiry = self.best_block.read().unwrap().height + CLTV_FAR_FAR_AWAY + + LATENCY_GRACE_PERIOD_BLOCKS; + + let payee_tlvs = UnauthenticatedReceiveTlvs { + payment_secret, + payment_constraints: PaymentConstraints { + max_cltv_expiry, + htlc_minimum_msat: 1, + }, + payment_context, + }; + let nonce = Nonce::from_entropy_source(entropy); + let payee_tlvs = payee_tlvs.authenticate(nonce, expanded_key); + + self.router.create_blinded_payment_paths( + payee_node_id, first_hops, payee_tlvs, amount_msats, secp_ctx + ) + } + + fn verify_bolt12_invoice( + &self, invoice: &Bolt12Invoice, context: Option<&OffersContext>, + ) -> Result { + let secp_ctx = &self.secp_ctx; + let expanded_key = &self.inbound_payment_key; + + match context { + None if invoice.is_for_refund_without_paths() => { + invoice.verify_using_metadata(expanded_key, secp_ctx) + }, + Some(&OffersContext::OutboundPayment { payment_id, nonce, .. }) => { + invoice.verify_using_payer_data(payment_id, nonce, expanded_key, secp_ctx) + }, + _ => Err(()), + } + } + + fn send_payment_for_verified_bolt12_invoice(&self, invoice: &Bolt12Invoice, payment_id: PaymentId) -> Result<(), Bolt12PaymentError> { + let best_block_height = self.best_block.read().unwrap().height; + let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self); + let features = self.bolt12_invoice_features(); + self.pending_outbound_payments + .send_payment_for_bolt12_invoice( + invoice, payment_id, &self.router, self.list_usable_channels(), features, + || self.compute_inflight_htlcs(), &self.entropy_source, &self.node_signer, &self, + &self.secp_ctx, best_block_height, &self.logger, &self.pending_events, + |args| self.send_payment_along_path(args) + ) + } + + fn abandon_payment_with_reason(&self, payment_id: PaymentId, reason: PaymentFailureReason) { + let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self); + self.pending_outbound_payments.abandon_payment(payment_id, reason, &self.pending_events); + } + + fn release_invoice_requests_awaiting_invoice(&self) -> Vec<(PaymentId, RetryableInvoiceRequest)> { + self.pending_outbound_payments.release_invoice_requests_awaiting_invoice() + } + + fn create_blinded_paths(&self, context: MessageContext) -> Result, ()> { + let recipient = self.get_our_node_id(); + let secp_ctx = &self.secp_ctx; + + let peers = self.per_peer_state.read().unwrap() + .iter() + .map(|(node_id, peer_state)| (node_id, peer_state.lock().unwrap())) + .filter(|(_, peer)| peer.is_connected) + .filter(|(_, peer)| peer.latest_features.supports_onion_messages()) + .map(|(node_id, _)| *node_id) + .collect::>(); + + self.message_router + .create_blinded_paths(recipient, context, peers, secp_ctx) + .and_then(|paths| (!paths.is_empty()).then(|| paths).ok_or(())) + } + + fn enqueue_invoice_request( + &self, + invoice_request: InvoiceRequest, + reply_paths: Vec, + ) -> Result<(), Bolt12SemanticError> { + let mut pending_offers_messages = self.pending_offers_messages.lock().unwrap(); + if !invoice_request.paths().is_empty() { + reply_paths + .iter() + .flat_map(|reply_path| invoice_request.paths().iter().map(move |path| (path, reply_path))) + .take(OFFERS_MESSAGE_REQUEST_LIMIT) + .for_each(|(path, reply_path)| { + let instructions = MessageSendInstructions::WithSpecifiedReplyPath { + destination: Destination::BlindedPath(path.clone()), + reply_path: reply_path.clone(), + }; + let message = OffersMessage::InvoiceRequest(invoice_request.clone()); + pending_offers_messages.push((message, instructions)); + }); + } else if let Some(node_id) = invoice_request.issuer_signing_pubkey() { + for reply_path in reply_paths { + let instructions = MessageSendInstructions::WithSpecifiedReplyPath { + destination: Destination::Node(node_id), + reply_path, + }; + let message = OffersMessage::InvoiceRequest(invoice_request.clone()); + pending_offers_messages.push((message, instructions)); + } + } else { + debug_assert!(false); + return Err(Bolt12SemanticError::MissingIssuerSigningPubkey); + } + + Ok(()) + } + + fn get_current_blocktime(&self) -> Duration { + Duration::from_secs(self.highest_seen_timestamp.load(Ordering::Acquire) as u64) + } +} + /// Defines the maximum number of [`OffersMessage`] including different reply paths to be sent /// along different paths. /// Sending multiple requests increases the chances of successful delivery in case some @@ -10133,42 +10262,6 @@ where self.enqueue_invoice_request(invoice_request, reply_paths) } - fn enqueue_invoice_request( - &self, - invoice_request: InvoiceRequest, - reply_paths: Vec, - ) -> Result<(), Bolt12SemanticError> { - let mut pending_offers_messages = self.pending_offers_messages.lock().unwrap(); - if !invoice_request.paths().is_empty() { - reply_paths - .iter() - .flat_map(|reply_path| invoice_request.paths().iter().map(move |path| (path, reply_path))) - .take(OFFERS_MESSAGE_REQUEST_LIMIT) - .for_each(|(path, reply_path)| { - let instructions = MessageSendInstructions::WithSpecifiedReplyPath { - destination: Destination::BlindedPath(path.clone()), - reply_path: reply_path.clone(), - }; - let message = OffersMessage::InvoiceRequest(invoice_request.clone()); - pending_offers_messages.push((message, instructions)); - }); - } else if let Some(node_id) = invoice_request.issuer_signing_pubkey() { - for reply_path in reply_paths { - let instructions = MessageSendInstructions::WithSpecifiedReplyPath { - destination: Destination::Node(node_id), - reply_path, - }; - let message = OffersMessage::InvoiceRequest(invoice_request.clone()); - pending_offers_messages.push((message, instructions)); - } - } else { - debug_assert!(false); - return Err(Bolt12SemanticError::MissingIssuerSigningPubkey); - } - - Ok(()) - } - /// Creates a [`Bolt12Invoice`] for a [`Refund`] and enqueues it to be sent via an onion /// message. /// @@ -10336,43 +10429,6 @@ where Ok(()) } - /// Gets a payment secret and payment hash for use in an invoice given to a third party wishing - /// to pay us. - /// - /// This differs from [`create_inbound_payment_for_hash`] only in that it generates the - /// [`PaymentHash`] and [`PaymentPreimage`] for you. - /// - /// The [`PaymentPreimage`] will ultimately be returned to you in the [`PaymentClaimable`] event, which - /// will have the [`PaymentClaimable::purpose`] return `Some` for [`PaymentPurpose::preimage`]. That - /// should then be passed directly to [`claim_funds`]. - /// - /// See [`create_inbound_payment_for_hash`] for detailed documentation on behavior and requirements. - /// - /// Note that a malicious eavesdropper can intuit whether an inbound payment was created by - /// `create_inbound_payment` or `create_inbound_payment_for_hash` based on runtime. - /// - /// # Note - /// - /// If you register an inbound payment with this method, then serialize the `ChannelManager`, then - /// deserialize it with a node running 0.0.103 and earlier, the payment will fail to be received. - /// - /// Errors if `min_value_msat` is greater than total bitcoin supply. - /// - /// If `min_final_cltv_expiry_delta` is set to some value, then the payment will not be receivable - /// on versions of LDK prior to 0.0.114. - /// - /// [`claim_funds`]: Self::claim_funds - /// [`PaymentClaimable`]: events::Event::PaymentClaimable - /// [`PaymentClaimable::purpose`]: events::Event::PaymentClaimable::purpose - /// [`PaymentPurpose::preimage`]: events::PaymentPurpose::preimage - /// [`create_inbound_payment_for_hash`]: Self::create_inbound_payment_for_hash - pub fn create_inbound_payment(&self, min_value_msat: Option, invoice_expiry_delta_secs: u32, - min_final_cltv_expiry_delta: Option) -> Result<(PaymentHash, PaymentSecret), ()> { - inbound_payment::create(&self.inbound_payment_key, min_value_msat, invoice_expiry_delta_secs, - &self.entropy_source, self.highest_seen_timestamp.load(Ordering::Acquire) as u64, - min_final_cltv_expiry_delta) - } - /// Gets a [`PaymentSecret`] for a given [`PaymentHash`], for which the payment preimage is /// stored external to LDK. /// @@ -10466,27 +10522,6 @@ where now } - /// Creates a collection of blinded paths by delegating to - /// [`MessageRouter::create_blinded_paths`]. - /// - /// Errors if the `MessageRouter` errors. - fn create_blinded_paths(&self, context: MessageContext) -> Result, ()> { - let recipient = self.get_our_node_id(); - let secp_ctx = &self.secp_ctx; - - let peers = self.per_peer_state.read().unwrap() - .iter() - .map(|(node_id, peer_state)| (node_id, peer_state.lock().unwrap())) - .filter(|(_, peer)| peer.is_connected) - .filter(|(_, peer)| peer.latest_features.supports_onion_messages()) - .map(|(node_id, _)| *node_id) - .collect::>(); - - self.message_router - .create_blinded_paths(recipient, context, peers, secp_ctx) - .and_then(|paths| (!paths.is_empty()).then(|| paths).ok_or(())) - } - /// Creates a collection of blinded paths by delegating to /// [`MessageRouter::create_compact_blinded_paths`]. /// @@ -10515,36 +10550,6 @@ where .and_then(|paths| (!paths.is_empty()).then(|| paths).ok_or(())) } - /// Creates multi-hop blinded payment paths for the given `amount_msats` by delegating to - /// [`Router::create_blinded_payment_paths`]. - fn create_blinded_payment_paths( - &self, amount_msats: u64, payment_secret: PaymentSecret, payment_context: PaymentContext - ) -> Result, ()> { - let expanded_key = &self.inbound_payment_key; - let entropy = &*self.entropy_source; - let secp_ctx = &self.secp_ctx; - - let first_hops = self.list_usable_channels(); - let payee_node_id = self.get_our_node_id(); - let max_cltv_expiry = self.best_block.read().unwrap().height + CLTV_FAR_FAR_AWAY - + LATENCY_GRACE_PERIOD_BLOCKS; - - let payee_tlvs = UnauthenticatedReceiveTlvs { - payment_secret, - payment_constraints: PaymentConstraints { - max_cltv_expiry, - htlc_minimum_msat: 1, - }, - payment_context, - }; - let nonce = Nonce::from_entropy_source(entropy); - let payee_tlvs = payee_tlvs.authenticate(nonce, expanded_key); - - self.router.create_blinded_payment_paths( - payee_node_id, first_hops, payee_tlvs, amount_msats, secp_ctx - ) - } - /// Gets a fake short channel id for use in receiving [phantom node payments]. These fake scids /// are used when constructing the phantom invoice's route hints. /// @@ -14356,7 +14361,7 @@ mod tests { use crate::events::{Event, HTLCDestination, MessageSendEvent, MessageSendEventsProvider, ClosureReason}; use crate::ln::types::ChannelId; use crate::types::payment::{PaymentPreimage, PaymentHash, PaymentSecret}; - use crate::ln::channelmanager::{create_recv_pending_htlc_info, HTLCForwardInfo, inbound_payment, PaymentId, PaymentSendFailure, RecipientOnionFields, InterceptId}; + use crate::ln::channelmanager::{create_recv_pending_htlc_info, inbound_payment, HTLCForwardInfo, InterceptId, PaymentId, PaymentSendFailure, RecipientOnionFields}; use crate::ln::functional_test_utils::*; use crate::ln::msgs::{self, ErrorAction}; use crate::ln::msgs::ChannelMessageHandler; diff --git a/lightning/src/ln/functional_test_utils.rs b/lightning/src/ln/functional_test_utils.rs index b4f172b4a27..79211e3fecc 100644 --- a/lightning/src/ln/functional_test_utils.rs +++ b/lightning/src/ln/functional_test_utils.rs @@ -17,7 +17,7 @@ use crate::events::{ClaimedHTLC, ClosureReason, Event, HTLCDestination, MessageS use crate::events::bump_transaction::{BumpTransactionEvent, BumpTransactionEventHandler, Wallet, WalletSource}; use crate::ln::types::ChannelId; use crate::types::payment::{PaymentPreimage, PaymentHash, PaymentSecret}; -use crate::ln::channelmanager::{AChannelManager, ChainParameters, ChannelManager, ChannelManagerReadArgs, RAACommitmentOrder, RecipientOnionFields, PaymentId, MIN_CLTV_EXPIRY_DELTA}; +use crate::ln::channelmanager::{AChannelManager, ChainParameters, ChannelManager, ChannelManagerReadArgs, PaymentId, RAACommitmentOrder, RecipientOnionFields, MIN_CLTV_EXPIRY_DELTA}; use crate::types::features::InitFeatures; use crate::ln::msgs; use crate::ln::msgs::{ChannelMessageHandler, OnionMessageHandler, RoutingMessageHandler}; diff --git a/lightning/src/ln/functional_tests.rs b/lightning/src/ln/functional_tests.rs index ac43efe4499..38a08c1ebae 100644 --- a/lightning/src/ln/functional_tests.rs +++ b/lightning/src/ln/functional_tests.rs @@ -17,12 +17,13 @@ use crate::chain::chaininterface::LowerBoundedFeeEstimator; use crate::chain::channelmonitor; use crate::chain::channelmonitor::{Balance, ChannelMonitorUpdateStep, CLTV_CLAIM_BUFFER, LATENCY_GRACE_PERIOD_BLOCKS, ANTI_REORG_DELAY}; use crate::chain::transaction::OutPoint; +use crate::offers::flow::OffersMessageCommons; use crate::sign::{ecdsa::EcdsaChannelSigner, EntropySource, OutputSpender, SignerProvider}; use crate::events::{Event, FundingInfo, MessageSendEvent, MessageSendEventsProvider, PathFailure, PaymentPurpose, ClosureReason, HTLCDestination, PaymentFailureReason}; use crate::ln::types::ChannelId; use crate::types::payment::{PaymentPreimage, PaymentSecret, PaymentHash}; use crate::ln::channel::{CONCURRENT_INBOUND_HTLC_FEE_BUFFER, FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE, MIN_AFFORDABLE_HTLC_COUNT, get_holder_selected_channel_reserve_satoshis, OutboundV1Channel, InboundV1Channel, COINBASE_MATURITY, ChannelPhase}; -use crate::ln::channelmanager::{self, PaymentId, RAACommitmentOrder, PaymentSendFailure, RecipientOnionFields, BREAKDOWN_TIMEOUT, ENABLE_GOSSIP_TICKS, DISABLE_GOSSIP_TICKS, MIN_CLTV_EXPIRY_DELTA}; +use crate::ln::channelmanager::{self, PaymentId, PaymentSendFailure, RAACommitmentOrder, RecipientOnionFields, BREAKDOWN_TIMEOUT, DISABLE_GOSSIP_TICKS, ENABLE_GOSSIP_TICKS, MIN_CLTV_EXPIRY_DELTA}; use crate::ln::channel::{DISCONNECT_PEER_AWAITING_RESPONSE_TICKS, ChannelError}; use crate::ln::{chan_utils, onion_utils}; use crate::ln::chan_utils::{commitment_tx_base_weight, COMMITMENT_TX_WEIGHT_PER_HTLC, OFFERED_HTLC_SCRIPT_WEIGHT, htlc_success_tx_weight, htlc_timeout_tx_weight, HTLCOutputInCommitment}; diff --git a/lightning/src/ln/inbound_payment.rs b/lightning/src/ln/inbound_payment.rs index 72f877978fe..7103f4273a0 100644 --- a/lightning/src/ln/inbound_payment.rs +++ b/lightning/src/ln/inbound_payment.rs @@ -125,7 +125,7 @@ fn min_final_cltv_expiry_delta_from_metadata(bytes: [u8; METADATA_LEN]) -> u16 { u16::from_be_bytes([expiry_bytes[0], expiry_bytes[1]]) } -/// Equivalent to [`crate::ln::channelmanager::ChannelManager::create_inbound_payment`], but no +/// Equivalent to [`crate::offers::flow::OffersMessageCommons::create_inbound_payment`], but no /// `ChannelManager` is required. Useful for generating invoices for [phantom node payments] without /// a `ChannelManager`. /// @@ -302,7 +302,7 @@ fn construct_payment_secret(iv_bytes: &[u8; IV_LEN], metadata_bytes: &[u8; METAD /// See [`ExpandedKey`] docs for more info on the individual keys used. /// /// [`NodeSigner::get_inbound_payment_key`]: crate::sign::NodeSigner::get_inbound_payment_key -/// [`create_inbound_payment`]: crate::ln::channelmanager::ChannelManager::create_inbound_payment +/// [`create_inbound_payment`]: crate::offers::flow::OffersMessageCommons::create_inbound_payment /// [`create_inbound_payment_for_hash`]: crate::ln::channelmanager::ChannelManager::create_inbound_payment_for_hash pub(super) fn verify(payment_hash: PaymentHash, payment_data: &msgs::FinalOnionHopData, highest_seen_timestamp: u64, keys: &ExpandedKey, logger: &L) -> Result< diff --git a/lightning/src/ln/invoice_utils.rs b/lightning/src/ln/invoice_utils.rs index 5be0b3f4b97..3a941b42c17 100644 --- a/lightning/src/ln/invoice_utils.rs +++ b/lightning/src/ln/invoice_utils.rs @@ -38,7 +38,7 @@ use core::iter::Iterator; /// down /// /// `payment_hash` can be specified if you have a specific need for a custom payment hash (see the difference -/// between [`ChannelManager::create_inbound_payment`] and [`ChannelManager::create_inbound_payment_for_hash`]). +/// between [`OffersMessageCommons::create_inbound_payment`] and [`ChannelManager::create_inbound_payment_for_hash`]). /// If `None` is provided for `payment_hash`, then one will be created. /// /// `invoice_expiry_delta_secs` describes the number of seconds that the invoice is valid for @@ -57,7 +57,7 @@ use core::iter::Iterator; /// /// [`PhantomKeysManager`]: crate::sign::PhantomKeysManager /// [`ChannelManager::get_phantom_route_hints`]: crate::ln::channelmanager::ChannelManager::get_phantom_route_hints -/// [`ChannelManager::create_inbound_payment`]: crate::ln::channelmanager::ChannelManager::create_inbound_payment +/// [`OffersMessageCommons::create_inbound_payment`]: crate::offers::flow::OffersMessageCommons::create_inbound_payment /// [`ChannelManager::create_inbound_payment_for_hash`]: crate::ln::channelmanager::ChannelManager::create_inbound_payment_for_hash /// [`PhantomRouteHints::channels`]: crate::ln::channelmanager::PhantomRouteHints::channels /// [`MIN_FINAL_CLTV_EXPIRY_DETLA`]: crate::ln::channelmanager::MIN_FINAL_CLTV_EXPIRY_DELTA @@ -99,7 +99,7 @@ where /// `description_hash` is a SHA-256 hash of the description text /// /// `payment_hash` can be specified if you have a specific need for a custom payment hash (see the difference -/// between [`ChannelManager::create_inbound_payment`] and [`ChannelManager::create_inbound_payment_for_hash`]). +/// between [`OffersMessageCommons::create_inbound_payment`] and [`ChannelManager::create_inbound_payment_for_hash`]). /// If `None` is provided for `payment_hash`, then one will be created. /// /// `invoice_expiry_delta_secs` describes the number of seconds that the invoice is valid for @@ -113,7 +113,7 @@ where /// /// [`PhantomKeysManager`]: crate::sign::PhantomKeysManager /// [`ChannelManager::get_phantom_route_hints`]: crate::ln::channelmanager::ChannelManager::get_phantom_route_hints -/// [`ChannelManager::create_inbound_payment`]: crate::ln::channelmanager::ChannelManager::create_inbound_payment +/// [`OffersMessageCommons::create_inbound_payment`]: crate::offers::flow::OffersMessageCommons::create_inbound_payment /// [`ChannelManager::create_inbound_payment_for_hash`]: crate::ln::channelmanager::ChannelManager::create_inbound_payment_for_hash /// [`PhantomRouteHints::channels`]: crate::ln::channelmanager::PhantomRouteHints::channels #[cfg_attr(feature = "std", doc = "")] @@ -710,10 +710,11 @@ mod test { use bitcoin::hashes::{Hash, sha256}; use bitcoin::hashes::sha256::Hash as Sha256; use bitcoin::network::Network; + use crate::offers::flow::OffersMessageCommons; use crate::sign::PhantomKeysManager; use crate::events::{MessageSendEvent, MessageSendEventsProvider}; use crate::types::payment::{PaymentHash, PaymentPreimage}; - use crate::ln::channelmanager::{Bolt11InvoiceParameters, PhantomRouteHints, MIN_FINAL_CLTV_EXPIRY_DELTA, PaymentId, RecipientOnionFields, Retry}; + use crate::ln::channelmanager::{Bolt11InvoiceParameters, PaymentId, PhantomRouteHints, RecipientOnionFields, Retry, MIN_FINAL_CLTV_EXPIRY_DELTA}; use crate::ln::functional_test_utils::*; use crate::ln::msgs::ChannelMessageHandler; use crate::routing::router::{PaymentParameters, RouteParameters}; diff --git a/lightning/src/ln/mod.rs b/lightning/src/ln/mod.rs index e1631a2892c..91a49c1ccc3 100644 --- a/lightning/src/ln/mod.rs +++ b/lightning/src/ln/mod.rs @@ -41,7 +41,7 @@ pub mod channel; pub(crate) mod channel; pub(crate) mod onion_utils; -mod outbound_payment; +pub(crate) mod outbound_payment; pub mod wire; #[allow(dead_code)] // TODO(dual_funding): Remove once contribution to V2 channels is enabled. diff --git a/lightning/src/ln/monitor_tests.rs b/lightning/src/ln/monitor_tests.rs index 9556e988b4e..7322d5420a5 100644 --- a/lightning/src/ln/monitor_tests.rs +++ b/lightning/src/ln/monitor_tests.rs @@ -18,7 +18,7 @@ use crate::events::{Event, MessageSendEvent, MessageSendEventsProvider, ClosureR use crate::ln::channel; use crate::ln::types::ChannelId; use crate::ln::chan_utils; -use crate::ln::channelmanager::{BREAKDOWN_TIMEOUT, PaymentId, RecipientOnionFields}; +use crate::ln::channelmanager::{PaymentId, RecipientOnionFields, BREAKDOWN_TIMEOUT}; use crate::ln::msgs::ChannelMessageHandler; use crate::crypto::utils::sign; use crate::util::ser::Writeable; diff --git a/lightning/src/ln/onion_route_tests.rs b/lightning/src/ln/onion_route_tests.rs index abd930a9a91..2c85e3ce9f3 100644 --- a/lightning/src/ln/onion_route_tests.rs +++ b/lightning/src/ln/onion_route_tests.rs @@ -16,7 +16,7 @@ use crate::sign::{EntropySource, NodeSigner, Recipient}; use crate::events::{Event, HTLCDestination, MessageSendEvent, MessageSendEventsProvider, PathFailure, PaymentFailureReason}; use crate::types::payment::{PaymentHash, PaymentSecret}; use crate::ln::channel::EXPIRE_PREV_CONFIG_TICKS; -use crate::ln::channelmanager::{HTLCForwardInfo, FailureCode, CLTV_FAR_FAR_AWAY, DISABLE_GOSSIP_TICKS, MIN_CLTV_EXPIRY_DELTA, PendingAddHTLCInfo, PendingHTLCInfo, PendingHTLCRouting, PaymentId, RecipientOnionFields}; +use crate::ln::channelmanager::{FailureCode, HTLCForwardInfo, PaymentId, PendingAddHTLCInfo, PendingHTLCInfo, PendingHTLCRouting, RecipientOnionFields, CLTV_FAR_FAR_AWAY, DISABLE_GOSSIP_TICKS, MIN_CLTV_EXPIRY_DELTA}; use crate::ln::onion_utils; use crate::routing::gossip::{NetworkUpdate, RoutingFees}; use crate::routing::router::{get_route, PaymentParameters, Route, RouteParameters, RouteHint, RouteHintHop}; diff --git a/lightning/src/ln/outbound_payment.rs b/lightning/src/ln/outbound_payment.rs index 80d93387ac3..2e2575f7755 100644 --- a/lightning/src/ln/outbound_payment.rs +++ b/lightning/src/ln/outbound_payment.rs @@ -134,7 +134,7 @@ pub(crate) enum PendingOutboundPayment { }, } -pub(crate) struct RetryableInvoiceRequest { +pub struct RetryableInvoiceRequest { pub(crate) invoice_request: InvoiceRequest, pub(crate) nonce: Nonce, } diff --git a/lightning/src/ln/payment_tests.rs b/lightning/src/ln/payment_tests.rs index 0c9c5d0e920..adaec799171 100644 --- a/lightning/src/ln/payment_tests.rs +++ b/lightning/src/ln/payment_tests.rs @@ -13,10 +13,11 @@ use crate::chain::{ChannelMonitorUpdateStatus, Confirm, Listen}; use crate::chain::channelmonitor::{ANTI_REORG_DELAY, HTLC_FAIL_BACK_BUFFER, LATENCY_GRACE_PERIOD_BLOCKS}; +use crate::offers::flow::OffersMessageCommons; use crate::sign::EntropySource; use crate::events::{ClosureReason, Event, HTLCDestination, MessageSendEvent, MessageSendEventsProvider, PathFailure, PaymentFailureReason, PaymentPurpose}; use crate::ln::channel::{EXPIRE_PREV_CONFIG_TICKS, get_holder_selected_channel_reserve_satoshis, ANCHOR_OUTPUT_VALUE_SATOSHI}; -use crate::ln::channelmanager::{BREAKDOWN_TIMEOUT, MPP_TIMEOUT_TICKS, MIN_CLTV_EXPIRY_DELTA, PaymentId, PaymentSendFailure, RecentPaymentDetails, RecipientOnionFields, HTLCForwardInfo, PendingHTLCRouting, PendingAddHTLCInfo}; +use crate::ln::channelmanager::{HTLCForwardInfo, PaymentId, PaymentSendFailure, PendingAddHTLCInfo, PendingHTLCRouting, RecentPaymentDetails, RecipientOnionFields, BREAKDOWN_TIMEOUT, MIN_CLTV_EXPIRY_DELTA, MPP_TIMEOUT_TICKS}; use crate::types::features::{Bolt11InvoiceFeatures, ChannelTypeFeatures}; use crate::ln::msgs; use crate::ln::types::ChannelId; diff --git a/lightning/src/ln/priv_short_conf_tests.rs b/lightning/src/ln/priv_short_conf_tests.rs index 55e97e06894..8aeadc3b82f 100644 --- a/lightning/src/ln/priv_short_conf_tests.rs +++ b/lightning/src/ln/priv_short_conf_tests.rs @@ -13,7 +13,7 @@ use crate::chain::ChannelMonitorUpdateStatus; use crate::events::{ClosureReason, Event, HTLCDestination, MessageSendEvent, MessageSendEventsProvider}; -use crate::ln::channelmanager::{MIN_CLTV_EXPIRY_DELTA, PaymentId, RecipientOnionFields}; +use crate::ln::channelmanager::{PaymentId, RecipientOnionFields, MIN_CLTV_EXPIRY_DELTA}; use crate::routing::gossip::RoutingFees; use crate::routing::router::{PaymentParameters, RouteHint, RouteHintHop}; use crate::types::features::ChannelTypeFeatures; diff --git a/lightning/src/ln/shutdown_tests.rs b/lightning/src/ln/shutdown_tests.rs index 9fd428329af..cf6e3bbabe8 100644 --- a/lightning/src/ln/shutdown_tests.rs +++ b/lightning/src/ln/shutdown_tests.rs @@ -15,7 +15,7 @@ use crate::chain::ChannelMonitorUpdateStatus; use crate::chain::transaction::OutPoint; use crate::events::{Event, MessageSendEvent, HTLCDestination, MessageSendEventsProvider, ClosureReason}; use crate::ln::channel_state::{ChannelDetails, ChannelShutdownState}; -use crate::ln::channelmanager::{self, PaymentSendFailure, PaymentId, RecipientOnionFields, Retry}; +use crate::ln::channelmanager::{self, PaymentId, PaymentSendFailure, RecipientOnionFields, Retry}; use crate::routing::router::{PaymentParameters, get_route, RouteParameters}; use crate::ln::msgs; use crate::ln::types::ChannelId; diff --git a/lightning/src/offers/flow.rs b/lightning/src/offers/flow.rs index 02471d9545f..a0ff3a3e786 100644 --- a/lightning/src/offers/flow.rs +++ b/lightning/src/offers/flow.rs @@ -12,9 +12,128 @@ use crate::prelude::*; use core::ops::Deref; +use core::time::Duration; +use bitcoin::secp256k1::schnorr; +use lightning_invoice::PaymentSecret; +use types::payment::PaymentHash; + +use crate::blinded_path::message::{BlindedMessagePath, MessageContext, OffersContext}; +use crate::blinded_path::payment::{BlindedPaymentPath, PaymentContext}; +use crate::events::PaymentFailureReason; +use crate::ln::channelmanager::{Bolt12PaymentError, PaymentId}; +use crate::ln::outbound_payment::RetryableInvoiceRequest; +use crate::offers::invoice::{Bolt12Invoice, UnsignedBolt12Invoice}; +use crate::offers::invoice_request::InvoiceRequest; +use crate::offers::parse::Bolt12SemanticError; +use crate::onion_message::messenger::MessageSendInstructions; +use crate::onion_message::offers::OffersMessage; +use crate::sync::MutexGuard; use crate::util::logger::Logger; +/// Functions commonly shared in usage between [`ChannelManager`] & `OffersMessageFlow` +/// +/// [`ChannelManager`]: crate::ln::channelmanager::ChannelManager +pub trait OffersMessageCommons { + /// Get pending offers messages + fn get_pending_offers_messages( + &self, + ) -> MutexGuard<'_, Vec<(OffersMessage, MessageSendInstructions)>>; + + /// Signs the [`TaggedHash`] of a BOLT 12 invoice. + /// + /// May be called by a function passed to [`UnsignedBolt12Invoice::sign`] where `invoice` is the + /// callee. + /// + /// Implementors may check that the `invoice` is expected rather than blindly signing the tagged + /// hash. An `Ok` result should sign `invoice.tagged_hash().as_digest()` with the node's signing + /// key or an ephemeral key to preserve privacy, whichever is associated with + /// [`UnsignedBolt12Invoice::signing_pubkey`]. + /// + /// [`TaggedHash`]: crate::offers::merkle::TaggedHash + fn sign_bolt12_invoice( + &self, invoice: &UnsignedBolt12Invoice, + ) -> Result; + + /// Gets a payment secret and payment hash for use in an invoice given to a third party wishing + /// to pay us. + /// + /// This differs from [`create_inbound_payment_for_hash`] only in that it generates the + /// [`PaymentHash`] and [`PaymentPreimage`] for you. + /// + /// The [`PaymentPreimage`] will ultimately be returned to you in the [`PaymentClaimable`] event, which + /// will have the [`PaymentClaimable::purpose`] return `Some` for [`PaymentPurpose::preimage`]. That + /// should then be passed directly to [`claim_funds`]. + /// + /// See [`create_inbound_payment_for_hash`] for detailed documentation on behavior and requirements. + /// + /// Note that a malicious eavesdropper can intuit whether an inbound payment was created by + /// `create_inbound_payment` or `create_inbound_payment_for_hash` based on runtime. + /// + /// # Note + /// + /// If you register an inbound payment with this method, then serialize the `ChannelManager`, then + /// deserialize it with a node running 0.0.103 and earlier, the payment will fail to be received. + /// + /// Errors if `min_value_msat` is greater than total bitcoin supply. + /// + /// If `min_final_cltv_expiry_delta` is set to some value, then the payment will not be receivable + /// on versions of LDK prior to 0.0.114. + /// + /// [`claim_funds`]: crate::ln::channelmanager::ChannelManager::claim_funds + /// [`PaymentClaimable`]: crate::events::Event::PaymentClaimable + /// [`PaymentClaimable::purpose`]: crate::events::Event::PaymentClaimable::purpose + /// [`PaymentPurpose::preimage`]: crate::events::PaymentPurpose::preimage + /// [`create_inbound_payment_for_hash`]: crate::ln::channelmanager::ChannelManager::create_inbound_payment_for_hash + /// [`PaymentPreimage`]: crate::types::payment::PaymentPreimage + fn create_inbound_payment( + &self, min_value_msat: Option, invoice_expiry_delta_secs: u32, + min_final_cltv_expiry_delta: Option, + ) -> Result<(PaymentHash, PaymentSecret), ()>; + + /// Creates multi-hop blinded payment paths for the given `amount_msats` by delegating to + /// [`Router::create_blinded_payment_paths`]. + /// + /// [`Router::create_blinded_payment_paths`]: crate::routing::router::Router::create_blinded_payment_paths + fn create_blinded_payment_paths( + &self, amount_msats: u64, payment_secret: PaymentSecret, payment_context: PaymentContext, + ) -> Result, ()>; + + /// Verify bolt12 invoice + fn verify_bolt12_invoice( + &self, invoice: &Bolt12Invoice, context: Option<&OffersContext>, + ) -> Result; + + /// Send payment for verified bolt12 invoice + fn send_payment_for_verified_bolt12_invoice( + &self, invoice: &Bolt12Invoice, payment_id: PaymentId, + ) -> Result<(), Bolt12PaymentError>; + + /// Abandon Payment with Reason + fn abandon_payment_with_reason(&self, payment_id: PaymentId, reason: PaymentFailureReason); + + /// Release invoice requests awaiting invoice + fn release_invoice_requests_awaiting_invoice( + &self, + ) -> Vec<(PaymentId, RetryableInvoiceRequest)>; + + /// Creates a collection of blinded paths by delegating to + /// [`MessageRouter::create_blinded_paths`]. + /// + /// Errors if the `MessageRouter` errors. + /// + /// [`MessageRouter::create_blinded_paths`]: crate::onion_message::messenger::MessageRouter::create_blinded_paths + fn create_blinded_paths(&self, context: MessageContext) -> Result, ()>; + + /// Enqueue invoice request + fn enqueue_invoice_request( + &self, invoice_request: InvoiceRequest, reply_paths: Vec, + ) -> Result<(), Bolt12SemanticError>; + + /// Get the current time determined by highest seen timestamp + fn get_current_blocktime(&self) -> Duration; +} + /// Facilitates the handling, communication, and management of Offers messages within a Lightning /// node, enabling the creation, verification, and resolution of BOLT 12 invoices and related /// payment flows. From c9cc37250b4b52292883fbfe3b884b20549e6a34 Mon Sep 17 00:00:00 2001 From: shaavan Date: Wed, 11 Dec 2024 17:24:06 +0530 Subject: [PATCH 06/30] f: Move initiate_async_payments to commons --- lightning/src/ln/channelmanager.rs | 120 ++++++++++++++--------------- lightning/src/offers/flow.rs | 9 +++ 2 files changed, 69 insertions(+), 60 deletions(-) diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 6c870e9870e..c19f9789169 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -4757,66 +4757,6 @@ where } } - #[cfg(async_payments)] - fn initiate_async_payment( - &self, invoice: &StaticInvoice, payment_id: PaymentId - ) -> Result<(), Bolt12PaymentError> { - let mut res = Ok(()); - PersistenceNotifierGuard::optionally_notify(self, || { - let best_block_height = self.best_block.read().unwrap().height; - let features = self.bolt12_invoice_features(); - let outbound_pmts_res = self.pending_outbound_payments.static_invoice_received( - invoice, payment_id, features, best_block_height, &*self.entropy_source, - &self.pending_events - ); - match outbound_pmts_res { - Ok(()) => {}, - Err(Bolt12PaymentError::UnexpectedInvoice) | Err(Bolt12PaymentError::DuplicateInvoice) => { - res = outbound_pmts_res.map(|_| ()); - return NotifyOption::SkipPersistNoEvents - }, - Err(e) => { - res = Err(e); - return NotifyOption::DoPersist - } - }; - - let nonce = Nonce::from_entropy_source(&*self.entropy_source); - let hmac = payment_id.hmac_for_async_payment(nonce, &self.inbound_payment_key); - let reply_paths = match self.create_blinded_paths( - MessageContext::AsyncPayments( - AsyncPaymentsContext::OutboundPayment { payment_id, nonce, hmac } - ) - ) { - Ok(paths) => paths, - Err(()) => { - self.abandon_payment_with_reason(payment_id, PaymentFailureReason::BlindedPathCreationFailed); - res = Err(Bolt12PaymentError::BlindedPathCreationFailed); - return NotifyOption::DoPersist - } - }; - - let mut pending_async_payments_messages = self.pending_async_payments_messages.lock().unwrap(); - const HTLC_AVAILABLE_LIMIT: usize = 10; - reply_paths - .iter() - .flat_map(|reply_path| invoice.message_paths().iter().map(move |invoice_path| (invoice_path, reply_path))) - .take(HTLC_AVAILABLE_LIMIT) - .for_each(|(invoice_path, reply_path)| { - let instructions = MessageSendInstructions::WithSpecifiedReplyPath { - destination: Destination::BlindedPath(invoice_path.clone()), - reply_path: reply_path.clone(), - }; - let message = AsyncPaymentsMessage::HeldHtlcAvailable(HeldHtlcAvailable {}); - pending_async_payments_messages.push((message, instructions)); - }); - - NotifyOption::DoPersist - }); - - res - } - #[cfg(async_payments)] fn send_payment_for_static_invoice( &self, payment_id: PaymentId @@ -10111,6 +10051,66 @@ where fn get_current_blocktime(&self) -> Duration { Duration::from_secs(self.highest_seen_timestamp.load(Ordering::Acquire) as u64) } + + #[cfg(async_payments)] + fn initiate_async_payment( + &self, invoice: &StaticInvoice, payment_id: PaymentId + ) -> Result<(), Bolt12PaymentError> { + let mut res = Ok(()); + PersistenceNotifierGuard::optionally_notify(self, || { + let best_block_height = self.best_block.read().unwrap().height; + let features = self.bolt12_invoice_features(); + let outbound_pmts_res = self.pending_outbound_payments.static_invoice_received( + invoice, payment_id, features, best_block_height, &*self.entropy_source, + &self.pending_events + ); + match outbound_pmts_res { + Ok(()) => {}, + Err(Bolt12PaymentError::UnexpectedInvoice) | Err(Bolt12PaymentError::DuplicateInvoice) => { + res = outbound_pmts_res.map(|_| ()); + return NotifyOption::SkipPersistNoEvents + }, + Err(e) => { + res = Err(e); + return NotifyOption::DoPersist + } + }; + + let nonce = Nonce::from_entropy_source(&*self.entropy_source); + let hmac = payment_id.hmac_for_async_payment(nonce, &self.inbound_payment_key); + let reply_paths = match self.create_blinded_paths( + MessageContext::AsyncPayments( + AsyncPaymentsContext::OutboundPayment { payment_id, nonce, hmac } + ) + ) { + Ok(paths) => paths, + Err(()) => { + self.abandon_payment_with_reason(payment_id, PaymentFailureReason::BlindedPathCreationFailed); + res = Err(Bolt12PaymentError::BlindedPathCreationFailed); + return NotifyOption::DoPersist + } + }; + + let mut pending_async_payments_messages = self.pending_async_payments_messages.lock().unwrap(); + const HTLC_AVAILABLE_LIMIT: usize = 10; + reply_paths + .iter() + .flat_map(|reply_path| invoice.message_paths().iter().map(move |invoice_path| (invoice_path, reply_path))) + .take(HTLC_AVAILABLE_LIMIT) + .for_each(|(invoice_path, reply_path)| { + let instructions = MessageSendInstructions::WithSpecifiedReplyPath { + destination: Destination::BlindedPath(invoice_path.clone()), + reply_path: reply_path.clone(), + }; + let message = AsyncPaymentsMessage::HeldHtlcAvailable(HeldHtlcAvailable {}); + pending_async_payments_messages.push((message, instructions)); + }); + + NotifyOption::DoPersist + }); + + res + } } /// Defines the maximum number of [`OffersMessage`] including different reply paths to be sent diff --git a/lightning/src/offers/flow.rs b/lightning/src/offers/flow.rs index a0ff3a3e786..74695d0ab1c 100644 --- a/lightning/src/offers/flow.rs +++ b/lightning/src/offers/flow.rs @@ -31,6 +31,9 @@ use crate::onion_message::offers::OffersMessage; use crate::sync::MutexGuard; use crate::util::logger::Logger; +#[cfg(async_payments)] +use crate::offers::static_invoice::StaticInvoice; + /// Functions commonly shared in usage between [`ChannelManager`] & `OffersMessageFlow` /// /// [`ChannelManager`]: crate::ln::channelmanager::ChannelManager @@ -132,6 +135,12 @@ pub trait OffersMessageCommons { /// Get the current time determined by highest seen timestamp fn get_current_blocktime(&self) -> Duration; + + /// Initiate a new async payment + #[cfg(async_payments)] + fn initiate_async_payment( + &self, invoice: &StaticInvoice, payment_id: PaymentId, + ) -> Result<(), Bolt12PaymentError>; } /// Facilitates the handling, communication, and management of Offers messages within a Lightning From 8240983ce18fa9202b4d641e2fb38d0113f74b13 Mon Sep 17 00:00:00 2001 From: shaavan Date: Fri, 15 Nov 2024 18:02:54 +0530 Subject: [PATCH 07/30] Refactor `OffersMessageHandler` Implementation to `OffersMessageFlow` This commit introduces the `OffersMessageHandler` implementation for `OffersMessageFlow`, enabling direct access to offer-specific functionality through `OffersMessageFlow`. With `OffersMessageFlow` now serving as the source of `OffersMessageHandler` implementation, the implementation in `ChannelManager` is no longer needed and has been safely removed. --- lightning-background-processor/src/lib.rs | 3 +- lightning/src/ln/channelmanager.rs | 278 +------------- lightning/src/ln/functional_test_utils.rs | 30 +- lightning/src/ln/inbound_payment.rs | 2 +- .../src/ln/max_payment_path_len_tests.rs | 2 +- lightning/src/offers/flow.rs | 359 +++++++++++++++++- lightning/src/onion_message/messenger.rs | 5 +- 7 files changed, 385 insertions(+), 294 deletions(-) diff --git a/lightning-background-processor/src/lib.rs b/lightning-background-processor/src/lib.rs index af7c7ffb003..b4fd983d9ab 100644 --- a/lightning-background-processor/src/lib.rs +++ b/lightning-background-processor/src/lib.rs @@ -647,7 +647,8 @@ use futures_util::{dummy_waker, OptionalSelector, Selector, SelectorOutput}; /// # type NetworkGraph = lightning::routing::gossip::NetworkGraph>; /// # type P2PGossipSync
    = lightning::routing::gossip::P2PGossipSync, Arc
      , Arc>; /// # type ChannelManager = lightning::ln::channelmanager::SimpleArcChannelManager, B, FE, Logger>; -/// # type OnionMessenger = lightning::onion_message::messenger::OnionMessenger, Arc, Arc, Arc>, Arc, Arc, Arc>>, Arc>, lightning::ln::peer_handler::IgnoringMessageHandler, lightning::ln::peer_handler::IgnoringMessageHandler, lightning::ln::peer_handler::IgnoringMessageHandler>; +/// # type OffersMessageFlow = lightning::offers::flow::OffersMessageFlow, Arc>, Arc>; +/// # type OnionMessenger = lightning::onion_message::messenger::OnionMessenger, Arc, Arc, Arc>, Arc, Arc, Arc>>, Arc>, lightning::ln::peer_handler::IgnoringMessageHandler, lightning::ln::peer_handler::IgnoringMessageHandler, lightning::ln::peer_handler::IgnoringMessageHandler>; /// # type Scorer = RwLock, Arc>>; /// # type PeerManager = lightning::ln::peer_handler::SimpleArcPeerManager, B, FE, Arc
        , Logger>; /// # diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index c19f9789169..8f0370744aa 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -36,7 +36,7 @@ use crate::events::FundingInfo; use crate::blinded_path::message::{AsyncPaymentsContext, MessageContext, OffersContext}; use crate::blinded_path::NodeIdLookUp; use crate::blinded_path::message::{BlindedMessagePath, MessageForwardNode}; -use crate::blinded_path::payment::{BlindedPaymentPath, Bolt12OfferContext, Bolt12RefundContext, PaymentConstraints, PaymentContext, UnauthenticatedReceiveTlvs}; +use crate::blinded_path::payment::{BlindedPaymentPath, Bolt12RefundContext, PaymentConstraints, PaymentContext, UnauthenticatedReceiveTlvs}; use crate::chain; use crate::chain::{Confirm, ChannelMonitorUpdateStatus, Watch, BestBlock}; use crate::chain::chaininterface::{BroadcasterInterface, ConfirmationTarget, FeeEstimator, LowerBoundedFeeEstimator}; @@ -65,8 +65,7 @@ use crate::ln::msgs::{ChannelMessageHandler, CommitmentUpdate, DecodeError, Ligh #[cfg(test)] use crate::ln::outbound_payment; use crate::ln::outbound_payment::{OutboundPayments, PendingOutboundPayment, RetryableInvoiceRequest, SendAlongPathArgs, StaleExpiration}; -use crate::offers::invoice::{Bolt12Invoice, DEFAULT_RELATIVE_EXPIRY, DerivedSigningPubkey, ExplicitSigningPubkey, InvoiceBuilder, UnsignedBolt12Invoice}; -use crate::offers::invoice_error::InvoiceError; +use crate::offers::invoice::{Bolt12Invoice, DerivedSigningPubkey, InvoiceBuilder, UnsignedBolt12Invoice, DEFAULT_RELATIVE_EXPIRY}; use crate::offers::invoice_request::{InvoiceRequest, InvoiceRequestBuilder}; use crate::offers::nonce::Nonce; use crate::offers::offer::{Offer, OfferBuilder}; @@ -78,7 +77,7 @@ use crate::offers::static_invoice::StaticInvoice; use crate::onion_message::async_payments::{AsyncPaymentsMessage, HeldHtlcAvailable, ReleaseHeldHtlc, AsyncPaymentsMessageHandler}; use crate::onion_message::dns_resolution::HumanReadableName; use crate::onion_message::messenger::{Destination, MessageRouter, Responder, ResponseInstruction, MessageSendInstructions}; -use crate::onion_message::offers::{OffersMessage, OffersMessageHandler}; +use crate::onion_message::offers::OffersMessage; use crate::sign::{EntropySource, NodeSigner, Recipient, SignerProvider}; use crate::sign::ecdsa::EcdsaChannelSigner; use crate::util::config::{UserConfig, ChannelConfig, ChannelConfigUpdate}; @@ -1618,7 +1617,6 @@ where /// Additionally, it implements the following traits: /// - [`ChannelMessageHandler`] to handle off-chain channel activity from peers /// - [`MessageSendEventsProvider`] to similarly send such messages to peers -/// - [`OffersMessageHandler`] for BOLT 12 message handling and sending /// - [`EventsProvider`] to generate user-actionable [`Event`]s /// - [`chain::Listen`] and [`chain::Confirm`] for notification of on-chain activity /// @@ -2042,10 +2040,9 @@ where /// /// The [`offers`] module is useful for creating BOLT 12 offers. An [`Offer`] is a precursor to a /// [`Bolt12Invoice`], which must first be requested by the payer. The interchange of these messages -/// as defined in the specification is handled by [`ChannelManager`] and its implementation of -/// [`OffersMessageHandler`]. However, this only works with an [`Offer`] created using a builder -/// returned by [`create_offer_builder`]. With this approach, BOLT 12 offers and invoices are -/// stateless just as BOLT 11 invoices are. +/// as defined in the specification is handled by [`ChannelManager`]. However, this only works with +/// an [`Offer`] created using a builder returned by [`create_offer_builder`]. With this approach, +/// BOLT 12 offers and invoices are stateless just as BOLT 11 invoices are. /// /// ``` /// # use lightning::events::{Event, EventsProvider, PaymentPurpose}; @@ -2514,7 +2511,7 @@ where our_network_pubkey: PublicKey, - inbound_payment_key: inbound_payment::ExpandedKey, + pub(crate) inbound_payment_key: inbound_payment::ExpandedKey, /// LDK puts the [fake scids] that it generates into namespaces, to identify the type of an /// incoming payment. To make it harder for a third-party to identify the type of a payment, @@ -6815,8 +6812,8 @@ where /// [`Event::PaymentClaimed`]: crate::events::Event::PaymentClaimed /// [`process_pending_events`]: EventsProvider::process_pending_events /// [`create_inbound_payment`]: Self::create_inbound_payment - /// [`create_inbound_payment_for_hash`]: Self::create_inbound_payment_for_hash - /// [`claim_funds_with_known_custom_tlvs`]: Self::claim_funds_with_known_custom_tlvs + /// [`create_inbound_payment_for_hash`]: ChannelManager::create_inbound_payment_for_hash + /// [`claim_funds_with_known_custom_tlvs`]: ChannelManager::claim_funds_with_known_custom_tlvs pub fn claim_funds(&self, payment_preimage: PaymentPreimage) { self.claim_payment_internal(payment_preimage, false); } @@ -11950,263 +11947,6 @@ where } } -impl -OffersMessageHandler for ChannelManager -where - M::Target: chain::Watch<::EcdsaSigner>, - T::Target: BroadcasterInterface, - ES::Target: EntropySource, - NS::Target: NodeSigner, - SP::Target: SignerProvider, - F::Target: FeeEstimator, - R::Target: Router, - MR::Target: MessageRouter, - L::Target: Logger, -{ - fn handle_message( - &self, message: OffersMessage, context: Option, responder: Option, - ) -> Option<(OffersMessage, ResponseInstruction)> { - let secp_ctx = &self.secp_ctx; - let expanded_key = &self.inbound_payment_key; - - macro_rules! handle_pay_invoice_res { - ($res: expr, $invoice: expr, $logger: expr) => {{ - let error = match $res { - Err(Bolt12PaymentError::UnknownRequiredFeatures) => { - log_trace!( - $logger, "Invoice requires unknown features: {:?}", - $invoice.invoice_features() - ); - InvoiceError::from(Bolt12SemanticError::UnknownRequiredFeatures) - }, - Err(Bolt12PaymentError::SendingFailed(e)) => { - 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); - InvoiceError::from_string(err_msg.to_string()) - }, - Err(Bolt12PaymentError::UnexpectedInvoice) - | Err(Bolt12PaymentError::DuplicateInvoice) - | Ok(()) => return None, - }; - - match responder { - Some(responder) => return Some((OffersMessage::InvoiceError(error), responder.respond())), - None => { - log_trace!($logger, "No reply path to send error: {:?}", error); - return None - }, - } - }} - } - - match message { - OffersMessage::InvoiceRequest(invoice_request) => { - let responder = match responder { - Some(responder) => responder, - None => return None, - }; - - let nonce = match context { - None if invoice_request.metadata().is_some() => None, - Some(OffersContext::InvoiceRequest { nonce }) => Some(nonce), - _ => return None, - }; - - let invoice_request = match nonce { - Some(nonce) => match invoice_request.verify_using_recipient_data( - nonce, expanded_key, secp_ctx, - ) { - Ok(invoice_request) => invoice_request, - Err(()) => return None, - }, - None => match invoice_request.verify_using_metadata(expanded_key, secp_ctx) { - Ok(invoice_request) => invoice_request, - Err(()) => return None, - }, - }; - - let amount_msats = match InvoiceBuilder::::amount_msats( - &invoice_request.inner - ) { - Ok(amount_msats) => amount_msats, - Err(error) => return Some((OffersMessage::InvoiceError(error.into()), responder.respond())), - }; - - let relative_expiry = DEFAULT_RELATIVE_EXPIRY.as_secs() as u32; - let (payment_hash, payment_secret) = match self.create_inbound_payment( - Some(amount_msats), relative_expiry, None - ) { - Ok((payment_hash, payment_secret)) => (payment_hash, payment_secret), - Err(()) => { - let error = Bolt12SemanticError::InvalidAmount; - return Some((OffersMessage::InvoiceError(error.into()), responder.respond())); - }, - }; - - let payment_context = PaymentContext::Bolt12Offer(Bolt12OfferContext { - offer_id: invoice_request.offer_id, - invoice_request: invoice_request.fields(), - }); - let payment_paths = match self.create_blinded_payment_paths( - amount_msats, payment_secret, payment_context - ) { - Ok(payment_paths) => payment_paths, - Err(()) => { - let error = Bolt12SemanticError::MissingPaths; - return Some((OffersMessage::InvoiceError(error.into()), responder.respond())); - }, - }; - - #[cfg(not(feature = "std"))] - let created_at = Duration::from_secs( - self.highest_seen_timestamp.load(Ordering::Acquire) as u64 - ); - - let response = if invoice_request.keys.is_some() { - #[cfg(feature = "std")] - let builder = invoice_request.respond_using_derived_keys( - payment_paths, payment_hash - ); - #[cfg(not(feature = "std"))] - let builder = invoice_request.respond_using_derived_keys_no_std( - payment_paths, payment_hash, created_at - ); - builder - .map(InvoiceBuilder::::from) - .and_then(|builder| builder.allow_mpp().build_and_sign(secp_ctx)) - .map_err(InvoiceError::from) - } else { - #[cfg(feature = "std")] - let builder = invoice_request.respond_with(payment_paths, payment_hash); - #[cfg(not(feature = "std"))] - let builder = invoice_request.respond_with_no_std( - payment_paths, payment_hash, created_at - ); - builder - .map(InvoiceBuilder::::from) - .and_then(|builder| builder.allow_mpp().build()) - .map_err(InvoiceError::from) - .and_then(|invoice| { - #[cfg(c_bindings)] - let mut invoice = invoice; - invoice - .sign(|invoice: &UnsignedBolt12Invoice| - self.node_signer.sign_bolt12_invoice(invoice) - ) - .map_err(InvoiceError::from) - }) - }; - - match response { - Ok(invoice) => { - let nonce = Nonce::from_entropy_source(&*self.entropy_source); - let hmac = payment_hash.hmac_for_offer_payment(nonce, expanded_key); - let context = MessageContext::Offers(OffersContext::InboundPayment { payment_hash, nonce, hmac }); - Some((OffersMessage::Invoice(invoice), responder.respond_with_reply_path(context))) - }, - Err(error) => Some((OffersMessage::InvoiceError(error.into()), responder.respond())), - } - }, - OffersMessage::Invoice(invoice) => { - let payment_id = match self.verify_bolt12_invoice(&invoice, context.as_ref()) { - Ok(payment_id) => payment_id, - Err(()) => return None, - }; - - let logger = WithContext::from( - &self.logger, None, None, Some(invoice.payment_hash()), - ); - - 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, nonce, hmac: Some(hmac) }) => { - if payment_id.verify_for_offer_payment(hmac, nonce, expanded_key).is_err() { - return None - } - payment_id - }, - _ => return None - }; - let res = self.initiate_async_payment(&invoice, payment_id); - handle_pay_invoice_res!(res, invoice, self.logger); - }, - OffersMessage::InvoiceError(invoice_error) => { - let payment_hash = match context { - Some(OffersContext::InboundPayment { payment_hash, nonce, hmac }) => { - match payment_hash.verify_for_offer_payment(hmac, nonce, expanded_key) { - Ok(_) => Some(payment_hash), - Err(_) => None, - } - }, - _ => None, - }; - - let logger = WithContext::from(&self.logger, None, None, payment_hash); - log_trace!(logger, "Received invoice_error: {}", invoice_error); - - match context { - Some(OffersContext::OutboundPayment { payment_id, nonce, hmac: Some(hmac) }) => { - if let Ok(()) = payment_id.verify_for_offer_payment(hmac, nonce, expanded_key) { - self.abandon_payment_with_reason( - payment_id, PaymentFailureReason::InvoiceRequestRejected, - ); - } - }, - _ => {}, - } - - None - }, - } - } - - fn message_received(&self) { - for (payment_id, retryable_invoice_request) in self - .pending_outbound_payments - .release_invoice_requests_awaiting_invoice() - { - let RetryableInvoiceRequest { invoice_request, nonce } = retryable_invoice_request; - let hmac = payment_id.hmac_for_offer_payment(nonce, &self.inbound_payment_key); - let context = MessageContext::Offers(OffersContext::OutboundPayment { - payment_id, - nonce, - hmac: Some(hmac) - }); - match self.create_blinded_paths(context) { - Ok(reply_paths) => match self.enqueue_invoice_request(invoice_request, reply_paths) { - Ok(_) => {} - Err(_) => { - log_warn!(self.logger, - "Retry failed for an invoice request with payment_id: {}", - payment_id - ); - } - }, - Err(_) => { - log_warn!(self.logger, - "Retry failed for an invoice request with payment_id: {}. \ - Reason: router could not find a blinded path to include as the reply path", - payment_id - ); - } - } - } - } - - fn release_pending_messages(&self) -> Vec<(OffersMessage, MessageSendInstructions)> { - core::mem::take(&mut self.pending_offers_messages.lock().unwrap()) - } -} - impl AsyncPaymentsMessageHandler for ChannelManager where diff --git a/lightning/src/ln/functional_test_utils.rs b/lightning/src/ln/functional_test_utils.rs index 79211e3fecc..44b5a3c11f4 100644 --- a/lightning/src/ln/functional_test_utils.rs +++ b/lightning/src/ln/functional_test_utils.rs @@ -16,6 +16,7 @@ use crate::chain::transaction::OutPoint; use crate::events::{ClaimedHTLC, ClosureReason, Event, HTLCDestination, MessageSendEvent, MessageSendEventsProvider, PathFailure, PaymentPurpose, PaymentFailureReason}; use crate::events::bump_transaction::{BumpTransactionEvent, BumpTransactionEventHandler, Wallet, WalletSource}; use crate::ln::types::ChannelId; +use crate::offers::flow::OffersMessageFlow; use crate::types::payment::{PaymentPreimage, PaymentHash, PaymentSecret}; use crate::ln::channelmanager::{AChannelManager, ChainParameters, ChannelManager, ChannelManagerReadArgs, PaymentId, RAACommitmentOrder, RecipientOnionFields, MIN_CLTV_EXPIRY_DELTA}; use crate::types::features::InitFeatures; @@ -411,6 +412,12 @@ type TestChannelManager<'node_cfg, 'chan_mon_cfg> = ChannelManager< &'chan_mon_cfg test_utils::TestLogger, >; +pub type TestOffersMessageFlow<'chan_man, 'node_cfg, 'chan_mon_cfg> = OffersMessageFlow< + &'node_cfg test_utils::TestKeysInterface, + &'chan_man TestChannelManager<'node_cfg, 'chan_mon_cfg>, + &'chan_mon_cfg test_utils::TestLogger, +>; + #[cfg(not(feature = "dnssec"))] type TestOnionMessenger<'chan_man, 'node_cfg, 'chan_mon_cfg> = OnionMessenger< DedicatedEntropy, @@ -418,7 +425,7 @@ type TestOnionMessenger<'chan_man, 'node_cfg, 'chan_mon_cfg> = OnionMessenger< &'chan_mon_cfg test_utils::TestLogger, &'chan_man TestChannelManager<'node_cfg, 'chan_mon_cfg>, &'node_cfg test_utils::TestMessageRouter<'chan_mon_cfg>, - &'chan_man TestChannelManager<'node_cfg, 'chan_mon_cfg>, + Arc>, &'chan_man TestChannelManager<'node_cfg, 'chan_mon_cfg>, IgnoringMessageHandler, IgnoringMessageHandler, @@ -431,7 +438,7 @@ type TestOnionMessenger<'chan_man, 'node_cfg, 'chan_mon_cfg> = OnionMessenger< &'chan_mon_cfg test_utils::TestLogger, &'chan_man TestChannelManager<'node_cfg, 'chan_mon_cfg>, &'node_cfg test_utils::TestMessageRouter<'chan_mon_cfg>, - &'chan_man TestChannelManager<'node_cfg, 'chan_mon_cfg>, + Arc>, &'chan_man TestChannelManager<'node_cfg, 'chan_mon_cfg>, &'chan_man TestChannelManager<'node_cfg, 'chan_mon_cfg>, IgnoringMessageHandler, @@ -458,6 +465,7 @@ pub struct Node<'chan_man, 'node_cfg: 'chan_man, 'chan_mon_cfg: 'node_cfg> { pub keys_manager: &'chan_mon_cfg test_utils::TestKeysInterface, pub node: &'chan_man TestChannelManager<'node_cfg, 'chan_mon_cfg>, pub onion_messenger: TestOnionMessenger<'chan_man, 'node_cfg, 'chan_mon_cfg>, + pub offers_handler: Arc>, pub network_graph: &'node_cfg NetworkGraph<&'chan_mon_cfg test_utils::TestLogger>, pub gossip_sync: P2PGossipSync<&'node_cfg NetworkGraph<&'chan_mon_cfg test_utils::TestLogger>, &'chan_mon_cfg test_utils::TestChainSource, &'chan_mon_cfg test_utils::TestLogger>, pub node_seed: [u8; 32], @@ -1189,8 +1197,14 @@ macro_rules! reload_node { $node.chain_monitor = &$new_chain_monitor; $new_channelmanager = _reload_node(&$node, $new_config, &chanman_encoded, $monitors_encoded); + + let offers_handler = $crate::sync::Arc::new($crate::offers::flow::OffersMessageFlow::new( + $new_channelmanager.inbound_payment_key, $new_channelmanager.get_our_node_id(), $node.keys_manager, &$new_channelmanager, $node.logger + )); + $node.node = &$new_channelmanager; - $node.onion_messenger.set_offers_handler(&$new_channelmanager); + $node.offers_handler = offers_handler.clone(); + $node.onion_messenger.set_offers_handler(offers_handler); }; ($node: expr, $chanman_encoded: expr, $monitors_encoded: expr, $persister: ident, $new_chain_monitor: ident, $new_channelmanager: ident) => { reload_node!($node, $crate::util::config::UserConfig::default(), $chanman_encoded, $monitors_encoded, $persister, $new_chain_monitor, $new_channelmanager); @@ -3330,16 +3344,19 @@ pub fn create_network<'a, 'b: 'a, 'c: 'b>(node_count: usize, cfgs: &'b Vec(node_count: usize, cfgs: &'b Vec +pub struct OffersMessageFlow where + ES::Target: EntropySource, + OMC::Target: OffersMessageCommons, L::Target: Logger, { + inbound_payment_key: inbound_payment::ExpandedKey, + our_network_pubkey: PublicKey, + + secp_ctx: Secp256k1, + + entropy_source: ES, + + /// Contains function shared between OffersMessageHandler, and ChannelManager. + commons: OMC, + /// The Logger for use in the OffersMessageFlow and which may be used to log /// information during deserialization. pub logger: L, } -impl OffersMessageFlow +impl OffersMessageFlow where + ES::Target: EntropySource, + OMC::Target: OffersMessageCommons, L::Target: Logger, { /// Creates a new [`OffersMessageFlow`] - pub fn new(logger: L) -> Self { - Self { logger } + pub fn new( + expanded_inbound_key: inbound_payment::ExpandedKey, our_network_pubkey: PublicKey, + entropy_source: ES, commons: OMC, logger: L, + ) -> Self { + let mut secp_ctx = Secp256k1::new(); + secp_ctx.seeded_randomize(&entropy_source.get_secure_random_bytes()); + + Self { + inbound_payment_key: expanded_inbound_key, + our_network_pubkey, + secp_ctx, + commons, + entropy_source, + logger, + } + } + + /// Gets the node_id held by this OffersMessageFlow + pub fn get_our_node_id(&self) -> PublicKey { + self.our_network_pubkey + } +} + +impl OffersMessageHandler for OffersMessageFlow +where + ES::Target: EntropySource, + OMC::Target: OffersMessageCommons, + L::Target: Logger, +{ + fn handle_message( + &self, message: OffersMessage, context: Option, responder: Option, + ) -> Option<(OffersMessage, ResponseInstruction)> { + let secp_ctx = &self.secp_ctx; + let expanded_key = &self.inbound_payment_key; + + macro_rules! handle_pay_invoice_res { + ($res: expr, $invoice: expr, $logger: expr) => {{ + let error = match $res { + Err(Bolt12PaymentError::UnknownRequiredFeatures) => { + log_trace!( + $logger, + "Invoice requires unknown features: {:?}", + $invoice.invoice_features() + ); + InvoiceError::from(Bolt12SemanticError::UnknownRequiredFeatures) + }, + Err(Bolt12PaymentError::SendingFailed(e)) => { + 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); + InvoiceError::from_string(err_msg.to_string()) + }, + Err(Bolt12PaymentError::UnexpectedInvoice) + | Err(Bolt12PaymentError::DuplicateInvoice) + | Ok(()) => return None, + }; + + match responder { + Some(responder) => { + return Some((OffersMessage::InvoiceError(error), responder.respond())) + }, + None => { + log_trace!($logger, "No reply path to send error: {:?}", error); + return None; + }, + } + }}; + } + + match message { + OffersMessage::InvoiceRequest(invoice_request) => { + let responder = match responder { + Some(responder) => responder, + None => return None, + }; + + let nonce = match context { + None if invoice_request.metadata().is_some() => None, + Some(OffersContext::InvoiceRequest { nonce }) => Some(nonce), + _ => return None, + }; + + let invoice_request = match nonce { + Some(nonce) => match invoice_request.verify_using_recipient_data( + nonce, + expanded_key, + secp_ctx, + ) { + Ok(invoice_request) => invoice_request, + Err(()) => return None, + }, + None => match invoice_request.verify_using_metadata(expanded_key, secp_ctx) { + Ok(invoice_request) => invoice_request, + Err(()) => return None, + }, + }; + + let amount_msats = match InvoiceBuilder::::amount_msats( + &invoice_request.inner, + ) { + Ok(amount_msats) => amount_msats, + Err(error) => { + return Some(( + OffersMessage::InvoiceError(error.into()), + responder.respond(), + )) + }, + }; + + let relative_expiry = DEFAULT_RELATIVE_EXPIRY.as_secs() as u32; + let (payment_hash, payment_secret) = match self.commons.create_inbound_payment( + Some(amount_msats), + relative_expiry, + None, + ) { + Ok((payment_hash, payment_secret)) => (payment_hash, payment_secret), + Err(()) => { + let error = Bolt12SemanticError::InvalidAmount; + return Some(( + OffersMessage::InvoiceError(error.into()), + responder.respond(), + )); + }, + }; + + let payment_context = PaymentContext::Bolt12Offer(Bolt12OfferContext { + offer_id: invoice_request.offer_id, + invoice_request: invoice_request.fields(), + }); + let payment_paths = match self.commons.create_blinded_payment_paths( + amount_msats, + payment_secret, + payment_context, + ) { + Ok(payment_paths) => payment_paths, + Err(()) => { + let error = Bolt12SemanticError::MissingPaths; + return Some(( + OffersMessage::InvoiceError(error.into()), + responder.respond(), + )); + }, + }; + + #[cfg(not(feature = "std"))] + let created_at = self.commons.get_current_blocktime(); + + let response = if invoice_request.keys.is_some() { + #[cfg(feature = "std")] + let builder = invoice_request.respond_using_derived_keys(payment_paths, payment_hash); + #[cfg(not(feature = "std"))] + let builder = invoice_request.respond_using_derived_keys_no_std( + payment_paths, + payment_hash, + created_at, + ); + builder + .map(InvoiceBuilder::::from) + .and_then(|builder| builder.allow_mpp().build_and_sign(secp_ctx)) + .map_err(InvoiceError::from) + } else { + #[cfg(feature = "std")] + let builder = invoice_request.respond_with(payment_paths, payment_hash); + #[cfg(not(feature = "std"))] + let builder = invoice_request.respond_with_no_std(payment_paths, payment_hash, created_at); + builder + .map(InvoiceBuilder::::from) + .and_then(|builder| builder.allow_mpp().build()) + .map_err(InvoiceError::from) + .and_then(|invoice| { + #[cfg(c_bindings)] + let mut invoice = invoice; + invoice + .sign(|invoice: &UnsignedBolt12Invoice| { + self.commons.sign_bolt12_invoice(invoice) + }) + .map_err(InvoiceError::from) + }) + }; + + match response { + Ok(invoice) => { + let nonce = Nonce::from_entropy_source(&*self.entropy_source); + let hmac = payment_hash.hmac_for_offer_payment(nonce, expanded_key); + let context = MessageContext::Offers(OffersContext::InboundPayment { + payment_hash, + nonce, + hmac, + }); + Some(( + OffersMessage::Invoice(invoice), + responder.respond_with_reply_path(context), + )) + }, + Err(error) => { + Some((OffersMessage::InvoiceError(error.into()), responder.respond())) + }, + } + }, + OffersMessage::Invoice(invoice) => { + let payment_id = + match self.commons.verify_bolt12_invoice(&invoice, context.as_ref()) { + Ok(payment_id) => payment_id, + Err(()) => return None, + }; + + let logger = + WithContext::from(&self.logger, None, None, Some(invoice.payment_hash())); + + let res = + self.commons.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, + nonce, + hmac: Some(hmac), + }) => { + if payment_id.verify_for_offer_payment(hmac, nonce, expanded_key).is_err() { + return None; + } + payment_id + }, + _ => return None, + }; + let res = self.commons.initiate_async_payment(&invoice, payment_id); + handle_pay_invoice_res!(res, invoice, self.logger); + }, + OffersMessage::InvoiceError(invoice_error) => { + let payment_hash = match context { + Some(OffersContext::InboundPayment { payment_hash, nonce, hmac }) => { + match payment_hash.verify_for_offer_payment(hmac, nonce, expanded_key) { + Ok(_) => Some(payment_hash), + Err(_) => None, + } + }, + _ => None, + }; + + let logger = WithContext::from(&self.logger, None, None, payment_hash); + log_trace!(logger, "Received invoice_error: {}", invoice_error); + + match context { + Some(OffersContext::OutboundPayment { + payment_id, + nonce, + hmac: Some(hmac), + }) => { + if let Ok(()) = + payment_id.verify_for_offer_payment(hmac, nonce, expanded_key) + { + self.commons.abandon_payment_with_reason( + payment_id, + PaymentFailureReason::InvoiceRequestRejected, + ); + } + }, + _ => {}, + } + + None + }, + } + } + + fn message_received(&self) { + for (payment_id, retryable_invoice_request) in + self.commons.release_invoice_requests_awaiting_invoice() + { + let RetryableInvoiceRequest { invoice_request, nonce } = retryable_invoice_request; + let hmac = payment_id.hmac_for_offer_payment(nonce, &self.inbound_payment_key); + let context = MessageContext::Offers(OffersContext::OutboundPayment { + payment_id, + nonce, + hmac: Some(hmac), + }); + match self.commons.create_blinded_paths(context) { + Ok(reply_paths) => { + match self.commons.enqueue_invoice_request(invoice_request, reply_paths) { + Ok(_) => {}, + Err(_) => { + log_warn!( + self.logger, + "Retry failed for an invoice request with payment_id: {}", + payment_id + ); + }, + } + }, + Err(_) => { + log_warn!( + self.logger, + "Retry failed for an invoice request with payment_id: {}. \ + Reason: router could not find a blinded path to include as the reply path", + payment_id + ); + }, + } + } + } + + fn release_pending_messages(&self) -> Vec<(OffersMessage, MessageSendInstructions)> { + core::mem::take(&mut self.commons.get_pending_offers_messages()) } } diff --git a/lightning/src/onion_message/messenger.rs b/lightning/src/onion_message/messenger.rs index 005d881b895..942c64fe0f8 100644 --- a/lightning/src/onion_message/messenger.rs +++ b/lightning/src/onion_message/messenger.rs @@ -46,6 +46,7 @@ use crate::prelude::*; #[cfg(not(c_bindings))] use { + crate::offers::flow::OffersMessageFlow, crate::sign::KeysManager, crate::ln::channelmanager::{SimpleArcChannelManager, SimpleRefChannelManager}, crate::ln::peer_handler::IgnoringMessageHandler, @@ -1864,7 +1865,7 @@ pub type SimpleArcOnionMessenger = OnionMessenger< Arc, Arc>, Arc>>, Arc, Arc>>, - Arc>, + Arc, Arc>, Arc>>, Arc>, Arc>, IgnoringMessageHandler @@ -1885,7 +1886,7 @@ pub type SimpleArcOnionMessenger = OnionMessenger< Arc, Arc>, Arc>>, Arc, Arc>>, - Arc>, + Arc, Arc>, Arc>>, Arc>, IgnoringMessageHandler, IgnoringMessageHandler From 85794cf92e4f83a4db10d8a9f2be5726170f9edf Mon Sep 17 00:00:00 2001 From: shaavan Date: Fri, 15 Nov 2024 19:34:22 +0530 Subject: [PATCH 08/30] Introduce `AnOffersMessageFlow` - This commit introduces a new struct, `AnOffersMessageFlow`, which generically implements `OffersMessageFlow`. - In subsequent commits, this struct will be utilized for documentation purposes. --- lightning/src/offers/flow.rs | 44 ++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/lightning/src/offers/flow.rs b/lightning/src/offers/flow.rs index 2b880045f4b..0dea47a1dc4 100644 --- a/lightning/src/offers/flow.rs +++ b/lightning/src/offers/flow.rs @@ -152,6 +152,50 @@ pub trait OffersMessageCommons { ) -> Result<(), Bolt12PaymentError>; } +/// A trivial trait which describes any [`OffersMessageFlow`]. +/// +/// This is not exported to bindings users as general cover traits aren't useful in other +/// languages. +pub trait AnOffersMessageFlow { + /// A type implementing [`EntropySource`]. + type EntropySource: EntropySource + ?Sized; + /// A type that may be dereferenced to [`Self::EntropySource`]. + type ES: Deref; + + /// A type implementing [`OffersMessageCommons`]. + type OffersMessageCommons: OffersMessageCommons + ?Sized; + /// A type that may be dereferenced to [`Self::OffersMessageCommons`]. + type OMC: Deref; + + /// A type implementing [`Logger`]. + type Logger: Logger + ?Sized; + /// A type that may be dereferenced to [`Self::Logger`]. + type L: Deref; + + /// Returns a reference to the actual [`OffersMessageFlow`] object. + fn get_omf(&self) -> &OffersMessageFlow; +} + +impl AnOffersMessageFlow for OffersMessageFlow +where + ES::Target: EntropySource, + OMC::Target: OffersMessageCommons, + L::Target: Logger, +{ + type EntropySource = ES::Target; + type ES = ES; + + type OffersMessageCommons = OMC::Target; + type OMC = OMC; + + type Logger = L::Target; + type L = L; + + fn get_omf(&self) -> &OffersMessageFlow { + self + } +} + /// Facilitates the handling, communication, and management of Offers messages within a Lightning /// node, enabling the creation, verification, and resolution of BOLT 12 invoices and related /// payment flows. From ada31aea4d5d084d65bf35fb4b5e0f0c71742937 Mon Sep 17 00:00:00 2001 From: shaavan Date: Fri, 15 Nov 2024 19:12:24 +0530 Subject: [PATCH 09/30] Move `create_offer_builder` to `OffersMessageFlow` --- fuzz/src/full_stack.rs | 1 + lightning-dns-resolver/src/lib.rs | 2 +- lightning/src/ln/channelmanager.rs | 162 +++------------- .../src/ln/max_payment_path_len_tests.rs | 2 +- lightning/src/ln/offers_tests.rs | 46 ++--- lightning/src/offers/flow.rs | 183 +++++++++++++++++- lightning/src/offers/offer.rs | 8 +- 7 files changed, 241 insertions(+), 163 deletions(-) diff --git a/fuzz/src/full_stack.rs b/fuzz/src/full_stack.rs index c1f2dd11b1e..6526405348e 100644 --- a/fuzz/src/full_stack.rs +++ b/fuzz/src/full_stack.rs @@ -50,6 +50,7 @@ use lightning::ln::peer_handler::{ }; use lightning::ln::script::ShutdownScript; use lightning::ln::types::ChannelId; +use lightning::offers::flow::OffersMessageCommons; use lightning::offers::invoice::UnsignedBolt12Invoice; use lightning::onion_message::messenger::{Destination, MessageRouter, OnionMessagePath}; use lightning::routing::gossip::{NetworkGraph, P2PGossipSync}; diff --git a/lightning-dns-resolver/src/lib.rs b/lightning-dns-resolver/src/lib.rs index 8f855cb5fb7..009eb6c8f64 100644 --- a/lightning-dns-resolver/src/lib.rs +++ b/lightning-dns-resolver/src/lib.rs @@ -391,7 +391,7 @@ mod test { let name = HumanReadableName::from_encoded("matt@mattcorallo.com").unwrap(); // When we get the proof back, override its contents to an offer from nodes[1] - let bs_offer = nodes[1].node.create_offer_builder(None).unwrap().build().unwrap(); + let bs_offer = nodes[1].offers_handler.create_offer_builder(None).unwrap().build().unwrap(); nodes[0] .node .testing_dnssec_proof_offer_resolution_override diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 8f0370744aa..ae51e1eda61 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -68,7 +68,7 @@ use crate::ln::outbound_payment::{OutboundPayments, PendingOutboundPayment, Retr use crate::offers::invoice::{Bolt12Invoice, DerivedSigningPubkey, InvoiceBuilder, UnsignedBolt12Invoice, DEFAULT_RELATIVE_EXPIRY}; use crate::offers::invoice_request::{InvoiceRequest, InvoiceRequestBuilder}; use crate::offers::nonce::Nonce; -use crate::offers::offer::{Offer, OfferBuilder}; +use crate::offers::offer::Offer; use crate::offers::parse::Bolt12SemanticError; use crate::offers::refund::{Refund, RefundBuilder}; use crate::offers::signer; @@ -96,7 +96,6 @@ use crate::onion_message::dns_resolution::{DNSResolverMessage, DNSResolverMessag #[cfg(not(c_bindings))] use { - crate::offers::offer::DerivedMetadata, crate::onion_message::messenger::DefaultMessageRouter, crate::routing::router::DefaultRouter, crate::routing::gossip::NetworkGraph, @@ -104,10 +103,7 @@ use { crate::sign::KeysManager, }; #[cfg(c_bindings)] -use { - crate::offers::offer::OfferWithDerivedMetadataBuilder, - crate::offers::refund::RefundMaybeWithDerivedMetadataBuilder, -}; +use crate::offers::refund::RefundMaybeWithDerivedMetadataBuilder; use lightning_invoice::{Bolt11Invoice, Bolt11InvoiceDescription, CreationError, Currency, Description, InvoiceBuilder as Bolt11InvoiceBuilder, SignOrCreationError, DEFAULT_EXPIRY_TIME}; @@ -1937,6 +1933,7 @@ where /// ``` /// # use lightning::events::{Event, EventsProvider, PaymentPurpose}; /// # use lightning::ln::channelmanager::{AChannelManager, Bolt11InvoiceParameters}; +/// # use lightning::offers::flow::OffersMessageCommons; /// # /// # fn example(channel_manager: T) { /// # let channel_manager = channel_manager.get_cm(); @@ -2037,56 +2034,8 @@ where /// ``` /// /// ## BOLT 12 Offers -/// -/// The [`offers`] module is useful for creating BOLT 12 offers. An [`Offer`] is a precursor to a -/// [`Bolt12Invoice`], which must first be requested by the payer. The interchange of these messages -/// as defined in the specification is handled by [`ChannelManager`]. However, this only works with -/// an [`Offer`] created using a builder returned by [`create_offer_builder`]. With this approach, -/// BOLT 12 offers and invoices are stateless just as BOLT 11 invoices are. -/// -/// ``` -/// # use lightning::events::{Event, EventsProvider, PaymentPurpose}; -/// # use lightning::ln::channelmanager::AChannelManager; -/// # use lightning::offers::parse::Bolt12SemanticError; -/// # -/// # fn example(channel_manager: T) -> Result<(), Bolt12SemanticError> { -/// # let channel_manager = channel_manager.get_cm(); -/// # let absolute_expiry = None; -/// let offer = channel_manager -/// .create_offer_builder(absolute_expiry)? -/// # ; -/// # // Needed for compiling for c_bindings -/// # let builder: lightning::offers::offer::OfferBuilder<_, _> = offer.into(); -/// # let offer = builder -/// .description("coffee".to_string()) -/// .amount_msats(10_000_000) -/// .build()?; -/// let bech32_offer = offer.to_string(); -/// -/// // On the event processing thread -/// channel_manager.process_pending_events(&|event| { -/// match event { -/// Event::PaymentClaimable { payment_hash, purpose, .. } => match purpose { -/// PaymentPurpose::Bolt12OfferPayment { payment_preimage: Some(payment_preimage), .. } => { -/// println!("Claiming payment {}", payment_hash); -/// channel_manager.claim_funds(payment_preimage); -/// }, -/// PaymentPurpose::Bolt12OfferPayment { payment_preimage: None, .. } => { -/// println!("Unknown payment hash: {}", payment_hash); -/// } -/// # _ => {}, -/// }, -/// Event::PaymentClaimed { payment_hash, amount_msat, .. } => { -/// println!("Claimed {} msats", amount_msat); -/// }, -/// // ... -/// # _ => {}, -/// } -/// Ok(()) -/// }); -/// # Ok(()) -/// # } -/// ``` +/// +/// For more information on creating offers, see [`create_offer_builder`]. /// /// Use [`pay_for_offer`] to initiated payment, which sends an [`InvoiceRequest`] for an [`Offer`] /// and pays the [`Bolt12Invoice`] response. @@ -2208,6 +2157,7 @@ where /// ``` /// # use lightning::events::{Event, EventsProvider, PaymentPurpose}; /// # use lightning::ln::channelmanager::AChannelManager; +/// # use lightning::offers::flow::OffersMessageCommons; /// # use lightning::offers::refund::Refund; /// # /// # fn example(channel_manager: T, refund: &Refund) { @@ -2326,7 +2276,6 @@ where /// [`claim_funds`]: Self::claim_funds /// [`send_payment`]: Self::send_payment /// [`offers`]: crate::offers -/// [`create_offer_builder`]: Self::create_offer_builder /// [`pay_for_offer`]: Self::pay_for_offer /// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest /// [`create_refund_builder`]: Self::create_refund_builder @@ -2338,6 +2287,7 @@ where /// [`update_channel`]: chain::Watch::update_channel /// [`ChannelUpdate`]: msgs::ChannelUpdate /// [`read`]: ReadableArgs::read +/// [`create_offer_builder`]: crate::offers::flow::OffersMessageFlow::create_offer_builder // // Lock order: // The tree structure below illustrates the lock order requirements for the different locks of the @@ -2836,11 +2786,13 @@ const MAX_NO_CHANNEL_PEERS: usize = 250; /// The maximum expiration from the current time where an [`Offer`] or [`Refund`] is considered /// short-lived, while anything with a greater expiration is considered long-lived. /// -/// Using [`ChannelManager::create_offer_builder`] or [`ChannelManager::create_refund_builder`], +/// Using [`OffersMessageFlow::create_offer_builder`] or [`ChannelManager::create_refund_builder`], /// will included a [`BlindedMessagePath`] created using: /// - [`MessageRouter::create_compact_blinded_paths`] when short-lived, and /// - [`MessageRouter::create_blinded_paths`] when long-lived. /// +/// [`OffersMessageFlow::create_offer_builder`]: crate::offers::flow::OffersMessageFlow::create_offer_builder +/// /// Using compact [`BlindedMessagePath`]s may provide better privacy as the [`MessageRouter`] could select /// more hops. However, since they use short channel ids instead of pubkeys, they are more likely to /// become invalid over time as channels are closed. Thus, they are only suitable for short-term use. @@ -6811,7 +6763,7 @@ where /// [`Event::PaymentClaimable::claim_deadline`]: crate::events::Event::PaymentClaimable::claim_deadline /// [`Event::PaymentClaimed`]: crate::events::Event::PaymentClaimed /// [`process_pending_events`]: EventsProvider::process_pending_events - /// [`create_inbound_payment`]: Self::create_inbound_payment + /// [`create_inbound_payment`]: ChannelManager::create_inbound_payment /// [`create_inbound_payment_for_hash`]: ChannelManager::create_inbound_payment_for_hash /// [`claim_funds_with_known_custom_tlvs`]: ChannelManager::claim_funds_with_known_custom_tlvs pub fn claim_funds(&self, payment_preimage: PaymentPreimage) { @@ -9762,57 +9714,6 @@ impl Default for Bolt11InvoiceParameters { } } -macro_rules! create_offer_builder { ($self: ident, $builder: ty) => { - /// Creates an [`OfferBuilder`] such that the [`Offer`] it builds is recognized by the - /// [`ChannelManager`] when handling [`InvoiceRequest`] messages for the offer. The offer's - /// expiration will be `absolute_expiry` if `Some`, otherwise it will not expire. - /// - /// # Privacy - /// - /// Uses [`MessageRouter`] to construct a [`BlindedMessagePath`] for the offer based on the given - /// `absolute_expiry` according to [`MAX_SHORT_LIVED_RELATIVE_EXPIRY`]. See those docs for - /// privacy implications as well as those of the parameterized [`Router`], which implements - /// [`MessageRouter`]. - /// - /// Also, uses a derived signing pubkey in the offer for recipient privacy. - /// - /// # Limitations - /// - /// Requires a direct connection to the introduction node in the responding [`InvoiceRequest`]'s - /// reply path. - /// - /// # Errors - /// - /// Errors if the parameterized [`Router`] is unable to create a blinded path for the offer. - /// - /// [`Offer`]: crate::offers::offer::Offer - /// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest - pub fn create_offer_builder( - &$self, absolute_expiry: Option - ) -> Result<$builder, Bolt12SemanticError> { - let node_id = $self.get_our_node_id(); - let expanded_key = &$self.inbound_payment_key; - let entropy = &*$self.entropy_source; - let secp_ctx = &$self.secp_ctx; - - let nonce = Nonce::from_entropy_source(entropy); - let context = OffersContext::InvoiceRequest { nonce }; - let path = $self.create_blinded_paths_using_absolute_expiry(context, absolute_expiry) - .and_then(|paths| paths.into_iter().next().ok_or(())) - .map_err(|_| Bolt12SemanticError::MissingPaths)?; - let builder = OfferBuilder::deriving_signing_pubkey(node_id, expanded_key, nonce, secp_ctx) - .chain_hash($self.chain_hash) - .path(path); - - let builder = match absolute_expiry { - None => builder, - Some(absolute_expiry) => builder.absolute_expiry(absolute_expiry), - }; - - Ok(builder.into()) - } -} } - macro_rules! create_refund_builder { ($self: ident, $builder: ty) => { /// Creates a [`RefundBuilder`] such that the [`Refund`] it builds is recognized by the /// [`ChannelManager`] when handling [`Bolt12Invoice`] messages for the refund. @@ -10108,6 +10009,23 @@ where res } + + fn create_blinded_paths_using_absolute_expiry( + &self, context: OffersContext, absolute_expiry: Option, + ) -> Result, ()> { + let now = self.duration_since_epoch(); + let max_short_lived_absolute_expiry = now.saturating_add(MAX_SHORT_LIVED_RELATIVE_EXPIRY); + + if absolute_expiry.unwrap_or(Duration::MAX) <= max_short_lived_absolute_expiry { + self.create_compact_blinded_paths(context) + } else { + self.create_blinded_paths(MessageContext::Offers(context)) + } + } + + fn get_chain_hash(&self) -> ChainHash { + self.chain_hash + } } /// Defines the maximum number of [`OffersMessage`] including different reply paths to be sent @@ -10129,13 +10047,9 @@ where MR::Target: MessageRouter, L::Target: Logger, { - #[cfg(not(c_bindings))] - create_offer_builder!(self, OfferBuilder); #[cfg(not(c_bindings))] create_refund_builder!(self, RefundBuilder); - #[cfg(c_bindings)] - create_offer_builder!(self, OfferWithDerivedMetadataBuilder); #[cfg(c_bindings)] create_refund_builder!(self, RefundMaybeWithDerivedMetadataBuilder); @@ -10487,25 +10401,6 @@ where inbound_payment::get_payment_preimage(payment_hash, payment_secret, &self.inbound_payment_key) } - /// Creates a collection of blinded paths by delegating to [`MessageRouter`] based on - /// the path's intended lifetime. - /// - /// Whether or not the path is compact depends on whether the path is short-lived or long-lived, - /// respectively, based on the given `absolute_expiry` as seconds since the Unix epoch. See - /// [`MAX_SHORT_LIVED_RELATIVE_EXPIRY`]. - fn create_blinded_paths_using_absolute_expiry( - &self, context: OffersContext, absolute_expiry: Option, - ) -> Result, ()> { - let now = self.duration_since_epoch(); - let max_short_lived_absolute_expiry = now.saturating_add(MAX_SHORT_LIVED_RELATIVE_EXPIRY); - - if absolute_expiry.unwrap_or(Duration::MAX) <= max_short_lived_absolute_expiry { - self.create_compact_blinded_paths(context) - } else { - self.create_blinded_paths(MessageContext::Offers(context)) - } - } - pub(super) fn duration_since_epoch(&self) -> Duration { #[cfg(not(feature = "std"))] let now = Duration::from_secs( @@ -15481,6 +15376,7 @@ pub mod bench { use crate::ln::channelmanager::{BestBlock, ChainParameters, ChannelManager, PaymentHash, PaymentPreimage, PaymentId, RecipientOnionFields, Retry}; use crate::ln::functional_test_utils::*; use crate::ln::msgs::{ChannelMessageHandler, Init}; + use crate::offers::flow::OffersMessageCommons; use crate::routing::gossip::NetworkGraph; use crate::routing::router::{PaymentParameters, RouteParameters}; use crate::util::test_utils; diff --git a/lightning/src/ln/max_payment_path_len_tests.rs b/lightning/src/ln/max_payment_path_len_tests.rs index 93e29be8e96..c38b439a0af 100644 --- a/lightning/src/ln/max_payment_path_len_tests.rs +++ b/lightning/src/ln/max_payment_path_len_tests.rs @@ -390,7 +390,7 @@ fn bolt12_invoice_too_large_blinded_paths() { ) ]); - let offer = nodes[1].node.create_offer_builder(None).unwrap().build().unwrap(); + let offer = nodes[1].offers_handler.create_offer_builder(None).unwrap().build().unwrap(); let payment_id = PaymentId([1; 32]); nodes[0].node.pay_for_offer(&offer, None, Some(5000), None, payment_id, Retry::Attempts(0), None).unwrap(); let invreq_om = nodes[0].onion_messenger.next_onion_message_for_peer(nodes[1].node.get_our_node_id()).unwrap(); diff --git a/lightning/src/ln/offers_tests.rs b/lightning/src/ln/offers_tests.rs index ba9c42d366a..f7da38a2157 100644 --- a/lightning/src/ln/offers_tests.rs +++ b/lightning/src/ln/offers_tests.rs @@ -294,7 +294,7 @@ fn prefers_non_tor_nodes_in_blinded_paths() { let tor = SocketAddress::OnionV2([255, 254, 253, 252, 251, 250, 249, 248, 247, 246, 38, 7]); announce_node_address(charlie, &[alice, bob, david, &nodes[4], &nodes[5]], tor.clone()); - let offer = bob.node + let offer = bob.offers_handler .create_offer_builder(None).unwrap() .amount_msats(10_000_000) .build().unwrap(); @@ -310,7 +310,7 @@ fn prefers_non_tor_nodes_in_blinded_paths() { announce_node_address(&nodes[4], &[alice, bob, charlie, david, &nodes[5]], tor.clone()); announce_node_address(&nodes[5], &[alice, bob, charlie, david, &nodes[4]], tor.clone()); - let offer = bob.node + let offer = bob.offers_handler .create_offer_builder(None).unwrap() .amount_msats(10_000_000) .build().unwrap(); @@ -361,7 +361,7 @@ fn prefers_more_connected_nodes_in_blinded_paths() { disconnect_peers(alice, &[charlie, david, &nodes[4], &nodes[5]]); disconnect_peers(david, &[bob, &nodes[4], &nodes[5]]); - let offer = bob.node + let offer = bob.offers_handler .create_offer_builder(None).unwrap() .amount_msats(10_000_000) .build().unwrap(); @@ -388,7 +388,7 @@ fn creates_short_lived_offer() { let bob = &nodes[1]; let absolute_expiry = alice.node.duration_since_epoch() + MAX_SHORT_LIVED_RELATIVE_EXPIRY; - let offer = alice.node + let offer = alice.offers_handler .create_offer_builder(Some(absolute_expiry)).unwrap() .build().unwrap(); assert_eq!(offer.absolute_expiry(), Some(absolute_expiry)); @@ -415,7 +415,7 @@ fn creates_long_lived_offer() { let absolute_expiry = alice.node.duration_since_epoch() + MAX_SHORT_LIVED_RELATIVE_EXPIRY + Duration::from_secs(1); - let offer = alice.node + let offer = alice.offers_handler .create_offer_builder(Some(absolute_expiry)) .unwrap() .build().unwrap(); @@ -425,7 +425,7 @@ fn creates_long_lived_offer() { assert_eq!(path.introduction_node(), &IntroductionNode::NodeId(alice_id)); } - let offer = alice.node + let offer = alice.offers_handler .create_offer_builder(None).unwrap() .build().unwrap(); assert_eq!(offer.absolute_expiry(), None); @@ -529,7 +529,7 @@ fn creates_and_pays_for_offer_using_two_hop_blinded_path() { disconnect_peers(alice, &[charlie, david, &nodes[4], &nodes[5]]); disconnect_peers(david, &[bob, &nodes[4], &nodes[5]]); - let offer = alice.node + let offer = alice.offers_handler .create_offer_builder(None) .unwrap() .amount_msats(10_000_000) @@ -699,7 +699,7 @@ fn creates_and_pays_for_offer_using_one_hop_blinded_path() { let bob = &nodes[1]; let bob_id = bob.node.get_our_node_id(); - let offer = alice.node + let offer = alice.offers_handler .create_offer_builder(None).unwrap() .amount_msats(10_000_000) .build().unwrap(); @@ -822,7 +822,7 @@ fn pays_for_offer_without_blinded_paths() { let bob = &nodes[1]; let bob_id = bob.node.get_our_node_id(); - let offer = alice.node + let offer = alice.offers_handler .create_offer_builder(None).unwrap() .clear_paths() .amount_msats(10_000_000) @@ -946,7 +946,7 @@ fn send_invoice_requests_with_distinct_reply_path() { disconnect_peers(alice, &[charlie, david, &nodes[4], &nodes[5], &nodes[6]]); disconnect_peers(david, &[bob, &nodes[4], &nodes[5]]); - let offer = alice.node + let offer = alice.offers_handler .create_offer_builder(None) .unwrap() .amount_msats(10_000_000) @@ -1082,7 +1082,7 @@ fn creates_and_pays_for_offer_with_retry() { let bob = &nodes[1]; let bob_id = bob.node.get_our_node_id(); - let offer = alice.node + let offer = alice.offers_handler .create_offer_builder(None).unwrap() .amount_msats(10_000_000) .build().unwrap(); @@ -1167,7 +1167,7 @@ fn creates_offer_with_blinded_path_using_unannounced_introduction_node() { let bob = &nodes[1]; let bob_id = bob.node.get_our_node_id(); - let offer = alice.node + let offer = alice.offers_handler .create_offer_builder(None).unwrap() .amount_msats(10_000_000) .build().unwrap(); @@ -1297,7 +1297,7 @@ fn fails_authentication_when_handling_invoice_request() { disconnect_peers(alice, &[charlie, david, &nodes[4], &nodes[5]]); disconnect_peers(david, &[bob, &nodes[4], &nodes[5]]); - let offer = alice.node + let offer = alice.offers_handler .create_offer_builder(None) .unwrap() .amount_msats(10_000_000) @@ -1309,7 +1309,7 @@ fn fails_authentication_when_handling_invoice_request() { assert_eq!(path.introduction_node(), &IntroductionNode::NodeId(bob_id)); } - let invalid_path = alice.node + let invalid_path = alice.offers_handler .create_offer_builder(None) .unwrap() .build().unwrap() @@ -1409,7 +1409,7 @@ fn fails_authentication_when_handling_invoice_for_offer() { disconnect_peers(alice, &[charlie, david, &nodes[4], &nodes[5]]); disconnect_peers(david, &[bob, &nodes[4], &nodes[5]]); - let offer = alice.node + let offer = alice.offers_handler .create_offer_builder(None) .unwrap() .amount_msats(10_000_000) @@ -1607,7 +1607,7 @@ fn fails_creating_or_paying_for_offer_without_connected_peers() { disconnect_peers(david, &[bob, charlie, &nodes[4], &nodes[5]]); let absolute_expiry = alice.node.duration_since_epoch() + MAX_SHORT_LIVED_RELATIVE_EXPIRY; - match alice.node.create_offer_builder(Some(absolute_expiry)) { + match alice.offers_handler.create_offer_builder(Some(absolute_expiry)) { Ok(_) => panic!("Expected error"), Err(e) => assert_eq!(e, Bolt12SemanticError::MissingPaths), } @@ -1616,7 +1616,7 @@ fn fails_creating_or_paying_for_offer_without_connected_peers() { args.send_channel_ready = (true, true); reconnect_nodes(args); - let offer = alice.node + let offer = alice.offers_handler .create_offer_builder(Some(absolute_expiry)).unwrap() .amount_msats(10_000_000) .build().unwrap(); @@ -1720,7 +1720,7 @@ fn fails_creating_invoice_request_for_unsupported_chain() { let alice = &nodes[0]; let bob = &nodes[1]; - let offer = alice.node + let offer = alice.offers_handler .create_offer_builder(None).unwrap() .clear_chains() .chain(Network::Signet) @@ -1779,7 +1779,7 @@ fn fails_creating_invoice_request_without_blinded_reply_path() { disconnect_peers(alice, &[charlie, david, &nodes[4], &nodes[5]]); disconnect_peers(david, &[bob, charlie, &nodes[4], &nodes[5]]); - let offer = alice.node + let offer = alice.offers_handler .create_offer_builder(None).unwrap() .amount_msats(10_000_000) .build().unwrap(); @@ -1813,7 +1813,7 @@ fn fails_creating_invoice_request_with_duplicate_payment_id() { disconnect_peers(alice, &[charlie, david, &nodes[4], &nodes[5]]); - let offer = alice.node + let offer = alice.offers_handler .create_offer_builder(None).unwrap() .amount_msats(10_000_000) .build().unwrap(); @@ -1899,7 +1899,7 @@ fn fails_sending_invoice_without_blinded_payment_paths_for_offer() { disconnect_peers(alice, &[charlie, david, &nodes[4], &nodes[5]]); disconnect_peers(david, &[bob, &nodes[4], &nodes[5]]); - let offer = alice.node + let offer = alice.offers_handler .create_offer_builder(None).unwrap() .amount_msats(10_000_000) .build().unwrap(); @@ -2108,7 +2108,7 @@ fn fails_paying_invoice_with_unknown_required_features() { disconnect_peers(alice, &[charlie, david, &nodes[4], &nodes[5]]); disconnect_peers(david, &[bob, &nodes[4], &nodes[5]]); - let offer = alice.node + let offer = alice.offers_handler .create_offer_builder(None).unwrap() .amount_msats(10_000_000) .build().unwrap(); @@ -2205,7 +2205,7 @@ fn no_double_pay_with_stale_channelmanager() { let bob_id = nodes[1].node.get_our_node_id(); let amt_msat = nodes[0].node.list_usable_channels()[0].next_outbound_htlc_limit_msat + 1; // Force MPP - let offer = nodes[1].node + let offer = nodes[1].offers_handler .create_offer_builder(None).unwrap() .clear_paths() .amount_msats(amt_msat) diff --git a/lightning/src/offers/flow.rs b/lightning/src/offers/flow.rs index 0dea47a1dc4..5ab3edef1f3 100644 --- a/lightning/src/offers/flow.rs +++ b/lightning/src/offers/flow.rs @@ -14,6 +14,7 @@ use crate::prelude::*; use core::ops::Deref; use core::time::Duration; +use bitcoin::constants::ChainHash; use bitcoin::secp256k1::{self, schnorr, PublicKey, Secp256k1}; use lightning_invoice::PaymentSecret; use types::payment::PaymentHash; @@ -36,6 +37,7 @@ use crate::sync::MutexGuard; use crate::offers::invoice_error::InvoiceError; use crate::offers::nonce::Nonce; +use crate::offers::offer::OfferBuilder; use crate::sign::EntropySource; use crate::util::logger::{Logger, WithContext}; @@ -43,6 +45,12 @@ use crate::util::logger::{Logger, WithContext}; #[cfg(async_payments)] use crate::offers::static_invoice::StaticInvoice; +#[cfg(not(c_bindings))] +use crate::offers::offer::DerivedMetadata; + +#[cfg(c_bindings)] +use crate::offers::offer::OfferWithDerivedMetadataBuilder; + /// Functions commonly shared in usage between [`ChannelManager`] & `OffersMessageFlow` /// /// [`ChannelManager`]: crate::ln::channelmanager::ChannelManager @@ -150,6 +158,22 @@ pub trait OffersMessageCommons { fn initiate_async_payment( &self, invoice: &StaticInvoice, payment_id: PaymentId, ) -> Result<(), Bolt12PaymentError>; + + /// Creates a collection of blinded paths by delegating to [`MessageRouter`] based on + /// the path's intended lifetime. + /// + /// Whether or not the path is compact depends on whether the path is short-lived or long-lived, + /// respectively, based on the given `absolute_expiry` as seconds since the Unix epoch. See + /// [`MAX_SHORT_LIVED_RELATIVE_EXPIRY`]. + /// + /// [`MessageRouter`]: crate::onion_message::messenger::MessageRouter + /// [`MAX_SHORT_LIVED_RELATIVE_EXPIRY`]: crate::ln::channelmanager::MAX_SHORT_LIVED_RELATIVE_EXPIRY + fn create_blinded_paths_using_absolute_expiry( + &self, context: OffersContext, absolute_expiry: Option, + ) -> Result, ()>; + + /// Get the [`ChainHash`] of the chain + fn get_chain_hash(&self) -> ChainHash; } /// A trivial trait which describes any [`OffersMessageFlow`]. @@ -221,7 +245,97 @@ where /// This struct is essential for enabling BOLT12 payment workflows in the Lightning network, /// providing the foundational mechanisms for Offers and related message exchanges. /// +/// ## Relationship with `ChannelManager` +/// +/// [`OffersMessageFlow`] and [`ChannelManager`] work in tandem to facilitate BOLT 12 functionality within +/// a Lightning node: +/// - The `OffersMessageFlow` is responsible for creating, managing, and verifying Offers messages, +/// such as BOLT 12 invoices and refunds. +/// - The `ChannelManager` manages the lifecycle of payments tied to these Offers messages, handling +/// tasks like payment execution, tracking payment states, and processing related events. +/// +/// The relationship is further reinforced through the [`OffersMessageCommons`] trait: +/// - `ChannelManager` implements the [`OffersMessageCommons`] trait, providing shared functionality +/// such as metadata verification and signature handling. +/// - `OffersMessageFlow` relies on this trait to perform common operations, enabling it to delegate +/// these tasks to `ChannelManager` by default. +/// +/// Practical Use Case: +/// - A BOLT 12 Offer is created using `OffersMessageFlow`, which provides the necessary interface +/// for Offer creation and invoice generation. +/// - Once a payment is initiated for the Offer, `ChannelManager` takes over, managing the payment's +/// lifecycle, including retries, claims, and event handling. +/// - Events such as `Event::PaymentClaimable` or `Event::PaymentFailed` are processed by +/// `ChannelManager`, which may invoke functionality defined in `OffersMessageCommons` to support +/// Offers-related operations. +/// +/// This modular and decoupled design ensures that `OffersMessageFlow` and `ChannelManager` can +/// operate independently while maintaining a seamless integration for handling Offers and payments +/// in the Lightning network. +/// +/// ## BOLT 12 Offers +/// +/// The [`offers`] module is useful for creating BOLT 12 offers. An [`Offer`] is a precursor to a +/// [`Bolt12Invoice`], which must first be requested by the payer. The interchange of these messages +/// as defined in the specification is handled by [`OffersMessageFlow`] and its implementation of +/// [`OffersMessageHandler`]. However, this only works with an [`Offer`] created using a builder +/// returned by [`create_offer_builder`]. With this approach, BOLT 12 offers and invoices are +/// stateless just as BOLT 11 invoices are. +/// +/// ``` +/// # use lightning::events::{Event, EventsProvider, PaymentPurpose}; +/// # use lightning::ln::channelmanager::AChannelManager; +/// # use lightning::offers::flow::AnOffersMessageFlow; +/// # use lightning::offers::parse::Bolt12SemanticError; +/// +/// # +/// # fn example(offers_flow: T, channel_manager: U) -> Result<(), Bolt12SemanticError> { +/// # let offers_flow = offers_flow.get_omf(); +/// # let channel_manager = channel_manager.get_cm(); +/// # let absolute_expiry = None; +/// # let offer = offers_flow +/// .create_offer_builder(absolute_expiry)? +/// # ; +/// # // Needed for compiling for c_bindings +/// # let builder: lightning::offers::offer::OfferBuilder<_, _> = offer.into(); +/// # let offer = builder +/// .description("coffee".to_string()) +/// .amount_msats(10_000_000) +/// .build()?; +/// let bech32_offer = offer.to_string(); +/// +/// // On the event processing thread +/// channel_manager.process_pending_events(&|event| { +/// match event { +/// Event::PaymentClaimable { payment_hash, purpose, .. } => match purpose { +/// PaymentPurpose::Bolt12OfferPayment { payment_preimage: Some(payment_preimage), .. } => { +/// println!("Claiming payment {}", payment_hash); +/// channel_manager.claim_funds(payment_preimage); +/// }, +/// PaymentPurpose::Bolt12OfferPayment { payment_preimage: None, .. } => { +/// println!("Unknown payment hash: {}", payment_hash); +/// } +/// # _ => {}, +/// }, +/// Event::PaymentClaimed { payment_hash, amount_msat, .. } => { +/// println!("Claimed {} msats", amount_msat); +/// }, +/// // ... +/// # _ => {}, +/// } +/// Ok(()) +/// }); +/// # Ok(()) +/// # } +/// ``` +/// /// [`DNSResolverMessage`]: crate::onion_message::dns_resolution::DNSResolverMessage +/// [`ChannelManager`]: crate::ln::channelmanager::ChannelManager +/// [`Bolt12Invoice`]: crate::offers::invoice +/// [`create_offer_builder`]: Self::create_offer_builder +/// [`Offer`]: crate::offers::offer +/// [`offers`]: crate::offers +/// pub struct OffersMessageFlow where ES::Target: EntropySource, @@ -235,7 +349,7 @@ where entropy_source: ES, - /// Contains function shared between OffersMessageHandler, and ChannelManager. + /// Contains functions shared between OffersMessageHandler and ChannelManager. commons: OMC, /// The Logger for use in the OffersMessageFlow and which may be used to log @@ -562,3 +676,70 @@ where core::mem::take(&mut self.commons.get_pending_offers_messages()) } } + +macro_rules! create_offer_builder { ($self: ident, $builder: ty) => { + /// Creates an [`OfferBuilder`] such that the [`Offer`] it builds is recognized by the + /// [`OffersMessageFlow`] when handling [`InvoiceRequest`] messages for the offer. The offer's + /// expiration will be `absolute_expiry` if `Some`, otherwise it will not expire. + /// + /// # Privacy + /// + /// Uses [`MessageRouter`] to construct a [`BlindedMessagePath`] for the offer based on the given + /// `absolute_expiry` according to [`MAX_SHORT_LIVED_RELATIVE_EXPIRY`]. See those docs for + /// privacy implications as well as those of the parameterized [`Router`], which implements + /// [`MessageRouter`]. + /// + /// Also, uses a derived signing pubkey in the offer for recipient privacy. + /// + /// # Limitations + /// + /// Requires a direct connection to the introduction node in the responding [`InvoiceRequest`]'s + /// reply path. + /// + /// # Errors + /// + /// Errors if the parameterized [`Router`] is unable to create a blinded path for the offer. + /// + /// [`BlindedMessagePath`]: crate::blinded_path::message::BlindedMessagePath + /// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest + /// [`MAX_SHORT_LIVED_RELATIVE_EXPIRY`]: crate::ln::channelmanager::MAX_SHORT_LIVED_RELATIVE_EXPIRY + /// [`MessageRouter`]: crate::onion_message::messenger::MessageRouter + /// [`Offer`]: crate::offers::offer + /// [`Router`]: crate::routing::router::Router + pub fn create_offer_builder( + &$self, absolute_expiry: Option + ) -> Result<$builder, Bolt12SemanticError> { + let node_id = $self.get_our_node_id(); + let expanded_key = &$self.inbound_payment_key; + let entropy = &*$self.entropy_source; + let secp_ctx = &$self.secp_ctx; + + let nonce = Nonce::from_entropy_source(entropy); + let context = OffersContext::InvoiceRequest { nonce }; + let path = $self.commons.create_blinded_paths_using_absolute_expiry(context, absolute_expiry) + .and_then(|paths| paths.into_iter().next().ok_or(())) + .map_err(|_| Bolt12SemanticError::MissingPaths)?; + let builder = OfferBuilder::deriving_signing_pubkey(node_id, expanded_key, nonce, secp_ctx) + .chain_hash($self.commons.get_chain_hash()) + .path(path); + + let builder = match absolute_expiry { + None => builder, + Some(absolute_expiry) => builder.absolute_expiry(absolute_expiry), + }; + + Ok(builder.into()) + } +} } + +impl OffersMessageFlow +where + ES::Target: EntropySource, + OMC::Target: OffersMessageCommons, + L::Target: Logger, +{ + #[cfg(not(c_bindings))] + create_offer_builder!(self, OfferBuilder); + #[cfg(c_bindings)] + create_offer_builder!(self, OfferWithDerivedMetadataBuilder); +} diff --git a/lightning/src/offers/offer.rs b/lightning/src/offers/offer.rs index 613f9accd47..3574f632db5 100644 --- a/lightning/src/offers/offer.rs +++ b/lightning/src/offers/offer.rs @@ -72,10 +72,10 @@ //! # Note //! //! If constructing an [`Offer`] for use with a [`ChannelManager`], use -//! [`ChannelManager::create_offer_builder`] instead of [`OfferBuilder::new`]. +//! [`OffersMessageFlow::create_offer_builder`] instead of [`OfferBuilder::new`]. //! //! [`ChannelManager`]: crate::ln::channelmanager::ChannelManager -//! [`ChannelManager::create_offer_builder`]: crate::ln::channelmanager::ChannelManager::create_offer_builder +//! [`OffersMessageFlow::create_offer_builder`]: crate::offers::flow::OffersMessageFlow::create_offer_builder use core::borrow::Borrow; use bitcoin::constants::ChainHash; @@ -229,10 +229,10 @@ macro_rules! offer_explicit_metadata_builder_methods { ( /// # Note /// /// If constructing an [`Offer`] for use with a [`ChannelManager`], use - /// [`ChannelManager::create_offer_builder`] instead of [`OfferBuilder::new`]. + /// [`OffersMessageFlow::create_offer_builder`] instead of [`OfferBuilder::new`]. /// /// [`ChannelManager`]: crate::ln::channelmanager::ChannelManager - /// [`ChannelManager::create_offer_builder`]: crate::ln::channelmanager::ChannelManager::create_offer_builder + /// [`OffersMessageFlow::create_offer_builder`]: crate::offers::flow::OffersMessageFlow::create_offer_builder pub fn new(signing_pubkey: PublicKey) -> Self { Self { offer: OfferContents { From 85431178e8aec2c605b32fa2ac24e05613997d98 Mon Sep 17 00:00:00 2001 From: shaavan Date: Fri, 15 Nov 2024 20:10:23 +0530 Subject: [PATCH 10/30] Move create_refund_builder to OffersMessageFlow --- lightning/src/ln/channelmanager.rs | 169 +++----------------------- lightning/src/ln/offers_tests.rs | 32 ++--- lightning/src/ln/outbound_payment.rs | 2 +- lightning/src/offers/flow.rs | 171 ++++++++++++++++++++++++++- lightning/src/offers/refund.rs | 8 +- 5 files changed, 206 insertions(+), 176 deletions(-) diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index ae51e1eda61..f5ff35ce03b 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -70,7 +70,7 @@ use crate::offers::invoice_request::{InvoiceRequest, InvoiceRequestBuilder}; use crate::offers::nonce::Nonce; use crate::offers::offer::Offer; use crate::offers::parse::Bolt12SemanticError; -use crate::offers::refund::{Refund, RefundBuilder}; +use crate::offers::refund::Refund; use crate::offers::signer; #[cfg(async_payments)] use crate::offers::static_invoice::StaticInvoice; @@ -102,8 +102,6 @@ use { crate::routing::scoring::{ProbabilisticScorer, ProbabilisticScoringFeeParameters}, crate::sign::KeysManager, }; -#[cfg(c_bindings)] -use crate::offers::refund::RefundMaybeWithDerivedMetadataBuilder; use lightning_invoice::{Bolt11Invoice, Bolt11InvoiceDescription, CreationError, Currency, Description, InvoiceBuilder as Bolt11InvoiceBuilder, SignOrCreationError, DEFAULT_EXPIRY_TIME}; @@ -1989,6 +1987,7 @@ where /// # use lightning::events::{Event, EventsProvider}; /// # use lightning::types::payment::PaymentHash; /// # use lightning::ln::channelmanager::{AChannelManager, PaymentId, RecentPaymentDetails, RecipientOnionFields, Retry}; +/// # use lightning::offers::flow::OffersMessageCommons; /// # use lightning::routing::router::RouteParameters; /// # /// # fn example( @@ -2043,6 +2042,7 @@ where /// ``` /// # use lightning::events::{Event, EventsProvider}; /// # use lightning::ln::channelmanager::{AChannelManager, PaymentId, RecentPaymentDetails, Retry}; +/// # use lightning::offers::flow::OffersMessageCommons; /// # use lightning::offers::offer::Offer; /// # /// # fn example( @@ -2089,67 +2089,8 @@ where /// ``` /// /// ## BOLT 12 Refunds -/// -/// A [`Refund`] is a request for an invoice to be paid. Like *paying* for an [`Offer`], *creating* -/// a [`Refund`] involves maintaining state since it represents a future outbound payment. -/// Therefore, use [`create_refund_builder`] when creating one, otherwise [`ChannelManager`] will -/// refuse to pay any corresponding [`Bolt12Invoice`] that it receives. -/// -/// ``` -/// # use core::time::Duration; -/// # use lightning::events::{Event, EventsProvider}; -/// # use lightning::ln::channelmanager::{AChannelManager, PaymentId, RecentPaymentDetails, Retry}; -/// # use lightning::offers::parse::Bolt12SemanticError; -/// # -/// # fn example( -/// # channel_manager: T, amount_msats: u64, absolute_expiry: Duration, retry: Retry, -/// # max_total_routing_fee_msat: Option -/// # ) -> Result<(), Bolt12SemanticError> { -/// # let channel_manager = channel_manager.get_cm(); -/// let payment_id = PaymentId([42; 32]); -/// let refund = channel_manager -/// .create_refund_builder( -/// amount_msats, absolute_expiry, payment_id, retry, max_total_routing_fee_msat -/// )? -/// # ; -/// # // Needed for compiling for c_bindings -/// # let builder: lightning::offers::refund::RefundBuilder<_> = refund.into(); -/// # let refund = builder -/// .description("coffee".to_string()) -/// .payer_note("refund for order 1234".to_string()) -/// .build()?; -/// let bech32_refund = refund.to_string(); -/// -/// // First the payment will be waiting on an invoice -/// let expected_payment_id = payment_id; -/// assert!( -/// channel_manager.list_recent_payments().iter().find(|details| matches!( -/// details, -/// RecentPaymentDetails::AwaitingInvoice { payment_id: expected_payment_id } -/// )).is_some() -/// ); -/// -/// // Once the invoice is received, a payment will be sent -/// assert!( -/// channel_manager.list_recent_payments().iter().find(|details| matches!( -/// details, -/// RecentPaymentDetails::Pending { payment_id: expected_payment_id, .. } -/// )).is_some() -/// ); -/// -/// // On the event processing thread -/// channel_manager.process_pending_events(&|event| { -/// match event { -/// Event::PaymentSent { payment_id: Some(payment_id), .. } => println!("Paid {}", payment_id), -/// Event::PaymentFailed { payment_id, .. } => println!("Failed paying {}", payment_id), -/// // ... -/// # _ => {}, -/// } -/// Ok(()) -/// }); -/// # Ok(()) -/// # } -/// ``` +/// +/// For more information on creating refunds, see [`create_refund_builder`]. /// /// Use [`request_refund_payment`] to send a [`Bolt12Invoice`] for receiving the refund. Similar to /// *creating* an [`Offer`], this is stateless as it represents an inbound payment. @@ -2278,7 +2219,6 @@ where /// [`offers`]: crate::offers /// [`pay_for_offer`]: Self::pay_for_offer /// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest -/// [`create_refund_builder`]: Self::create_refund_builder /// [`request_refund_payment`]: Self::request_refund_payment /// [`peer_disconnected`]: msgs::ChannelMessageHandler::peer_disconnected /// [`funding_created`]: msgs::FundingCreated @@ -2288,6 +2228,7 @@ where /// [`ChannelUpdate`]: msgs::ChannelUpdate /// [`read`]: ReadableArgs::read /// [`create_offer_builder`]: crate::offers::flow::OffersMessageFlow::create_offer_builder +/// [`create_refund_builder`]: crate::offers::flow::OffersMessageFlow::create_refund_builder // // Lock order: // The tree structure below illustrates the lock order requirements for the different locks of the @@ -2786,12 +2727,14 @@ const MAX_NO_CHANNEL_PEERS: usize = 250; /// The maximum expiration from the current time where an [`Offer`] or [`Refund`] is considered /// short-lived, while anything with a greater expiration is considered long-lived. /// -/// Using [`OffersMessageFlow::create_offer_builder`] or [`ChannelManager::create_refund_builder`], +/// Using [`OffersMessageFlow::create_offer_builder`] or [`OffersMessageFlow::create_refund_builder`], /// will included a [`BlindedMessagePath`] created using: /// - [`MessageRouter::create_compact_blinded_paths`] when short-lived, and /// - [`MessageRouter::create_blinded_paths`] when long-lived. /// /// [`OffersMessageFlow::create_offer_builder`]: crate::offers::flow::OffersMessageFlow::create_offer_builder +/// [`OffersMessageFlow::create_refund_builder`]: crate::offers::flow::OffersMessageFlow::create_refund_builder +/// /// /// Using compact [`BlindedMessagePath`]s may provide better privacy as the [`MessageRouter`] could select /// more hops. However, since they use short channel ids instead of pubkeys, they are more likely to @@ -9714,87 +9657,6 @@ impl Default for Bolt11InvoiceParameters { } } -macro_rules! create_refund_builder { ($self: ident, $builder: ty) => { - /// Creates a [`RefundBuilder`] such that the [`Refund`] it builds is recognized by the - /// [`ChannelManager`] when handling [`Bolt12Invoice`] messages for the refund. - /// - /// # Payment - /// - /// The provided `payment_id` is used to ensure that only one invoice is paid for the refund. - /// See [Avoiding Duplicate Payments] for other requirements once the payment has been sent. - /// - /// The builder will have the provided expiration set. Any changes to the expiration on the - /// returned builder will not be honored by [`ChannelManager`]. For non-`std`, the highest seen - /// block time minus two hours is used for the current time when determining if the refund has - /// expired. - /// - /// To revoke the refund, use [`ChannelManager::abandon_payment`] prior to receiving the - /// invoice. If abandoned, or an invoice isn't received before expiration, the payment will fail - /// with an [`Event::PaymentFailed`]. - /// - /// If `max_total_routing_fee_msat` is not specified, The default from - /// [`RouteParameters::from_payment_params_and_value`] is applied. - /// - /// # Privacy - /// - /// Uses [`MessageRouter`] to construct a [`BlindedMessagePath`] for the refund based on the given - /// `absolute_expiry` according to [`MAX_SHORT_LIVED_RELATIVE_EXPIRY`]. See those docs for - /// privacy implications as well as those of the parameterized [`Router`], which implements - /// [`MessageRouter`]. - /// - /// Also, uses a derived payer id in the refund for payer privacy. - /// - /// # Limitations - /// - /// Requires a direct connection to an introduction node in the responding - /// [`Bolt12Invoice::payment_paths`]. - /// - /// # Errors - /// - /// Errors if: - /// - a duplicate `payment_id` is provided given the caveats in the aforementioned link, - /// - `amount_msats` is invalid, or - /// - the parameterized [`Router`] is unable to create a blinded path for the refund. - /// - /// [`Refund`]: crate::offers::refund::Refund - /// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice - /// [`Bolt12Invoice::payment_paths`]: crate::offers::invoice::Bolt12Invoice::payment_paths - /// [Avoiding Duplicate Payments]: #avoiding-duplicate-payments - pub fn create_refund_builder( - &$self, amount_msats: u64, absolute_expiry: Duration, payment_id: PaymentId, - retry_strategy: Retry, max_total_routing_fee_msat: Option - ) -> Result<$builder, Bolt12SemanticError> { - let node_id = $self.get_our_node_id(); - let expanded_key = &$self.inbound_payment_key; - let entropy = &*$self.entropy_source; - let secp_ctx = &$self.secp_ctx; - - let nonce = Nonce::from_entropy_source(entropy); - let context = OffersContext::OutboundPayment { payment_id, nonce, hmac: None }; - let path = $self.create_blinded_paths_using_absolute_expiry(context, Some(absolute_expiry)) - .and_then(|paths| paths.into_iter().next().ok_or(())) - .map_err(|_| Bolt12SemanticError::MissingPaths)?; - - let builder = RefundBuilder::deriving_signing_pubkey( - node_id, expanded_key, nonce, secp_ctx, amount_msats, payment_id - )? - .chain_hash($self.chain_hash) - .absolute_expiry(absolute_expiry) - .path(path); - - let _persistence_guard = PersistenceNotifierGuard::notify_on_drop($self); - - let expiration = StaleExpiration::AbsoluteTimeout(absolute_expiry); - $self.pending_outbound_payments - .add_new_awaiting_invoice( - payment_id, expiration, retry_strategy, max_total_routing_fee_msat, None, - ) - .map_err(|_| Bolt12SemanticError::DuplicatePaymentId)?; - - Ok(builder.into()) - } -} } - impl OffersMessageCommons for ChannelManager where M::Target: chain::Watch<::EcdsaSigner>, @@ -10026,6 +9888,13 @@ where fn get_chain_hash(&self) -> ChainHash { self.chain_hash } + + fn add_new_awaiting_invoice(&self, payment_id: PaymentId, expiration: StaleExpiration, retry_strategy: Retry, max_total_routing_fee_msat: Option, retryable_invoice_request: Option) -> Result<(), ()> { + let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self); + self.pending_outbound_payments.add_new_awaiting_invoice ( + payment_id, expiration, retry_strategy, max_total_routing_fee_msat, retryable_invoice_request, + ) + } } /// Defines the maximum number of [`OffersMessage`] including different reply paths to be sent @@ -10047,12 +9916,6 @@ where MR::Target: MessageRouter, L::Target: Logger, { - #[cfg(not(c_bindings))] - create_refund_builder!(self, RefundBuilder); - - #[cfg(c_bindings)] - create_refund_builder!(self, RefundMaybeWithDerivedMetadataBuilder); - /// Pays for an [`Offer`] using the given parameters by creating an [`InvoiceRequest`] and /// enqueuing it to be sent via an onion message. [`ChannelManager`] will pay the actual /// [`Bolt12Invoice`] once it is received. diff --git a/lightning/src/ln/offers_tests.rs b/lightning/src/ln/offers_tests.rs index f7da38a2157..5c62f308d9e 100644 --- a/lightning/src/ln/offers_tests.rs +++ b/lightning/src/ln/offers_tests.rs @@ -451,7 +451,7 @@ fn creates_short_lived_refund() { let absolute_expiry = bob.node.duration_since_epoch() + MAX_SHORT_LIVED_RELATIVE_EXPIRY; let payment_id = PaymentId([1; 32]); - let refund = bob.node + let refund = bob.offers_handler .create_refund_builder(10_000_000, absolute_expiry, payment_id, Retry::Attempts(0), None) .unwrap() .build().unwrap(); @@ -480,7 +480,7 @@ fn creates_long_lived_refund() { let absolute_expiry = bob.node.duration_since_epoch() + MAX_SHORT_LIVED_RELATIVE_EXPIRY + Duration::from_secs(1); let payment_id = PaymentId([1; 32]); - let refund = bob.node + let refund = bob.offers_handler .create_refund_builder(10_000_000, absolute_expiry, payment_id, Retry::Attempts(0), None) .unwrap() .build().unwrap(); @@ -639,7 +639,7 @@ fn creates_and_pays_for_refund_using_two_hop_blinded_path() { let absolute_expiry = Duration::from_secs(u64::MAX); let payment_id = PaymentId([1; 32]); - let refund = david.node + let refund = david.offers_handler .create_refund_builder(10_000_000, absolute_expiry, payment_id, Retry::Attempts(0), None) .unwrap() .build().unwrap(); @@ -768,7 +768,7 @@ fn creates_and_pays_for_refund_using_one_hop_blinded_path() { let absolute_expiry = Duration::from_secs(u64::MAX); let payment_id = PaymentId([1; 32]); - let refund = bob.node + let refund = bob.offers_handler .create_refund_builder(10_000_000, absolute_expiry, payment_id, Retry::Attempts(0), None) .unwrap() .build().unwrap(); @@ -877,7 +877,7 @@ fn pays_for_refund_without_blinded_paths() { let absolute_expiry = Duration::from_secs(u64::MAX); let payment_id = PaymentId([1; 32]); - let refund = bob.node + let refund = bob.offers_handler .create_refund_builder(10_000_000, absolute_expiry, payment_id, Retry::Attempts(0), None) .unwrap() .clear_paths() @@ -1032,7 +1032,7 @@ fn send_invoice_for_refund_with_distinct_reply_path() { let absolute_expiry = Duration::from_secs(u64::MAX); let payment_id = PaymentId([1; 32]); - let refund = alice.node + let refund = alice.offers_handler .create_refund_builder(10_000_000, absolute_expiry, payment_id, Retry::Attempts(0), None) .unwrap() .build().unwrap(); @@ -1235,7 +1235,7 @@ fn creates_refund_with_blinded_path_using_unannounced_introduction_node() { let absolute_expiry = Duration::from_secs(u64::MAX); let payment_id = PaymentId([1; 32]); - let refund = bob.node + let refund = bob.offers_handler .create_refund_builder(10_000_000, absolute_expiry, payment_id, Retry::Attempts(0), None) .unwrap() .build().unwrap(); @@ -1518,7 +1518,7 @@ fn fails_authentication_when_handling_invoice_for_refund() { let absolute_expiry = Duration::from_secs(u64::MAX); let payment_id = PaymentId([1; 32]); - let refund = david.node + let refund = david.offers_handler .create_refund_builder(10_000_000, absolute_expiry, payment_id, Retry::Attempts(0), None) .unwrap() .build().unwrap(); @@ -1552,7 +1552,7 @@ fn fails_authentication_when_handling_invoice_for_refund() { // Send the invoice to David using an invalid blinded path. let invalid_path = refund.paths().first().unwrap().clone(); let payment_id = PaymentId([2; 32]); - let refund = david.node + let refund = david.offers_handler .create_refund_builder(10_000_000, absolute_expiry, payment_id, Retry::Attempts(0), None) .unwrap() .build().unwrap(); @@ -1679,7 +1679,7 @@ fn fails_creating_refund_or_sending_invoice_without_connected_peers() { let absolute_expiry = david.node.duration_since_epoch() + MAX_SHORT_LIVED_RELATIVE_EXPIRY; let payment_id = PaymentId([1; 32]); - match david.node.create_refund_builder( + match david.offers_handler.create_refund_builder( 10_000_000, absolute_expiry, payment_id, Retry::Attempts(0), None ) { Ok(_) => panic!("Expected error"), @@ -1690,7 +1690,7 @@ fn fails_creating_refund_or_sending_invoice_without_connected_peers() { args.send_channel_ready = (true, true); reconnect_nodes(args); - let refund = david.node + let refund = david.offers_handler .create_refund_builder(10_000_000, absolute_expiry, payment_id, Retry::Attempts(0), None) .unwrap() .build().unwrap(); @@ -1748,7 +1748,7 @@ fn fails_sending_invoice_with_unsupported_chain_for_refund() { let absolute_expiry = Duration::from_secs(u64::MAX); let payment_id = PaymentId([1; 32]); - let refund = bob.node + let refund = bob.offers_handler .create_refund_builder(10_000_000, absolute_expiry, payment_id, Retry::Attempts(0), None) .unwrap() .chain(Network::Signet) @@ -1846,13 +1846,13 @@ fn fails_creating_refund_with_duplicate_payment_id() { let absolute_expiry = Duration::from_secs(u64::MAX); let payment_id = PaymentId([1; 32]); assert!( - nodes[0].node.create_refund_builder( + nodes[0].offers_handler.create_refund_builder( 10_000, absolute_expiry, payment_id, Retry::Attempts(0), None ).is_ok() ); expect_recent_payment!(nodes[0], RecentPaymentDetails::AwaitingInvoice, payment_id); - match nodes[0].node.create_refund_builder( + match nodes[0].offers_handler.create_refund_builder( 10_000, absolute_expiry, payment_id, Retry::Attempts(0), None ) { Ok(_) => panic!("Expected error"), @@ -1972,7 +1972,7 @@ fn fails_sending_invoice_without_blinded_payment_paths_for_refund() { let absolute_expiry = Duration::from_secs(u64::MAX); let payment_id = PaymentId([1; 32]); - let refund = david.node + let refund = david.offers_handler .create_refund_builder(10_000_000, absolute_expiry, payment_id, Retry::Attempts(0), None) .unwrap() .build().unwrap(); @@ -2021,7 +2021,7 @@ fn fails_paying_invoice_more_than_once() { let absolute_expiry = Duration::from_secs(u64::MAX); let payment_id = PaymentId([1; 32]); - let refund = david.node + let refund = david.offers_handler .create_refund_builder(10_000_000, absolute_expiry, payment_id, Retry::Attempts(0), None) .unwrap() .build().unwrap(); diff --git a/lightning/src/ln/outbound_payment.rs b/lightning/src/ln/outbound_payment.rs index 2e2575f7755..186f45aa17b 100644 --- a/lightning/src/ln/outbound_payment.rs +++ b/lightning/src/ln/outbound_payment.rs @@ -434,7 +434,7 @@ impl Display for PaymentAttempts { /// [`PendingOutboundPayment::AwaitingOffer`] should be considered stale and candidate for removal /// in [`OutboundPayments::remove_stale_payments`]. #[derive(Clone, Copy)] -pub(crate) enum StaleExpiration { +pub enum StaleExpiration { /// Number of times [`OutboundPayments::remove_stale_payments`] is called. TimerTicks(u64), /// Duration since the Unix epoch. diff --git a/lightning/src/offers/flow.rs b/lightning/src/offers/flow.rs index 5ab3edef1f3..4d81e5c7485 100644 --- a/lightning/src/offers/flow.rs +++ b/lightning/src/offers/flow.rs @@ -24,7 +24,7 @@ use crate::blinded_path::payment::{BlindedPaymentPath, Bolt12OfferContext, Payme use crate::events::PaymentFailureReason; use crate::ln::channelmanager::{Bolt12PaymentError, PaymentId, Verification}; use crate::ln::inbound_payment; -use crate::ln::outbound_payment::RetryableInvoiceRequest; +use crate::ln::outbound_payment::{Retry, RetryableInvoiceRequest, StaleExpiration}; use crate::offers::invoice::{ Bolt12Invoice, DerivedSigningPubkey, ExplicitSigningPubkey, InvoiceBuilder, UnsignedBolt12Invoice, DEFAULT_RELATIVE_EXPIRY, @@ -38,6 +38,7 @@ use crate::sync::MutexGuard; use crate::offers::invoice_error::InvoiceError; use crate::offers::nonce::Nonce; use crate::offers::offer::OfferBuilder; +use crate::offers::refund::RefundBuilder; use crate::sign::EntropySource; use crate::util::logger::{Logger, WithContext}; @@ -49,7 +50,10 @@ use crate::offers::static_invoice::StaticInvoice; use crate::offers::offer::DerivedMetadata; #[cfg(c_bindings)] -use crate::offers::offer::OfferWithDerivedMetadataBuilder; +use { + crate::offers::offer::OfferWithDerivedMetadataBuilder, + crate::offers::refund::RefundMaybeWithDerivedMetadataBuilder, +}; /// Functions commonly shared in usage between [`ChannelManager`] & `OffersMessageFlow` /// @@ -174,6 +178,13 @@ pub trait OffersMessageCommons { /// Get the [`ChainHash`] of the chain fn get_chain_hash(&self) -> ChainHash; + + /// Add new awaiting invoice + fn add_new_awaiting_invoice( + &self, payment_id: PaymentId, expiration: StaleExpiration, retry_strategy: Retry, + max_total_routing_fee_msat: Option, + retryable_invoice_request: Option, + ) -> Result<(), ()>; } /// A trivial trait which describes any [`OffersMessageFlow`]. @@ -329,10 +340,75 @@ where /// # } /// ``` /// +/// A [`Refund`] is a request for an invoice to be paid. Like *paying* for an [`Offer`], *creating* +/// a [`Refund`] involves maintaining state since it represents a future outbound payment. +/// Therefore, use [`create_refund_builder`] when creating one, otherwise [`OffersMessageFlow`] will +/// refuse to pay any corresponding [`Bolt12Invoice`] that it receives. +/// +/// ``` +/// # use core::time::Duration; +/// # use lightning::events::{Event, EventsProvider}; +/// # use lightning::ln::channelmanager::{AChannelManager, PaymentId, RecentPaymentDetails, Retry}; +/// # use lightning::offers::flow::AnOffersMessageFlow; +/// # use lightning::offers::parse::Bolt12SemanticError; +/// # +/// # fn example( +/// # offers_flow: T, channel_manager: U, amount_msats: u64, absolute_expiry: Duration, retry: Retry, +/// # max_total_routing_fee_msat: Option +/// # ) -> Result<(), Bolt12SemanticError> { +/// # let offers_flow = offers_flow.get_omf(); +/// # let channel_manager = channel_manager.get_cm(); +/// # let payment_id = PaymentId([42; 32]); +/// # let refund = offers_flow +/// .create_refund_builder( +/// amount_msats, absolute_expiry, payment_id, retry, max_total_routing_fee_msat +/// )? +/// # ; +/// # // Needed for compiling for c_bindings +/// # let builder: lightning::offers::refund::RefundBuilder<_> = refund.into(); +/// # let refund = builder +/// .description("coffee".to_string()) +/// .payer_note("refund for order 1234".to_string()) +/// .build()?; +/// let bech32_refund = refund.to_string(); +/// +/// // First the payment will be waiting on an invoice +/// let expected_payment_id = payment_id; +/// assert!( +/// channel_manager.list_recent_payments().iter().find(|details| matches!( +/// details, +/// RecentPaymentDetails::AwaitingInvoice { payment_id: expected_payment_id } +/// )).is_some() +/// ); +/// +/// // Once the invoice is received, a payment will be sent +/// assert!( +/// channel_manager.list_recent_payments().iter().find(|details| matches!( +/// details, +/// RecentPaymentDetails::Pending { payment_id: expected_payment_id, .. } +/// )).is_some() +/// ); +/// +/// // On the event processing thread +/// channel_manager.process_pending_events(&|event| { +/// match event { +/// Event::PaymentSent { payment_id: Some(payment_id), .. } => println!("Paid {}", payment_id), +/// Event::PaymentFailed { payment_id, .. } => println!("Failed paying {}", payment_id), +/// // ... +/// # _ => {}, +/// } +/// Ok(()) +/// }); +/// # Ok(()) +/// # } +/// ``` +/// /// [`DNSResolverMessage`]: crate::onion_message::dns_resolution::DNSResolverMessage /// [`ChannelManager`]: crate::ln::channelmanager::ChannelManager /// [`Bolt12Invoice`]: crate::offers::invoice /// [`create_offer_builder`]: Self::create_offer_builder +/// [`create_refund_builder`]: Self::create_refund_builder +/// [`Refund`]: crate::offers::refund::Refund /// [`Offer`]: crate::offers::offer /// [`offers`]: crate::offers /// @@ -732,6 +808,91 @@ macro_rules! create_offer_builder { ($self: ident, $builder: ty) => { } } } +macro_rules! create_refund_builder { ($self: ident, $builder: ty) => { + /// Creates a [`RefundBuilder`] such that the [`Refund`] it builds is recognized by the + /// [`OffersMessageFlow`] when handling [`Bolt12Invoice`] messages for the refund. + /// + /// # Payment + /// + /// The provided `payment_id` is used to ensure that only one invoice is paid for the refund. + /// See [Avoiding Duplicate Payments] for other requirements once the payment has been sent. + /// + /// The builder will have the provided expiration set. Any changes to the expiration on the + /// returned builder will not be honored by [`OffersMessageFlow`]. For non-`std`, the highest seen + /// block time minus two hours is used for the current time when determining if the refund has + /// expired. + /// + /// To revoke the refund, use [`ChannelManager::abandon_payment`] prior to receiving the + /// invoice. If abandoned, or an invoice isn't received before expiration, the payment will fail + /// with an [`Event::PaymentFailed`]. + /// + /// If `max_total_routing_fee_msat` is not specified, The default from + /// [`RouteParameters::from_payment_params_and_value`] is applied. + /// + /// # Privacy + /// + /// Uses [`MessageRouter`] to construct a [`BlindedMessagePath`] for the refund based on the given + /// `absolute_expiry` according to [`MAX_SHORT_LIVED_RELATIVE_EXPIRY`]. See those docs for + /// privacy implications as well as those of the parameterized [`Router`], which implements + /// [`MessageRouter`]. + /// + /// Also, uses a derived payer id in the refund for payer privacy. + /// + /// # Limitations + /// + /// Requires a direct connection to an introduction node in the responding + /// [`Bolt12Invoice::payment_paths`]. + /// + /// # Errors + /// + /// Errors if: + /// - a duplicate `payment_id` is provided given the caveats in the aforementioned link, + /// - `amount_msats` is invalid, or + /// - the parameterized [`Router`] is unable to create a blinded path for the refund. + /// + /// [`Refund`]: crate::offers::refund::Refund + /// [`BlindedMessagePath`]: crate::blinded_path::message::BlindedMessagePath + /// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice + /// [`Bolt12Invoice::payment_paths`]: crate::offers::invoice::Bolt12Invoice::payment_paths + /// [`ChannelManager::abandon_payment`]: crate::ln::channelmanager::ChannelManager::abandon_payment + /// [`MAX_SHORT_LIVED_RELATIVE_EXPIRY`]: crate::ln::channelmanager::MAX_SHORT_LIVED_RELATIVE_EXPIRY + /// [`MessageRouter`]: crate::onion_message::messenger::MessageRouter + /// [`RouteParameters::from_payment_params_and_value`]: crate::routing::router::RouteParameters::from_payment_params_and_value + /// [`Router`]: crate::routing::router::Router + /// [`Event::PaymentFailed`]: crate::events::Event::PaymentFailed + /// [Avoiding Duplicate Payments]: #avoiding-duplicate-payments + pub fn create_refund_builder( + &$self, amount_msats: u64, absolute_expiry: Duration, payment_id: PaymentId, + retry_strategy: Retry, max_total_routing_fee_msat: Option + ) -> Result<$builder, Bolt12SemanticError> { + let node_id = $self.get_our_node_id(); + let expanded_key = &$self.inbound_payment_key; + let entropy = &*$self.entropy_source; + let secp_ctx = &$self.secp_ctx; + + let nonce = Nonce::from_entropy_source(entropy); + let context = OffersContext::OutboundPayment { payment_id, nonce, hmac: None }; + let path = $self.commons.create_blinded_paths_using_absolute_expiry(context, Some(absolute_expiry)) + .and_then(|paths| paths.into_iter().next().ok_or(())) + .map_err(|_| Bolt12SemanticError::MissingPaths)?; + + let builder = RefundBuilder::deriving_signing_pubkey( + node_id, expanded_key, nonce, secp_ctx, amount_msats, payment_id + )? + .chain_hash($self.commons.get_chain_hash()) + .absolute_expiry(absolute_expiry) + .path(path); + + let expiration = StaleExpiration::AbsoluteTimeout(absolute_expiry); + + $self.commons.add_new_awaiting_invoice( + payment_id, expiration, retry_strategy, max_total_routing_fee_msat, None + ).map_err(|_| Bolt12SemanticError::DuplicatePaymentId)?; + + Ok(builder.into()) + } +} } + impl OffersMessageFlow where ES::Target: EntropySource, @@ -742,4 +903,10 @@ where create_offer_builder!(self, OfferBuilder); #[cfg(c_bindings)] create_offer_builder!(self, OfferWithDerivedMetadataBuilder); + + #[cfg(not(c_bindings))] + create_refund_builder!(self, RefundBuilder); + + #[cfg(c_bindings)] + create_refund_builder!(self, RefundMaybeWithDerivedMetadataBuilder); } diff --git a/lightning/src/offers/refund.rs b/lightning/src/offers/refund.rs index a68d0eb658e..a27234b8e31 100644 --- a/lightning/src/offers/refund.rs +++ b/lightning/src/offers/refund.rs @@ -77,10 +77,10 @@ //! # Note //! //! If constructing a [`Refund`] for use with a [`ChannelManager`], use -//! [`ChannelManager::create_refund_builder`] instead of [`RefundBuilder::new`]. +//! [`OffersMessageFlow::create_refund_builder`] instead of [`RefundBuilder::new`]. //! //! [`ChannelManager`]: crate::ln::channelmanager::ChannelManager -//! [`ChannelManager::create_refund_builder`]: crate::ln::channelmanager::ChannelManager::create_refund_builder +//! [`OffersMessageFlow::create_refund_builder`]: crate::offers::flow::OffersMessageFlow::create_refund_builder use bitcoin::constants::ChainHash; use bitcoin::network::Network; @@ -159,10 +159,10 @@ macro_rules! refund_explicit_metadata_builder_methods { () => { /// # Note /// /// If constructing a [`Refund`] for use with a [`ChannelManager`], use - /// [`ChannelManager::create_refund_builder`] instead of [`RefundBuilder::new`]. + /// [`OffersMessageFlow::create_refund_builder`] instead of [`RefundBuilder::new`]. /// /// [`ChannelManager`]: crate::ln::channelmanager::ChannelManager - /// [`ChannelManager::create_refund_builder`]: crate::ln::channelmanager::ChannelManager::create_refund_builder + /// [`OffersMessageFlow::create_refund_builder`]: crate::offers::flow::OffersMessageFlow::create_refund_builder pub fn new( metadata: Vec, signing_pubkey: PublicKey, amount_msats: u64 ) -> Result { From 03a69e462ef1db436463f6ca48d8b342c4f576a1 Mon Sep 17 00:00:00 2001 From: shaavan Date: Sat, 16 Nov 2024 18:03:10 +0530 Subject: [PATCH 11/30] Move pay_for_offer to OffersMessageFlow --- lightning/src/ln/channelmanager.rs | 188 ++++-------------- .../src/ln/max_payment_path_len_tests.rs | 2 +- lightning/src/ln/offers_tests.rs | 38 ++-- lightning/src/offers/flow.rs | 157 ++++++++++++++- 4 files changed, 211 insertions(+), 174 deletions(-) diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index f5ff35ce03b..c894eff61ab 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -47,6 +47,7 @@ use crate::events::{self, Event, EventHandler, EventsProvider, InboundChannelFun // construct one themselves. use crate::ln::inbound_payment; use crate::ln::types::ChannelId; +use crate::offers::offer::Offer; use crate::offers::flow::OffersMessageCommons; use crate::types::payment::{PaymentHash, PaymentPreimage, PaymentSecret}; use crate::ln::channel::{self, Channel, ChannelPhase, ChannelError, ChannelUpdateStatus, ShutdownResult, UpdateFulfillCommitFetch, OutboundV1Channel, InboundV1Channel, WithChannelContext, InboundV2Channel, InteractivelyFunded as _}; @@ -68,7 +69,6 @@ use crate::ln::outbound_payment::{OutboundPayments, PendingOutboundPayment, Retr use crate::offers::invoice::{Bolt12Invoice, DerivedSigningPubkey, InvoiceBuilder, UnsignedBolt12Invoice, DEFAULT_RELATIVE_EXPIRY}; use crate::offers::invoice_request::{InvoiceRequest, InvoiceRequestBuilder}; use crate::offers::nonce::Nonce; -use crate::offers::offer::Offer; use crate::offers::parse::Bolt12SemanticError; use crate::offers::refund::Refund; use crate::offers::signer; @@ -2036,57 +2036,7 @@ where /// /// For more information on creating offers, see [`create_offer_builder`]. /// -/// Use [`pay_for_offer`] to initiated payment, which sends an [`InvoiceRequest`] for an [`Offer`] -/// and pays the [`Bolt12Invoice`] response. -/// -/// ``` -/// # use lightning::events::{Event, EventsProvider}; -/// # use lightning::ln::channelmanager::{AChannelManager, PaymentId, RecentPaymentDetails, Retry}; -/// # use lightning::offers::flow::OffersMessageCommons; -/// # use lightning::offers::offer::Offer; -/// # -/// # fn example( -/// # channel_manager: T, offer: &Offer, quantity: Option, amount_msats: Option, -/// # payer_note: Option, retry: Retry, max_total_routing_fee_msat: Option -/// # ) { -/// # let channel_manager = channel_manager.get_cm(); -/// let payment_id = PaymentId([42; 32]); -/// match channel_manager.pay_for_offer( -/// offer, quantity, amount_msats, payer_note, payment_id, retry, max_total_routing_fee_msat -/// ) { -/// Ok(()) => println!("Requesting invoice for offer"), -/// Err(e) => println!("Unable to request invoice for offer: {:?}", e), -/// } -/// -/// // First the payment will be waiting on an invoice -/// let expected_payment_id = payment_id; -/// assert!( -/// channel_manager.list_recent_payments().iter().find(|details| matches!( -/// details, -/// RecentPaymentDetails::AwaitingInvoice { payment_id: expected_payment_id } -/// )).is_some() -/// ); -/// -/// // Once the invoice is received, a payment will be sent -/// assert!( -/// channel_manager.list_recent_payments().iter().find(|details| matches!( -/// details, -/// RecentPaymentDetails::Pending { payment_id: expected_payment_id, .. } -/// )).is_some() -/// ); -/// -/// // On the event processing thread -/// channel_manager.process_pending_events(&|event| { -/// match event { -/// Event::PaymentSent { payment_id: Some(payment_id), .. } => println!("Paid {}", payment_id), -/// Event::PaymentFailed { payment_id, .. } => println!("Failed paying {}", payment_id), -/// // ... -/// # _ => {}, -/// } -/// Ok(()) -/// }); -/// # } -/// ``` +/// For details on initiating payments for offers, see [`pay_for_offer`]. /// /// ## BOLT 12 Refunds /// @@ -2217,7 +2167,7 @@ where /// [`claim_funds`]: Self::claim_funds /// [`send_payment`]: Self::send_payment /// [`offers`]: crate::offers -/// [`pay_for_offer`]: Self::pay_for_offer +/// [`Offer`]: crate::offers::offer /// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest /// [`request_refund_payment`]: Self::request_refund_payment /// [`peer_disconnected`]: msgs::ChannelMessageHandler::peer_disconnected @@ -2228,6 +2178,7 @@ where /// [`ChannelUpdate`]: msgs::ChannelUpdate /// [`read`]: ReadableArgs::read /// [`create_offer_builder`]: crate::offers::flow::OffersMessageFlow::create_offer_builder +/// [`pay_for_offer`]: crate::offers::flow::OffersMessageFlow::pay_for_offer /// [`create_refund_builder`]: crate::offers::flow::OffersMessageFlow::create_refund_builder // // Lock order: @@ -2739,6 +2690,8 @@ const MAX_NO_CHANNEL_PEERS: usize = 250; /// Using compact [`BlindedMessagePath`]s may provide better privacy as the [`MessageRouter`] could select /// more hops. However, since they use short channel ids instead of pubkeys, they are more likely to /// become invalid over time as channels are closed. Thus, they are only suitable for short-term use. +/// +/// [`Offer`]: crate::offers::offer pub const MAX_SHORT_LIVED_RELATIVE_EXPIRY: Duration = Duration::from_secs(60 * 60 * 24); /// Used by [`ChannelManager::list_recent_payments`] to express the status of recent payments. @@ -2747,8 +2700,10 @@ pub const MAX_SHORT_LIVED_RELATIVE_EXPIRY: Duration = Duration::from_secs(60 * 6 pub enum RecentPaymentDetails { /// When an invoice was requested and thus a payment has not yet been sent. AwaitingInvoice { - /// A user-provided identifier in [`ChannelManager::pay_for_offer`] used to uniquely identify a + /// A user-provided identifier in [`OffersMessageFlow::pay_for_offer`] used to uniquely identify a /// payment and ensure idempotency in LDK. + /// + /// [`OffersMessageFlow::pay_for_offer`]: crate::offers::flow::OffersMessageFlow::pay_for_offer payment_id: PaymentId, }, /// When a payment is still being sent and awaiting successful delivery. @@ -2757,7 +2712,7 @@ pub enum RecentPaymentDetails { /// identify a payment and ensure idempotency in LDK. /// /// [`send_payment`]: crate::ln::channelmanager::ChannelManager::send_payment - /// [`pay_for_offer`]: crate::ln::channelmanager::ChannelManager::pay_for_offer + /// [`pay_for_offer`]: crate::offers::flow::OffersMessageFlow::pay_for_offer payment_id: PaymentId, /// Hash of the payment that is currently being sent but has yet to be fulfilled or /// abandoned. @@ -2774,7 +2729,7 @@ pub enum RecentPaymentDetails { /// identify a payment and ensure idempotency in LDK. /// /// [`send_payment`]: crate::ln::channelmanager::ChannelManager::send_payment - /// [`pay_for_offer`]: crate::ln::channelmanager::ChannelManager::pay_for_offer + /// [`pay_for_offer`]: crate::offers::flow::OffersMessageFlow::pay_for_offer payment_id: PaymentId, /// Hash of the payment that was claimed. `None` for serializations of [`ChannelManager`] /// made before LDK version 0.0.104. @@ -2788,7 +2743,7 @@ pub enum RecentPaymentDetails { /// identify a payment and ensure idempotency in LDK. /// /// [`send_payment`]: crate::ln::channelmanager::ChannelManager::send_payment - /// [`pay_for_offer`]: crate::ln::channelmanager::ChannelManager::pay_for_offer + /// [`pay_for_offer`]: crate::offers::flow::OffersMessageFlow::pay_for_offer payment_id: PaymentId, /// Hash of the payment that we have given up trying to send. payment_hash: PaymentHash, @@ -4690,7 +4645,7 @@ where /// /// # Requested Invoices /// - /// In the case of paying a [`Bolt12Invoice`] via [`ChannelManager::pay_for_offer`], abandoning + /// In the case of paying a [`Bolt12Invoice`] via [`OffersMessageFlow::pay_for_offer`], abandoning /// the payment prior to receiving the invoice will result in an [`Event::PaymentFailed`] and /// prevent any attempts at paying it once received. /// @@ -4700,6 +4655,7 @@ where /// [`ChannelManager`], another [`Event::PaymentFailed`] may be generated. /// /// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice + /// [`OffersMessageFlow::pay_for_offer`]: crate::offers::flow::OffersMessageFlow::pay_for_offer pub fn abandon_payment(&self, payment_id: PaymentId) { self.abandon_payment_with_reason(payment_id, PaymentFailureReason::UserAbandoned) } @@ -9890,104 +9846,10 @@ where } fn add_new_awaiting_invoice(&self, payment_id: PaymentId, expiration: StaleExpiration, retry_strategy: Retry, max_total_routing_fee_msat: Option, retryable_invoice_request: Option) -> Result<(), ()> { - let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self); self.pending_outbound_payments.add_new_awaiting_invoice ( payment_id, expiration, retry_strategy, max_total_routing_fee_msat, retryable_invoice_request, ) } -} - -/// Defines the maximum number of [`OffersMessage`] including different reply paths to be sent -/// along different paths. -/// Sending multiple requests increases the chances of successful delivery in case some -/// paths are unavailable. However, only one invoice for a given [`PaymentId`] will be paid, -/// even if multiple invoices are received. -const OFFERS_MESSAGE_REQUEST_LIMIT: usize = 10; - -impl ChannelManager -where - M::Target: chain::Watch<::EcdsaSigner>, - T::Target: BroadcasterInterface, - ES::Target: EntropySource, - NS::Target: NodeSigner, - SP::Target: SignerProvider, - F::Target: FeeEstimator, - R::Target: Router, - MR::Target: MessageRouter, - L::Target: Logger, -{ - /// Pays for an [`Offer`] using the given parameters by creating an [`InvoiceRequest`] and - /// enqueuing it to be sent via an onion message. [`ChannelManager`] will pay the actual - /// [`Bolt12Invoice`] once it is received. - /// - /// Uses [`InvoiceRequestBuilder`] such that the [`InvoiceRequest`] it builds is recognized by - /// the [`ChannelManager`] when handling a [`Bolt12Invoice`] message in response to the request. - /// The optional parameters are used in the builder, if `Some`: - /// - `quantity` for [`InvoiceRequest::quantity`] which must be set if - /// [`Offer::expects_quantity`] is `true`. - /// - `amount_msats` if overpaying what is required for the given `quantity` is desired, and - /// - `payer_note` for [`InvoiceRequest::payer_note`]. - /// - /// If `max_total_routing_fee_msat` is not specified, The default from - /// [`RouteParameters::from_payment_params_and_value`] is applied. - /// - /// # Payment - /// - /// The provided `payment_id` is used to ensure that only one invoice is paid for the request - /// when received. See [Avoiding Duplicate Payments] for other requirements once the payment has - /// been sent. - /// - /// To revoke the request, use [`ChannelManager::abandon_payment`] prior to receiving the - /// invoice. If abandoned, or an invoice isn't received in a reasonable amount of time, the - /// payment will fail with an [`Event::PaymentFailed`]. - /// - /// # Privacy - /// - /// For payer privacy, uses a derived payer id and uses [`MessageRouter::create_blinded_paths`] - /// to construct a [`BlindedMessagePath`] for the reply path. For further privacy implications, see the - /// docs of the parameterized [`Router`], which implements [`MessageRouter`]. - /// - /// # Limitations - /// - /// Requires a direct connection to an introduction node in [`Offer::paths`] or to - /// [`Offer::issuer_signing_pubkey`], if empty. A similar restriction applies to the responding - /// [`Bolt12Invoice::payment_paths`]. - /// - /// # Errors - /// - /// Errors if: - /// - a duplicate `payment_id` is provided given the caveats in the aforementioned link, - /// - the provided parameters are invalid for the offer, - /// - the offer is for an unsupported chain, or - /// - the parameterized [`Router`] is unable to create a blinded reply path for the invoice - /// request. - /// - /// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest - /// [`InvoiceRequest::quantity`]: crate::offers::invoice_request::InvoiceRequest::quantity - /// [`InvoiceRequest::payer_note`]: crate::offers::invoice_request::InvoiceRequest::payer_note - /// [`InvoiceRequestBuilder`]: crate::offers::invoice_request::InvoiceRequestBuilder - /// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice - /// [`Bolt12Invoice::payment_paths`]: crate::offers::invoice::Bolt12Invoice::payment_paths - /// [Avoiding Duplicate Payments]: #avoiding-duplicate-payments - pub fn pay_for_offer( - &self, offer: &Offer, quantity: Option, amount_msats: Option, - payer_note: Option, payment_id: PaymentId, retry_strategy: Retry, - max_total_routing_fee_msat: Option - ) -> Result<(), Bolt12SemanticError> { - self.pay_for_offer_intern(offer, quantity, amount_msats, payer_note, payment_id, None, |invoice_request, nonce| { - let expiration = StaleExpiration::TimerTicks(1); - let retryable_invoice_request = RetryableInvoiceRequest { - invoice_request: invoice_request.clone(), - nonce, - }; - self.pending_outbound_payments - .add_new_awaiting_invoice( - payment_id, expiration, retry_strategy, max_total_routing_fee_msat, - Some(retryable_invoice_request) - ) - .map_err(|_| Bolt12SemanticError::DuplicatePaymentId) - }) - } fn pay_for_offer_intern Result<(), Bolt12SemanticError>>( &self, offer: &Offer, quantity: Option, amount_msats: Option, @@ -10035,7 +9897,27 @@ where self.enqueue_invoice_request(invoice_request, reply_paths) } +} + +/// Defines the maximum number of [`OffersMessage`] including different reply paths to be sent +/// along different paths. +/// Sending multiple requests increases the chances of successful delivery in case some +/// paths are unavailable. However, only one invoice for a given [`PaymentId`] will be paid, +/// even if multiple invoices are received. +const OFFERS_MESSAGE_REQUEST_LIMIT: usize = 10; +impl ChannelManager +where + M::Target: chain::Watch<::EcdsaSigner>, + T::Target: BroadcasterInterface, + ES::Target: EntropySource, + NS::Target: NodeSigner, + SP::Target: SignerProvider, + F::Target: FeeEstimator, + R::Target: Router, + MR::Target: MessageRouter, + L::Target: Logger, +{ /// Creates a [`Bolt12Invoice`] for a [`Refund`] and enqueues it to be sent via an onion /// message. /// @@ -12607,6 +12489,8 @@ where pub router: R, /// The [`MessageRouter`] used for constructing [`BlindedMessagePath`]s for [`Offer`]s, /// [`Refund`]s, and any reply paths. + /// + /// [`Offer`]: crate::offers::offer pub message_router: MR, /// The Logger for use in the ChannelManager and which may be used to log information during /// deserialization. diff --git a/lightning/src/ln/max_payment_path_len_tests.rs b/lightning/src/ln/max_payment_path_len_tests.rs index c38b439a0af..f718212985c 100644 --- a/lightning/src/ln/max_payment_path_len_tests.rs +++ b/lightning/src/ln/max_payment_path_len_tests.rs @@ -392,7 +392,7 @@ fn bolt12_invoice_too_large_blinded_paths() { let offer = nodes[1].offers_handler.create_offer_builder(None).unwrap().build().unwrap(); let payment_id = PaymentId([1; 32]); - nodes[0].node.pay_for_offer(&offer, None, Some(5000), None, payment_id, Retry::Attempts(0), None).unwrap(); + nodes[0].offers_handler.pay_for_offer(&offer, None, Some(5000), None, payment_id, Retry::Attempts(0), None).unwrap(); let invreq_om = nodes[0].onion_messenger.next_onion_message_for_peer(nodes[1].node.get_our_node_id()).unwrap(); nodes[1].onion_messenger.handle_onion_message(nodes[0].node.get_our_node_id(), &invreq_om); diff --git a/lightning/src/ln/offers_tests.rs b/lightning/src/ln/offers_tests.rs index 5c62f308d9e..67f24036a2b 100644 --- a/lightning/src/ln/offers_tests.rs +++ b/lightning/src/ln/offers_tests.rs @@ -541,7 +541,7 @@ fn creates_and_pays_for_offer_using_two_hop_blinded_path() { } let payment_id = PaymentId([1; 32]); - david.node.pay_for_offer(&offer, None, None, None, payment_id, Retry::Attempts(0), None) + david.offers_handler.pay_for_offer(&offer, None, None, None, payment_id, Retry::Attempts(0), None) .unwrap(); expect_recent_payment!(david, RecentPaymentDetails::AwaitingInvoice, payment_id); @@ -710,7 +710,7 @@ fn creates_and_pays_for_offer_using_one_hop_blinded_path() { } let payment_id = PaymentId([1; 32]); - bob.node.pay_for_offer(&offer, None, None, None, payment_id, Retry::Attempts(0), None).unwrap(); + bob.offers_handler.pay_for_offer(&offer, None, None, None, payment_id, Retry::Attempts(0), None).unwrap(); expect_recent_payment!(bob, RecentPaymentDetails::AwaitingInvoice, payment_id); let onion_message = bob.onion_messenger.next_onion_message_for_peer(alice_id).unwrap(); @@ -831,7 +831,7 @@ fn pays_for_offer_without_blinded_paths() { assert!(offer.paths().is_empty()); let payment_id = PaymentId([1; 32]); - bob.node.pay_for_offer(&offer, None, None, None, payment_id, Retry::Attempts(0), None).unwrap(); + bob.offers_handler.pay_for_offer(&offer, None, None, None, payment_id, Retry::Attempts(0), None).unwrap(); expect_recent_payment!(bob, RecentPaymentDetails::AwaitingInvoice, payment_id); let onion_message = bob.onion_messenger.next_onion_message_for_peer(alice_id).unwrap(); @@ -958,7 +958,7 @@ fn send_invoice_requests_with_distinct_reply_path() { } let payment_id = PaymentId([1; 32]); - david.node.pay_for_offer(&offer, None, None, None, payment_id, Retry::Attempts(0), None) + david.offers_handler.pay_for_offer(&offer, None, None, None, payment_id, Retry::Attempts(0), None) .unwrap(); expect_recent_payment!(david, RecentPaymentDetails::AwaitingInvoice, payment_id); connect_peers(david, bob); @@ -1092,7 +1092,7 @@ fn creates_and_pays_for_offer_with_retry() { assert_eq!(path.introduction_node(), &IntroductionNode::NodeId(alice_id)); } let payment_id = PaymentId([1; 32]); - bob.node.pay_for_offer(&offer, None, None, None, payment_id, Retry::Attempts(0), None).unwrap(); + bob.offers_handler.pay_for_offer(&offer, None, None, None, payment_id, Retry::Attempts(0), None).unwrap(); expect_recent_payment!(bob, RecentPaymentDetails::AwaitingInvoice, payment_id); let _lost_onion_message = bob.onion_messenger.next_onion_message_for_peer(alice_id).unwrap(); @@ -1178,7 +1178,7 @@ fn creates_offer_with_blinded_path_using_unannounced_introduction_node() { } let payment_id = PaymentId([1; 32]); - bob.node.pay_for_offer(&offer, None, None, None, payment_id, Retry::Attempts(0), None).unwrap(); + bob.offers_handler.pay_for_offer(&offer, None, None, None, payment_id, Retry::Attempts(0), None).unwrap(); expect_recent_payment!(bob, RecentPaymentDetails::AwaitingInvoice, payment_id); let onion_message = bob.onion_messenger.next_onion_message_for_peer(alice_id).unwrap(); @@ -1319,7 +1319,7 @@ fn fails_authentication_when_handling_invoice_request() { // Send the invoice request directly to Alice instead of using a blinded path. let payment_id = PaymentId([1; 32]); - david.node.pay_for_offer(&offer, None, None, None, payment_id, Retry::Attempts(0), None) + david.offers_handler.pay_for_offer(&offer, None, None, None, payment_id, Retry::Attempts(0), None) .unwrap(); expect_recent_payment!(david, RecentPaymentDetails::AwaitingInvoice, payment_id); @@ -1345,7 +1345,7 @@ fn fails_authentication_when_handling_invoice_request() { // Send the invoice request to Alice using an invalid blinded path. let payment_id = PaymentId([2; 32]); - david.node.pay_for_offer(&offer, None, None, None, payment_id, Retry::Attempts(0), None) + david.offers_handler.pay_for_offer(&offer, None, None, None, payment_id, Retry::Attempts(0), None) .unwrap(); expect_recent_payment!(david, RecentPaymentDetails::AwaitingInvoice, payment_id); @@ -1422,7 +1422,7 @@ fn fails_authentication_when_handling_invoice_for_offer() { // Initiate an invoice request, but abandon tracking it. let payment_id = PaymentId([1; 32]); - david.node.pay_for_offer(&offer, None, None, None, payment_id, Retry::Attempts(0), None) + david.offers_handler.pay_for_offer(&offer, None, None, None, payment_id, Retry::Attempts(0), None) .unwrap(); david.node.abandon_payment(payment_id); get_event!(david, Event::PaymentFailed); @@ -1439,7 +1439,7 @@ fn fails_authentication_when_handling_invoice_for_offer() { }; let payment_id = PaymentId([2; 32]); - david.node.pay_for_offer(&offer, None, None, None, payment_id, Retry::Attempts(0), None) + david.offers_handler.pay_for_offer(&offer, None, None, None, payment_id, Retry::Attempts(0), None) .unwrap(); expect_recent_payment!(david, RecentPaymentDetails::AwaitingInvoice, payment_id); @@ -1623,7 +1623,7 @@ fn fails_creating_or_paying_for_offer_without_connected_peers() { let payment_id = PaymentId([1; 32]); - match david.node.pay_for_offer(&offer, None, None, None, payment_id, Retry::Attempts(0), None) { + match david.offers_handler.pay_for_offer(&offer, None, None, None, payment_id, Retry::Attempts(0), None) { Ok(_) => panic!("Expected error"), Err(e) => assert_eq!(e, Bolt12SemanticError::MissingPaths), } @@ -1635,7 +1635,7 @@ fn fails_creating_or_paying_for_offer_without_connected_peers() { reconnect_nodes(args); assert!( - david.node.pay_for_offer( + david.offers_handler.pay_for_offer( &offer, None, None, None, payment_id, Retry::Attempts(0), None ).is_ok() ); @@ -1727,7 +1727,7 @@ fn fails_creating_invoice_request_for_unsupported_chain() { .build().unwrap(); let payment_id = PaymentId([1; 32]); - match bob.node.pay_for_offer(&offer, None, None, None, payment_id, Retry::Attempts(0), None) { + match bob.offers_handler.pay_for_offer(&offer, None, None, None, payment_id, Retry::Attempts(0), None) { Ok(_) => panic!("Expected error"), Err(e) => assert_eq!(e, Bolt12SemanticError::UnsupportedChain), } @@ -1786,7 +1786,7 @@ fn fails_creating_invoice_request_without_blinded_reply_path() { let payment_id = PaymentId([1; 32]); - match david.node.pay_for_offer(&offer, None, None, None, payment_id, Retry::Attempts(0), None) { + match david.offers_handler.pay_for_offer(&offer, None, None, None, payment_id, Retry::Attempts(0), None) { Ok(_) => panic!("Expected error"), Err(e) => assert_eq!(e, Bolt12SemanticError::MissingPaths), } @@ -1820,13 +1820,13 @@ fn fails_creating_invoice_request_with_duplicate_payment_id() { let payment_id = PaymentId([1; 32]); assert!( - david.node.pay_for_offer( + david.offers_handler.pay_for_offer( &offer, None, None, None, payment_id, Retry::Attempts(0), None ).is_ok() ); expect_recent_payment!(david, RecentPaymentDetails::AwaitingInvoice, payment_id); - match david.node.pay_for_offer(&offer, None, None, None, payment_id, Retry::Attempts(0), None) { + match david.offers_handler.pay_for_offer(&offer, None, None, None, payment_id, Retry::Attempts(0), None) { Ok(_) => panic!("Expected error"), Err(e) => assert_eq!(e, Bolt12SemanticError::DuplicatePaymentId), } @@ -1905,7 +1905,7 @@ fn fails_sending_invoice_without_blinded_payment_paths_for_offer() { .build().unwrap(); let payment_id = PaymentId([1; 32]); - david.node.pay_for_offer(&offer, None, None, None, payment_id, Retry::Attempts(0), None) + david.offers_handler.pay_for_offer(&offer, None, None, None, payment_id, Retry::Attempts(0), None) .unwrap(); connect_peers(david, bob); @@ -2114,7 +2114,7 @@ fn fails_paying_invoice_with_unknown_required_features() { .build().unwrap(); let payment_id = PaymentId([1; 32]); - david.node.pay_for_offer(&offer, None, None, None, payment_id, Retry::Attempts(0), None) + david.offers_handler.pay_for_offer(&offer, None, None, None, payment_id, Retry::Attempts(0), None) .unwrap(); connect_peers(david, bob); @@ -2214,7 +2214,7 @@ fn no_double_pay_with_stale_channelmanager() { assert!(offer.paths().is_empty()); let payment_id = PaymentId([1; 32]); - nodes[0].node.pay_for_offer(&offer, None, None, None, payment_id, Retry::Attempts(0), None).unwrap(); + nodes[0].offers_handler.pay_for_offer(&offer, None, None, None, payment_id, Retry::Attempts(0), None).unwrap(); expect_recent_payment!(nodes[0], RecentPaymentDetails::AwaitingInvoice, payment_id); let invreq_om = nodes[0].onion_messenger.next_onion_message_for_peer(bob_id).unwrap(); diff --git a/lightning/src/offers/flow.rs b/lightning/src/offers/flow.rs index 4d81e5c7485..b66b507d560 100644 --- a/lightning/src/offers/flow.rs +++ b/lightning/src/offers/flow.rs @@ -31,13 +31,14 @@ use crate::offers::invoice::{ }; use crate::offers::invoice_request::InvoiceRequest; use crate::offers::parse::Bolt12SemanticError; +use crate::onion_message::dns_resolution::HumanReadableName; use crate::onion_message::messenger::{MessageSendInstructions, Responder, ResponseInstruction}; use crate::onion_message::offers::{OffersMessage, OffersMessageHandler}; use crate::sync::MutexGuard; use crate::offers::invoice_error::InvoiceError; use crate::offers::nonce::Nonce; -use crate::offers::offer::OfferBuilder; +use crate::offers::offer::{Offer, OfferBuilder}; use crate::offers::refund::RefundBuilder; use crate::sign::EntropySource; @@ -185,6 +186,15 @@ pub trait OffersMessageCommons { max_total_routing_fee_msat: Option, retryable_invoice_request: Option, ) -> Result<(), ()>; + + /// Internal pay_for_offer + fn pay_for_offer_intern< + CPP: FnOnce(&InvoiceRequest, Nonce) -> Result<(), Bolt12SemanticError>, + >( + &self, offer: &Offer, quantity: Option, amount_msats: Option, + payer_note: Option, payment_id: PaymentId, + human_readable_name: Option, create_pending_payment: CPP, + ) -> Result<(), Bolt12SemanticError>; } /// A trivial trait which describes any [`OffersMessageFlow`]. @@ -403,15 +413,69 @@ where /// # } /// ``` /// +/// Use [`pay_for_offer`] to initiated payment, which sends an [`InvoiceRequest`] for an [`Offer`] +/// and pays the [`Bolt12Invoice`] response. +/// +/// ``` +/// # use lightning::events::{Event, EventsProvider}; +/// # use lightning::ln::channelmanager::{AChannelManager, PaymentId, RecentPaymentDetails, Retry}; +/// # use lightning::offers::flow::{AnOffersMessageFlow, OffersMessageCommons}; +/// # use lightning::offers::offer::Offer; +/// # +/// # fn example( +/// # offers_flow: T, channel_manager: U, offer: &Offer, quantity: Option, amount_msats: Option, +/// # payer_note: Option, retry: Retry, max_total_routing_fee_msat: Option +/// # ) { +/// # let offers_flow = offers_flow.get_omf(); +/// # let channel_manager = channel_manager.get_cm(); +/// let payment_id = PaymentId([42; 32]); +/// match offers_flow.pay_for_offer( +/// offer, quantity, amount_msats, payer_note, payment_id, retry, max_total_routing_fee_msat +/// ) { +/// Ok(()) => println!("Requesting invoice for offer"), +/// Err(e) => println!("Unable to request invoice for offer: {:?}", e), +/// } +/// +/// // First the payment will be waiting on an invoice +/// let expected_payment_id = payment_id; +/// assert!( +/// channel_manager.list_recent_payments().iter().find(|details| matches!( +/// details, +/// RecentPaymentDetails::AwaitingInvoice { payment_id: expected_payment_id } +/// )).is_some() +/// ); +/// +/// // Once the invoice is received, a payment will be sent +/// assert!( +/// channel_manager.list_recent_payments().iter().find(|details| matches!( +/// details, +/// RecentPaymentDetails::Pending { payment_id: expected_payment_id, .. } +/// )).is_some() +/// ); +/// +/// // On the event processing thread +/// channel_manager.process_pending_events(&|event| { +/// match event { +/// Event::PaymentSent { payment_id: Some(payment_id), .. } => println!("Paid {}", payment_id), +/// Event::PaymentFailed { payment_id, .. } => println!("Failed paying {}", payment_id), +/// // ... +/// # _ => {}, +/// } +/// Ok(()) +/// }); +/// # } +/// ``` +/// /// [`DNSResolverMessage`]: crate::onion_message::dns_resolution::DNSResolverMessage /// [`ChannelManager`]: crate::ln::channelmanager::ChannelManager /// [`Bolt12Invoice`]: crate::offers::invoice /// [`create_offer_builder`]: Self::create_offer_builder /// [`create_refund_builder`]: Self::create_refund_builder /// [`Refund`]: crate::offers::refund::Refund +/// [`InvoiceRequest`]: crate::offers::invoice_request /// [`Offer`]: crate::offers::offer /// [`offers`]: crate::offers -/// +/// [`pay_for_offer`]: Self::pay_for_offer pub struct OffersMessageFlow where ES::Target: EntropySource, @@ -909,4 +973,93 @@ where #[cfg(c_bindings)] create_refund_builder!(self, RefundMaybeWithDerivedMetadataBuilder); + + /// Pays for an [`Offer`] using the given parameters by creating an [`InvoiceRequest`] and + /// enqueuing it to be sent via an onion message. [`OffersMessageFlow`] will pay the actual + /// [`Bolt12Invoice`] once it is received. + /// + /// Uses [`InvoiceRequestBuilder`] such that the [`InvoiceRequest`] it builds is recognized by + /// the [`OffersMessageFlow`] when handling a [`Bolt12Invoice`] message in response to the request. + /// The optional parameters are used in the builder, if `Some`: + /// - `quantity` for [`InvoiceRequest::quantity`] which must be set if + /// [`Offer::expects_quantity`] is `true`. + /// - `amount_msats` if overpaying what is required for the given `quantity` is desired, and + /// - `payer_note` for [`InvoiceRequest::payer_note`]. + /// + /// If `max_total_routing_fee_msat` is not specified, The default from + /// [`RouteParameters::from_payment_params_and_value`] is applied. + /// + /// # Payment + /// + /// The provided `payment_id` is used to ensure that only one invoice is paid for the request + /// when received. See [Avoiding Duplicate Payments] for other requirements once the payment has + /// been sent. + /// + /// To revoke the request, use [`ChannelManager::abandon_payment`] prior to receiving the + /// invoice. If abandoned, or an invoice isn't received in a reasonable amount of time, the + /// payment will fail with an [`Event::PaymentFailed`]. + /// + /// # Privacy + /// + /// For payer privacy, uses a derived payer id and uses [`MessageRouter::create_blinded_paths`] + /// to construct a [`BlindedMessagePath`] for the reply path. For further privacy implications, see the + /// docs of the parameterized [`Router`], which implements [`MessageRouter`]. + /// + /// # Limitations + /// + /// Requires a direct connection to an introduction node in [`Offer::paths`] or to + /// [`Offer::issuer_signing_pubkey`], if empty. A similar restriction applies to the responding + /// [`Bolt12Invoice::payment_paths`]. + /// + /// # Errors + /// + /// Errors if: + /// - a duplicate `payment_id` is provided given the caveats in the aforementioned link, + /// - the provided parameters are invalid for the offer, + /// - the offer is for an unsupported chain, or + /// - the parameterized [`Router`] is unable to create a blinded reply path for the invoice + /// request. + /// + /// [`BlindedMessagePath`]: crate::blinded_path::message::BlindedMessagePath + /// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice + /// [`Bolt12Invoice::payment_paths`]: crate::offers::invoice::Bolt12Invoice::payment_paths + /// [`ChannelManager::abandon_payment`]: crate::ln::channelmanager::ChannelManager::abandon_payment + /// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest + /// [`InvoiceRequest::quantity`]: crate::offers::invoice_request::InvoiceRequest::quantity + /// [`InvoiceRequest::payer_note`]: crate::offers::invoice_request::InvoiceRequest::payer_note + /// [`InvoiceRequestBuilder`]: crate::offers::invoice_request::InvoiceRequestBuilder + /// [`MessageRouter`]: crate::onion_message::messenger::MessageRouter + /// [`MessageRouter::create_blinded_paths`]: crate::onion_message::messenger::MessageRouter::create_blinded_paths + /// [`RouteParameters::from_payment_params_and_value`]: crate::routing::router::RouteParameters::from_payment_params_and_value + /// [`Router`]: crate::routing::router::Router + /// [`Event::PaymentFailed`]: crate::events::Event::PaymentFailed + /// [Avoiding Duplicate Payments]: #avoiding-duplicate-payments + pub fn pay_for_offer( + &self, offer: &Offer, quantity: Option, amount_msats: Option, + payer_note: Option, payment_id: PaymentId, retry_strategy: Retry, + max_total_routing_fee_msat: Option, + ) -> Result<(), Bolt12SemanticError> { + self.commons.pay_for_offer_intern( + offer, + quantity, + amount_msats, + payer_note, + payment_id, + None, + |invoice_request, nonce| { + let expiration = StaleExpiration::TimerTicks(1); + let retryable_invoice_request = + RetryableInvoiceRequest { invoice_request: invoice_request.clone(), nonce }; + self.commons + .add_new_awaiting_invoice( + payment_id, + expiration, + retry_strategy, + max_total_routing_fee_msat, + Some(retryable_invoice_request), + ) + .map_err(|_| Bolt12SemanticError::DuplicatePaymentId) + }, + ) + } } From abf34ecd87dddbc3ddeb1de674f7bc1baa318b49 Mon Sep 17 00:00:00 2001 From: shaavan Date: Sat, 16 Nov 2024 18:18:16 +0530 Subject: [PATCH 12/30] Move request_refund_payment to OffersMessageFlow --- lightning/src/ln/channelmanager.rs | 161 ++----------------------- lightning/src/ln/offers_tests.rs | 26 ++-- lightning/src/offers/flow.rs | 183 ++++++++++++++++++++++++++++- 3 files changed, 201 insertions(+), 169 deletions(-) diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index c894eff61ab..3724f250c4a 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -36,7 +36,7 @@ use crate::events::FundingInfo; use crate::blinded_path::message::{AsyncPaymentsContext, MessageContext, OffersContext}; use crate::blinded_path::NodeIdLookUp; use crate::blinded_path::message::{BlindedMessagePath, MessageForwardNode}; -use crate::blinded_path::payment::{BlindedPaymentPath, Bolt12RefundContext, PaymentConstraints, PaymentContext, UnauthenticatedReceiveTlvs}; +use crate::blinded_path::payment::{BlindedPaymentPath, PaymentConstraints, PaymentContext, UnauthenticatedReceiveTlvs}; use crate::chain; use crate::chain::{Confirm, ChannelMonitorUpdateStatus, Watch, BestBlock}; use crate::chain::chaininterface::{BroadcasterInterface, ConfirmationTarget, FeeEstimator, LowerBoundedFeeEstimator}; @@ -66,11 +66,11 @@ use crate::ln::msgs::{ChannelMessageHandler, CommitmentUpdate, DecodeError, Ligh #[cfg(test)] use crate::ln::outbound_payment; use crate::ln::outbound_payment::{OutboundPayments, PendingOutboundPayment, RetryableInvoiceRequest, SendAlongPathArgs, StaleExpiration}; -use crate::offers::invoice::{Bolt12Invoice, DerivedSigningPubkey, InvoiceBuilder, UnsignedBolt12Invoice, DEFAULT_RELATIVE_EXPIRY}; +use crate::offers::invoice::Bolt12Invoice; +use crate::offers::invoice::UnsignedBolt12Invoice; use crate::offers::invoice_request::{InvoiceRequest, InvoiceRequestBuilder}; use crate::offers::nonce::Nonce; use crate::offers::parse::Bolt12SemanticError; -use crate::offers::refund::Refund; use crate::offers::signer; #[cfg(async_payments)] use crate::offers::static_invoice::StaticInvoice; @@ -2042,53 +2042,8 @@ where /// /// For more information on creating refunds, see [`create_refund_builder`]. /// -/// Use [`request_refund_payment`] to send a [`Bolt12Invoice`] for receiving the refund. Similar to -/// *creating* an [`Offer`], this is stateless as it represents an inbound payment. -/// -/// ``` -/// # use lightning::events::{Event, EventsProvider, PaymentPurpose}; -/// # use lightning::ln::channelmanager::AChannelManager; -/// # use lightning::offers::flow::OffersMessageCommons; -/// # use lightning::offers::refund::Refund; -/// # -/// # fn example(channel_manager: T, refund: &Refund) { -/// # let channel_manager = channel_manager.get_cm(); -/// let known_payment_hash = match channel_manager.request_refund_payment(refund) { -/// Ok(invoice) => { -/// let payment_hash = invoice.payment_hash(); -/// println!("Requesting refund payment {}", payment_hash); -/// payment_hash -/// }, -/// Err(e) => panic!("Unable to request payment for refund: {:?}", e), -/// }; -/// -/// // On the event processing thread -/// channel_manager.process_pending_events(&|event| { -/// match event { -/// Event::PaymentClaimable { payment_hash, purpose, .. } => match purpose { -/// PaymentPurpose::Bolt12RefundPayment { payment_preimage: Some(payment_preimage), .. } => { -/// assert_eq!(payment_hash, known_payment_hash); -/// println!("Claiming payment {}", payment_hash); -/// channel_manager.claim_funds(payment_preimage); -/// }, -/// PaymentPurpose::Bolt12RefundPayment { payment_preimage: None, .. } => { -/// println!("Unknown payment hash: {}", payment_hash); -/// }, -/// // ... -/// # _ => {}, -/// }, -/// Event::PaymentClaimed { payment_hash, amount_msat, .. } => { -/// assert_eq!(payment_hash, known_payment_hash); -/// println!("Claimed {} msats", amount_msat); -/// }, -/// // ... -/// # _ => {}, -/// } -/// Ok(()) -/// }); -/// # } -/// ``` -/// +/// For requesting refund payments, see [`request_refund_payment`]. +/// /// # Persistence /// /// Implements [`Writeable`] to write out all channel state to disk. Implies [`peer_disconnected`] for @@ -2169,7 +2124,7 @@ where /// [`offers`]: crate::offers /// [`Offer`]: crate::offers::offer /// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest -/// [`request_refund_payment`]: Self::request_refund_payment +/// [`request_refund_payment`]: crate::offers::flow::OffersMessageFlow::request_refund_payment /// [`peer_disconnected`]: msgs::ChannelMessageHandler::peer_disconnected /// [`funding_created`]: msgs::FundingCreated /// [`funding_transaction_generated`]: Self::funding_transaction_generated @@ -2692,6 +2647,7 @@ const MAX_NO_CHANNEL_PEERS: usize = 250; /// become invalid over time as channels are closed. Thus, they are only suitable for short-term use. /// /// [`Offer`]: crate::offers::offer +/// [`Refund`]: crate::offers::refund pub const MAX_SHORT_LIVED_RELATIVE_EXPIRY: Duration = Duration::from_secs(60 * 60 * 24); /// Used by [`ChannelManager::list_recent_payments`] to express the status of recent payments. @@ -9904,7 +9860,7 @@ where /// Sending multiple requests increases the chances of successful delivery in case some /// paths are unavailable. However, only one invoice for a given [`PaymentId`] will be paid, /// even if multiple invoices are received. -const OFFERS_MESSAGE_REQUEST_LIMIT: usize = 10; +pub const OFFERS_MESSAGE_REQUEST_LIMIT: usize = 10; impl ChannelManager where @@ -9918,106 +9874,6 @@ where MR::Target: MessageRouter, L::Target: Logger, { - /// Creates a [`Bolt12Invoice`] for a [`Refund`] and enqueues it to be sent via an onion - /// message. - /// - /// The resulting invoice uses a [`PaymentHash`] recognized by the [`ChannelManager`] and a - /// [`BlindedPaymentPath`] containing the [`PaymentSecret`] needed to reconstruct the - /// corresponding [`PaymentPreimage`]. It is returned purely for informational purposes. - /// - /// # Limitations - /// - /// Requires a direct connection to an introduction node in [`Refund::paths`] or to - /// [`Refund::payer_signing_pubkey`], if empty. This request is best effort; an invoice will be - /// sent to each node meeting the aforementioned criteria, but there's no guarantee that they - /// will be received and no retries will be made. - /// - /// # Errors - /// - /// Errors if: - /// - the refund is for an unsupported chain, or - /// - the parameterized [`Router`] is unable to create a blinded payment path or reply path for - /// the invoice. - /// - /// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice - pub fn request_refund_payment( - &self, refund: &Refund - ) -> Result { - let expanded_key = &self.inbound_payment_key; - let entropy = &*self.entropy_source; - let secp_ctx = &self.secp_ctx; - - let amount_msats = refund.amount_msats(); - let relative_expiry = DEFAULT_RELATIVE_EXPIRY.as_secs() as u32; - - if refund.chain() != self.chain_hash { - return Err(Bolt12SemanticError::UnsupportedChain); - } - - let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self); - - match self.create_inbound_payment(Some(amount_msats), relative_expiry, None) { - Ok((payment_hash, payment_secret)) => { - let payment_context = PaymentContext::Bolt12Refund(Bolt12RefundContext {}); - let payment_paths = self.create_blinded_payment_paths( - amount_msats, payment_secret, payment_context - ) - .map_err(|_| Bolt12SemanticError::MissingPaths)?; - - #[cfg(feature = "std")] - let builder = refund.respond_using_derived_keys( - payment_paths, payment_hash, expanded_key, entropy - )?; - #[cfg(not(feature = "std"))] - let created_at = Duration::from_secs( - self.highest_seen_timestamp.load(Ordering::Acquire) as u64 - ); - #[cfg(not(feature = "std"))] - let builder = refund.respond_using_derived_keys_no_std( - payment_paths, payment_hash, created_at, expanded_key, entropy - )?; - let builder: InvoiceBuilder = builder.into(); - let invoice = builder.allow_mpp().build_and_sign(secp_ctx)?; - - let nonce = Nonce::from_entropy_source(entropy); - let hmac = payment_hash.hmac_for_offer_payment(nonce, expanded_key); - let context = MessageContext::Offers(OffersContext::InboundPayment { - payment_hash: invoice.payment_hash(), nonce, hmac - }); - let reply_paths = self.create_blinded_paths(context) - .map_err(|_| Bolt12SemanticError::MissingPaths)?; - - let mut pending_offers_messages = self.pending_offers_messages.lock().unwrap(); - if refund.paths().is_empty() { - for reply_path in reply_paths { - let instructions = MessageSendInstructions::WithSpecifiedReplyPath { - destination: Destination::Node(refund.payer_signing_pubkey()), - reply_path, - }; - let message = OffersMessage::Invoice(invoice.clone()); - pending_offers_messages.push((message, instructions)); - } - } else { - reply_paths - .iter() - .flat_map(|reply_path| refund.paths().iter().map(move |path| (path, reply_path))) - .take(OFFERS_MESSAGE_REQUEST_LIMIT) - .for_each(|(path, reply_path)| { - let instructions = MessageSendInstructions::WithSpecifiedReplyPath { - destination: Destination::BlindedPath(path.clone()), - reply_path: reply_path.clone(), - }; - let message = OffersMessage::Invoice(invoice.clone()); - pending_offers_messages.push((message, instructions)); - }); - } - - Ok(invoice) - }, - Err(()) => Err(Bolt12SemanticError::InvalidAmount), - } - } - /// Pays for an [`Offer`] looked up using [BIP 353] Human Readable Names resolved by the DNS /// resolver(s) at `dns_resolvers` which resolve names according to bLIP 32. /// @@ -12491,6 +12347,7 @@ where /// [`Refund`]s, and any reply paths. /// /// [`Offer`]: crate::offers::offer + /// [`Refund`]: crate::offers::refund pub message_router: MR, /// The Logger for use in the ChannelManager and which may be used to log information during /// deserialization. diff --git a/lightning/src/ln/offers_tests.rs b/lightning/src/ln/offers_tests.rs index 67f24036a2b..2395e8e01d1 100644 --- a/lightning/src/ln/offers_tests.rs +++ b/lightning/src/ln/offers_tests.rs @@ -653,7 +653,7 @@ fn creates_and_pays_for_refund_using_two_hop_blinded_path() { expect_recent_payment!(david, RecentPaymentDetails::AwaitingInvoice, payment_id); let payment_context = PaymentContext::Bolt12Refund(Bolt12RefundContext {}); - let expected_invoice = alice.node.request_refund_payment(&refund).unwrap(); + let expected_invoice = alice.offers_handler.request_refund_payment(&refund).unwrap(); connect_peers(alice, charlie); @@ -782,7 +782,7 @@ fn creates_and_pays_for_refund_using_one_hop_blinded_path() { expect_recent_payment!(bob, RecentPaymentDetails::AwaitingInvoice, payment_id); let payment_context = PaymentContext::Bolt12Refund(Bolt12RefundContext {}); - let expected_invoice = alice.node.request_refund_payment(&refund).unwrap(); + let expected_invoice = alice.offers_handler.request_refund_payment(&refund).unwrap(); let onion_message = alice.onion_messenger.next_onion_message_for_peer(bob_id).unwrap(); bob.onion_messenger.handle_onion_message(alice_id, &onion_message); @@ -887,7 +887,7 @@ fn pays_for_refund_without_blinded_paths() { expect_recent_payment!(bob, RecentPaymentDetails::AwaitingInvoice, payment_id); let payment_context = PaymentContext::Bolt12Refund(Bolt12RefundContext {}); - let expected_invoice = alice.node.request_refund_payment(&refund).unwrap(); + let expected_invoice = alice.offers_handler.request_refund_payment(&refund).unwrap(); let onion_message = alice.onion_messenger.next_onion_message_for_peer(bob_id).unwrap(); bob.onion_messenger.handle_onion_message(alice_id, &onion_message); @@ -1042,7 +1042,7 @@ fn send_invoice_for_refund_with_distinct_reply_path() { } expect_recent_payment!(alice, RecentPaymentDetails::AwaitingInvoice, payment_id); - let _expected_invoice = david.node.request_refund_payment(&refund).unwrap(); + let _expected_invoice = david.offers_handler.request_refund_payment(&refund).unwrap(); connect_peers(david, bob); @@ -1246,7 +1246,7 @@ fn creates_refund_with_blinded_path_using_unannounced_introduction_node() { } expect_recent_payment!(bob, RecentPaymentDetails::AwaitingInvoice, payment_id); - let expected_invoice = alice.node.request_refund_payment(&refund).unwrap(); + let expected_invoice = alice.offers_handler.request_refund_payment(&refund).unwrap(); let onion_message = alice.onion_messenger.next_onion_message_for_peer(bob_id).unwrap(); @@ -1530,7 +1530,7 @@ fn fails_authentication_when_handling_invoice_for_refund() { expect_recent_payment!(david, RecentPaymentDetails::AwaitingInvoice, payment_id); // Send the invoice directly to David instead of using a blinded path. - let expected_invoice = alice.node.request_refund_payment(&refund).unwrap(); + let expected_invoice = alice.offers_handler.request_refund_payment(&refund).unwrap(); connect_peers(david, alice); match &mut alice.node.pending_offers_messages.lock().unwrap().first_mut().unwrap().1 { @@ -1562,7 +1562,7 @@ fn fails_authentication_when_handling_invoice_for_refund() { assert_eq!(path.introduction_node(), &IntroductionNode::NodeId(charlie_id)); } - let expected_invoice = alice.node.request_refund_payment(&refund).unwrap(); + let expected_invoice = alice.offers_handler.request_refund_payment(&refund).unwrap(); match &mut alice.node.pending_offers_messages.lock().unwrap().first_mut().unwrap().1 { MessageSendInstructions::WithSpecifiedReplyPath { destination, .. } => @@ -1695,7 +1695,7 @@ fn fails_creating_refund_or_sending_invoice_without_connected_peers() { .unwrap() .build().unwrap(); - match alice.node.request_refund_payment(&refund) { + match alice.offers_handler.request_refund_payment(&refund) { Ok(_) => panic!("Expected error"), Err(e) => assert_eq!(e, Bolt12SemanticError::MissingPaths), } @@ -1704,7 +1704,7 @@ fn fails_creating_refund_or_sending_invoice_without_connected_peers() { args.send_channel_ready = (true, true); reconnect_nodes(args); - assert!(alice.node.request_refund_payment(&refund).is_ok()); + assert!(alice.offers_handler.request_refund_payment(&refund).is_ok()); } /// Fails creating an invoice request when the offer contains an unsupported chain. @@ -1754,7 +1754,7 @@ fn fails_sending_invoice_with_unsupported_chain_for_refund() { .chain(Network::Signet) .build().unwrap(); - match alice.node.request_refund_payment(&refund) { + match alice.offers_handler.request_refund_payment(&refund) { Ok(_) => panic!("Expected error"), Err(e) => assert_eq!(e, Bolt12SemanticError::UnsupportedChain), } @@ -1977,7 +1977,7 @@ fn fails_sending_invoice_without_blinded_payment_paths_for_refund() { .unwrap() .build().unwrap(); - match alice.node.request_refund_payment(&refund) { + match alice.offers_handler.request_refund_payment(&refund) { Ok(_) => panic!("Expected error"), Err(e) => assert_eq!(e, Bolt12SemanticError::MissingPaths), } @@ -2028,7 +2028,7 @@ fn fails_paying_invoice_more_than_once() { expect_recent_payment!(david, RecentPaymentDetails::AwaitingInvoice, payment_id); // Alice sends the first invoice - alice.node.request_refund_payment(&refund).unwrap(); + alice.offers_handler.request_refund_payment(&refund).unwrap(); connect_peers(alice, charlie); @@ -2048,7 +2048,7 @@ fn fails_paying_invoice_more_than_once() { disconnect_peers(alice, &[charlie]); // Alice sends the second invoice - alice.node.request_refund_payment(&refund).unwrap(); + alice.offers_handler.request_refund_payment(&refund).unwrap(); connect_peers(alice, charlie); connect_peers(david, bob); diff --git a/lightning/src/offers/flow.rs b/lightning/src/offers/flow.rs index b66b507d560..33294c068cb 100644 --- a/lightning/src/offers/flow.rs +++ b/lightning/src/offers/flow.rs @@ -20,9 +20,13 @@ use lightning_invoice::PaymentSecret; use types::payment::PaymentHash; use crate::blinded_path::message::{BlindedMessagePath, MessageContext, OffersContext}; -use crate::blinded_path::payment::{BlindedPaymentPath, Bolt12OfferContext, PaymentContext}; +use crate::blinded_path::payment::{ + BlindedPaymentPath, Bolt12OfferContext, Bolt12RefundContext, PaymentContext, +}; use crate::events::PaymentFailureReason; -use crate::ln::channelmanager::{Bolt12PaymentError, PaymentId, Verification}; +use crate::ln::channelmanager::{ + Bolt12PaymentError, PaymentId, Verification, OFFERS_MESSAGE_REQUEST_LIMIT, +}; use crate::ln::inbound_payment; use crate::ln::outbound_payment::{Retry, RetryableInvoiceRequest, StaleExpiration}; use crate::offers::invoice::{ @@ -32,14 +36,16 @@ use crate::offers::invoice::{ use crate::offers::invoice_request::InvoiceRequest; use crate::offers::parse::Bolt12SemanticError; use crate::onion_message::dns_resolution::HumanReadableName; -use crate::onion_message::messenger::{MessageSendInstructions, Responder, ResponseInstruction}; +use crate::onion_message::messenger::{ + Destination, MessageSendInstructions, Responder, ResponseInstruction, +}; use crate::onion_message::offers::{OffersMessage, OffersMessageHandler}; use crate::sync::MutexGuard; use crate::offers::invoice_error::InvoiceError; use crate::offers::nonce::Nonce; use crate::offers::offer::{Offer, OfferBuilder}; -use crate::offers::refund::RefundBuilder; +use crate::offers::refund::{Refund, RefundBuilder}; use crate::sign::EntropySource; use crate::util::logger::{Logger, WithContext}; @@ -466,6 +472,56 @@ where /// # } /// ``` /// +/// ## BOLT 12 Refunds +/// +/// Use [`request_refund_payment`] to send a [`Bolt12Invoice`] for receiving the refund. Similar to +/// *creating* an [`Offer`], this is stateless as it represents an inbound payment. +/// +/// ``` +/// # use lightning::events::{Event, EventsProvider, PaymentPurpose}; +/// # use lightning::ln::channelmanager::AChannelManager; +/// # use lightning::offers::flow::{AnOffersMessageFlow, OffersMessageCommons}; +/// # use lightning::offers::refund::Refund; +/// # +/// # fn example(offers_flow: T, channel_manager: U, refund: &Refund) { +/// # let offers_flow = offers_flow.get_omf(); +/// # let channel_manager = channel_manager.get_cm(); +/// let known_payment_hash = match offers_flow.request_refund_payment(refund) { +/// Ok(invoice) => { +/// let payment_hash = invoice.payment_hash(); +/// println!("Requesting refund payment {}", payment_hash); +/// payment_hash +/// }, +/// Err(e) => panic!("Unable to request payment for refund: {:?}", e), +/// }; +/// +/// // On the event processing thread +/// channel_manager.process_pending_events(&|event| { +/// match event { +/// Event::PaymentClaimable { payment_hash, purpose, .. } => match purpose { +/// PaymentPurpose::Bolt12RefundPayment { payment_preimage: Some(payment_preimage), .. } => { +/// assert_eq!(payment_hash, known_payment_hash); +/// println!("Claiming payment {}", payment_hash); +/// channel_manager.claim_funds(payment_preimage); +/// }, +/// PaymentPurpose::Bolt12RefundPayment { payment_preimage: None, .. } => { +/// println!("Unknown payment hash: {}", payment_hash); +/// }, +/// // ... +/// # _ => {}, +/// }, +/// Event::PaymentClaimed { payment_hash, amount_msat, .. } => { +/// assert_eq!(payment_hash, known_payment_hash); +/// println!("Claimed {} msats", amount_msat); +/// }, +/// // ... +/// # _ => {}, +/// } +/// Ok(()) +/// }); +/// # } +/// ``` +/// /// [`DNSResolverMessage`]: crate::onion_message::dns_resolution::DNSResolverMessage /// [`ChannelManager`]: crate::ln::channelmanager::ChannelManager /// [`Bolt12Invoice`]: crate::offers::invoice @@ -476,6 +532,7 @@ where /// [`Offer`]: crate::offers::offer /// [`offers`]: crate::offers /// [`pay_for_offer`]: Self::pay_for_offer +/// [`request_refund_payment`]: Self::request_refund_payment pub struct OffersMessageFlow where ES::Target: EntropySource, @@ -1062,4 +1119,122 @@ where }, ) } + + /// Creates a [`Bolt12Invoice`] for a [`Refund`] and enqueues it to be sent via an onion + /// message. + /// + /// The resulting invoice uses a [`PaymentHash`] recognized by the [`ChannelManager`] and a + /// [`BlindedPaymentPath`] containing the [`PaymentSecret`] needed to reconstruct the + /// corresponding [`PaymentPreimage`]. It is returned purely for informational purposes. + /// + /// [`PaymentPreimage`]: crate::types::payment::PaymentPreimage + /// + /// # Limitations + /// + /// Requires a direct connection to an introduction node in [`Refund::paths`] or to + /// [`Refund::payer_signing_pubkey`], if empty. This request is best effort; an invoice will be + /// sent to each node meeting the aforementioned criteria, but there's no guarantee that they + /// will be received and no retries will be made. + /// + /// # Errors + /// + /// Errors if: + /// - the refund is for an unsupported chain, or + /// - the parameterized [`Router`] is unable to create a blinded payment path or reply path for + /// the invoice. + /// + /// [`BlindedPaymentPath`]: crate::blinded_path::payment::BlindedPaymentPath + /// [`ChannelManager`]: crate::ln::channelmanager::ChannelManager + /// [`PaymentHash`]: crate::types::payment::PaymentHash + /// [`PaymentSecret`]: crate::types::payment::PaymentSecret + /// [`Router`]: crate::routing::router::Router + pub fn request_refund_payment( + &self, refund: &Refund, + ) -> Result { + let expanded_key = &self.inbound_payment_key; + let entropy = &*self.entropy_source; + let secp_ctx = &self.secp_ctx; + + let amount_msats = refund.amount_msats(); + let relative_expiry = DEFAULT_RELATIVE_EXPIRY.as_secs() as u32; + + if refund.chain() != self.commons.get_chain_hash() { + return Err(Bolt12SemanticError::UnsupportedChain); + } + + // TODO: Add persistance through `commons` internal function. Shouldn't be exposed here. + // let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(&*self.commons); + + match self.commons.create_inbound_payment(Some(amount_msats), relative_expiry, None) { + Ok((payment_hash, payment_secret)) => { + let payment_context = PaymentContext::Bolt12Refund(Bolt12RefundContext {}); + let payment_paths = self + .commons + .create_blinded_payment_paths(amount_msats, payment_secret, payment_context) + .map_err(|_| Bolt12SemanticError::MissingPaths)?; + + #[cfg(feature = "std")] + let builder = refund.respond_using_derived_keys( + payment_paths, + payment_hash, + expanded_key, + entropy, + )?; + #[cfg(not(feature = "std"))] + let created_at = self.commons.get_current_blocktime(); + #[cfg(not(feature = "std"))] + let builder = refund.respond_using_derived_keys_no_std( + payment_paths, + payment_hash, + created_at, + expanded_key, + entropy, + )?; + let builder: InvoiceBuilder = builder.into(); + let invoice = builder.allow_mpp().build_and_sign(secp_ctx)?; + + let nonce = Nonce::from_entropy_source(entropy); + let hmac = payment_hash.hmac_for_offer_payment(nonce, expanded_key); + let context = MessageContext::Offers(OffersContext::InboundPayment { + payment_hash: invoice.payment_hash(), + nonce, + hmac, + }); + let reply_paths = self + .commons + .create_blinded_paths(context) + .map_err(|_| Bolt12SemanticError::MissingPaths)?; + + let mut pending_offers_messages = self.commons.get_pending_offers_messages(); + if refund.paths().is_empty() { + for reply_path in reply_paths { + let instructions = MessageSendInstructions::WithSpecifiedReplyPath { + destination: Destination::Node(refund.payer_signing_pubkey()), + reply_path, + }; + let message = OffersMessage::Invoice(invoice.clone()); + pending_offers_messages.push((message, instructions)); + } + } else { + reply_paths + .iter() + .flat_map(|reply_path| { + refund.paths().iter().map(move |path| (path, reply_path)) + }) + .take(OFFERS_MESSAGE_REQUEST_LIMIT) + .for_each(|(path, reply_path)| { + let instructions = MessageSendInstructions::WithSpecifiedReplyPath { + destination: Destination::BlindedPath(path.clone()), + reply_path: reply_path.clone(), + }; + let message = OffersMessage::Invoice(invoice.clone()); + pending_offers_messages.push((message, instructions)); + }); + } + + Ok(invoice) + }, + Err(()) => Err(Bolt12SemanticError::InvalidAmount), + } + } } From f49930bee314ed3a7f7af7c59e5b8055f9c226a3 Mon Sep 17 00:00:00 2001 From: shaavan Date: Wed, 11 Dec 2024 17:39:33 +0530 Subject: [PATCH 13/30] f: Remove spurious _persistence_guard check - The _persistence_guard here was not serving any purpose, and hence can be removed in this refactoring. --- lightning/src/offers/flow.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/lightning/src/offers/flow.rs b/lightning/src/offers/flow.rs index 33294c068cb..ecd3929c213 100644 --- a/lightning/src/offers/flow.rs +++ b/lightning/src/offers/flow.rs @@ -1162,9 +1162,6 @@ where return Err(Bolt12SemanticError::UnsupportedChain); } - // TODO: Add persistance through `commons` internal function. Shouldn't be exposed here. - // let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(&*self.commons); - match self.commons.create_inbound_payment(Some(amount_msats), relative_expiry, None) { Ok((payment_hash, payment_secret)) => { let payment_context = PaymentContext::Bolt12Refund(Bolt12RefundContext {}); From 0d0732c3854d3140d0b9a1f9922d7b2c4394310b Mon Sep 17 00:00:00 2001 From: shaavan Date: Wed, 27 Nov 2024 16:53:16 +0530 Subject: [PATCH 14/30] Move pay_for_offer_human_readable to OffersMessageFlow --- lightning-dns-resolver/src/lib.rs | 2 +- lightning/src/ln/channelmanager.rs | 85 ++++++-------------------- lightning/src/offers/flow.rs | 97 ++++++++++++++++++++++++++++++ 3 files changed, 116 insertions(+), 68 deletions(-) diff --git a/lightning-dns-resolver/src/lib.rs b/lightning-dns-resolver/src/lib.rs index 009eb6c8f64..8b62cfdf569 100644 --- a/lightning-dns-resolver/src/lib.rs +++ b/lightning-dns-resolver/src/lib.rs @@ -404,7 +404,7 @@ mod test { let retry = Retry::Attempts(0); let amt = 42_000; nodes[0] - .node + .offers_handler .pay_for_offer_from_human_readable_name(name, amt, payment_id, retry, None, resolvers) .unwrap(); diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 3724f250c4a..e75dbd3a2c7 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -9585,6 +9585,16 @@ where self.pending_offers_messages.lock().expect("Mutex is locked by other thread.") } + #[cfg(feature = "dnssec")] + fn get_pending_dns_onion_messages(&self) -> MutexGuard<'_, Vec<(DNSResolverMessage, MessageSendInstructions)>> { + self.pending_dns_onion_messages.lock().expect("Mutex is locked by other thread.") + } + + #[cfg(feature = "dnssec")] + fn get_hrn_resolver(&self) -> &OMNameResolver { + &self.hrn_resolver + } + fn sign_bolt12_invoice( &self, invoice: &UnsignedBolt12Invoice, ) -> Result { @@ -9807,6 +9817,14 @@ where ) } + #[cfg(feature = "dnssec")] + fn add_new_awaiting_offer( + &self, payment_id: PaymentId, expiration: StaleExpiration, retry_strategy: Retry, + max_total_routing_fee_msat: Option, amount_msats: u64, + ) -> Result<(), ()> { + self.pending_outbound_payments.add_new_awaiting_offer(payment_id, expiration, retry_strategy, max_total_routing_fee_msat, amount_msats) + } + fn pay_for_offer_intern Result<(), Bolt12SemanticError>>( &self, offer: &Offer, quantity: Option, amount_msats: Option, payer_note: Option, payment_id: PaymentId, @@ -9874,73 +9892,6 @@ where MR::Target: MessageRouter, L::Target: Logger, { - /// Pays for an [`Offer`] looked up using [BIP 353] Human Readable Names resolved by the DNS - /// resolver(s) at `dns_resolvers` which resolve names according to bLIP 32. - /// - /// If the wallet supports paying on-chain schemes, you should instead use - /// [`OMNameResolver::resolve_name`] and [`OMNameResolver::handle_dnssec_proof_for_uri`] (by - /// implementing [`DNSResolverMessageHandler`]) directly to look up a URI and then delegate to - /// your normal URI handling. - /// - /// If `max_total_routing_fee_msat` is not specified, the default from - /// [`RouteParameters::from_payment_params_and_value`] is applied. - /// - /// # Payment - /// - /// The provided `payment_id` is used to ensure that only one invoice is paid for the request - /// when received. See [Avoiding Duplicate Payments] for other requirements once the payment has - /// been sent. - /// - /// To revoke the request, use [`ChannelManager::abandon_payment`] prior to receiving the - /// invoice. If abandoned, or an invoice isn't received in a reasonable amount of time, the - /// payment will fail with an [`Event::InvoiceRequestFailed`]. - /// - /// # Privacy - /// - /// For payer privacy, uses a derived payer id and uses [`MessageRouter::create_blinded_paths`] - /// to construct a [`BlindedPath`] for the reply path. For further privacy implications, see the - /// docs of the parameterized [`Router`], which implements [`MessageRouter`]. - /// - /// # Limitations - /// - /// Requires a direct connection to the given [`Destination`] as well as an introduction node in - /// [`Offer::paths`] or to [`Offer::signing_pubkey`], if empty. A similar restriction applies to - /// the responding [`Bolt12Invoice::payment_paths`]. - /// - /// # Errors - /// - /// Errors if: - /// - a duplicate `payment_id` is provided given the caveats in the aforementioned link, - /// - /// [`Bolt12Invoice::payment_paths`]: crate::offers::invoice::Bolt12Invoice::payment_paths - /// [Avoiding Duplicate Payments]: #avoiding-duplicate-payments - #[cfg(feature = "dnssec")] - pub fn pay_for_offer_from_human_readable_name( - &self, name: HumanReadableName, amount_msats: u64, payment_id: PaymentId, - retry_strategy: Retry, max_total_routing_fee_msat: Option, - dns_resolvers: Vec, - ) -> Result<(), ()> { - let (onion_message, context) = - self.hrn_resolver.resolve_name(payment_id, name, &*self.entropy_source)?; - let reply_paths = self.create_blinded_paths(MessageContext::DNSResolver(context))?; - let expiration = StaleExpiration::TimerTicks(1); - self.pending_outbound_payments.add_new_awaiting_offer(payment_id, expiration, retry_strategy, max_total_routing_fee_msat, amount_msats)?; - let message_params = dns_resolvers - .iter() - .flat_map(|destination| reply_paths.iter().map(move |path| (path, destination))) - .take(OFFERS_MESSAGE_REQUEST_LIMIT); - for (reply_path, destination) in message_params { - self.pending_dns_onion_messages.lock().unwrap().push(( - DNSResolverMessage::DNSSECQuery(onion_message.clone()), - MessageSendInstructions::WithSpecifiedReplyPath { - destination: destination.clone(), - reply_path: reply_path.clone(), - }, - )); - } - Ok(()) - } - /// Gets a [`PaymentSecret`] for a given [`PaymentHash`], for which the payment preimage is /// stored external to LDK. /// diff --git a/lightning/src/offers/flow.rs b/lightning/src/offers/flow.rs index ecd3929c213..88508dd6643 100644 --- a/lightning/src/offers/flow.rs +++ b/lightning/src/offers/flow.rs @@ -62,6 +62,9 @@ use { crate::offers::refund::RefundMaybeWithDerivedMetadataBuilder, }; +#[cfg(feature = "dnssec")] +use crate::onion_message::dns_resolution::{DNSResolverMessage, HumanReadableName, OMNameResolver}; + /// Functions commonly shared in usage between [`ChannelManager`] & `OffersMessageFlow` /// /// [`ChannelManager`]: crate::ln::channelmanager::ChannelManager @@ -71,6 +74,16 @@ pub trait OffersMessageCommons { &self, ) -> MutexGuard<'_, Vec<(OffersMessage, MessageSendInstructions)>>; + #[cfg(feature = "dnssec")] + /// Get pending DNS onion messages + fn get_pending_dns_onion_messages( + &self, + ) -> MutexGuard<'_, Vec<(DNSResolverMessage, MessageSendInstructions)>>; + + #[cfg(feature = "dnssec")] + /// Get hrn resolver + fn get_hrn_resolver(&self) -> &OMNameResolver; + /// Signs the [`TaggedHash`] of a BOLT 12 invoice. /// /// May be called by a function passed to [`UnsignedBolt12Invoice::sign`] where `invoice` is the @@ -193,6 +206,13 @@ pub trait OffersMessageCommons { retryable_invoice_request: Option, ) -> Result<(), ()>; + #[cfg(feature = "dnssec")] + /// Add new awaiting offer + fn add_new_awaiting_offer( + &self, payment_id: PaymentId, expiration: StaleExpiration, retry_strategy: Retry, + max_total_routing_fee_msat: Option, amount_msats: u64, + ) -> Result<(), ()>; + /// Internal pay_for_offer fn pay_for_offer_intern< CPP: FnOnce(&InvoiceRequest, Nonce) -> Result<(), Bolt12SemanticError>, @@ -1234,4 +1254,81 @@ where Err(()) => Err(Bolt12SemanticError::InvalidAmount), } } + + /// Pays for an [`Offer`] looked up using [BIP 353] Human Readable Names resolved by the DNS + /// resolver(s) at `dns_resolvers` which resolve names according to bLIP 32. + /// + /// If the wallet supports paying on-chain schemes, you should instead use + /// [`OMNameResolver::resolve_name`] and [`OMNameResolver::handle_dnssec_proof_for_uri`] (by + /// implementing [`DNSResolverMessageHandler`]) directly to look up a URI and then delegate to + /// your normal URI handling. + /// + /// If `max_total_routing_fee_msat` is not specified, the default from + /// [`RouteParameters::from_payment_params_and_value`] is applied. + /// + /// # Payment + /// + /// The provided `payment_id` is used to ensure that only one invoice is paid for the request + /// when received. See [Avoiding Duplicate Payments] for other requirements once the payment has + /// been sent. + /// + /// To revoke the request, use [`ChannelManager::abandon_payment`] prior to receiving the + /// invoice. If abandoned, or an invoice isn't received in a reasonable amount of time, the + /// payment will fail with an [`Event::InvoiceRequestFailed`]. + /// + /// # Privacy + /// + /// For payer privacy, uses a derived payer id and uses [`MessageRouter::create_blinded_paths`] + /// to construct a [`BlindedPath`] for the reply path. For further privacy implications, see the + /// docs of the parameterized [`Router`], which implements [`MessageRouter`]. + /// + /// # Limitations + /// + /// Requires a direct connection to the given [`Destination`] as well as an introduction node in + /// [`Offer::paths`] or to [`Offer::signing_pubkey`], if empty. A similar restriction applies to + /// the responding [`Bolt12Invoice::payment_paths`]. + /// + /// # Errors + /// + /// Errors if: + /// - a duplicate `payment_id` is provided given the caveats in the aforementioned link, + /// + /// [`Bolt12Invoice::payment_paths`]: crate::offers::invoice::Bolt12Invoice::payment_paths + /// [Avoiding Duplicate Payments]: #avoiding-duplicate-payments + #[cfg(feature = "dnssec")] + pub fn pay_for_offer_from_human_readable_name( + &self, name: HumanReadableName, amount_msats: u64, payment_id: PaymentId, + retry_strategy: Retry, max_total_routing_fee_msat: Option, + dns_resolvers: Vec, + ) -> Result<(), ()> { + let (onion_message, context) = self.commons.get_hrn_resolver().resolve_name( + payment_id, + name, + &*self.entropy_source, + )?; + let reply_paths = + self.commons.create_blinded_paths(MessageContext::DNSResolver(context))?; + let expiration = StaleExpiration::TimerTicks(1); + self.commons.add_new_awaiting_offer( + payment_id, + expiration, + retry_strategy, + max_total_routing_fee_msat, + amount_msats, + )?; + let message_params = dns_resolvers + .iter() + .flat_map(|destination| reply_paths.iter().map(move |path| (path, destination))) + .take(OFFERS_MESSAGE_REQUEST_LIMIT); + for (reply_path, destination) in message_params { + self.commons.get_pending_dns_onion_messages().push(( + DNSResolverMessage::DNSSECQuery(onion_message.clone()), + MessageSendInstructions::WithSpecifiedReplyPath { + destination: destination.clone(), + reply_path: reply_path.clone(), + }, + )); + } + Ok(()) + } } From 7b44fe07dd44eae1379cf1e34e641d0edf7b635f Mon Sep 17 00:00:00 2001 From: shaavan Date: Wed, 27 Nov 2024 17:35:44 +0530 Subject: [PATCH 15/30] Move DNSResolverMessageHandler impl to OffersMessageFlow --- lightning-dns-resolver/src/lib.rs | 2 +- lightning/src/ln/channelmanager.rs | 96 ++++------------------ lightning/src/ln/functional_test_utils.rs | 4 +- lightning/src/offers/flow.rs | 98 ++++++++++++++++++++++- 4 files changed, 114 insertions(+), 86 deletions(-) diff --git a/lightning-dns-resolver/src/lib.rs b/lightning-dns-resolver/src/lib.rs index 8b62cfdf569..9e67389756a 100644 --- a/lightning-dns-resolver/src/lib.rs +++ b/lightning-dns-resolver/src/lib.rs @@ -393,7 +393,7 @@ mod test { // When we get the proof back, override its contents to an offer from nodes[1] let bs_offer = nodes[1].offers_handler.create_offer_builder(None).unwrap().build().unwrap(); nodes[0] - .node + .offers_handler .testing_dnssec_proof_offer_resolution_override .lock() .unwrap() diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index e75dbd3a2c7..e7a1d38b583 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -76,7 +76,7 @@ use crate::offers::signer; use crate::offers::static_invoice::StaticInvoice; use crate::onion_message::async_payments::{AsyncPaymentsMessage, HeldHtlcAvailable, ReleaseHeldHtlc, AsyncPaymentsMessageHandler}; use crate::onion_message::dns_resolution::HumanReadableName; -use crate::onion_message::messenger::{Destination, MessageRouter, Responder, ResponseInstruction, MessageSendInstructions}; +use crate::onion_message::messenger::{DefaultMessageRouter, Destination, MessageRouter, MessageSendInstructions, Responder, ResponseInstruction}; use crate::onion_message::offers::OffersMessage; use crate::sign::{EntropySource, NodeSigner, Recipient, SignerProvider}; use crate::sign::ecdsa::EcdsaChannelSigner; @@ -90,13 +90,10 @@ use crate::util::logger::{Level, Logger, WithContext}; use crate::util::errors::APIError; #[cfg(feature = "dnssec")] -use crate::blinded_path::message::DNSResolverContext; -#[cfg(feature = "dnssec")] -use crate::onion_message::dns_resolution::{DNSResolverMessage, DNSResolverMessageHandler, DNSSECQuery, DNSSECProof, OMNameResolver}; +use crate::onion_message::dns_resolution::{DNSResolverMessage, OMNameResolver}; #[cfg(not(c_bindings))] use { - crate::onion_message::messenger::DefaultMessageRouter, crate::routing::router::DefaultRouter, crate::routing::gossip::NetworkGraph, crate::routing::scoring::{ProbabilisticScorer, ProbabilisticScoringFeeParameters}, @@ -2426,14 +2423,6 @@ where #[cfg(feature = "dnssec")] pending_dns_onion_messages: Mutex>, - #[cfg(feature = "_test_utils")] - /// In testing, it is useful be able to forge a name -> offer mapping so that we can pay an - /// offer generated in the test. - /// - /// This allows for doing so, validating proofs as normal, but, if they pass, replacing the - /// offer they resolve to to the given one. - pub testing_dnssec_proof_offer_resolution_override: Mutex>, - #[cfg(test)] pub(super) entropy_source: ES, #[cfg(not(test))] @@ -3364,9 +3353,6 @@ where hrn_resolver: OMNameResolver::new(current_timestamp, params.best_block.height), #[cfg(feature = "dnssec")] pending_dns_onion_messages: Mutex::new(Vec::new()), - - #[cfg(feature = "_test_utils")] - testing_dnssec_proof_offer_resolution_override: Mutex::new(new_hash_map()), } } @@ -9871,6 +9857,18 @@ where self.enqueue_invoice_request(invoice_request, reply_paths) } + + #[cfg(feature = "dnssec")] + fn amt_msats_for_payment_awaiting_offer(&self, payment_id: PaymentId) -> Result { + self.pending_outbound_payments.amt_msats_for_payment_awaiting_offer(payment_id) + } + + #[cfg(feature = "dnssec")] + fn received_offer( + &self, payment_id: PaymentId, retryable_invoice_request: Option, + ) -> Result<(), ()> { + self.pending_outbound_payments.received_offer(payment_id, retryable_invoice_request) + } } /// Defines the maximum number of [`OffersMessage`] including different reply paths to be sent @@ -11430,69 +11428,6 @@ where } } -#[cfg(feature = "dnssec")] -impl -DNSResolverMessageHandler for ChannelManager -where - M::Target: chain::Watch<::EcdsaSigner>, - T::Target: BroadcasterInterface, - ES::Target: EntropySource, - NS::Target: NodeSigner, - SP::Target: SignerProvider, - F::Target: FeeEstimator, - R::Target: Router, - MR::Target: MessageRouter, - L::Target: Logger, -{ - fn handle_dnssec_query( - &self, _message: DNSSECQuery, _responder: Option, - ) -> Option<(DNSResolverMessage, ResponseInstruction)> { - None - } - - fn handle_dnssec_proof(&self, message: DNSSECProof, context: DNSResolverContext) { - let offer_opt = self.hrn_resolver.handle_dnssec_proof_for_offer(message, context); - #[cfg_attr(not(feature = "_test_utils"), allow(unused_mut))] - if let Some((completed_requests, mut offer)) = offer_opt { - for (name, payment_id) in completed_requests { - #[cfg(feature = "_test_utils")] - if let Some(replacement_offer) = self.testing_dnssec_proof_offer_resolution_override.lock().unwrap().remove(&name) { - // If we have multiple pending requests we may end up over-using the override - // offer, but tests can deal with that. - offer = replacement_offer; - } - if let Ok(amt_msats) = self.pending_outbound_payments.amt_msats_for_payment_awaiting_offer(payment_id) { - let offer_pay_res = - self.pay_for_offer_intern(&offer, None, Some(amt_msats), None, payment_id, Some(name), - |invoice_request, nonce| { - let retryable_invoice_request = RetryableInvoiceRequest { - invoice_request: invoice_request.clone(), - nonce, - }; - self.pending_outbound_payments - .received_offer(payment_id, Some(retryable_invoice_request)) - .map_err(|_| Bolt12SemanticError::DuplicatePaymentId) - }); - if offer_pay_res.is_err() { - // The offer we tried to pay is the canonical current offer for the name we - // wanted to pay. If we can't pay it, there's no way to recover so fail the - // payment. - // Note that the PaymentFailureReason should be ignored for an - // AwaitingInvoice payment. - self.pending_outbound_payments.abandon_payment( - payment_id, PaymentFailureReason::RouteNotFound, &self.pending_events, - ); - } - } - } - } - } - - fn release_pending_messages(&self) -> Vec<(DNSResolverMessage, MessageSendInstructions)> { - core::mem::take(&mut self.pending_dns_onion_messages.lock().unwrap()) - } -} - impl NodeIdLookUp for ChannelManager where @@ -13372,9 +13307,6 @@ where hrn_resolver: OMNameResolver::new(highest_seen_timestamp, best_block_height), #[cfg(feature = "dnssec")] pending_dns_onion_messages: Mutex::new(Vec::new()), - - #[cfg(feature = "_test_utils")] - testing_dnssec_proof_offer_resolution_override: Mutex::new(new_hash_map()), }; for (_, monitor) in args.channel_monitors.iter() { diff --git a/lightning/src/ln/functional_test_utils.rs b/lightning/src/ln/functional_test_utils.rs index 44b5a3c11f4..f459caa7a6c 100644 --- a/lightning/src/ln/functional_test_utils.rs +++ b/lightning/src/ln/functional_test_utils.rs @@ -440,7 +440,7 @@ type TestOnionMessenger<'chan_man, 'node_cfg, 'chan_mon_cfg> = OnionMessenger< &'node_cfg test_utils::TestMessageRouter<'chan_mon_cfg>, Arc>, &'chan_man TestChannelManager<'node_cfg, 'chan_mon_cfg>, - &'chan_man TestChannelManager<'node_cfg, 'chan_mon_cfg>, + Arc>, IgnoringMessageHandler, >; @@ -3350,7 +3350,7 @@ pub fn create_network<'a, 'b: 'a, 'c: 'b>(node_count: usize, cfgs: &'b Vec, payment_id: PaymentId, human_readable_name: Option, create_pending_payment: CPP, ) -> Result<(), Bolt12SemanticError>; + + #[cfg(feature = "dnssec")] + /// Amount for payment awaiting offer + fn amt_msats_for_payment_awaiting_offer(&self, payment_id: PaymentId) -> Result; + + #[cfg(feature = "dnssec")] + /// Received Offer + fn received_offer( + &self, payment_id: PaymentId, retryable_invoice_request: Option, + ) -> Result<(), ()>; } /// A trivial trait which describes any [`OffersMessageFlow`]. @@ -569,6 +585,14 @@ where /// Contains functions shared between OffersMessageHandler and ChannelManager. commons: OMC, + #[cfg(feature = "_test_utils")] + /// In testing, it is useful be able to forge a name -> offer mapping so that we can pay an + /// offer generated in the test. + /// + /// This allows for doing so, validating proofs as normal, but, if they pass, replacing the + /// offer they resolve to to the given one. + pub testing_dnssec_proof_offer_resolution_override: Mutex>, + /// The Logger for use in the OffersMessageFlow and which may be used to log /// information during deserialization. pub logger: L, @@ -594,6 +618,8 @@ where secp_ctx, commons, entropy_source, + #[cfg(feature = "_test_utils")] + testing_dnssec_proof_offer_resolution_override: Mutex::new(new_hash_map()), logger, } } @@ -1332,3 +1358,73 @@ where Ok(()) } } + +#[cfg(feature = "dnssec")] +impl DNSResolverMessageHandler for OffersMessageFlow +where + ES::Target: EntropySource, + OMC::Target: OffersMessageCommons, + L::Target: Logger, +{ + fn handle_dnssec_query( + &self, _message: DNSSECQuery, _responder: Option, + ) -> Option<(DNSResolverMessage, ResponseInstruction)> { + None + } + + fn handle_dnssec_proof(&self, message: DNSSECProof, context: DNSResolverContext) { + let offer_opt = + self.commons.get_hrn_resolver().handle_dnssec_proof_for_offer(message, context); + #[cfg_attr(not(feature = "_test_utils"), allow(unused_mut))] + if let Some((completed_requests, mut offer)) = offer_opt { + for (name, payment_id) in completed_requests { + #[cfg(feature = "_test_utils")] + if let Some(replacement_offer) = self + .testing_dnssec_proof_offer_resolution_override + .lock() + .unwrap() + .remove(&name) + { + // If we have multiple pending requests we may end up over-using the override + // offer, but tests can deal with that. + offer = replacement_offer; + } + if let Ok(amt_msats) = self.commons.amt_msats_for_payment_awaiting_offer(payment_id) + { + let offer_pay_res = self.commons.pay_for_offer_intern( + &offer, + None, + Some(amt_msats), + None, + payment_id, + Some(name), + |invoice_request, nonce| { + let retryable_invoice_request = RetryableInvoiceRequest { + invoice_request: invoice_request.clone(), + nonce, + }; + self.commons + .received_offer(payment_id, Some(retryable_invoice_request)) + .map_err(|_| Bolt12SemanticError::DuplicatePaymentId) + }, + ); + if offer_pay_res.is_err() { + // The offer we tried to pay is the canonical current offer for the name we + // wanted to pay. If we can't pay it, there's no way to recover so fail the + // payment. + // Note that the PaymentFailureReason should be ignored for an + // AwaitingInvoice payment. + self.commons.abandon_payment_with_reason( + payment_id, + PaymentFailureReason::RouteNotFound, + ); + } + } + } + } + } + + fn release_pending_messages(&self) -> Vec<(DNSResolverMessage, MessageSendInstructions)> { + core::mem::take(&mut self.commons.get_pending_dns_onion_messages()) + } +} From a24579ae54f857959e8145f76ec7a35d75f4d07c Mon Sep 17 00:00:00 2001 From: shaavan Date: Wed, 27 Nov 2024 18:06:27 +0530 Subject: [PATCH 16/30] Move create_blinded_path_using_absolute_expiry to flow.rs --- lightning-background-processor/src/lib.rs | 2 +- lightning/src/ln/blinded_payment_tests.rs | 4 +- lightning/src/ln/channelmanager.rs | 151 +++------------ lightning/src/ln/functional_test_utils.rs | 5 +- lightning/src/ln/offers_tests.rs | 17 +- lightning/src/offers/flow.rs | 225 ++++++++++++++++++---- lightning/src/onion_message/messenger.rs | 4 +- 7 files changed, 226 insertions(+), 182 deletions(-) diff --git a/lightning-background-processor/src/lib.rs b/lightning-background-processor/src/lib.rs index b4fd983d9ab..7e7a10e7f16 100644 --- a/lightning-background-processor/src/lib.rs +++ b/lightning-background-processor/src/lib.rs @@ -647,7 +647,7 @@ use futures_util::{dummy_waker, OptionalSelector, Selector, SelectorOutput}; /// # type NetworkGraph = lightning::routing::gossip::NetworkGraph>; /// # type P2PGossipSync
          = lightning::routing::gossip::P2PGossipSync, Arc
            , Arc>; /// # type ChannelManager = lightning::ln::channelmanager::SimpleArcChannelManager, B, FE, Logger>; -/// # type OffersMessageFlow = lightning::offers::flow::OffersMessageFlow, Arc>, Arc>; +/// # type OffersMessageFlow = lightning::offers::flow::OffersMessageFlow, Arc>, Arc, Arc, Arc>>, Arc>; /// # type OnionMessenger = lightning::onion_message::messenger::OnionMessenger, Arc, Arc, Arc>, Arc, Arc, Arc>>, Arc>, lightning::ln::peer_handler::IgnoringMessageHandler, lightning::ln::peer_handler::IgnoringMessageHandler, lightning::ln::peer_handler::IgnoringMessageHandler>; /// # type Scorer = RwLock, Arc>>; /// # type PeerManager = lightning::ln::peer_handler::SimpleArcPeerManager, B, FE, Arc
              , Logger>; diff --git a/lightning/src/ln/blinded_payment_tests.rs b/lightning/src/ln/blinded_payment_tests.rs index c1fad65c14f..d21f00f7685 100644 --- a/lightning/src/ln/blinded_payment_tests.rs +++ b/lightning/src/ln/blinded_payment_tests.rs @@ -1238,7 +1238,7 @@ fn blinded_keysend() { let inbound_payment_key = nodes[2].keys_manager.get_inbound_payment_key(); let payment_secret = inbound_payment::create_for_spontaneous_payment( - &inbound_payment_key, None, u32::MAX, nodes[2].node.duration_since_epoch().as_secs(), None + &inbound_payment_key, None, u32::MAX, nodes[2].offers_handler.duration_since_epoch().as_secs(), None ).unwrap(); let amt_msat = 5000; @@ -1277,7 +1277,7 @@ fn blinded_mpp_keysend() { let inbound_payment_key = nodes[3].keys_manager.get_inbound_payment_key(); let payment_secret = inbound_payment::create_for_spontaneous_payment( - &inbound_payment_key, None, u32::MAX, nodes[3].node.duration_since_epoch().as_secs(), None + &inbound_payment_key, None, u32::MAX, nodes[3].offers_handler.duration_since_epoch().as_secs(), None ).unwrap(); let amt_msat = 15_000_000; diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index e7a1d38b583..eb1b2ce2457 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -33,9 +33,9 @@ use bitcoin::secp256k1::Secp256k1; use bitcoin::{secp256k1, Sequence, Weight}; use crate::events::FundingInfo; -use crate::blinded_path::message::{AsyncPaymentsContext, MessageContext, OffersContext}; +use crate::blinded_path::message::{AsyncPaymentsContext, MessageContext, MessageForwardNode, OffersContext}; use crate::blinded_path::NodeIdLookUp; -use crate::blinded_path::message::{BlindedMessagePath, MessageForwardNode}; +use crate::blinded_path::message::BlindedMessagePath; use crate::blinded_path::payment::{BlindedPaymentPath, PaymentConstraints, PaymentContext, UnauthenticatedReceiveTlvs}; use crate::chain; use crate::chain::{Confirm, ChannelMonitorUpdateStatus, Watch, BestBlock}; @@ -47,7 +47,6 @@ use crate::events::{self, Event, EventHandler, EventsProvider, InboundChannelFun // construct one themselves. use crate::ln::inbound_payment; use crate::ln::types::ChannelId; -use crate::offers::offer::Offer; use crate::offers::flow::OffersMessageCommons; use crate::types::payment::{PaymentHash, PaymentPreimage, PaymentSecret}; use crate::ln::channel::{self, Channel, ChannelPhase, ChannelError, ChannelUpdateStatus, ShutdownResult, UpdateFulfillCommitFetch, OutboundV1Channel, InboundV1Channel, WithChannelContext, InboundV2Channel, InteractivelyFunded as _}; @@ -68,14 +67,13 @@ use crate::ln::outbound_payment; use crate::ln::outbound_payment::{OutboundPayments, PendingOutboundPayment, RetryableInvoiceRequest, SendAlongPathArgs, StaleExpiration}; use crate::offers::invoice::Bolt12Invoice; use crate::offers::invoice::UnsignedBolt12Invoice; -use crate::offers::invoice_request::{InvoiceRequest, InvoiceRequestBuilder}; +use crate::offers::invoice_request::InvoiceRequest; use crate::offers::nonce::Nonce; use crate::offers::parse::Bolt12SemanticError; use crate::offers::signer; #[cfg(async_payments)] use crate::offers::static_invoice::StaticInvoice; use crate::onion_message::async_payments::{AsyncPaymentsMessage, HeldHtlcAvailable, ReleaseHeldHtlc, AsyncPaymentsMessageHandler}; -use crate::onion_message::dns_resolution::HumanReadableName; use crate::onion_message::messenger::{DefaultMessageRouter, Destination, MessageRouter, MessageSendInstructions, Responder, ResponseInstruction}; use crate::onion_message::offers::OffersMessage; use crate::sign::{EntropySource, NodeSigner, Recipient, SignerProvider}; @@ -2619,26 +2617,6 @@ const MAX_UNFUNDED_CHANNEL_PEERS: usize = 50; /// many peers we reject new (inbound) connections. const MAX_NO_CHANNEL_PEERS: usize = 250; -/// The maximum expiration from the current time where an [`Offer`] or [`Refund`] is considered -/// short-lived, while anything with a greater expiration is considered long-lived. -/// -/// Using [`OffersMessageFlow::create_offer_builder`] or [`OffersMessageFlow::create_refund_builder`], -/// will included a [`BlindedMessagePath`] created using: -/// - [`MessageRouter::create_compact_blinded_paths`] when short-lived, and -/// - [`MessageRouter::create_blinded_paths`] when long-lived. -/// -/// [`OffersMessageFlow::create_offer_builder`]: crate::offers::flow::OffersMessageFlow::create_offer_builder -/// [`OffersMessageFlow::create_refund_builder`]: crate::offers::flow::OffersMessageFlow::create_refund_builder -/// -/// -/// Using compact [`BlindedMessagePath`]s may provide better privacy as the [`MessageRouter`] could select -/// more hops. However, since they use short channel ids instead of pubkeys, they are more likely to -/// become invalid over time as channels are closed. Thus, they are only suitable for short-term use. -/// -/// [`Offer`]: crate::offers::offer -/// [`Refund`]: crate::offers::refund -pub const MAX_SHORT_LIVED_RELATIVE_EXPIRY: Duration = Duration::from_secs(60 * 60 * 24); - /// Used by [`ChannelManager::list_recent_payments`] to express the status of recent payments. /// These include payments that have yet to find a successful path, or have unresolved HTLCs. #[derive(Debug, PartialEq)] @@ -9624,6 +9602,23 @@ where ) } + fn get_peer_for_blinded_path(&self) -> Vec { + self.per_peer_state.read().unwrap() + .iter() + .map(|(node_id, peer_state)| (node_id, peer_state.lock().unwrap())) + .filter(|(_, peer)| peer.is_connected) + .filter(|(_, peer)| peer.latest_features.supports_onion_messages()) + .map(|(node_id, peer)| MessageForwardNode { + node_id: *node_id, + short_channel_id: peer.channel_by_id + .iter() + .filter(|(_, channel)| channel.context().is_usable()) + .min_by_key(|(_, channel)| channel.context().channel_creation_height) + .and_then(|(_, channel)| channel.context().get_short_channel_id()), + }) + .collect::>() + } + fn verify_bolt12_invoice( &self, invoice: &Bolt12Invoice, context: Option<&OffersContext>, ) -> Result { @@ -9780,19 +9775,6 @@ where res } - fn create_blinded_paths_using_absolute_expiry( - &self, context: OffersContext, absolute_expiry: Option, - ) -> Result, ()> { - let now = self.duration_since_epoch(); - let max_short_lived_absolute_expiry = now.saturating_add(MAX_SHORT_LIVED_RELATIVE_EXPIRY); - - if absolute_expiry.unwrap_or(Duration::MAX) <= max_short_lived_absolute_expiry { - self.create_compact_blinded_paths(context) - } else { - self.create_blinded_paths(MessageContext::Offers(context)) - } - } - fn get_chain_hash(&self) -> ChainHash { self.chain_hash } @@ -9811,53 +9793,6 @@ where self.pending_outbound_payments.add_new_awaiting_offer(payment_id, expiration, retry_strategy, max_total_routing_fee_msat, amount_msats) } - fn pay_for_offer_intern Result<(), Bolt12SemanticError>>( - &self, offer: &Offer, quantity: Option, amount_msats: Option, - payer_note: Option, payment_id: PaymentId, - human_readable_name: Option, create_pending_payment: CPP, - ) -> Result<(), Bolt12SemanticError> { - let expanded_key = &self.inbound_payment_key; - let entropy = &*self.entropy_source; - let secp_ctx = &self.secp_ctx; - - let nonce = Nonce::from_entropy_source(entropy); - let builder: InvoiceRequestBuilder = offer - .request_invoice(expanded_key, nonce, secp_ctx, payment_id)? - .into(); - let builder = builder.chain_hash(self.chain_hash)?; - - let builder = match quantity { - None => builder, - Some(quantity) => builder.quantity(quantity)?, - }; - let builder = match amount_msats { - None => builder, - Some(amount_msats) => builder.amount_msats(amount_msats)?, - }; - let builder = match payer_note { - None => builder, - Some(payer_note) => builder.payer_note(payer_note), - }; - let builder = match human_readable_name { - None => builder, - Some(hrn) => builder.sourced_from_human_readable_name(hrn), - }; - let invoice_request = builder.build_and_sign()?; - - let hmac = payment_id.hmac_for_offer_payment(nonce, expanded_key); - let context = MessageContext::Offers( - OffersContext::OutboundPayment { payment_id, nonce, hmac: Some(hmac) } - ); - let reply_paths = self.create_blinded_paths(context) - .map_err(|_| Bolt12SemanticError::MissingPaths)?; - - let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self); - - create_pending_payment(&invoice_request, nonce)?; - - self.enqueue_invoice_request(invoice_request, reply_paths) - } - #[cfg(feature = "dnssec")] fn amt_msats_for_payment_awaiting_offer(&self, payment_id: PaymentId) -> Result { self.pending_outbound_payments.amt_msats_for_payment_awaiting_offer(payment_id) @@ -9869,6 +9804,11 @@ where ) -> Result<(), ()> { self.pending_outbound_payments.received_offer(payment_id, retryable_invoice_request) } + + #[cfg(not(feature = "std"))] + fn get_highest_seen_timestamp(&self) -> Duration { + Duration::from_secs(self.highest_seen_timestamp.load(Ordering::Acquire) as u64) + } } /// Defines the maximum number of [`OffersMessage`] including different reply paths to be sent @@ -9951,47 +9891,6 @@ where inbound_payment::get_payment_preimage(payment_hash, payment_secret, &self.inbound_payment_key) } - pub(super) fn duration_since_epoch(&self) -> Duration { - #[cfg(not(feature = "std"))] - let now = Duration::from_secs( - self.highest_seen_timestamp.load(Ordering::Acquire) as u64 - ); - #[cfg(feature = "std")] - let now = std::time::SystemTime::now() - .duration_since(std::time::SystemTime::UNIX_EPOCH) - .expect("SystemTime::now() should come after SystemTime::UNIX_EPOCH"); - - now - } - - /// Creates a collection of blinded paths by delegating to - /// [`MessageRouter::create_compact_blinded_paths`]. - /// - /// Errors if the `MessageRouter` errors. - fn create_compact_blinded_paths(&self, context: OffersContext) -> Result, ()> { - let recipient = self.get_our_node_id(); - let secp_ctx = &self.secp_ctx; - - let peers = self.per_peer_state.read().unwrap() - .iter() - .map(|(node_id, peer_state)| (node_id, peer_state.lock().unwrap())) - .filter(|(_, peer)| peer.is_connected) - .filter(|(_, peer)| peer.latest_features.supports_onion_messages()) - .map(|(node_id, peer)| MessageForwardNode { - node_id: *node_id, - short_channel_id: peer.channel_by_id - .iter() - .filter(|(_, channel)| channel.context().is_usable()) - .min_by_key(|(_, channel)| channel.context().channel_creation_height) - .and_then(|(_, channel)| channel.context().get_short_channel_id()), - }) - .collect::>(); - - self.message_router - .create_compact_blinded_paths(recipient, MessageContext::Offers(context), peers, secp_ctx) - .and_then(|paths| (!paths.is_empty()).then(|| paths).ok_or(())) - } - /// Gets a fake short channel id for use in receiving [phantom node payments]. These fake scids /// are used when constructing the phantom invoice's route hints. /// diff --git a/lightning/src/ln/functional_test_utils.rs b/lightning/src/ln/functional_test_utils.rs index f459caa7a6c..5ea7ce8fa49 100644 --- a/lightning/src/ln/functional_test_utils.rs +++ b/lightning/src/ln/functional_test_utils.rs @@ -415,6 +415,7 @@ type TestChannelManager<'node_cfg, 'chan_mon_cfg> = ChannelManager< pub type TestOffersMessageFlow<'chan_man, 'node_cfg, 'chan_mon_cfg> = OffersMessageFlow< &'node_cfg test_utils::TestKeysInterface, &'chan_man TestChannelManager<'node_cfg, 'chan_mon_cfg>, + &'node_cfg test_utils::TestMessageRouter<'chan_mon_cfg>, &'chan_mon_cfg test_utils::TestLogger, >; @@ -1199,7 +1200,7 @@ macro_rules! reload_node { $new_channelmanager = _reload_node(&$node, $new_config, &chanman_encoded, $monitors_encoded); let offers_handler = $crate::sync::Arc::new($crate::offers::flow::OffersMessageFlow::new( - $new_channelmanager.inbound_payment_key, $new_channelmanager.get_our_node_id(), $node.keys_manager, &$new_channelmanager, $node.logger + $new_channelmanager.inbound_payment_key, $new_channelmanager.get_our_node_id(), $node.keys_manager, &$new_channelmanager, $node.message_router, $node.logger )); $node.node = &$new_channelmanager; @@ -3345,7 +3346,7 @@ pub fn create_network<'a, 'b: 'a, 'c: 'b>(node_count: usize, cfgs: &'b Vec panic!("Expected error"), Err(e) => assert_eq!(e, Bolt12SemanticError::MissingPaths), @@ -1677,7 +1678,7 @@ fn fails_creating_refund_or_sending_invoice_without_connected_peers() { disconnect_peers(alice, &[bob, charlie, david, &nodes[4], &nodes[5]]); disconnect_peers(david, &[bob, charlie, &nodes[4], &nodes[5]]); - let absolute_expiry = david.node.duration_since_epoch() + MAX_SHORT_LIVED_RELATIVE_EXPIRY; + let absolute_expiry = david.offers_handler.duration_since_epoch() + MAX_SHORT_LIVED_RELATIVE_EXPIRY; let payment_id = PaymentId([1; 32]); match david.offers_handler.create_refund_builder( 10_000_000, absolute_expiry, payment_id, Retry::Attempts(0), None @@ -2143,7 +2144,7 @@ fn fails_paying_invoice_with_unknown_required_features() { let expanded_key = alice.keys_manager.get_inbound_payment_key(); let secp_ctx = Secp256k1::new(); - let created_at = alice.node.duration_since_epoch(); + let created_at = alice.offers_handler.duration_since_epoch(); let invoice = invoice_request .verify_using_recipient_data(nonce, &expanded_key, &secp_ctx).unwrap() .respond_using_derived_keys_no_std(payment_paths, payment_hash, created_at).unwrap() diff --git a/lightning/src/offers/flow.rs b/lightning/src/offers/flow.rs index 340cb4b0485..fe140b965b9 100644 --- a/lightning/src/offers/flow.rs +++ b/lightning/src/offers/flow.rs @@ -19,7 +19,9 @@ use bitcoin::secp256k1::{self, schnorr, PublicKey, Secp256k1}; use lightning_invoice::PaymentSecret; use types::payment::PaymentHash; -use crate::blinded_path::message::{BlindedMessagePath, MessageContext, OffersContext}; +use crate::blinded_path::message::{ + BlindedMessagePath, MessageContext, MessageForwardNode, OffersContext, +}; use crate::blinded_path::payment::{ BlindedPaymentPath, Bolt12OfferContext, Bolt12RefundContext, PaymentContext, }; @@ -33,11 +35,11 @@ use crate::offers::invoice::{ Bolt12Invoice, DerivedSigningPubkey, ExplicitSigningPubkey, InvoiceBuilder, UnsignedBolt12Invoice, DEFAULT_RELATIVE_EXPIRY, }; -use crate::offers::invoice_request::InvoiceRequest; +use crate::offers::invoice_request::{InvoiceRequest, InvoiceRequestBuilder}; use crate::offers::parse::Bolt12SemanticError; use crate::onion_message::dns_resolution::HumanReadableName; use crate::onion_message::messenger::{ - Destination, MessageSendInstructions, Responder, ResponseInstruction, + Destination, MessageRouter, MessageSendInstructions, Responder, ResponseInstruction, }; use crate::onion_message::offers::{OffersMessage, OffersMessageHandler}; use crate::sync::MutexGuard; @@ -149,6 +151,9 @@ pub trait OffersMessageCommons { &self, amount_msats: u64, payment_secret: PaymentSecret, payment_context: PaymentContext, ) -> Result, ()>; + /// Get the vector of peers that can be used for a blinded path + fn get_peer_for_blinded_path(&self) -> Vec; + /// Verify bolt12 invoice fn verify_bolt12_invoice( &self, invoice: &Bolt12Invoice, context: Option<&OffersContext>, @@ -189,19 +194,6 @@ pub trait OffersMessageCommons { &self, invoice: &StaticInvoice, payment_id: PaymentId, ) -> Result<(), Bolt12PaymentError>; - /// Creates a collection of blinded paths by delegating to [`MessageRouter`] based on - /// the path's intended lifetime. - /// - /// Whether or not the path is compact depends on whether the path is short-lived or long-lived, - /// respectively, based on the given `absolute_expiry` as seconds since the Unix epoch. See - /// [`MAX_SHORT_LIVED_RELATIVE_EXPIRY`]. - /// - /// [`MessageRouter`]: crate::onion_message::messenger::MessageRouter - /// [`MAX_SHORT_LIVED_RELATIVE_EXPIRY`]: crate::ln::channelmanager::MAX_SHORT_LIVED_RELATIVE_EXPIRY - fn create_blinded_paths_using_absolute_expiry( - &self, context: OffersContext, absolute_expiry: Option, - ) -> Result, ()>; - /// Get the [`ChainHash`] of the chain fn get_chain_hash(&self) -> ChainHash; @@ -219,15 +211,6 @@ pub trait OffersMessageCommons { max_total_routing_fee_msat: Option, amount_msats: u64, ) -> Result<(), ()>; - /// Internal pay_for_offer - fn pay_for_offer_intern< - CPP: FnOnce(&InvoiceRequest, Nonce) -> Result<(), Bolt12SemanticError>, - >( - &self, offer: &Offer, quantity: Option, amount_msats: Option, - payer_note: Option, payment_id: PaymentId, - human_readable_name: Option, create_pending_payment: CPP, - ) -> Result<(), Bolt12SemanticError>; - #[cfg(feature = "dnssec")] /// Amount for payment awaiting offer fn amt_msats_for_payment_awaiting_offer(&self, payment_id: PaymentId) -> Result; @@ -237,6 +220,10 @@ pub trait OffersMessageCommons { fn received_offer( &self, payment_id: PaymentId, retryable_invoice_request: Option, ) -> Result<(), ()>; + + #[cfg(not(feature = "std"))] + /// Get the approximate current time using the highest seen timestamp + fn get_highest_seen_timestamp(&self) -> Duration; } /// A trivial trait which describes any [`OffersMessageFlow`]. @@ -254,19 +241,26 @@ pub trait AnOffersMessageFlow { /// A type that may be dereferenced to [`Self::OffersMessageCommons`]. type OMC: Deref; + /// A type implementing [`MessageRouter`]. + type MessageRouter: MessageRouter + ?Sized; + /// A type that may be dereferenced to [`Self::MessageRouter`]. + type MR: Deref; + /// A type implementing [`Logger`]. type Logger: Logger + ?Sized; /// A type that may be dereferenced to [`Self::Logger`]. type L: Deref; /// Returns a reference to the actual [`OffersMessageFlow`] object. - fn get_omf(&self) -> &OffersMessageFlow; + fn get_omf(&self) -> &OffersMessageFlow; } -impl AnOffersMessageFlow for OffersMessageFlow +impl AnOffersMessageFlow + for OffersMessageFlow where ES::Target: EntropySource, OMC::Target: OffersMessageCommons, + MR::Target: MessageRouter, L::Target: Logger, { type EntropySource = ES::Target; @@ -275,10 +269,13 @@ where type OffersMessageCommons = OMC::Target; type OMC = OMC; + type MessageRouter = MR::Target; + type MR = MR; + type Logger = L::Target; type L = L; - fn get_omf(&self) -> &OffersMessageFlow { + fn get_omf(&self) -> &OffersMessageFlow { self } } @@ -293,7 +290,7 @@ where /// - [`Logger`] for detailed operational logging of Offers-related activity. /// - [`OffersMessageCommons`] for core operations shared across Offers messages, such as metadata /// verification and signature handling. -/// - MessageRouter for routing Offers messages to their appropriate destinations within the +/// - [`MessageRouter`] for routing Offers messages to their appropriate destinations within the /// Lightning network. /// - Manages [`OffersMessage`] for creating and processing Offers-related messages. /// - Handles [`DNSResolverMessage`] for resolving human-readable names in Offers messages @@ -569,10 +566,11 @@ where /// [`offers`]: crate::offers /// [`pay_for_offer`]: Self::pay_for_offer /// [`request_refund_payment`]: Self::request_refund_payment -pub struct OffersMessageFlow +pub struct OffersMessageFlow where ES::Target: EntropySource, OMC::Target: OffersMessageCommons, + MR::Target: MessageRouter, L::Target: Logger, { inbound_payment_key: inbound_payment::ExpandedKey, @@ -585,6 +583,8 @@ where /// Contains functions shared between OffersMessageHandler and ChannelManager. commons: OMC, + message_router: MR, + #[cfg(feature = "_test_utils")] /// In testing, it is useful be able to forge a name -> offer mapping so that we can pay an /// offer generated in the test. @@ -598,16 +598,17 @@ where pub logger: L, } -impl OffersMessageFlow +impl OffersMessageFlow where ES::Target: EntropySource, OMC::Target: OffersMessageCommons, + MR::Target: MessageRouter, L::Target: Logger, { /// Creates a new [`OffersMessageFlow`] pub fn new( expanded_inbound_key: inbound_payment::ExpandedKey, our_network_pubkey: PublicKey, - entropy_source: ES, commons: OMC, logger: L, + entropy_source: ES, commons: OMC, message_router: MR, logger: L, ) -> Self { let mut secp_ctx = Secp256k1::new(); secp_ctx.seeded_randomize(&entropy_source.get_secure_random_bytes()); @@ -617,6 +618,7 @@ where our_network_pubkey, secp_ctx, commons, + message_router, entropy_source, #[cfg(feature = "_test_utils")] testing_dnssec_proof_offer_resolution_override: Mutex::new(new_hash_map()), @@ -630,10 +632,150 @@ where } } -impl OffersMessageHandler for OffersMessageFlow +/// The maximum expiration from the current time where an [`Offer`] or [`Refund`] is considered +/// short-lived, while anything with a greater expiration is considered long-lived. +/// +/// Using [`OffersMessageFlow::create_offer_builder`] or [`OffersMessageFlow::create_refund_builder`], +/// will included a [`BlindedMessagePath`] created using: +/// - [`MessageRouter::create_compact_blinded_paths`] when short-lived, and +/// - [`MessageRouter::create_blinded_paths`] when long-lived. +/// +/// [`OffersMessageFlow::create_offer_builder`]: crate::offers::flow::OffersMessageFlow::create_offer_builder +/// [`OffersMessageFlow::create_refund_builder`]: crate::offers::flow::OffersMessageFlow::create_refund_builder +/// +/// +/// Using compact [`BlindedMessagePath`]s may provide better privacy as the [`MessageRouter`] could select +/// more hops. However, since they use short channel ids instead of pubkeys, they are more likely to +/// become invalid over time as channels are closed. Thus, they are only suitable for short-term use. +/// +/// [`Offer`]: crate::offers::offer +/// [`Refund`]: crate::offers::refund +pub const MAX_SHORT_LIVED_RELATIVE_EXPIRY: Duration = Duration::from_secs(60 * 60 * 24); + +impl OffersMessageFlow +where + ES::Target: EntropySource, + OMC::Target: OffersMessageCommons, + MR::Target: MessageRouter, + L::Target: Logger, +{ + /// Creates a collection of blinded paths by delegating to [`MessageRouter`] based on + /// the path's intended lifetime. + /// + /// Whether or not the path is compact depends on whether the path is short-lived or long-lived, + /// respectively, based on the given `absolute_expiry` as seconds since the Unix epoch. See + /// [`MAX_SHORT_LIVED_RELATIVE_EXPIRY`]. + pub fn create_blinded_paths_using_absolute_expiry( + &self, context: OffersContext, absolute_expiry: Option, + ) -> Result, ()> { + let now = self.duration_since_epoch(); + let max_short_lived_absolute_expiry = now.saturating_add(MAX_SHORT_LIVED_RELATIVE_EXPIRY); + + if absolute_expiry.unwrap_or(Duration::MAX) <= max_short_lived_absolute_expiry { + self.create_compact_blinded_paths(context) + } else { + self.commons.create_blinded_paths(MessageContext::Offers(context)) + } + } + + pub(crate) fn duration_since_epoch(&self) -> Duration { + #[cfg(not(feature = "std"))] + let now = self.commons.get_highest_seen_timestamp(); + #[cfg(feature = "std")] + let now = std::time::SystemTime::now() + .duration_since(std::time::SystemTime::UNIX_EPOCH) + .expect("SystemTime::now() should come after SystemTime::UNIX_EPOCH"); + + now + } + + /// Creates a collection of blinded paths by delegating to + /// [`MessageRouter::create_compact_blinded_paths`]. + /// + /// Errors if the `MessageRouter` errors. + fn create_compact_blinded_paths( + &self, context: OffersContext, + ) -> Result, ()> { + let recipient = self.get_our_node_id(); + let secp_ctx = &self.secp_ctx; + + let peers = self.commons.get_peer_for_blinded_path(); + + self.message_router + .create_compact_blinded_paths( + recipient, + MessageContext::Offers(context), + peers, + secp_ctx, + ) + .and_then(|paths| (!paths.is_empty()).then(|| paths).ok_or(())) + } +} + +impl OffersMessageFlow +where + ES::Target: EntropySource, + OMC::Target: OffersMessageCommons, + MR::Target: MessageRouter, + L::Target: Logger, +{ + fn pay_for_offer_intern< + CPP: FnOnce(&InvoiceRequest, Nonce) -> Result<(), Bolt12SemanticError>, + >( + &self, offer: &Offer, quantity: Option, amount_msats: Option, + payer_note: Option, payment_id: PaymentId, + human_readable_name: Option, create_pending_payment: CPP, + ) -> Result<(), Bolt12SemanticError> { + let expanded_key = &self.inbound_payment_key; + let entropy = &*self.entropy_source; + let secp_ctx = &self.secp_ctx; + + let nonce = Nonce::from_entropy_source(entropy); + let builder: InvoiceRequestBuilder = + offer.request_invoice(expanded_key, nonce, secp_ctx, payment_id)?.into(); + let builder = builder.chain_hash(self.commons.get_chain_hash())?; + + let builder = match quantity { + None => builder, + Some(quantity) => builder.quantity(quantity)?, + }; + let builder = match amount_msats { + None => builder, + Some(amount_msats) => builder.amount_msats(amount_msats)?, + }; + let builder = match payer_note { + None => builder, + Some(payer_note) => builder.payer_note(payer_note), + }; + let builder = match human_readable_name { + None => builder, + Some(hrn) => builder.sourced_from_human_readable_name(hrn), + }; + let invoice_request = builder.build_and_sign()?; + + let hmac = payment_id.hmac_for_offer_payment(nonce, expanded_key); + let context = MessageContext::Offers(OffersContext::OutboundPayment { + payment_id, + nonce, + hmac: Some(hmac), + }); + let reply_paths = self + .commons + .create_blinded_paths(context) + .map_err(|_| Bolt12SemanticError::MissingPaths)?; + + create_pending_payment(&invoice_request, nonce)?; + + self.commons.enqueue_invoice_request(invoice_request, reply_paths) + } +} + +impl OffersMessageHandler + for OffersMessageFlow where ES::Target: EntropySource, OMC::Target: OffersMessageCommons, + MR::Target: MessageRouter, L::Target: Logger, { fn handle_message( @@ -945,7 +1087,6 @@ macro_rules! create_offer_builder { ($self: ident, $builder: ty) => { /// /// [`BlindedMessagePath`]: crate::blinded_path::message::BlindedMessagePath /// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest - /// [`MAX_SHORT_LIVED_RELATIVE_EXPIRY`]: crate::ln::channelmanager::MAX_SHORT_LIVED_RELATIVE_EXPIRY /// [`MessageRouter`]: crate::onion_message::messenger::MessageRouter /// [`Offer`]: crate::offers::offer /// [`Router`]: crate::routing::router::Router @@ -959,7 +1100,7 @@ macro_rules! create_offer_builder { ($self: ident, $builder: ty) => { let nonce = Nonce::from_entropy_source(entropy); let context = OffersContext::InvoiceRequest { nonce }; - let path = $self.commons.create_blinded_paths_using_absolute_expiry(context, absolute_expiry) + let path = $self.create_blinded_paths_using_absolute_expiry(context, absolute_expiry) .and_then(|paths| paths.into_iter().next().ok_or(())) .map_err(|_| Bolt12SemanticError::MissingPaths)?; let builder = OfferBuilder::deriving_signing_pubkey(node_id, expanded_key, nonce, secp_ctx) @@ -1022,7 +1163,6 @@ macro_rules! create_refund_builder { ($self: ident, $builder: ty) => { /// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice /// [`Bolt12Invoice::payment_paths`]: crate::offers::invoice::Bolt12Invoice::payment_paths /// [`ChannelManager::abandon_payment`]: crate::ln::channelmanager::ChannelManager::abandon_payment - /// [`MAX_SHORT_LIVED_RELATIVE_EXPIRY`]: crate::ln::channelmanager::MAX_SHORT_LIVED_RELATIVE_EXPIRY /// [`MessageRouter`]: crate::onion_message::messenger::MessageRouter /// [`RouteParameters::from_payment_params_and_value`]: crate::routing::router::RouteParameters::from_payment_params_and_value /// [`Router`]: crate::routing::router::Router @@ -1039,7 +1179,7 @@ macro_rules! create_refund_builder { ($self: ident, $builder: ty) => { let nonce = Nonce::from_entropy_source(entropy); let context = OffersContext::OutboundPayment { payment_id, nonce, hmac: None }; - let path = $self.commons.create_blinded_paths_using_absolute_expiry(context, Some(absolute_expiry)) + let path = $self.create_blinded_paths_using_absolute_expiry(context, Some(absolute_expiry)) .and_then(|paths| paths.into_iter().next().ok_or(())) .map_err(|_| Bolt12SemanticError::MissingPaths)?; @@ -1060,10 +1200,11 @@ macro_rules! create_refund_builder { ($self: ident, $builder: ty) => { } } } -impl OffersMessageFlow +impl OffersMessageFlow where ES::Target: EntropySource, OMC::Target: OffersMessageCommons, + MR::Target: MessageRouter, L::Target: Logger, { #[cfg(not(c_bindings))] @@ -1142,7 +1283,7 @@ where payer_note: Option, payment_id: PaymentId, retry_strategy: Retry, max_total_routing_fee_msat: Option, ) -> Result<(), Bolt12SemanticError> { - self.commons.pay_for_offer_intern( + self.pay_for_offer_intern( offer, quantity, amount_msats, @@ -1360,10 +1501,12 @@ where } #[cfg(feature = "dnssec")] -impl DNSResolverMessageHandler for OffersMessageFlow +impl DNSResolverMessageHandler + for OffersMessageFlow where ES::Target: EntropySource, OMC::Target: OffersMessageCommons, + MR::Target: MessageRouter, L::Target: Logger, { fn handle_dnssec_query( @@ -1391,7 +1534,7 @@ where } if let Ok(amt_msats) = self.commons.amt_msats_for_payment_awaiting_offer(payment_id) { - let offer_pay_res = self.commons.pay_for_offer_intern( + let offer_pay_res = self.pay_for_offer_intern( &offer, None, Some(amt_msats), diff --git a/lightning/src/onion_message/messenger.rs b/lightning/src/onion_message/messenger.rs index 942c64fe0f8..61d9ccd33e3 100644 --- a/lightning/src/onion_message/messenger.rs +++ b/lightning/src/onion_message/messenger.rs @@ -1865,7 +1865,7 @@ pub type SimpleArcOnionMessenger = OnionMessenger< Arc, Arc>, Arc>>, Arc, Arc>>, - Arc, Arc>, Arc>>, + Arc, Arc>, Arc>>, Arc, Arc>>, Arc>>, Arc>, Arc>, IgnoringMessageHandler @@ -1886,7 +1886,7 @@ pub type SimpleArcOnionMessenger = OnionMessenger< Arc, Arc>, Arc>>, Arc, Arc>>, - Arc, Arc>, Arc>>, + Arc, Arc>, Arc>>, Arc, Arc>>, Arc>>, Arc>, IgnoringMessageHandler, IgnoringMessageHandler From f12c83509bd76cb4eb34e31a124937c7ff02a33a Mon Sep 17 00:00:00 2001 From: shaavan Date: Thu, 12 Dec 2024 12:33:59 +0530 Subject: [PATCH 17/30] Introduce create_blinded_paths in flow.rs 1. This allow removing an extra function from commons, simplifying the flow trait. --- lightning/src/ln/channelmanager.rs | 40 ++++++++++++++++------------ lightning/src/offers/flow.rs | 42 ++++++++++++++++++------------ 2 files changed, 48 insertions(+), 34 deletions(-) diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index eb1b2ce2457..6582a4226ee 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -9658,23 +9658,6 @@ where self.pending_outbound_payments.release_invoice_requests_awaiting_invoice() } - fn create_blinded_paths(&self, context: MessageContext) -> Result, ()> { - let recipient = self.get_our_node_id(); - let secp_ctx = &self.secp_ctx; - - let peers = self.per_peer_state.read().unwrap() - .iter() - .map(|(node_id, peer_state)| (node_id, peer_state.lock().unwrap())) - .filter(|(_, peer)| peer.is_connected) - .filter(|(_, peer)| peer.latest_features.supports_onion_messages()) - .map(|(node_id, _)| *node_id) - .collect::>(); - - self.message_router - .create_blinded_paths(recipient, context, peers, secp_ctx) - .and_then(|paths| (!paths.is_empty()).then(|| paths).ok_or(())) - } - fn enqueue_invoice_request( &self, invoice_request: InvoiceRequest, @@ -9891,6 +9874,29 @@ where inbound_payment::get_payment_preimage(payment_hash, payment_secret, &self.inbound_payment_key) } + /// Creates a collection of blinded paths by delegating to + /// [`MessageRouter::create_blinded_paths`]. + /// + /// Errors if the `MessageRouter` errors. + /// + /// [`MessageRouter::create_blinded_paths`]: crate::onion_message::messenger::MessageRouter::create_blinded_paths + pub fn create_blinded_paths(&self, context: MessageContext) -> Result, ()> { + let recipient = self.get_our_node_id(); + let secp_ctx = &self.secp_ctx; + + let peers = self.per_peer_state.read().unwrap() + .iter() + .map(|(node_id, peer_state)| (node_id, peer_state.lock().unwrap())) + .filter(|(_, peer)| peer.is_connected) + .filter(|(_, peer)| peer.latest_features.supports_onion_messages()) + .map(|(node_id, _)| *node_id) + .collect::>(); + + self.message_router + .create_blinded_paths(recipient, context, peers, secp_ctx) + .and_then(|paths| (!paths.is_empty()).then(|| paths).ok_or(())) + } + /// Gets a fake short channel id for use in receiving [phantom node payments]. These fake scids /// are used when constructing the phantom invoice's route hints. /// diff --git a/lightning/src/offers/flow.rs b/lightning/src/offers/flow.rs index fe140b965b9..43a6f4a1377 100644 --- a/lightning/src/offers/flow.rs +++ b/lightning/src/offers/flow.rs @@ -172,14 +172,6 @@ pub trait OffersMessageCommons { &self, ) -> Vec<(PaymentId, RetryableInvoiceRequest)>; - /// Creates a collection of blinded paths by delegating to - /// [`MessageRouter::create_blinded_paths`]. - /// - /// Errors if the `MessageRouter` errors. - /// - /// [`MessageRouter::create_blinded_paths`]: crate::onion_message::messenger::MessageRouter::create_blinded_paths - fn create_blinded_paths(&self, context: MessageContext) -> Result, ()>; - /// Enqueue invoice request fn enqueue_invoice_request( &self, invoice_request: InvoiceRequest, reply_paths: Vec, @@ -674,7 +666,7 @@ where if absolute_expiry.unwrap_or(Duration::MAX) <= max_short_lived_absolute_expiry { self.create_compact_blinded_paths(context) } else { - self.commons.create_blinded_paths(MessageContext::Offers(context)) + self.create_blinded_paths(MessageContext::Offers(context)) } } @@ -689,6 +681,26 @@ where now } + /// Creates a collection of blinded paths by delegating to + /// [`MessageRouter::create_blinded_paths`]. + /// + /// Errors if the `MessageRouter` errors. + /// + /// [`MessageRouter::create_blinded_paths`]: crate::onion_message::messenger::MessageRouter::create_blinded_paths + pub fn create_blinded_paths( + &self, context: MessageContext, + ) -> Result, ()> { + let recipient = self.get_our_node_id(); + let secp_ctx = &self.secp_ctx; + + let peers = + self.commons.get_peer_for_blinded_path().into_iter().map(|node| node.node_id).collect(); + + self.message_router + .create_blinded_paths(recipient, context, peers, secp_ctx) + .and_then(|paths| (!paths.is_empty()).then(|| paths).ok_or(())) + } + /// Creates a collection of blinded paths by delegating to /// [`MessageRouter::create_compact_blinded_paths`]. /// @@ -759,10 +771,8 @@ where nonce, hmac: Some(hmac), }); - let reply_paths = self - .commons - .create_blinded_paths(context) - .map_err(|_| Bolt12SemanticError::MissingPaths)?; + let reply_paths = + self.create_blinded_paths(context).map_err(|_| Bolt12SemanticError::MissingPaths)?; create_pending_payment(&invoice_request, nonce)?; @@ -1032,7 +1042,7 @@ where nonce, hmac: Some(hmac), }); - match self.commons.create_blinded_paths(context) { + match self.create_blinded_paths(context) { Ok(reply_paths) => { match self.commons.enqueue_invoice_request(invoice_request, reply_paths) { Ok(_) => {}, @@ -1385,7 +1395,6 @@ where hmac, }); let reply_paths = self - .commons .create_blinded_paths(context) .map_err(|_| Bolt12SemanticError::MissingPaths)?; @@ -1473,8 +1482,7 @@ where name, &*self.entropy_source, )?; - let reply_paths = - self.commons.create_blinded_paths(MessageContext::DNSResolver(context))?; + let reply_paths = self.create_blinded_paths(MessageContext::DNSResolver(context))?; let expiration = StaleExpiration::TimerTicks(1); self.commons.add_new_awaiting_offer( payment_id, From dd177d2c214dfd3fd306d26c0b06e9074a6bd4c3 Mon Sep 17 00:00:00 2001 From: shaavan Date: Wed, 27 Nov 2024 18:38:28 +0530 Subject: [PATCH 18/30] Move pending_offers_message to flows.rs --- lightning/src/ln/channelmanager.rs | 69 ++++----------------------- lightning/src/ln/offers_tests.rs | 14 +++--- lightning/src/offers/flow.rs | 76 +++++++++++++++++++++++------- 3 files changed, 73 insertions(+), 86 deletions(-) diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 6582a4226ee..a0ce4430746 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -67,15 +67,10 @@ use crate::ln::outbound_payment; use crate::ln::outbound_payment::{OutboundPayments, PendingOutboundPayment, RetryableInvoiceRequest, SendAlongPathArgs, StaleExpiration}; use crate::offers::invoice::Bolt12Invoice; use crate::offers::invoice::UnsignedBolt12Invoice; -use crate::offers::invoice_request::InvoiceRequest; use crate::offers::nonce::Nonce; -use crate::offers::parse::Bolt12SemanticError; use crate::offers::signer; -#[cfg(async_payments)] -use crate::offers::static_invoice::StaticInvoice; use crate::onion_message::async_payments::{AsyncPaymentsMessage, HeldHtlcAvailable, ReleaseHeldHtlc, AsyncPaymentsMessageHandler}; -use crate::onion_message::messenger::{DefaultMessageRouter, Destination, MessageRouter, MessageSendInstructions, Responder, ResponseInstruction}; -use crate::onion_message::offers::OffersMessage; +use crate::onion_message::messenger::{MessageRouter, MessageSendInstructions, Responder, ResponseInstruction}; use crate::sign::{EntropySource, NodeSigner, Recipient, SignerProvider}; use crate::sign::ecdsa::EcdsaChannelSigner; use crate::util::config::{UserConfig, ChannelConfig, ChannelConfigUpdate}; @@ -90,8 +85,15 @@ use crate::util::errors::APIError; #[cfg(feature = "dnssec")] use crate::onion_message::dns_resolution::{DNSResolverMessage, OMNameResolver}; +#[cfg(async_payments)] +use { + crate::offers::static_invoice::StaticInvoice, + crate::onion_message::messenger::Destination, +}; + #[cfg(not(c_bindings))] use { + crate::onion_message::messenger::DefaultMessageRouter, crate::routing::router::DefaultRouter, crate::routing::gossip::NetworkGraph, crate::routing::scoring::{ProbabilisticScorer, ProbabilisticScoringFeeParameters}, @@ -2140,8 +2142,6 @@ where // // Lock order tree: // -// `pending_offers_messages` -// // `pending_async_payments_messages` // // `total_consistency_lock` @@ -2392,10 +2392,6 @@ where event_persist_notifier: Notifier, needs_persist_flag: AtomicBool, - #[cfg(not(any(test, feature = "_test_utils")))] - pending_offers_messages: Mutex>, - #[cfg(any(test, feature = "_test_utils"))] - pub(crate) pending_offers_messages: Mutex>, pending_async_payments_messages: Mutex>, /// Tracks the message events that are to be broadcasted when we are connected to some peer. @@ -3315,7 +3311,6 @@ where needs_persist_flag: AtomicBool::new(false), funding_batch_states: Mutex::new(BTreeMap::new()), - pending_offers_messages: Mutex::new(Vec::new()), pending_async_payments_messages: Mutex::new(Vec::new()), pending_broadcast_messages: Mutex::new(Vec::new()), @@ -9545,10 +9540,6 @@ where MR::Target: MessageRouter, L::Target: Logger, { - fn get_pending_offers_messages(&self) -> MutexGuard<'_, Vec<(OffersMessage, MessageSendInstructions)>> { - self.pending_offers_messages.lock().expect("Mutex is locked by other thread.") - } - #[cfg(feature = "dnssec")] fn get_pending_dns_onion_messages(&self) -> MutexGuard<'_, Vec<(DNSResolverMessage, MessageSendInstructions)>> { self.pending_dns_onion_messages.lock().expect("Mutex is locked by other thread.") @@ -9658,42 +9649,6 @@ where self.pending_outbound_payments.release_invoice_requests_awaiting_invoice() } - fn enqueue_invoice_request( - &self, - invoice_request: InvoiceRequest, - reply_paths: Vec, - ) -> Result<(), Bolt12SemanticError> { - let mut pending_offers_messages = self.pending_offers_messages.lock().unwrap(); - if !invoice_request.paths().is_empty() { - reply_paths - .iter() - .flat_map(|reply_path| invoice_request.paths().iter().map(move |path| (path, reply_path))) - .take(OFFERS_MESSAGE_REQUEST_LIMIT) - .for_each(|(path, reply_path)| { - let instructions = MessageSendInstructions::WithSpecifiedReplyPath { - destination: Destination::BlindedPath(path.clone()), - reply_path: reply_path.clone(), - }; - let message = OffersMessage::InvoiceRequest(invoice_request.clone()); - pending_offers_messages.push((message, instructions)); - }); - } else if let Some(node_id) = invoice_request.issuer_signing_pubkey() { - for reply_path in reply_paths { - let instructions = MessageSendInstructions::WithSpecifiedReplyPath { - destination: Destination::Node(node_id), - reply_path, - }; - let message = OffersMessage::InvoiceRequest(invoice_request.clone()); - pending_offers_messages.push((message, instructions)); - } - } else { - debug_assert!(false); - return Err(Bolt12SemanticError::MissingIssuerSigningPubkey); - } - - Ok(()) - } - fn get_current_blocktime(&self) -> Duration { Duration::from_secs(self.highest_seen_timestamp.load(Ordering::Acquire) as u64) } @@ -9794,13 +9749,6 @@ where } } -/// Defines the maximum number of [`OffersMessage`] including different reply paths to be sent -/// along different paths. -/// Sending multiple requests increases the chances of successful delivery in case some -/// paths are unavailable. However, only one invoice for a given [`PaymentId`] will be paid, -/// even if multiple invoices are received. -pub const OFFERS_MESSAGE_REQUEST_LIMIT: usize = 10; - impl ChannelManager where M::Target: chain::Watch<::EcdsaSigner>, @@ -13194,7 +13142,6 @@ where funding_batch_states: Mutex::new(BTreeMap::new()), - pending_offers_messages: Mutex::new(Vec::new()), pending_async_payments_messages: Mutex::new(Vec::new()), pending_broadcast_messages: Mutex::new(Vec::new()), diff --git a/lightning/src/ln/offers_tests.rs b/lightning/src/ln/offers_tests.rs index 9682f43f31b..96926530af3 100644 --- a/lightning/src/ln/offers_tests.rs +++ b/lightning/src/ln/offers_tests.rs @@ -1325,7 +1325,7 @@ fn fails_authentication_when_handling_invoice_request() { expect_recent_payment!(david, RecentPaymentDetails::AwaitingInvoice, payment_id); connect_peers(david, alice); - match &mut david.node.pending_offers_messages.lock().unwrap().first_mut().unwrap().1 { + match &mut david.offers_handler.pending_offers_messages.lock().unwrap().first_mut().unwrap().1 { MessageSendInstructions::WithSpecifiedReplyPath { destination, .. } => *destination = Destination::Node(alice_id), _ => panic!(), @@ -1350,7 +1350,7 @@ fn fails_authentication_when_handling_invoice_request() { .unwrap(); expect_recent_payment!(david, RecentPaymentDetails::AwaitingInvoice, payment_id); - match &mut david.node.pending_offers_messages.lock().unwrap().first_mut().unwrap().1 { + match &mut david.offers_handler.pending_offers_messages.lock().unwrap().first_mut().unwrap().1 { MessageSendInstructions::WithSpecifiedReplyPath { destination, .. } => *destination = Destination::BlindedPath(invalid_path), _ => panic!(), @@ -1430,7 +1430,7 @@ fn fails_authentication_when_handling_invoice_for_offer() { // Don't send the invoice request, but grab its reply path to use with a different request. let invalid_reply_path = { - let mut pending_offers_messages = david.node.pending_offers_messages.lock().unwrap(); + let mut pending_offers_messages = david.offers_handler.pending_offers_messages.lock().unwrap(); let pending_invoice_request = pending_offers_messages.pop().unwrap(); pending_offers_messages.clear(); match pending_invoice_request.1 { @@ -1447,7 +1447,7 @@ fn fails_authentication_when_handling_invoice_for_offer() { // Swap out the reply path to force authentication to fail when handling the invoice since it // will be sent over the wrong blinded path. { - let mut pending_offers_messages = david.node.pending_offers_messages.lock().unwrap(); + let mut pending_offers_messages = david.offers_handler.pending_offers_messages.lock().unwrap(); let mut pending_invoice_request = pending_offers_messages.first_mut().unwrap(); match &mut pending_invoice_request.1 { MessageSendInstructions::WithSpecifiedReplyPath { reply_path, .. } => @@ -1534,7 +1534,7 @@ fn fails_authentication_when_handling_invoice_for_refund() { let expected_invoice = alice.offers_handler.request_refund_payment(&refund).unwrap(); connect_peers(david, alice); - match &mut alice.node.pending_offers_messages.lock().unwrap().first_mut().unwrap().1 { + match &mut alice.offers_handler.pending_offers_messages.lock().unwrap().first_mut().unwrap().1 { MessageSendInstructions::WithSpecifiedReplyPath { destination, .. } => *destination = Destination::Node(david_id), _ => panic!(), @@ -1565,7 +1565,7 @@ fn fails_authentication_when_handling_invoice_for_refund() { let expected_invoice = alice.offers_handler.request_refund_payment(&refund).unwrap(); - match &mut alice.node.pending_offers_messages.lock().unwrap().first_mut().unwrap().1 { + match &mut alice.offers_handler.pending_offers_messages.lock().unwrap().first_mut().unwrap().1 { MessageSendInstructions::WithSpecifiedReplyPath { destination, .. } => *destination = Destination::BlindedPath(invalid_path), _ => panic!(), @@ -2156,7 +2156,7 @@ fn fails_paying_invoice_with_unknown_required_features() { destination: Destination::BlindedPath(reply_path), }; let message = OffersMessage::Invoice(invoice); - alice.node.pending_offers_messages.lock().unwrap().push((message, instructions)); + alice.offers_handler.pending_offers_messages.lock().unwrap().push((message, instructions)); let onion_message = alice.onion_messenger.next_onion_message_for_peer(charlie_id).unwrap(); charlie.onion_messenger.handle_onion_message(alice_id, &onion_message); diff --git a/lightning/src/offers/flow.rs b/lightning/src/offers/flow.rs index 43a6f4a1377..a94eb125591 100644 --- a/lightning/src/offers/flow.rs +++ b/lightning/src/offers/flow.rs @@ -26,9 +26,7 @@ use crate::blinded_path::payment::{ BlindedPaymentPath, Bolt12OfferContext, Bolt12RefundContext, PaymentContext, }; use crate::events::PaymentFailureReason; -use crate::ln::channelmanager::{ - Bolt12PaymentError, PaymentId, Verification, OFFERS_MESSAGE_REQUEST_LIMIT, -}; +use crate::ln::channelmanager::{Bolt12PaymentError, PaymentId, Verification}; use crate::ln::inbound_payment; use crate::ln::outbound_payment::{Retry, RetryableInvoiceRequest, StaleExpiration}; use crate::offers::invoice::{ @@ -77,11 +75,6 @@ use { /// /// [`ChannelManager`]: crate::ln::channelmanager::ChannelManager pub trait OffersMessageCommons { - /// Get pending offers messages - fn get_pending_offers_messages( - &self, - ) -> MutexGuard<'_, Vec<(OffersMessage, MessageSendInstructions)>>; - #[cfg(feature = "dnssec")] /// Get pending DNS onion messages fn get_pending_dns_onion_messages( @@ -172,11 +165,6 @@ pub trait OffersMessageCommons { &self, ) -> Vec<(PaymentId, RetryableInvoiceRequest)>; - /// Enqueue invoice request - fn enqueue_invoice_request( - &self, invoice_request: InvoiceRequest, reply_paths: Vec, - ) -> Result<(), Bolt12SemanticError>; - /// Get the current time determined by highest seen timestamp fn get_current_blocktime(&self) -> Duration; @@ -577,6 +565,11 @@ where message_router: MR, + #[cfg(not(any(test, feature = "_test_utils")))] + pending_offers_messages: Mutex>, + #[cfg(any(test, feature = "_test_utils"))] + pub(crate) pending_offers_messages: Mutex>, + #[cfg(feature = "_test_utils")] /// In testing, it is useful be able to forge a name -> offer mapping so that we can pay an /// offer generated in the test. @@ -609,9 +602,13 @@ where inbound_payment_key: expanded_inbound_key, our_network_pubkey, secp_ctx, + entropy_source, + commons, + message_router, - entropy_source, + + pending_offers_messages: Mutex::new(Vec::new()), #[cfg(feature = "_test_utils")] testing_dnssec_proof_offer_resolution_override: Mutex::new(new_hash_map()), logger, @@ -644,6 +641,13 @@ where /// [`Refund`]: crate::offers::refund pub const MAX_SHORT_LIVED_RELATIVE_EXPIRY: Duration = Duration::from_secs(60 * 60 * 24); +/// Defines the maximum number of [`OffersMessage`] including different reply paths to be sent +/// along different paths. +/// Sending multiple requests increases the chances of successful delivery in case some +/// paths are unavailable. However, only one invoice for a given [`PaymentId`] will be paid, +/// even if multiple invoices are received. +pub const OFFERS_MESSAGE_REQUEST_LIMIT: usize = 10; + impl OffersMessageFlow where ES::Target: EntropySource, @@ -722,6 +726,42 @@ where ) .and_then(|paths| (!paths.is_empty()).then(|| paths).ok_or(())) } + + fn enqueue_invoice_request( + &self, invoice_request: InvoiceRequest, reply_paths: Vec, + ) -> Result<(), Bolt12SemanticError> { + let mut pending_offers_messages = self.pending_offers_messages.lock().unwrap(); + if !invoice_request.paths().is_empty() { + reply_paths + .iter() + .flat_map(|reply_path| { + invoice_request.paths().iter().map(move |path| (path, reply_path)) + }) + .take(OFFERS_MESSAGE_REQUEST_LIMIT) + .for_each(|(path, reply_path)| { + let instructions = MessageSendInstructions::WithSpecifiedReplyPath { + destination: Destination::BlindedPath(path.clone()), + reply_path: reply_path.clone(), + }; + let message = OffersMessage::InvoiceRequest(invoice_request.clone()); + pending_offers_messages.push((message, instructions)); + }); + } else if let Some(node_id) = invoice_request.issuer_signing_pubkey() { + for reply_path in reply_paths { + let instructions = MessageSendInstructions::WithSpecifiedReplyPath { + destination: Destination::Node(node_id), + reply_path, + }; + let message = OffersMessage::InvoiceRequest(invoice_request.clone()); + pending_offers_messages.push((message, instructions)); + } + } else { + debug_assert!(false); + return Err(Bolt12SemanticError::MissingIssuerSigningPubkey); + } + + Ok(()) + } } impl OffersMessageFlow @@ -776,7 +816,7 @@ where create_pending_payment(&invoice_request, nonce)?; - self.commons.enqueue_invoice_request(invoice_request, reply_paths) + self.enqueue_invoice_request(invoice_request, reply_paths) } } @@ -1044,7 +1084,7 @@ where }); match self.create_blinded_paths(context) { Ok(reply_paths) => { - match self.commons.enqueue_invoice_request(invoice_request, reply_paths) { + match self.enqueue_invoice_request(invoice_request, reply_paths) { Ok(_) => {}, Err(_) => { log_warn!( @@ -1068,7 +1108,7 @@ where } fn release_pending_messages(&self) -> Vec<(OffersMessage, MessageSendInstructions)> { - core::mem::take(&mut self.commons.get_pending_offers_messages()) + core::mem::take(&mut self.pending_offers_messages.lock().unwrap()) } } @@ -1398,7 +1438,7 @@ where .create_blinded_paths(context) .map_err(|_| Bolt12SemanticError::MissingPaths)?; - let mut pending_offers_messages = self.commons.get_pending_offers_messages(); + let mut pending_offers_messages = self.pending_offers_messages.lock().unwrap(); if refund.paths().is_empty() { for reply_path in reply_paths { let instructions = MessageSendInstructions::WithSpecifiedReplyPath { From defec295d4757bcb2063a2493ce9da63d64e6fa8 Mon Sep 17 00:00:00 2001 From: shaavan Date: Wed, 27 Nov 2024 18:45:40 +0530 Subject: [PATCH 19/30] Move pending_dns_onion_messages to flow.rs --- lightning/src/ln/channelmanager.rs | 15 ++------------- lightning/src/offers/flow.rs | 18 +++++++++--------- 2 files changed, 11 insertions(+), 22 deletions(-) diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index a0ce4430746..3978c242c95 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -83,7 +83,7 @@ use crate::util::logger::{Level, Logger, WithContext}; use crate::util::errors::APIError; #[cfg(feature = "dnssec")] -use crate::onion_message::dns_resolution::{DNSResolverMessage, OMNameResolver}; +use crate::onion_message::dns_resolution::OMNameResolver; #[cfg(async_payments)] use { @@ -110,7 +110,7 @@ use core::{cmp, mem}; use core::borrow::Borrow; use core::cell::RefCell; use crate::io::Read; -use crate::sync::{Arc, FairRwLock, LockHeldState, LockTestExt, Mutex, MutexGuard, RwLock, RwLockReadGuard}; +use crate::sync::{Arc, FairRwLock, LockHeldState, LockTestExt, Mutex, RwLock, RwLockReadGuard}; use core::sync::atomic::{AtomicUsize, AtomicBool, Ordering}; use core::time::Duration; use core::ops::Deref; @@ -2414,8 +2414,6 @@ where #[cfg(feature = "dnssec")] hrn_resolver: OMNameResolver, - #[cfg(feature = "dnssec")] - pending_dns_onion_messages: Mutex>, #[cfg(test)] pub(super) entropy_source: ES, @@ -3324,8 +3322,6 @@ where #[cfg(feature = "dnssec")] hrn_resolver: OMNameResolver::new(current_timestamp, params.best_block.height), - #[cfg(feature = "dnssec")] - pending_dns_onion_messages: Mutex::new(Vec::new()), } } @@ -9540,11 +9536,6 @@ where MR::Target: MessageRouter, L::Target: Logger, { - #[cfg(feature = "dnssec")] - fn get_pending_dns_onion_messages(&self) -> MutexGuard<'_, Vec<(DNSResolverMessage, MessageSendInstructions)>> { - self.pending_dns_onion_messages.lock().expect("Mutex is locked by other thread.") - } - #[cfg(feature = "dnssec")] fn get_hrn_resolver(&self) -> &OMNameResolver { &self.hrn_resolver @@ -13157,8 +13148,6 @@ where #[cfg(feature = "dnssec")] hrn_resolver: OMNameResolver::new(highest_seen_timestamp, best_block_height), - #[cfg(feature = "dnssec")] - pending_dns_onion_messages: Mutex::new(Vec::new()), }; for (_, monitor) in args.channel_monitors.iter() { diff --git a/lightning/src/offers/flow.rs b/lightning/src/offers/flow.rs index a94eb125591..d0c76b33315 100644 --- a/lightning/src/offers/flow.rs +++ b/lightning/src/offers/flow.rs @@ -40,7 +40,6 @@ use crate::onion_message::messenger::{ Destination, MessageRouter, MessageSendInstructions, Responder, ResponseInstruction, }; use crate::onion_message::offers::{OffersMessage, OffersMessageHandler}; -use crate::sync::MutexGuard; use crate::offers::invoice_error::InvoiceError; use crate::offers::nonce::Nonce; @@ -75,12 +74,6 @@ use { /// /// [`ChannelManager`]: crate::ln::channelmanager::ChannelManager pub trait OffersMessageCommons { - #[cfg(feature = "dnssec")] - /// Get pending DNS onion messages - fn get_pending_dns_onion_messages( - &self, - ) -> MutexGuard<'_, Vec<(DNSResolverMessage, MessageSendInstructions)>>; - #[cfg(feature = "dnssec")] /// Get hrn resolver fn get_hrn_resolver(&self) -> &OMNameResolver; @@ -570,6 +563,9 @@ where #[cfg(any(test, feature = "_test_utils"))] pub(crate) pending_offers_messages: Mutex>, + #[cfg(feature = "dnssec")] + pending_dns_onion_messages: Mutex>, + #[cfg(feature = "_test_utils")] /// In testing, it is useful be able to forge a name -> offer mapping so that we can pay an /// offer generated in the test. @@ -609,6 +605,10 @@ where message_router, pending_offers_messages: Mutex::new(Vec::new()), + + #[cfg(feature = "dnssec")] + pending_dns_onion_messages: Mutex::new(Vec::new()), + #[cfg(feature = "_test_utils")] testing_dnssec_proof_offer_resolution_override: Mutex::new(new_hash_map()), logger, @@ -1536,7 +1536,7 @@ where .flat_map(|destination| reply_paths.iter().map(move |path| (path, destination))) .take(OFFERS_MESSAGE_REQUEST_LIMIT); for (reply_path, destination) in message_params { - self.commons.get_pending_dns_onion_messages().push(( + self.pending_dns_onion_messages.lock().unwrap().push(( DNSResolverMessage::DNSSECQuery(onion_message.clone()), MessageSendInstructions::WithSpecifiedReplyPath { destination: destination.clone(), @@ -1616,6 +1616,6 @@ where } fn release_pending_messages(&self) -> Vec<(DNSResolverMessage, MessageSendInstructions)> { - core::mem::take(&mut self.commons.get_pending_dns_onion_messages()) + core::mem::take(&mut self.pending_dns_onion_messages.lock().unwrap()) } } From 4818efe4c411ff6098a2b06c17e382060786a4d4 Mon Sep 17 00:00:00 2001 From: shaavan Date: Wed, 27 Nov 2024 18:57:56 +0530 Subject: [PATCH 20/30] Move send_payment_for_bolt12_invoice to flow.rs --- lightning/src/ln/channelmanager.rs | 63 +++++++------------------- lightning/src/offers/flow.rs | 72 +++++++++++++++++++++++++----- 2 files changed, 77 insertions(+), 58 deletions(-) diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 3978c242c95..e647dee8d0c 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -33,7 +33,7 @@ use bitcoin::secp256k1::Secp256k1; use bitcoin::{secp256k1, Sequence, Weight}; use crate::events::FundingInfo; -use crate::blinded_path::message::{AsyncPaymentsContext, MessageContext, MessageForwardNode, OffersContext}; +use crate::blinded_path::message::{AsyncPaymentsContext, MessageContext, MessageForwardNode}; use crate::blinded_path::NodeIdLookUp; use crate::blinded_path::message::BlindedMessagePath; use crate::blinded_path::payment::{BlindedPaymentPath, PaymentConstraints, PaymentContext, UnauthenticatedReceiveTlvs}; @@ -443,11 +443,15 @@ impl Ord for ClaimableHTLC { pub trait Verification { /// Constructs an HMAC to include in [`OffersContext`] for the data along with the given /// [`Nonce`]. + /// + /// [`OffersContext`]: crate::blinded_path::message::OffersContext fn hmac_for_offer_payment( &self, nonce: Nonce, expanded_key: &inbound_payment::ExpandedKey, ) -> Hmac; /// Authenticates the data using an HMAC and a [`Nonce`] taken from an [`OffersContext`]. + /// + /// [`OffersContext`]: crate::blinded_path::message::OffersContext fn verify_for_offer_payment( &self, hmac: Hmac, nonce: Nonce, expanded_key: &inbound_payment::ExpandedKey, ) -> Result<(), ()>; @@ -456,6 +460,8 @@ pub trait Verification { impl Verification for PaymentHash { /// Constructs an HMAC to include in [`OffersContext::InboundPayment`] for the payment hash /// along with the given [`Nonce`]. + /// + /// [`OffersContext::InboundPayment`]: crate::blinded_path::message::OffersContext::InboundPayment fn hmac_for_offer_payment( &self, nonce: Nonce, expanded_key: &inbound_payment::ExpandedKey, ) -> Hmac { @@ -464,6 +470,8 @@ impl Verification for PaymentHash { /// Authenticates the payment id using an HMAC and a [`Nonce`] taken from an /// [`OffersContext::InboundPayment`]. + /// + /// [`OffersContext::InboundPayment`]: crate::blinded_path::message::OffersContext::InboundPayment fn verify_for_offer_payment( &self, hmac: Hmac, nonce: Nonce, expanded_key: &inbound_payment::ExpandedKey, ) -> Result<(), ()> { @@ -518,6 +526,8 @@ impl PaymentId { impl Verification for PaymentId { /// Constructs an HMAC to include in [`OffersContext::OutboundPayment`] for the payment id /// along with the given [`Nonce`]. + /// + /// [`OffersContext::OutboundPayment`]: crate::blinded_path::message::OffersContext::OutboundPayment fn hmac_for_offer_payment( &self, nonce: Nonce, expanded_key: &inbound_payment::ExpandedKey, ) -> Hmac { @@ -526,6 +536,8 @@ impl Verification for PaymentId { /// Authenticates the payment id using an HMAC and a [`Nonce`] taken from an /// [`OffersContext::OutboundPayment`]. + /// + /// [`OffersContext::OutboundPayment`]: crate::blinded_path::message::OffersContext::OutboundPayment fn verify_for_offer_payment( &self, hmac: Hmac, nonce: Nonce, expanded_key: &inbound_payment::ExpandedKey, ) -> Result<(), ()> { @@ -4486,35 +4498,6 @@ where self.pending_outbound_payments.test_set_payment_metadata(payment_id, new_payment_metadata); } - /// Pays the [`Bolt12Invoice`] associated with the `payment_id` encoded in its `payer_metadata`. - /// - /// The invoice's `payer_metadata` is used to authenticate that the invoice was indeed requested - /// before attempting a payment. [`Bolt12PaymentError::UnexpectedInvoice`] is returned if this - /// fails or if the encoded `payment_id` is not recognized. The latter may happen once the - /// payment is no longer tracked because the payment was attempted after: - /// - an invoice for the `payment_id` was already paid, - /// - one full [timer tick] has elapsed since initially requesting the invoice when paying an - /// offer, or - /// - the refund corresponding to the invoice has already expired. - /// - /// To retry the payment, request another invoice using a new `payment_id`. - /// - /// Attempting to pay the same invoice twice while the first payment is still pending will - /// result in a [`Bolt12PaymentError::DuplicateInvoice`]. - /// - /// Otherwise, either [`Event::PaymentSent`] or [`Event::PaymentFailed`] are used to indicate - /// whether or not the payment was successful. - /// - /// [timer tick]: Self::timer_tick_occurred - pub fn send_payment_for_bolt12_invoice( - &self, invoice: &Bolt12Invoice, context: Option<&OffersContext>, - ) -> Result<(), Bolt12PaymentError> { - match self.verify_bolt12_invoice(invoice, context) { - Ok(payment_id) => self.send_payment_for_verified_bolt12_invoice(invoice, payment_id), - Err(()) => Err(Bolt12PaymentError::UnexpectedInvoice), - } - } - #[cfg(async_payments)] fn send_payment_for_static_invoice( &self, payment_id: PaymentId @@ -9601,27 +9584,11 @@ where .collect::>() } - fn verify_bolt12_invoice( - &self, invoice: &Bolt12Invoice, context: Option<&OffersContext>, - ) -> Result { - let secp_ctx = &self.secp_ctx; - let expanded_key = &self.inbound_payment_key; - - match context { - None if invoice.is_for_refund_without_paths() => { - invoice.verify_using_metadata(expanded_key, secp_ctx) - }, - Some(&OffersContext::OutboundPayment { payment_id, nonce, .. }) => { - invoice.verify_using_payer_data(payment_id, nonce, expanded_key, secp_ctx) - }, - _ => Err(()), - } - } - fn send_payment_for_verified_bolt12_invoice(&self, invoice: &Bolt12Invoice, payment_id: PaymentId) -> Result<(), Bolt12PaymentError> { let best_block_height = self.best_block.read().unwrap().height; - let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self); let features = self.bolt12_invoice_features(); + let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self); + self.pending_outbound_payments .send_payment_for_bolt12_invoice( invoice, payment_id, &self.router, self.list_usable_channels(), features, diff --git a/lightning/src/offers/flow.rs b/lightning/src/offers/flow.rs index d0c76b33315..501b8a9e449 100644 --- a/lightning/src/offers/flow.rs +++ b/lightning/src/offers/flow.rs @@ -140,11 +140,6 @@ pub trait OffersMessageCommons { /// Get the vector of peers that can be used for a blinded path fn get_peer_for_blinded_path(&self) -> Vec; - /// Verify bolt12 invoice - fn verify_bolt12_invoice( - &self, invoice: &Bolt12Invoice, context: Option<&OffersContext>, - ) -> Result; - /// Send payment for verified bolt12 invoice fn send_payment_for_verified_bolt12_invoice( &self, invoice: &Bolt12Invoice, payment_id: PaymentId, @@ -820,6 +815,64 @@ where } } +impl OffersMessageFlow +where + ES::Target: EntropySource, + OMC::Target: OffersMessageCommons, + MR::Target: MessageRouter, + L::Target: Logger, +{ + fn verify_bolt12_invoice( + &self, invoice: &Bolt12Invoice, context: Option<&OffersContext>, + ) -> Result { + let secp_ctx = &self.secp_ctx; + let expanded_key = &self.inbound_payment_key; + + match context { + None if invoice.is_for_refund_without_paths() => { + invoice.verify_using_metadata(expanded_key, secp_ctx) + }, + Some(&OffersContext::OutboundPayment { payment_id, nonce, .. }) => { + invoice.verify_using_payer_data(payment_id, nonce, expanded_key, secp_ctx) + }, + _ => Err(()), + } + } + + /// Pays the [`Bolt12Invoice`] associated with the `payment_id` encoded in its `payer_metadata`. + /// + /// The invoice's `payer_metadata` is used to authenticate that the invoice was indeed requested + /// before attempting a payment. [`Bolt12PaymentError::UnexpectedInvoice`] is returned if this + /// fails or if the encoded `payment_id` is not recognized. The latter may happen once the + /// payment is no longer tracked because the payment was attempted after: + /// - an invoice for the `payment_id` was already paid, + /// - one full [timer tick] has elapsed since initially requesting the invoice when paying an + /// offer, or + /// - the refund corresponding to the invoice has already expired. + /// + /// To retry the payment, request another invoice using a new `payment_id`. + /// + /// Attempting to pay the same invoice twice while the first payment is still pending will + /// result in a [`Bolt12PaymentError::DuplicateInvoice`]. + /// + /// Otherwise, either [`Event::PaymentSent`] or [`Event::PaymentFailed`] are used to indicate + /// whether or not the payment was successful. + /// + /// [`Event::PaymentSent`]: crate::events::Event::PaymentSent + /// [`Event::PaymentFailed`]: crate::events::Event::PaymentFailed + /// [timer tick]: crate::ln::channelmanager::ChannelManager::timer_tick_occurred + pub fn send_payment_for_bolt12_invoice( + &self, invoice: &Bolt12Invoice, context: Option<&OffersContext>, + ) -> Result<(), Bolt12PaymentError> { + match self.verify_bolt12_invoice(invoice, context) { + Ok(payment_id) => { + self.commons.send_payment_for_verified_bolt12_invoice(invoice, payment_id) + }, + Err(()) => Err(Bolt12PaymentError::UnexpectedInvoice), + } + } +} + impl OffersMessageHandler for OffersMessageFlow where @@ -1003,11 +1056,10 @@ where } }, OffersMessage::Invoice(invoice) => { - let payment_id = - match self.commons.verify_bolt12_invoice(&invoice, context.as_ref()) { - Ok(payment_id) => payment_id, - Err(()) => return None, - }; + let payment_id = match self.verify_bolt12_invoice(&invoice, context.as_ref()) { + Ok(payment_id) => payment_id, + Err(()) => return None, + }; let logger = WithContext::from(&self.logger, None, None, Some(invoice.payment_hash())); From d71a607874adce79ae2688ffea9634af687db8e6 Mon Sep 17 00:00:00 2001 From: shaavan Date: Fri, 13 Dec 2024 16:45:57 +0530 Subject: [PATCH 21/30] Introduce is_compact parameter for DefaultMessageRouter Add a new `is_compact` to the `DefaultMessageRouter`, enabling control over the type of blinded path generated when `create_blinded_paths` is invoked. This enhancement introduces flexibility to create blinded path of required type while also unifying the blinded path creation logic into a single, cohesive function. --- lightning-background-processor/src/lib.rs | 2 +- lightning/src/onion_message/functional_tests.rs | 2 +- lightning/src/onion_message/messenger.rs | 14 +++++++++++--- lightning/src/util/test_utils.rs | 2 +- 4 files changed, 14 insertions(+), 6 deletions(-) diff --git a/lightning-background-processor/src/lib.rs b/lightning-background-processor/src/lib.rs index 7e7a10e7f16..24a4ad59835 100644 --- a/lightning-background-processor/src/lib.rs +++ b/lightning-background-processor/src/lib.rs @@ -1556,7 +1556,7 @@ mod tests { scorer.clone(), Default::default(), )); - let msg_router = Arc::new(DefaultMessageRouter::new( + let msg_router = Arc::new(DefaultMessageRouter::normal_blinded_paths( network_graph.clone(), Arc::clone(&keys_manager), )); diff --git a/lightning/src/onion_message/functional_tests.rs b/lightning/src/onion_message/functional_tests.rs index f9d73f05ff3..022aec6e9de 100644 --- a/lightning/src/onion_message/functional_tests.rs +++ b/lightning/src/onion_message/functional_tests.rs @@ -273,7 +273,7 @@ fn create_nodes_using_cfgs(cfgs: Vec) -> Vec { let node_id_lookup = Arc::new(EmptyNodeIdLookUp {}); let message_router = Arc::new( - DefaultMessageRouter::new(network_graph.clone(), entropy_source.clone()) + DefaultMessageRouter::normal_blinded_paths(network_graph.clone(), entropy_source.clone()) ); let offers_message_handler = Arc::new(TestOffersMessageHandler {}); let async_payments_message_handler = Arc::new(TestAsyncPaymentsMessageHandler {}); diff --git a/lightning/src/onion_message/messenger.rs b/lightning/src/onion_message/messenger.rs index 61d9ccd33e3..2aa1c268e1d 100644 --- a/lightning/src/onion_message/messenger.rs +++ b/lightning/src/onion_message/messenger.rs @@ -504,6 +504,7 @@ where { network_graph: G, entropy_source: ES, + is_compact: bool, } impl>, L: Deref, ES: Deref> DefaultMessageRouter @@ -512,10 +513,18 @@ where ES::Target: EntropySource, { /// Creates a [`DefaultMessageRouter`] using the given [`NetworkGraph`]. - pub fn new(network_graph: G, entropy_source: ES) -> Self { - Self { network_graph, entropy_source } + pub fn normal_blinded_paths(network_graph: G, entropy_source: ES) -> Self { + Self { network_graph, entropy_source, is_compact: false } } + /// Configures the [`DefaultMessageRouter`] to create compact blinded paths. + /// + /// Compact blinded paths use short channel IDs (SCIDs) instead of public keys, reducing serialization + /// size. This is useful for constrained mediums like QR codes. SCIDs are passed via [`MessageForwardNode`]. + pub fn compact_blinded_paths(network_graph: G, entropy_source: ES) -> Self { + Self { network_graph, entropy_source, is_compact: true } + } + fn create_blinded_paths_from_iter< I: ExactSizeIterator, T: secp256k1::Signing + secp256k1::Verification @@ -667,7 +676,6 @@ where ) -> Result, ()> { Self::create_compact_blinded_paths(&self.network_graph, recipient, context, peers, &self.entropy_source, secp_ctx) } - } /// A path for sending an [`OnionMessage`]. diff --git a/lightning/src/util/test_utils.rs b/lightning/src/util/test_utils.rs index eb4d196b452..dd981326438 100644 --- a/lightning/src/util/test_utils.rs +++ b/lightning/src/util/test_utils.rs @@ -280,7 +280,7 @@ pub struct TestMessageRouter<'a> { impl<'a> TestMessageRouter<'a> { pub fn new(network_graph: Arc>, entropy_source: &'a TestKeysInterface) -> Self { - Self { inner: DefaultMessageRouter::new(network_graph, entropy_source) } + Self { inner: DefaultMessageRouter::normal_blinded_paths(network_graph, entropy_source) } } } From 71c8591d844cc87951a1416818ab3a5da36725c2 Mon Sep 17 00:00:00 2001 From: shaavan Date: Wed, 8 Jan 2025 22:34:36 +0530 Subject: [PATCH 22/30] Introduce NullMessageRouter - To be used when the user wants to create No Blinded Path --- lightning/src/onion_message/messenger.rs | 32 ++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/lightning/src/onion_message/messenger.rs b/lightning/src/onion_message/messenger.rs index 2aa1c268e1d..22b0de49b1b 100644 --- a/lightning/src/onion_message/messenger.rs +++ b/lightning/src/onion_message/messenger.rs @@ -678,6 +678,38 @@ where } } +/// A [`MessageRouter`] implementation that does not perform routing. +/// +/// # Behavior +/// +/// `NullMessageRouter` serves as a placeholder router that neither creates +/// [`BlindedMessagePath`]s nor finds any paths for messages. It can be used +/// in cases where no routing is required or desired. +pub struct NullMessageRouter {} + +impl MessageRouter for NullMessageRouter { + fn find_path( + &self, _sender: PublicKey, _peers: Vec, _destination: Destination + ) -> Result { + Err(()) + } + fn create_blinded_paths< + T: secp256k1::Signing + secp256k1::Verification + >( + &self, _recipient: PublicKey, _context: MessageContext, _peers: Vec, _secp_ctx: &Secp256k1, + ) -> Result, ()> { + Ok(Vec::new()) + } + + fn create_compact_blinded_paths< + T: secp256k1::Signing + secp256k1::Verification + >( + &self, _recipient: PublicKey, _context: MessageContext, _peers: Vec, _secp_ctx: &Secp256k1, + ) -> Result, ()> { + Ok(Vec::new()) + } +} + /// A path for sending an [`OnionMessage`]. #[derive(Clone)] pub struct OnionMessagePath { From 28cd8a2b9de4159b35c04408901c53f235c94f86 Mon Sep 17 00:00:00 2001 From: shaavan Date: Fri, 13 Dec 2024 17:14:51 +0530 Subject: [PATCH 23/30] Allow create_blinded_paths to take `Vec` as input Prepare `create_blinded_paths` to handle both normal blinded paths and compact blinded paths creation by enabling it to accept a `Vec` as input. This change ensures the function has sufficient information for flexible and future-proof path creation, aligning with upcoming changes. --- fuzz/src/chanmon_consistency.rs | 4 ++-- fuzz/src/full_stack.rs | 4 ++-- fuzz/src/onion_message.rs | 4 ++-- lightning-dns-resolver/src/lib.rs | 6 ++++-- lightning/src/ln/channelmanager.rs | 8 +------- lightning/src/offers/flow.rs | 3 +-- lightning/src/onion_message/messenger.rs | 22 +++++++++------------- lightning/src/util/test_utils.rs | 2 +- 8 files changed, 22 insertions(+), 31 deletions(-) diff --git a/fuzz/src/chanmon_consistency.rs b/fuzz/src/chanmon_consistency.rs index e9a6c919e6d..f60e2f0195d 100644 --- a/fuzz/src/chanmon_consistency.rs +++ b/fuzz/src/chanmon_consistency.rs @@ -33,7 +33,7 @@ use bitcoin::hashes::sha256d::Hash as Sha256dHash; use bitcoin::hashes::Hash as TraitImport; use bitcoin::WPubkeyHash; -use lightning::blinded_path::message::{BlindedMessagePath, MessageContext}; +use lightning::blinded_path::message::{BlindedMessagePath, MessageContext, MessageForwardNode}; use lightning::blinded_path::payment::{BlindedPaymentPath, ReceiveTlvs}; use lightning::chain; use lightning::chain::chaininterface::{BroadcasterInterface, ConfirmationTarget, FeeEstimator}; @@ -148,7 +148,7 @@ impl MessageRouter for FuzzRouter { } fn create_blinded_paths( - &self, _recipient: PublicKey, _context: MessageContext, _peers: Vec, + &self, _recipient: PublicKey, _context: MessageContext, _peers: Vec, _secp_ctx: &Secp256k1, ) -> Result, ()> { unreachable!() diff --git a/fuzz/src/full_stack.rs b/fuzz/src/full_stack.rs index 6526405348e..658845e6f13 100644 --- a/fuzz/src/full_stack.rs +++ b/fuzz/src/full_stack.rs @@ -30,7 +30,7 @@ use bitcoin::hashes::Hash as _; use bitcoin::hex::FromHex; use bitcoin::WPubkeyHash; -use lightning::blinded_path::message::{BlindedMessagePath, MessageContext}; +use lightning::blinded_path::message::{BlindedMessagePath, MessageContext, MessageForwardNode}; use lightning::blinded_path::payment::{BlindedPaymentPath, ReceiveTlvs}; use lightning::chain; use lightning::chain::chaininterface::{BroadcasterInterface, ConfirmationTarget, FeeEstimator}; @@ -175,7 +175,7 @@ impl MessageRouter for FuzzRouter { } fn create_blinded_paths( - &self, _recipient: PublicKey, _context: MessageContext, _peers: Vec, + &self, _recipient: PublicKey, _context: MessageContext, _peers: Vec, _secp_ctx: &Secp256k1, ) -> Result, ()> { unreachable!() diff --git a/fuzz/src/onion_message.rs b/fuzz/src/onion_message.rs index 94da4d09be5..69a6ca4dd25 100644 --- a/fuzz/src/onion_message.rs +++ b/fuzz/src/onion_message.rs @@ -6,7 +6,7 @@ use bitcoin::secp256k1::schnorr; use bitcoin::secp256k1::{self, PublicKey, Scalar, Secp256k1, SecretKey}; use lightning::blinded_path::message::{ - AsyncPaymentsContext, BlindedMessagePath, MessageContext, OffersContext, + AsyncPaymentsContext, BlindedMessagePath, MessageContext, MessageForwardNode, OffersContext, }; use lightning::blinded_path::EmptyNodeIdLookUp; use lightning::ln::inbound_payment::ExpandedKey; @@ -100,7 +100,7 @@ impl MessageRouter for TestMessageRouter { } fn create_blinded_paths( - &self, _recipient: PublicKey, _context: MessageContext, _peers: Vec, + &self, _recipient: PublicKey, _context: MessageContext, _peers: Vec, _secp_ctx: &Secp256k1, ) -> Result, ()> { unreachable!() diff --git a/lightning-dns-resolver/src/lib.rs b/lightning-dns-resolver/src/lib.rs index 9e67389756a..ce4c877201b 100644 --- a/lightning-dns-resolver/src/lib.rs +++ b/lightning-dns-resolver/src/lib.rs @@ -159,7 +159,9 @@ mod test { use bitcoin::secp256k1::{self, PublicKey, Secp256k1}; use bitcoin::Block; - use lightning::blinded_path::message::{BlindedMessagePath, MessageContext}; + use lightning::blinded_path::message::{ + BlindedMessagePath, MessageContext, MessageForwardNode, + }; use lightning::blinded_path::NodeIdLookUp; use lightning::events::{Event, PaymentPurpose}; use lightning::ln::channelmanager::{PaymentId, Retry}; @@ -225,7 +227,7 @@ mod test { } fn create_blinded_paths( - &self, recipient: PublicKey, context: MessageContext, _peers: Vec, + &self, recipient: PublicKey, context: MessageContext, _peers: Vec, secp_ctx: &Secp256k1, ) -> Result, ()> { let keys = KeysManager::new(&[0; 32], 42, 43); diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index e647dee8d0c..f8680b90d3b 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -9790,13 +9790,7 @@ where let recipient = self.get_our_node_id(); let secp_ctx = &self.secp_ctx; - let peers = self.per_peer_state.read().unwrap() - .iter() - .map(|(node_id, peer_state)| (node_id, peer_state.lock().unwrap())) - .filter(|(_, peer)| peer.is_connected) - .filter(|(_, peer)| peer.latest_features.supports_onion_messages()) - .map(|(node_id, _)| *node_id) - .collect::>(); + let peers = self.get_peer_for_blinded_path(); self.message_router .create_blinded_paths(recipient, context, peers, secp_ctx) diff --git a/lightning/src/offers/flow.rs b/lightning/src/offers/flow.rs index 501b8a9e449..6706aba9810 100644 --- a/lightning/src/offers/flow.rs +++ b/lightning/src/offers/flow.rs @@ -692,8 +692,7 @@ where let recipient = self.get_our_node_id(); let secp_ctx = &self.secp_ctx; - let peers = - self.commons.get_peer_for_blinded_path().into_iter().map(|node| node.node_id).collect(); + let peers = self.commons.get_peer_for_blinded_path(); self.message_router .create_blinded_paths(recipient, context, peers, secp_ctx) diff --git a/lightning/src/onion_message/messenger.rs b/lightning/src/onion_message/messenger.rs index 22b0de49b1b..a09d5d3a768 100644 --- a/lightning/src/onion_message/messenger.rs +++ b/lightning/src/onion_message/messenger.rs @@ -184,7 +184,7 @@ for OnionMessenger where /// # }) /// # } /// # fn create_blinded_paths( -/// # &self, _recipient: PublicKey, _context: MessageContext, _peers: Vec, _secp_ctx: &Secp256k1 +/// # &self, _recipient: PublicKey, _context: MessageContext, _peers: Vec, _secp_ctx: &Secp256k1 /// # ) -> Result, ()> { /// # unreachable!() /// # } @@ -459,7 +459,7 @@ pub trait MessageRouter { fn create_blinded_paths< T: secp256k1::Signing + secp256k1::Verification >( - &self, recipient: PublicKey, context: MessageContext, peers: Vec, secp_ctx: &Secp256k1, + &self, recipient: PublicKey, context: MessageContext, peers: Vec, secp_ctx: &Secp256k1, ) -> Result, ()>; /// Creates compact [`BlindedMessagePath`]s to the `recipient` node. The nodes in `peers` are @@ -481,10 +481,6 @@ pub trait MessageRouter { &self, recipient: PublicKey, context: MessageContext, peers: Vec, secp_ctx: &Secp256k1, ) -> Result, ()> { - let peers = peers - .into_iter() - .map(|MessageForwardNode { node_id, short_channel_id: _ }| node_id) - .collect(); self.create_blinded_paths(recipient, context, peers, secp_ctx) } } @@ -632,11 +628,8 @@ where T: secp256k1::Signing + secp256k1::Verification >( network_graph: &G, recipient: PublicKey, context: MessageContext, - peers: Vec, entropy_source: &ES, secp_ctx: &Secp256k1, + peers: Vec, entropy_source: &ES, secp_ctx: &Secp256k1, ) -> Result, ()> { - let peers = peers - .into_iter() - .map(|node_id| MessageForwardNode { node_id, short_channel_id: None }); Self::create_blinded_paths_from_iter(network_graph, recipient, context, peers.into_iter(), entropy_source, secp_ctx, false) } @@ -664,7 +657,7 @@ where fn create_blinded_paths< T: secp256k1::Signing + secp256k1::Verification >( - &self, recipient: PublicKey, context: MessageContext, peers: Vec, secp_ctx: &Secp256k1, + &self, recipient: PublicKey, context: MessageContext, peers: Vec, secp_ctx: &Secp256k1, ) -> Result, ()> { Self::create_blinded_paths(&self.network_graph, recipient, context, peers, &self.entropy_source, secp_ctx) } @@ -696,7 +689,7 @@ impl MessageRouter for NullMessageRouter { fn create_blinded_paths< T: secp256k1::Signing + secp256k1::Verification >( - &self, _recipient: PublicKey, _context: MessageContext, _peers: Vec, _secp_ctx: &Secp256k1, + &self, _recipient: PublicKey, _context: MessageContext, _peers: Vec, _secp_ctx: &Secp256k1, ) -> Result, ()> { Ok(Vec::new()) } @@ -1308,7 +1301,10 @@ where let peers = self.message_recipients.lock().unwrap() .iter() .filter(|(_, peer)| matches!(peer, OnionMessageRecipient::ConnectedPeer(_))) - .map(|(node_id, _ )| *node_id) + .map(|(node_id, _ )| MessageForwardNode { + node_id: *node_id, + short_channel_id: None, + }) .collect::>(); self.message_router diff --git a/lightning/src/util/test_utils.rs b/lightning/src/util/test_utils.rs index dd981326438..22df5afd7dc 100644 --- a/lightning/src/util/test_utils.rs +++ b/lightning/src/util/test_utils.rs @@ -293,7 +293,7 @@ impl<'a> MessageRouter for TestMessageRouter<'a> { fn create_blinded_paths( &self, recipient: PublicKey, context: MessageContext, - peers: Vec, secp_ctx: &Secp256k1, + peers: Vec, secp_ctx: &Secp256k1, ) -> Result, ()> { self.inner.create_blinded_paths(recipient, context, peers, secp_ctx) } From dd5d84f9bab316e9ab3aae680c19364df7bb50db Mon Sep 17 00:00:00 2001 From: shaavan Date: Fri, 13 Dec 2024 18:06:49 +0530 Subject: [PATCH 24/30] Update DefaultMessageRouter's MessageRouter Implementation This commit updates the `create_blinded_path` implementation to use the `config` field introduced earlier. The `config` field determines which type of blinded path to create or whether to skip creating blinded paths altogether. --- lightning/src/onion_message/messenger.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lightning/src/onion_message/messenger.rs b/lightning/src/onion_message/messenger.rs index a09d5d3a768..25cc3f2fc07 100644 --- a/lightning/src/onion_message/messenger.rs +++ b/lightning/src/onion_message/messenger.rs @@ -624,13 +624,13 @@ where } } - pub(crate) fn create_blinded_paths< + pub(crate) fn create_blinded_paths_internal< T: secp256k1::Signing + secp256k1::Verification >( - network_graph: &G, recipient: PublicKey, context: MessageContext, + &self, network_graph: &G, recipient: PublicKey, context: MessageContext, peers: Vec, entropy_source: &ES, secp_ctx: &Secp256k1, ) -> Result, ()> { - Self::create_blinded_paths_from_iter(network_graph, recipient, context, peers.into_iter(), entropy_source, secp_ctx, false) + Self::create_blinded_paths_from_iter(network_graph, recipient, context, peers.into_iter(), entropy_source, secp_ctx, self.is_compact) } pub(crate) fn create_compact_blinded_paths< @@ -659,7 +659,7 @@ where >( &self, recipient: PublicKey, context: MessageContext, peers: Vec, secp_ctx: &Secp256k1, ) -> Result, ()> { - Self::create_blinded_paths(&self.network_graph, recipient, context, peers, &self.entropy_source, secp_ctx) + self.create_blinded_paths_internal(&self.network_graph, recipient, context, peers, &self.entropy_source, secp_ctx) } fn create_compact_blinded_paths< From cfcb508b08acf5856fead941a2498d2012fb1dce Mon Sep 17 00:00:00 2001 From: shaavan Date: Mon, 30 Dec 2024 16:48:14 +0530 Subject: [PATCH 25/30] Introduce create_offer_builder_using_router This update adds the flexibility to pass a router directly to the `create_offer_builder_using_router` for blinded path creation. The reasoning behind this change is twofold: 1. It streamlines the blinded path creation process by allowing users to specify the type of blinded path (or no blinded paths) they want in the offer through the selected router behavior. 2. It introduces the option to pass a custom router, enabling users to implement custom behavior for blinded path handling if needed. --- lightning/src/offers/flow.rs | 55 ++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/lightning/src/offers/flow.rs b/lightning/src/offers/flow.rs index 6706aba9810..643d256e365 100644 --- a/lightning/src/offers/flow.rs +++ b/lightning/src/offers/flow.rs @@ -1215,6 +1215,61 @@ macro_rules! create_offer_builder { ($self: ident, $builder: ty) => { Ok(builder.into()) } + + /// Creates an [`OfferBuilder`] to build an [`Offer`] recognized by the + /// [`OffersMessageFlow`] for handling [`InvoiceRequest`] messages. + /// + /// # Privacy + /// + /// - Constructs a [`BlindedMessagePath`] for the offer using a custom [`MessageRouter`]. + /// Users can implement a custom [`MessageRouter`] to define properties of the + /// [`BlindedMessagePath`] as required or opt not to create any `BlindedMessagePath`. + /// - Uses a derived signing pubkey in the offer for recipient privacy. + /// + /// # Limitations + /// + /// - Requires a direct connection to the introduction node in the responding + /// [`InvoiceRequest`]'s reply path. + /// + /// # Errors + /// + /// - Returns an error if the parameterized [`Router`] fails to create a blinded path. + /// + /// [`BlindedMessagePath`]: crate::blinded_path::message::BlindedMessagePath + /// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest + /// [`MessageRouter`]: crate::onion_message::messenger::MessageRouter + /// [`Offer`]: crate::offers::offer + /// [`Router`]: crate::routing::router::Router + pub fn create_offer_builder_using_router( + &$self, + router: M, + ) -> Result<$builder, Bolt12SemanticError> { + // Extract necessary values from `self` + let node_id = $self.get_our_node_id(); + let expanded_key = &$self.inbound_payment_key; + let entropy = &*$self.entropy_source; + let secp_ctx = &$self.secp_ctx; + + // Generate nonce and context for the offer + let nonce = Nonce::from_entropy_source(entropy); + let context = MessageContext::Offers(OffersContext::InvoiceRequest { nonce }); + + let peers = $self.commons.get_peer_for_blinded_path(); + + let paths = router.create_blinded_paths(node_id, context, peers, secp_ctx) + .map_err(|_| Bolt12SemanticError::MissingPaths)?; + + // Initialize the OfferBuilder with the required parameters + let mut builder = OfferBuilder::deriving_signing_pubkey(node_id, expanded_key, nonce, secp_ctx) + .chain_hash($self.commons.get_chain_hash()); + + for path in paths { + builder = builder.path(path) + } + + // Return the constructed builder + Ok(builder.into()) + } } } macro_rules! create_refund_builder { ($self: ident, $builder: ty) => { From 0dc1b1ee9b4dbca4192631cf57fd883a83945b06 Mon Sep 17 00:00:00 2001 From: shaavan Date: Tue, 31 Dec 2024 15:40:44 +0530 Subject: [PATCH 26/30] Update `create_offer_builder` to exclusively use `create_blinded_paths` Reasoning: With the introduction of `create_offer_builder_using_router`, which allows the use of a custom router to create user-defined blinded paths, this commit ensures the default implementation (`create_offer_builder`) systematically generates only standard blinded paths. This change makes the default behavior predictable, while still providing the flexibility to create custom paths (e.g., compact blinded paths) through `create_offer_builder_with_router`. --- lightning-dns-resolver/src/lib.rs | 2 +- .../src/ln/max_payment_path_len_tests.rs | 2 +- lightning/src/ln/offers_tests.rs | 54 ++++++++++--------- lightning/src/offers/flow.rs | 23 ++++---- 4 files changed, 41 insertions(+), 40 deletions(-) diff --git a/lightning-dns-resolver/src/lib.rs b/lightning-dns-resolver/src/lib.rs index ce4c877201b..f6e8ac1a98f 100644 --- a/lightning-dns-resolver/src/lib.rs +++ b/lightning-dns-resolver/src/lib.rs @@ -393,7 +393,7 @@ mod test { let name = HumanReadableName::from_encoded("matt@mattcorallo.com").unwrap(); // When we get the proof back, override its contents to an offer from nodes[1] - let bs_offer = nodes[1].offers_handler.create_offer_builder(None).unwrap().build().unwrap(); + let bs_offer = nodes[1].offers_handler.create_offer_builder().unwrap().build().unwrap(); nodes[0] .offers_handler .testing_dnssec_proof_offer_resolution_override diff --git a/lightning/src/ln/max_payment_path_len_tests.rs b/lightning/src/ln/max_payment_path_len_tests.rs index f718212985c..8a37aa2e36d 100644 --- a/lightning/src/ln/max_payment_path_len_tests.rs +++ b/lightning/src/ln/max_payment_path_len_tests.rs @@ -390,7 +390,7 @@ fn bolt12_invoice_too_large_blinded_paths() { ) ]); - let offer = nodes[1].offers_handler.create_offer_builder(None).unwrap().build().unwrap(); + let offer = nodes[1].offers_handler.create_offer_builder().unwrap().build().unwrap(); let payment_id = PaymentId([1; 32]); nodes[0].offers_handler.pay_for_offer(&offer, None, Some(5000), None, payment_id, Retry::Attempts(0), None).unwrap(); let invreq_om = nodes[0].onion_messenger.next_onion_message_for_peer(nodes[1].node.get_our_node_id()).unwrap(); diff --git a/lightning/src/ln/offers_tests.rs b/lightning/src/ln/offers_tests.rs index 96926530af3..4efa3640509 100644 --- a/lightning/src/ln/offers_tests.rs +++ b/lightning/src/ln/offers_tests.rs @@ -58,7 +58,7 @@ use crate::offers::invoice_error::InvoiceError; use crate::offers::invoice_request::{InvoiceRequest, InvoiceRequestFields}; use crate::offers::nonce::Nonce; use crate::offers::parse::Bolt12SemanticError; -use crate::onion_message::messenger::{Destination, PeeledOnion, MessageSendInstructions}; +use crate::onion_message::messenger::{DefaultMessageRouter, Destination, MessageSendInstructions, PeeledOnion}; use crate::onion_message::offers::OffersMessage; use crate::onion_message::packet::ParsedOnionMessageContents; use crate::routing::gossip::{NodeAlias, NodeId}; @@ -296,7 +296,7 @@ fn prefers_non_tor_nodes_in_blinded_paths() { announce_node_address(charlie, &[alice, bob, david, &nodes[4], &nodes[5]], tor.clone()); let offer = bob.offers_handler - .create_offer_builder(None).unwrap() + .create_offer_builder().unwrap() .amount_msats(10_000_000) .build().unwrap(); assert_ne!(offer.issuer_signing_pubkey(), Some(bob_id)); @@ -312,7 +312,7 @@ fn prefers_non_tor_nodes_in_blinded_paths() { announce_node_address(&nodes[5], &[alice, bob, charlie, david, &nodes[4]], tor.clone()); let offer = bob.offers_handler - .create_offer_builder(None).unwrap() + .create_offer_builder().unwrap() .amount_msats(10_000_000) .build().unwrap(); assert_ne!(offer.issuer_signing_pubkey(), Some(bob_id)); @@ -363,12 +363,12 @@ fn prefers_more_connected_nodes_in_blinded_paths() { disconnect_peers(david, &[bob, &nodes[4], &nodes[5]]); let offer = bob.offers_handler - .create_offer_builder(None).unwrap() + .create_offer_builder().unwrap() .amount_msats(10_000_000) .build().unwrap(); assert_ne!(offer.issuer_signing_pubkey(), Some(bob_id)); assert!(!offer.paths().is_empty()); - for path in offer.paths() { + if let Some(path) = offer.paths().first() { let introduction_node_id = resolve_introduction_node(david, &path); assert_eq!(introduction_node_id, nodes[4].node.get_our_node_id()); } @@ -389,8 +389,10 @@ fn creates_short_lived_offer() { let bob = &nodes[1]; let absolute_expiry = alice.offers_handler.duration_since_epoch() + MAX_SHORT_LIVED_RELATIVE_EXPIRY; + let router = DefaultMessageRouter::compact_blinded_paths(alice.network_graph, alice.node.entropy_source); let offer = alice.offers_handler - .create_offer_builder(Some(absolute_expiry)).unwrap() + .create_offer_builder_using_router(router).unwrap() + .absolute_expiry(absolute_expiry) .build().unwrap(); assert_eq!(offer.absolute_expiry(), Some(absolute_expiry)); assert!(!offer.paths().is_empty()); @@ -417,8 +419,9 @@ fn creates_long_lived_offer() { let absolute_expiry = alice.offers_handler.duration_since_epoch() + MAX_SHORT_LIVED_RELATIVE_EXPIRY + Duration::from_secs(1); let offer = alice.offers_handler - .create_offer_builder(Some(absolute_expiry)) + .create_offer_builder() .unwrap() + .absolute_expiry(absolute_expiry) .build().unwrap(); assert_eq!(offer.absolute_expiry(), Some(absolute_expiry)); assert!(!offer.paths().is_empty()); @@ -427,7 +430,7 @@ fn creates_long_lived_offer() { } let offer = alice.offers_handler - .create_offer_builder(None).unwrap() + .create_offer_builder().unwrap() .build().unwrap(); assert_eq!(offer.absolute_expiry(), None); assert!(!offer.paths().is_empty()); @@ -531,7 +534,7 @@ fn creates_and_pays_for_offer_using_two_hop_blinded_path() { disconnect_peers(david, &[bob, &nodes[4], &nodes[5]]); let offer = alice.offers_handler - .create_offer_builder(None) + .create_offer_builder() .unwrap() .amount_msats(10_000_000) .build().unwrap(); @@ -701,7 +704,7 @@ fn creates_and_pays_for_offer_using_one_hop_blinded_path() { let bob_id = bob.node.get_our_node_id(); let offer = alice.offers_handler - .create_offer_builder(None).unwrap() + .create_offer_builder().unwrap() .amount_msats(10_000_000) .build().unwrap(); assert_ne!(offer.issuer_signing_pubkey(), Some(alice_id)); @@ -824,7 +827,7 @@ fn pays_for_offer_without_blinded_paths() { let bob_id = bob.node.get_our_node_id(); let offer = alice.offers_handler - .create_offer_builder(None).unwrap() + .create_offer_builder().unwrap() .clear_paths() .amount_msats(10_000_000) .build().unwrap(); @@ -948,7 +951,7 @@ fn send_invoice_requests_with_distinct_reply_path() { disconnect_peers(david, &[bob, &nodes[4], &nodes[5]]); let offer = alice.offers_handler - .create_offer_builder(None) + .create_offer_builder() .unwrap() .amount_msats(10_000_000) .build().unwrap(); @@ -1084,7 +1087,7 @@ fn creates_and_pays_for_offer_with_retry() { let bob_id = bob.node.get_our_node_id(); let offer = alice.offers_handler - .create_offer_builder(None).unwrap() + .create_offer_builder().unwrap() .amount_msats(10_000_000) .build().unwrap(); assert_ne!(offer.issuer_signing_pubkey(), Some(alice_id)); @@ -1169,7 +1172,7 @@ fn creates_offer_with_blinded_path_using_unannounced_introduction_node() { let bob_id = bob.node.get_our_node_id(); let offer = alice.offers_handler - .create_offer_builder(None).unwrap() + .create_offer_builder().unwrap() .amount_msats(10_000_000) .build().unwrap(); assert_ne!(offer.issuer_signing_pubkey(), Some(alice_id)); @@ -1299,7 +1302,7 @@ fn fails_authentication_when_handling_invoice_request() { disconnect_peers(david, &[bob, &nodes[4], &nodes[5]]); let offer = alice.offers_handler - .create_offer_builder(None) + .create_offer_builder() .unwrap() .amount_msats(10_000_000) .build().unwrap(); @@ -1311,7 +1314,7 @@ fn fails_authentication_when_handling_invoice_request() { } let invalid_path = alice.offers_handler - .create_offer_builder(None) + .create_offer_builder() .unwrap() .build().unwrap() .paths().first().unwrap() @@ -1411,7 +1414,7 @@ fn fails_authentication_when_handling_invoice_for_offer() { disconnect_peers(david, &[bob, &nodes[4], &nodes[5]]); let offer = alice.offers_handler - .create_offer_builder(None) + .create_offer_builder() .unwrap() .amount_msats(10_000_000) .build().unwrap(); @@ -1608,7 +1611,7 @@ fn fails_creating_or_paying_for_offer_without_connected_peers() { disconnect_peers(david, &[bob, charlie, &nodes[4], &nodes[5]]); let absolute_expiry = alice.offers_handler.duration_since_epoch() + MAX_SHORT_LIVED_RELATIVE_EXPIRY; - match alice.offers_handler.create_offer_builder(Some(absolute_expiry)) { + match alice.offers_handler.create_offer_builder() { Ok(_) => panic!("Expected error"), Err(e) => assert_eq!(e, Bolt12SemanticError::MissingPaths), } @@ -1618,7 +1621,8 @@ fn fails_creating_or_paying_for_offer_without_connected_peers() { reconnect_nodes(args); let offer = alice.offers_handler - .create_offer_builder(Some(absolute_expiry)).unwrap() + .create_offer_builder().unwrap() + .absolute_expiry(absolute_expiry) .amount_msats(10_000_000) .build().unwrap(); @@ -1722,7 +1726,7 @@ fn fails_creating_invoice_request_for_unsupported_chain() { let bob = &nodes[1]; let offer = alice.offers_handler - .create_offer_builder(None).unwrap() + .create_offer_builder().unwrap() .clear_chains() .chain(Network::Signet) .build().unwrap(); @@ -1781,7 +1785,7 @@ fn fails_creating_invoice_request_without_blinded_reply_path() { disconnect_peers(david, &[bob, charlie, &nodes[4], &nodes[5]]); let offer = alice.offers_handler - .create_offer_builder(None).unwrap() + .create_offer_builder().unwrap() .amount_msats(10_000_000) .build().unwrap(); @@ -1815,7 +1819,7 @@ fn fails_creating_invoice_request_with_duplicate_payment_id() { disconnect_peers(alice, &[charlie, david, &nodes[4], &nodes[5]]); let offer = alice.offers_handler - .create_offer_builder(None).unwrap() + .create_offer_builder().unwrap() .amount_msats(10_000_000) .build().unwrap(); @@ -1901,7 +1905,7 @@ fn fails_sending_invoice_without_blinded_payment_paths_for_offer() { disconnect_peers(david, &[bob, &nodes[4], &nodes[5]]); let offer = alice.offers_handler - .create_offer_builder(None).unwrap() + .create_offer_builder().unwrap() .amount_msats(10_000_000) .build().unwrap(); @@ -2110,7 +2114,7 @@ fn fails_paying_invoice_with_unknown_required_features() { disconnect_peers(david, &[bob, &nodes[4], &nodes[5]]); let offer = alice.offers_handler - .create_offer_builder(None).unwrap() + .create_offer_builder().unwrap() .amount_msats(10_000_000) .build().unwrap(); @@ -2207,7 +2211,7 @@ fn no_double_pay_with_stale_channelmanager() { let amt_msat = nodes[0].node.list_usable_channels()[0].next_outbound_htlc_limit_msat + 1; // Force MPP let offer = nodes[1].offers_handler - .create_offer_builder(None).unwrap() + .create_offer_builder().unwrap() .clear_paths() .amount_msats(amt_msat) .build().unwrap(); diff --git a/lightning/src/offers/flow.rs b/lightning/src/offers/flow.rs index 643d256e365..4f7ab62b65e 100644 --- a/lightning/src/offers/flow.rs +++ b/lightning/src/offers/flow.rs @@ -320,9 +320,8 @@ where /// # fn example(offers_flow: T, channel_manager: U) -> Result<(), Bolt12SemanticError> { /// # let offers_flow = offers_flow.get_omf(); /// # let channel_manager = channel_manager.get_cm(); -/// # let absolute_expiry = None; /// # let offer = offers_flow -/// .create_offer_builder(absolute_expiry)? +/// .create_offer_builder()? /// # ; /// # // Needed for compiling for c_bindings /// # let builder: lightning::offers::offer::OfferBuilder<_, _> = offer.into(); @@ -1192,7 +1191,7 @@ macro_rules! create_offer_builder { ($self: ident, $builder: ty) => { /// [`Offer`]: crate::offers::offer /// [`Router`]: crate::routing::router::Router pub fn create_offer_builder( - &$self, absolute_expiry: Option + &$self ) -> Result<$builder, Bolt12SemanticError> { let node_id = $self.get_our_node_id(); let expanded_key = &$self.inbound_payment_key; @@ -1200,18 +1199,16 @@ macro_rules! create_offer_builder { ($self: ident, $builder: ty) => { let secp_ctx = &$self.secp_ctx; let nonce = Nonce::from_entropy_source(entropy); - let context = OffersContext::InvoiceRequest { nonce }; - let path = $self.create_blinded_paths_using_absolute_expiry(context, absolute_expiry) - .and_then(|paths| paths.into_iter().next().ok_or(())) + let context = MessageContext::Offers(OffersContext::InvoiceRequest { nonce }); + let paths = $self.create_blinded_paths(context) .map_err(|_| Bolt12SemanticError::MissingPaths)?; - let builder = OfferBuilder::deriving_signing_pubkey(node_id, expanded_key, nonce, secp_ctx) - .chain_hash($self.commons.get_chain_hash()) - .path(path); - let builder = match absolute_expiry { - None => builder, - Some(absolute_expiry) => builder.absolute_expiry(absolute_expiry), - }; + let mut builder = OfferBuilder::deriving_signing_pubkey(node_id, expanded_key, nonce, secp_ctx) + .chain_hash($self.commons.get_chain_hash()); + + for path in paths { + builder = builder.path(path) + } Ok(builder.into()) } From 20b6de6cfba5dce6d9770010cb52b085eb46dca8 Mon Sep 17 00:00:00 2001 From: shaavan Date: Mon, 30 Dec 2024 16:52:40 +0530 Subject: [PATCH 27/30] Introduce create_refund_builder_using_router This update introduces `create_refund_builder_using_router` to accept a router as a parameter. The reasoning for this change aligns with the `create_offer_builder_using_router` case, enabling users to specify the desired type of blinded path (or no blinded paths) and allowing flexibility to pass a custom router for tailored behavior. --- lightning/src/offers/flow.rs | 107 +++++++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) diff --git a/lightning/src/offers/flow.rs b/lightning/src/offers/flow.rs index 4f7ab62b65e..6c15ee532f8 100644 --- a/lightning/src/offers/flow.rs +++ b/lightning/src/offers/flow.rs @@ -1351,6 +1351,113 @@ macro_rules! create_refund_builder { ($self: ident, $builder: ty) => { Ok(builder.into()) } + + /// Creates a [`RefundBuilder`] such that the [`Refund`] it builds is recognized by the + /// [`OffersMessageFlow`] when handling [`Bolt12Invoice`] messages for the refund. + /// + /// # Payment + /// + /// The provided `payment_id` is used to ensure that only one invoice is paid for the refund. + /// See [Avoiding Duplicate Payments] for other requirements once the payment has been sent. + /// + /// The builder will have the provided expiration set. Any changes to the expiration on the + /// returned builder will not be honored by [`OffersMessageFlow`]. For non-`std`, the highest seen + /// block time minus two hours is used for the current time when determining if the refund has + /// expired. + /// + /// To revoke the refund, use [`ChannelManager::abandon_payment`] prior to receiving the + /// invoice. If abandoned, or an invoice isn't received before expiration, the payment will fail + /// with an [`Event::PaymentFailed`]. + /// + /// If `max_total_routing_fee_msat` is not specified, The default from + /// [`RouteParameters::from_payment_params_and_value`] is applied. + /// + /// # Privacy + /// + /// Constructs a [`BlindedMessagePath`] for the refund using a custom [`MessageRouter`]. + /// Users can implement a custom [`MessageRouter`] to define properties of the + /// [`BlindedMessagePath`] as required or opt not to create any `BlindedMessagePath`. + /// + /// Also, uses a derived payer id in the refund for payer privacy. + /// + /// # Limitations + /// + /// Requires a direct connection to an introduction node in the responding + /// [`Bolt12Invoice::payment_paths`]. + /// + /// # Errors + /// + /// Errors if: + /// - a duplicate `payment_id` is provided given the caveats in the aforementioned link, + /// - `amount_msats` is invalid, or + /// - the parameterized [`Router`] is unable to create a blinded path for the refund. + /// + /// [`Refund`]: crate::offers::refund::Refund + /// [`BlindedMessagePath`]: crate::blinded_path::message::BlindedMessagePath + /// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice + /// [`Bolt12Invoice::payment_paths`]: crate::offers::invoice::Bolt12Invoice::payment_paths + /// [`ChannelManager::abandon_payment`]: crate::ln::channelmanager::ChannelManager::abandon_payment + /// [`MessageRouter`]: crate::onion_message::messenger::MessageRouter + /// [`RouteParameters::from_payment_params_and_value`]: crate::routing::router::RouteParameters::from_payment_params_and_value + /// [`Router`]: crate::routing::router::Router + /// [`Event::PaymentFailed`]: crate::events::Event::PaymentFailed + /// [Avoiding Duplicate Payments]: #avoiding-duplicate-payments + pub fn create_refund_builder_using_router( + &$self, + router: M, + amount_msats: u64, + absolute_expiry: Duration, + payment_id: PaymentId, + retry_strategy: Retry, + max_total_routing_fee_msat: Option, + ) -> Result, Bolt12SemanticError> { + let node_id = $self.get_our_node_id(); + let expanded_key = &$self.inbound_payment_key; + let entropy = &*$self.entropy_source; + let secp_ctx = &$self.secp_ctx; + + let nonce = Nonce::from_entropy_source(entropy); + let context = MessageContext::Offers(OffersContext::OutboundPayment { + payment_id, + nonce, + hmac: None, + }); + + let peers = $self.commons.get_peer_for_blinded_path(); + + let paths = router.create_blinded_paths(node_id, context, peers, secp_ctx) + .map_err(|_| Bolt12SemanticError::MissingPaths)?; + + let mut builder = RefundBuilder::deriving_signing_pubkey( + node_id, + expanded_key, + nonce, + secp_ctx, + amount_msats, + payment_id, + )? + .chain_hash($self.commons.get_chain_hash()) + .absolute_expiry(absolute_expiry); + + for path in paths { + builder = builder.path(path); + } + + let expiration = StaleExpiration::AbsoluteTimeout(absolute_expiry); + + $self + .commons + .add_new_awaiting_invoice( + payment_id, + expiration, + retry_strategy, + max_total_routing_fee_msat, + None, + ) + .map_err(|_| Bolt12SemanticError::DuplicatePaymentId)?; + + Ok(builder.into()) + } } } impl OffersMessageFlow From 855ce29af6414916f3e5767b0e5163cbdb386d12 Mon Sep 17 00:00:00 2001 From: shaavan Date: Tue, 31 Dec 2024 15:46:05 +0530 Subject: [PATCH 28/30] Update `create_refund_builder` to exclusively use `create_blinded_paths` Reasoning: The reasoning remains the same as for the `create_offer_builder` case, ensuring consistency in the default behavior and maintaining flexibility for custom implementations. --- lightning/src/ln/offers_tests.rs | 3 ++- lightning/src/offers/flow.rs | 14 ++++++++------ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/lightning/src/ln/offers_tests.rs b/lightning/src/ln/offers_tests.rs index 4efa3640509..d5d4040c34e 100644 --- a/lightning/src/ln/offers_tests.rs +++ b/lightning/src/ln/offers_tests.rs @@ -455,8 +455,9 @@ fn creates_short_lived_refund() { let absolute_expiry = bob.offers_handler.duration_since_epoch() + MAX_SHORT_LIVED_RELATIVE_EXPIRY; let payment_id = PaymentId([1; 32]); + let router = DefaultMessageRouter::compact_blinded_paths(bob.network_graph, bob.node.entropy_source); let refund = bob.offers_handler - .create_refund_builder(10_000_000, absolute_expiry, payment_id, Retry::Attempts(0), None) + .create_refund_builder_using_router(router, 10_000_000, absolute_expiry, payment_id, Retry::Attempts(0), None) .unwrap() .build().unwrap(); assert_eq!(refund.absolute_expiry(), Some(absolute_expiry)); diff --git a/lightning/src/offers/flow.rs b/lightning/src/offers/flow.rs index 6c15ee532f8..deb2f744b4a 100644 --- a/lightning/src/offers/flow.rs +++ b/lightning/src/offers/flow.rs @@ -1331,17 +1331,19 @@ macro_rules! create_refund_builder { ($self: ident, $builder: ty) => { let secp_ctx = &$self.secp_ctx; let nonce = Nonce::from_entropy_source(entropy); - let context = OffersContext::OutboundPayment { payment_id, nonce, hmac: None }; - let path = $self.create_blinded_paths_using_absolute_expiry(context, Some(absolute_expiry)) - .and_then(|paths| paths.into_iter().next().ok_or(())) + let context = MessageContext::Offers(OffersContext::OutboundPayment { payment_id, nonce, hmac: None }); + let paths = $self.create_blinded_paths(context) .map_err(|_| Bolt12SemanticError::MissingPaths)?; - let builder = RefundBuilder::deriving_signing_pubkey( + let mut builder = RefundBuilder::deriving_signing_pubkey( node_id, expanded_key, nonce, secp_ctx, amount_msats, payment_id )? .chain_hash($self.commons.get_chain_hash()) - .absolute_expiry(absolute_expiry) - .path(path); + .absolute_expiry(absolute_expiry); + + for path in paths { + builder = builder.path(path); + } let expiration = StaleExpiration::AbsoluteTimeout(absolute_expiry); From aba369ecb98e4a1da25dee99f6cb13aa1f50e41b Mon Sep 17 00:00:00 2001 From: shaavan Date: Fri, 13 Dec 2024 20:30:09 +0530 Subject: [PATCH 29/30] Remove Redundant Variants of create_blinded_paths MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Following the updates in previous commits, the other variants of `create_blinded_paths`—namely `create_compact_blinded_paths` and `create_blinded_paths_using_absolute_expiry`—are now redundant. This commit removes these variants as they are no longer necessary. --- lightning/src/offers/flow.rs | 46 +---------------------- lightning/src/onion_message/messenger.rs | 47 ------------------------ lightning/src/util/test_utils.rs | 7 ---- 3 files changed, 2 insertions(+), 98 deletions(-) diff --git a/lightning/src/offers/flow.rs b/lightning/src/offers/flow.rs index deb2f744b4a..e8f66906c9e 100644 --- a/lightning/src/offers/flow.rs +++ b/lightning/src/offers/flow.rs @@ -619,9 +619,7 @@ where /// short-lived, while anything with a greater expiration is considered long-lived. /// /// Using [`OffersMessageFlow::create_offer_builder`] or [`OffersMessageFlow::create_refund_builder`], -/// will included a [`BlindedMessagePath`] created using: -/// - [`MessageRouter::create_compact_blinded_paths`] when short-lived, and -/// - [`MessageRouter::create_blinded_paths`] when long-lived. +/// will included a [`BlindedMessagePath`] created using: [`MessageRouter::create_blinded_paths`] /// /// [`OffersMessageFlow::create_offer_builder`]: crate::offers::flow::OffersMessageFlow::create_offer_builder /// [`OffersMessageFlow::create_refund_builder`]: crate::offers::flow::OffersMessageFlow::create_refund_builder @@ -649,25 +647,7 @@ where MR::Target: MessageRouter, L::Target: Logger, { - /// Creates a collection of blinded paths by delegating to [`MessageRouter`] based on - /// the path's intended lifetime. - /// - /// Whether or not the path is compact depends on whether the path is short-lived or long-lived, - /// respectively, based on the given `absolute_expiry` as seconds since the Unix epoch. See - /// [`MAX_SHORT_LIVED_RELATIVE_EXPIRY`]. - pub fn create_blinded_paths_using_absolute_expiry( - &self, context: OffersContext, absolute_expiry: Option, - ) -> Result, ()> { - let now = self.duration_since_epoch(); - let max_short_lived_absolute_expiry = now.saturating_add(MAX_SHORT_LIVED_RELATIVE_EXPIRY); - - if absolute_expiry.unwrap_or(Duration::MAX) <= max_short_lived_absolute_expiry { - self.create_compact_blinded_paths(context) - } else { - self.create_blinded_paths(MessageContext::Offers(context)) - } - } - + #[cfg(test)] pub(crate) fn duration_since_epoch(&self) -> Duration { #[cfg(not(feature = "std"))] let now = self.commons.get_highest_seen_timestamp(); @@ -698,28 +678,6 @@ where .and_then(|paths| (!paths.is_empty()).then(|| paths).ok_or(())) } - /// Creates a collection of blinded paths by delegating to - /// [`MessageRouter::create_compact_blinded_paths`]. - /// - /// Errors if the `MessageRouter` errors. - fn create_compact_blinded_paths( - &self, context: OffersContext, - ) -> Result, ()> { - let recipient = self.get_our_node_id(); - let secp_ctx = &self.secp_ctx; - - let peers = self.commons.get_peer_for_blinded_path(); - - self.message_router - .create_compact_blinded_paths( - recipient, - MessageContext::Offers(context), - peers, - secp_ctx, - ) - .and_then(|paths| (!paths.is_empty()).then(|| paths).ok_or(())) - } - fn enqueue_invoice_request( &self, invoice_request: InvoiceRequest, reply_paths: Vec, ) -> Result<(), Bolt12SemanticError> { diff --git a/lightning/src/onion_message/messenger.rs b/lightning/src/onion_message/messenger.rs index 25cc3f2fc07..8e73961860e 100644 --- a/lightning/src/onion_message/messenger.rs +++ b/lightning/src/onion_message/messenger.rs @@ -461,28 +461,6 @@ pub trait MessageRouter { >( &self, recipient: PublicKey, context: MessageContext, peers: Vec, secp_ctx: &Secp256k1, ) -> Result, ()>; - - /// Creates compact [`BlindedMessagePath`]s to the `recipient` node. The nodes in `peers` are - /// assumed to be direct peers with the `recipient`. - /// - /// Compact blinded paths use short channel ids instead of pubkeys for a smaller serialization, - /// which is beneficial when a QR code is used to transport the data. The SCID is passed using - /// a [`MessageForwardNode`] but may be `None` for graceful degradation. - /// - /// Implementations using additional intermediate nodes are responsible for using a - /// [`MessageForwardNode`] with `Some` short channel id, if possible. Similarly, implementations - /// should call [`BlindedMessagePath::use_compact_introduction_node`]. - /// - /// The provided implementation simply delegates to [`MessageRouter::create_blinded_paths`], - /// ignoring the short channel ids. - fn create_compact_blinded_paths< - T: secp256k1::Signing + secp256k1::Verification - >( - &self, recipient: PublicKey, context: MessageContext, - peers: Vec, secp_ctx: &Secp256k1, - ) -> Result, ()> { - self.create_blinded_paths(recipient, context, peers, secp_ctx) - } } /// A [`MessageRouter`] that can only route to a directly connected [`Destination`]. @@ -632,15 +610,6 @@ where ) -> Result, ()> { Self::create_blinded_paths_from_iter(network_graph, recipient, context, peers.into_iter(), entropy_source, secp_ctx, self.is_compact) } - - pub(crate) fn create_compact_blinded_paths< - T: secp256k1::Signing + secp256k1::Verification - >( - network_graph: &G, recipient: PublicKey, context: MessageContext, - peers: Vec, entropy_source: &ES, secp_ctx: &Secp256k1, - ) -> Result, ()> { - Self::create_blinded_paths_from_iter(network_graph, recipient, context, peers.into_iter(), entropy_source, secp_ctx, true) - } } impl>, L: Deref, ES: Deref> MessageRouter for DefaultMessageRouter @@ -661,14 +630,6 @@ where ) -> Result, ()> { self.create_blinded_paths_internal(&self.network_graph, recipient, context, peers, &self.entropy_source, secp_ctx) } - - fn create_compact_blinded_paths< - T: secp256k1::Signing + secp256k1::Verification - >( - &self, recipient: PublicKey, context: MessageContext, peers: Vec, secp_ctx: &Secp256k1, - ) -> Result, ()> { - Self::create_compact_blinded_paths(&self.network_graph, recipient, context, peers, &self.entropy_source, secp_ctx) - } } /// A [`MessageRouter`] implementation that does not perform routing. @@ -693,14 +654,6 @@ impl MessageRouter for NullMessageRouter { ) -> Result, ()> { Ok(Vec::new()) } - - fn create_compact_blinded_paths< - T: secp256k1::Signing + secp256k1::Verification - >( - &self, _recipient: PublicKey, _context: MessageContext, _peers: Vec, _secp_ctx: &Secp256k1, - ) -> Result, ()> { - Ok(Vec::new()) - } } /// A path for sending an [`OnionMessage`]. diff --git a/lightning/src/util/test_utils.rs b/lightning/src/util/test_utils.rs index 22df5afd7dc..fc2d9d959b2 100644 --- a/lightning/src/util/test_utils.rs +++ b/lightning/src/util/test_utils.rs @@ -297,13 +297,6 @@ impl<'a> MessageRouter for TestMessageRouter<'a> { ) -> Result, ()> { self.inner.create_blinded_paths(recipient, context, peers, secp_ctx) } - - fn create_compact_blinded_paths( - &self, recipient: PublicKey, context: MessageContext, - peers: Vec, secp_ctx: &Secp256k1, - ) -> Result, ()> { - self.inner.create_compact_blinded_paths(recipient, context, peers, secp_ctx) - } } pub struct OnlyReadsKeysInterface {} From 268d590262cf383a83f9a647a0642bf50728c964 Mon Sep 17 00:00:00 2001 From: shaavan Date: Thu, 3 Oct 2024 17:34:08 +0530 Subject: [PATCH 30/30] Introduce Tests for Offers and Refunds Without Blinded Paths --- lightning/src/ln/offers_tests.rs | 51 +++++++++++++++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/lightning/src/ln/offers_tests.rs b/lightning/src/ln/offers_tests.rs index d5d4040c34e..00cad97c1dc 100644 --- a/lightning/src/ln/offers_tests.rs +++ b/lightning/src/ln/offers_tests.rs @@ -58,7 +58,7 @@ use crate::offers::invoice_error::InvoiceError; use crate::offers::invoice_request::{InvoiceRequest, InvoiceRequestFields}; use crate::offers::nonce::Nonce; use crate::offers::parse::Bolt12SemanticError; -use crate::onion_message::messenger::{DefaultMessageRouter, Destination, MessageSendInstructions, PeeledOnion}; +use crate::onion_message::messenger::{DefaultMessageRouter, Destination, MessageSendInstructions, NullMessageRouter, PeeledOnion}; use crate::onion_message::offers::OffersMessage; use crate::onion_message::packet::ParsedOnionMessageContents; use crate::routing::gossip::{NodeAlias, NodeId}; @@ -254,6 +254,55 @@ fn extract_invoice_error<'a, 'b, 'c>( } } +/// Checks that an offer can be created with no blinded paths. +#[test] +fn create_offer_with_no_blinded_path() { + 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_announced_chan_between_nodes_with_value(&nodes, 0, 1, 10_000_000, 1_000_000_000); + + let alice = &nodes[0]; + let alice_id = alice.node.get_our_node_id(); + + let router = NullMessageRouter {}; + let offer = alice.offers_handler + .create_offer_builder_using_router(router).unwrap() + .amount_msats(10_000_000) + .build().unwrap(); + assert_eq!(offer.issuer_signing_pubkey(), Some(alice_id)); + assert!(offer.paths().is_empty()); +} + +/// Checks that a refund can be created with no blinded paths. +#[test] +fn create_refund_with_no_blinded_path() { + 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_announced_chan_between_nodes_with_value(&nodes, 0, 1, 10_000_000, 1_000_000_000); + + let alice = &nodes[0]; + let alice_id = alice.node.get_our_node_id(); + + let absolute_expiry = Duration::from_secs(u64::MAX); + let payment_id = PaymentId([1; 32]); + + let router = NullMessageRouter {}; + let refund = alice.offers_handler + .create_refund_builder_using_router(router, 10_000_000, absolute_expiry, payment_id, Retry::Attempts(0), None) + .unwrap() + .build().unwrap(); + assert_eq!(refund.amount_msats(), 10_000_000); + assert_eq!(refund.absolute_expiry(), Some(absolute_expiry)); + assert_eq!(refund.payer_signing_pubkey(), alice_id); + assert!(refund.paths().is_empty()); +} + /// Checks that blinded paths without Tor-only nodes are preferred when constructing an offer. #[test] fn prefers_non_tor_nodes_in_blinded_paths() {