From 4174d39d5278925b6742f7e2ae60e63ac7583b08 Mon Sep 17 00:00:00 2001 From: shaavan Date: Tue, 12 Nov 2024 19:29:21 +0530 Subject: [PATCH 01/20] 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/20] 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/20] 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 | 37 ------------- lightning/src/ln/offers_tests.rs | 87 ++---------------------------- lightning/src/util/config.rs | 16 ------ 4 files changed, 7 insertions(+), 197 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..a4126a8340d 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -4720,35 +4720,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), - } - } - fn verify_bolt12_invoice( &self, invoice: &Bolt12Invoice, context: Option<&OffersContext>, ) -> Result { @@ -12117,14 +12088,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..8fdd213445e 100644 --- a/lightning/src/util/config.rs +++ b/lightning/src/util/config.rs @@ -844,20 +844,6 @@ 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. - /// - /// 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. - /// - /// 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, } impl Default for UserConfig { @@ -870,7 +856,6 @@ impl Default for UserConfig { accept_inbound_channels: true, manually_accept_inbound_channels: false, accept_intercept_htlcs: false, - manually_handle_bolt12_invoices: false, } } } @@ -889,7 +874,6 @@ 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)?, }) } } From fc9060c14ddf8c66e88cec8c4239d276d2ce05cf Mon Sep 17 00:00:00 2001 From: shaavan Date: Fri, 15 Nov 2024 17:56:06 +0530 Subject: [PATCH 04/20] 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 8dfaacd79030d7687fdd731f1031d178f3ecd3e8 Mon Sep 17 00:00:00 2001 From: shaavan Date: Fri, 15 Nov 2024 18:45:45 +0530 Subject: [PATCH 05/20] 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 a4126a8340d..6fc734f5bda 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, @@ -4720,36 +4728,6 @@ where self.pending_outbound_payments.test_set_payment_metadata(payment_id, new_payment_metadata); } - 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 @@ -4865,11 +4843,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 @@ -9955,6 +9928,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 @@ -10104,42 +10233,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. /// @@ -10307,43 +10400,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. /// @@ -10437,27 +10493,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`]. /// @@ -10486,36 +10521,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. /// @@ -14327,7 +14332,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 8bca807864c8d9eb4f02e3c08698d96be35b1354 Mon Sep 17 00:00:00 2001 From: shaavan Date: Wed, 11 Dec 2024 17:24:06 +0530 Subject: [PATCH 06/20] 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 6fc734f5bda..c7a6be16e67 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -4728,66 +4728,6 @@ where self.pending_outbound_payments.test_set_payment_metadata(payment_id, new_payment_metadata); } - #[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 @@ -10082,6 +10022,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 7abead8cb7cc83f6d4ab782bfefe3cbf2d3a6356 Mon Sep 17 00:00:00 2001 From: shaavan Date: Fri, 15 Nov 2024 18:02:54 +0530 Subject: [PATCH 07/20] 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 c7a6be16e67..06d3a11d5a2 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, @@ -6786,8 +6783,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); } @@ -11921,263 +11918,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 4f277a9a3f4428bf36770fd8dd4d52b1ab5a4dbe Mon Sep 17 00:00:00 2001 From: shaavan Date: Fri, 15 Nov 2024 19:34:22 +0530 Subject: [PATCH 08/20] 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 14b1c1730577314fc0452d962670d57198bf84c1 Mon Sep 17 00:00:00 2001 From: shaavan Date: Fri, 15 Nov 2024 19:12:24 +0530 Subject: [PATCH 09/20] 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 06d3a11d5a2..61497c596a4 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. @@ -6782,7 +6734,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) { @@ -9733,57 +9685,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. @@ -10079,6 +9980,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 @@ -10100,13 +10018,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); @@ -10458,25 +10372,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( @@ -15452,6 +15347,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 344eb4c2aac1cec0925d66db1a7d0ebabdcf18fb Mon Sep 17 00:00:00 2001 From: shaavan Date: Fri, 15 Nov 2024 20:10:23 +0530 Subject: [PATCH 10/20] 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 61497c596a4..e2581ee803e 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 @@ -9685,87 +9628,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>, @@ -9997,6 +9859,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 @@ -10018,12 +9887,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 6eabb741fe4682604d28bb07a876ca1efd137ab4 Mon Sep 17 00:00:00 2001 From: shaavan Date: Sat, 16 Nov 2024 18:03:10 +0530 Subject: [PATCH 11/20] 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 e2581ee803e..9537b379d4a 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, @@ -4661,7 +4616,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. /// @@ -4671,6 +4626,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) } @@ -9861,104 +9817,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, @@ -10006,7 +9868,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. /// @@ -12578,6 +12460,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 6ab36b6900b772b69b9e27df7c1b2b14126f303f Mon Sep 17 00:00:00 2001 From: shaavan Date: Sat, 16 Nov 2024 18:18:16 +0530 Subject: [PATCH 12/20] 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 9537b379d4a..0863a495cf6 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. @@ -9875,7 +9831,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 @@ -9889,106 +9845,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. /// @@ -12462,6 +12318,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 80a59d62b72a436181bb5cab5d7319a6e97e0e54 Mon Sep 17 00:00:00 2001 From: shaavan Date: Wed, 11 Dec 2024 17:39:33 +0530 Subject: [PATCH 13/20] 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 d59e5226446b49d3d6aa386e8bb3f2fc78b2627a Mon Sep 17 00:00:00 2001 From: shaavan Date: Wed, 27 Nov 2024 16:53:16 +0530 Subject: [PATCH 14/20] 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 0863a495cf6..7f54ed34731 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -9556,6 +9556,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 { @@ -9778,6 +9788,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, @@ -9845,73 +9863,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 53a7713e77c19e28fbf6f7d8d750f24da61250f8 Mon Sep 17 00:00:00 2001 From: shaavan Date: Wed, 27 Nov 2024 17:35:44 +0530 Subject: [PATCH 15/20] 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 7f54ed34731..80a6877abd4 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()), } } @@ -9842,6 +9828,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 @@ -11401,69 +11399,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 @@ -13343,9 +13278,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 b01b656ec4b279adc0b95dbff564869a89b01e06 Mon Sep 17 00:00:00 2001 From: shaavan Date: Wed, 27 Nov 2024 18:06:27 +0530 Subject: [PATCH 16/20] 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 80a6877abd4..b1bb584fbab 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)] @@ -9595,6 +9573,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 { @@ -9751,19 +9746,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 } @@ -9782,53 +9764,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) @@ -9840,6 +9775,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 @@ -9922,47 +9862,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 309e72c4afce859c93c5bd771d0c645e2ff4581a Mon Sep 17 00:00:00 2001 From: shaavan Date: Thu, 12 Dec 2024 12:33:59 +0530 Subject: [PATCH 17/20] 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 b1bb584fbab..f3c275612a5 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -9629,23 +9629,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, @@ -9862,6 +9845,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 d6ee2daa70fd3b48d0dfe53d901db4d43056b991 Mon Sep 17 00:00:00 2001 From: shaavan Date: Wed, 27 Nov 2024 18:38:28 +0530 Subject: [PATCH 18/20] 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 f3c275612a5..8716b1bfc0b 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()), @@ -9516,10 +9511,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.") @@ -9629,42 +9620,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) } @@ -9765,13 +9720,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>, @@ -13165,7 +13113,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 bfab3f9fb7b185478cd25e58792fa3ce399a65b9 Mon Sep 17 00:00:00 2001 From: shaavan Date: Wed, 27 Nov 2024 18:45:40 +0530 Subject: [PATCH 19/20] 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 8716b1bfc0b..f3a46e7f175 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()), } } @@ -9511,11 +9507,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 @@ -13128,8 +13119,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 10b25f4d5954c2880d1b4a38374100b47b8c7552 Mon Sep 17 00:00:00 2001 From: shaavan Date: Wed, 27 Nov 2024 18:57:56 +0530 Subject: [PATCH 20/20] Move verify_bolt12_invoice to flow.rs --- lightning/src/ln/channelmanager.rs | 40 ++++++++++++++---------------- lightning/src/offers/flow.rs | 39 +++++++++++++++++++++-------- 2 files changed, 47 insertions(+), 32 deletions(-) diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index f3a46e7f175..de31ff44c8a 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<(), ()> { @@ -2030,17 +2042,17 @@ where /// ``` /// /// ## BOLT 12 Offers -/// +/// /// For more information on creating offers, see [`create_offer_builder`]. /// /// For details on initiating payments for offers, see [`pay_for_offer`]. /// /// ## BOLT 12 Refunds -/// +/// /// For more information on creating refunds, see [`create_refund_builder`]. /// /// For requesting refund payments, see [`request_refund_payment`]. -/// +/// /// # Persistence /// /// Implements [`Writeable`] to write out all channel state to disk. Implies [`peer_disconnected`] for @@ -9572,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..0c97288bad9 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,31 @@ 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(()), + } + } +} + impl OffersMessageHandler for OffersMessageFlow where @@ -1003,11 +1023,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()));