diff --git a/lightning-dns-resolver/src/lib.rs b/lightning-dns-resolver/src/lib.rs index fc591c8c153..dd5ddb8fd9c 100644 --- a/lightning-dns-resolver/src/lib.rs +++ b/lightning-dns-resolver/src/lib.rs @@ -164,7 +164,7 @@ mod test { }; use lightning::blinded_path::NodeIdLookUp; use lightning::events::{Event, PaymentPurpose}; - use lightning::ln::channelmanager::{PaymentId, Retry}; + use lightning::ln::channelmanager::{OptionalOfferPaymentParams, PaymentId}; use lightning::ln::functional_test_utils::*; use lightning::ln::msgs::{ BaseMessageHandler, ChannelMessageHandler, Init, OnionMessageHandler, @@ -175,7 +175,6 @@ mod test { use lightning::onion_message::messenger::{ AOnionMessenger, Destination, MessageRouter, OnionMessagePath, OnionMessenger, }; - use lightning::routing::router::RouteParametersConfig; use lightning::sign::{KeysManager, NodeSigner, ReceiveAuthKey, Recipient}; use lightning::types::features::InitFeatures; use lightning::types::payment::PaymentHash; @@ -382,23 +381,18 @@ mod test { async fn pay_offer_flow<'a, 'b, 'c>( nodes: &[Node<'a, 'b, 'c>], resolver_messenger: &impl AOnionMessenger, resolver_id: PublicKey, payer_id: PublicKey, payee_id: PublicKey, offer: Offer, - name: HumanReadableName, amt: u64, payment_id: PaymentId, payer_note: Option, - retry: Retry, params: RouteParametersConfig, resolvers: Vec, + name: HumanReadableName, payment_id: PaymentId, payer_note: Option, + resolvers: Vec, ) { // Override contents to offer provided let proof_override = &nodes[0].node.testing_dnssec_proof_offer_resolution_override; proof_override.lock().unwrap().insert(name.clone(), offer); + let amt = 42_000; + let mut opts = OptionalOfferPaymentParams::default(); + opts.payer_note = payer_note.clone(); nodes[0] .node - .pay_for_offer_from_human_readable_name( - name, - amt, - payment_id, - payer_note.clone(), - retry, - params, - resolvers, - ) + .pay_for_offer_from_human_readable_name(name, amt, payment_id, opts, resolvers) .unwrap(); let query = nodes[0].onion_messenger.next_onion_message_for_peer(resolver_id).unwrap(); @@ -495,9 +489,6 @@ mod test { let bs_offer = nodes[1].node.create_offer_builder().unwrap().build().unwrap(); let resolvers = vec![Destination::Node(resolver_id)]; - let retry = Retry::Attempts(0); - let amt = 42_000; - let params = RouteParametersConfig::default(); pay_offer_flow( &nodes, @@ -507,11 +498,8 @@ mod test { payee_id, bs_offer.clone(), name.clone(), - amt, PaymentId([42; 32]), None, - retry, - params, resolvers.clone(), ) .await; @@ -525,11 +513,8 @@ mod test { payee_id, bs_offer, name, - amt, PaymentId([21; 32]), Some("foo".into()), - retry, - params, resolvers, ) .await; diff --git a/lightning/src/ln/async_payments_tests.rs b/lightning/src/ln/async_payments_tests.rs index e617f6fbf1f..a7e0ac45918 100644 --- a/lightning/src/ln/async_payments_tests.rs +++ b/lightning/src/ln/async_payments_tests.rs @@ -15,7 +15,7 @@ use crate::events::{ Event, HTLCHandlingFailureType, PaidBolt12Invoice, PaymentFailureReason, PaymentPurpose, }; use crate::ln::blinded_payment_tests::{fail_blinded_htlc_backwards, get_blinded_route_parameters}; -use crate::ln::channelmanager::{PaymentId, RecipientOnionFields}; +use crate::ln::channelmanager::{OptionalOfferPaymentParams, PaymentId, RecipientOnionFields}; use crate::ln::functional_test_utils::*; use crate::ln::inbound_payment; use crate::ln::msgs; @@ -49,7 +49,7 @@ use crate::onion_message::messenger::{ use crate::onion_message::offers::OffersMessage; use crate::onion_message::packet::ParsedOnionMessageContents; use crate::prelude::*; -use crate::routing::router::{Payee, PaymentParameters, RouteParametersConfig}; +use crate::routing::router::{Payee, PaymentParameters}; use crate::sign::NodeSigner; use crate::sync::Mutex; use crate::types::features::Bolt12InvoiceFeatures; @@ -480,11 +480,7 @@ fn static_invoice_unknown_required_features() { // unknown required features. let amt_msat = 5000; let payment_id = PaymentId([1; 32]); - let params = RouteParametersConfig::default(); - nodes[0] - .node - .pay_for_offer(&offer, None, Some(amt_msat), None, payment_id, Retry::Attempts(0), params) - .unwrap(); + nodes[0].node.pay_for_offer(&offer, Some(amt_msat), payment_id, Default::default()).unwrap(); // Don't forward the invreq since the invoice was created outside of the normal flow, instead // manually construct the response. @@ -555,11 +551,7 @@ fn ignore_unexpected_static_invoice() { let amt_msat = 5000; let payment_id = PaymentId([1; 32]); - let params = RouteParametersConfig::default(); - nodes[0] - .node - .pay_for_offer(&offer, None, Some(amt_msat), None, payment_id, Retry::Attempts(0), params) - .unwrap(); + nodes[0].node.pay_for_offer(&offer, Some(amt_msat), payment_id, Default::default()).unwrap(); let invreq_om = nodes[0] .onion_messenger @@ -648,11 +640,7 @@ fn async_receive_flow_success() { let offer = nodes[2].node.get_async_receive_offer().unwrap(); let amt_msat = 5000; let payment_id = PaymentId([1; 32]); - let params = RouteParametersConfig::default(); - nodes[0] - .node - .pay_for_offer(&offer, None, Some(amt_msat), None, payment_id, Retry::Attempts(0), params) - .unwrap(); + nodes[0].node.pay_for_offer(&offer, Some(amt_msat), payment_id, Default::default()).unwrap(); let release_held_htlc_om = pass_async_payments_oms( static_invoice.clone(), &nodes[0], @@ -710,11 +698,7 @@ fn expired_static_invoice_fail() { let amt_msat = 5000; let payment_id = PaymentId([1; 32]); - let params = RouteParametersConfig::default(); - nodes[0] - .node - .pay_for_offer(&offer, None, Some(amt_msat), None, payment_id, Retry::Attempts(0), params) - .unwrap(); + nodes[0].node.pay_for_offer(&offer, Some(amt_msat), payment_id, Default::default()).unwrap(); let invreq_om = nodes[0] .onion_messenger @@ -786,11 +770,7 @@ fn timeout_unreleased_payment() { let amt_msat = 5000; let payment_id = PaymentId([1; 32]); - let params = RouteParametersConfig::default(); - sender - .node - .pay_for_offer(&offer, None, Some(amt_msat), None, payment_id, Retry::Attempts(0), params) - .unwrap(); + sender.node.pay_for_offer(&offer, Some(amt_msat), payment_id, Default::default()).unwrap(); let invreq_om = sender.onion_messenger.next_onion_message_for_peer(server.node.get_our_node_id()).unwrap(); @@ -872,11 +852,7 @@ fn async_receive_mpp() { let amt_msat = 15_000_000; let payment_id = PaymentId([1; 32]); - let params = RouteParametersConfig::default(); - nodes[0] - .node - .pay_for_offer(&offer, None, Some(amt_msat), None, payment_id, Retry::Attempts(1), params) - .unwrap(); + nodes[0].node.pay_for_offer(&offer, Some(amt_msat), payment_id, Default::default()).unwrap(); let release_held_htlc_om_3_0 = pass_async_payments_oms(static_invoice, &nodes[0], &nodes[1], &nodes[3], recipient_id).1; nodes[0] @@ -966,11 +942,7 @@ fn amount_doesnt_match_invreq() { let amt_msat = 5000; let payment_id = PaymentId([1; 32]); - let params = RouteParametersConfig::default(); - nodes[0] - .node - .pay_for_offer(&offer, None, Some(amt_msat), None, payment_id, Retry::Attempts(1), params) - .unwrap(); + nodes[0].node.pay_for_offer(&offer, Some(amt_msat), payment_id, Default::default()).unwrap(); let release_held_htlc_om_3_0 = pass_async_payments_oms(static_invoice, &nodes[0], &nodes[1], &nodes[3], recipient_id).1; @@ -1205,11 +1177,7 @@ fn invalid_async_receive_with_retry( pass_static_invoice_server_messages(&nodes[1], &nodes[2], recipient_id.clone()).invoice; let offer = nodes[2].node.get_async_receive_offer().unwrap(); - let params = RouteParametersConfig::default(); - nodes[0] - .node - .pay_for_offer(&offer, None, Some(amt_msat), None, payment_id, Retry::Attempts(2), params) - .unwrap(); + nodes[0].node.pay_for_offer(&offer, Some(amt_msat), payment_id, Default::default()).unwrap(); let release_held_htlc_om_2_0 = pass_async_payments_oms(static_invoice, &nodes[0], &nodes[1], &nodes[2], recipient_id).1; nodes[0] @@ -1295,11 +1263,7 @@ fn expired_static_invoice_message_path() { let amt_msat = 5000; let payment_id = PaymentId([1; 32]); - let params = RouteParametersConfig::default(); - nodes[0] - .node - .pay_for_offer(&offer, None, Some(amt_msat), None, payment_id, Retry::Attempts(1), params) - .unwrap(); + nodes[0].node.pay_for_offer(&offer, Some(amt_msat), payment_id, Default::default()).unwrap(); // While the invoice is unexpired, respond with release_held_htlc. let (held_htlc_available_om, _release_held_htlc_om) = pass_async_payments_oms( @@ -1410,11 +1374,9 @@ fn expired_static_invoice_payment_path() { let amt_msat = 5000; let payment_id = PaymentId([1; 32]); - let params = RouteParametersConfig::default(); - nodes[0] - .node - .pay_for_offer(&offer, None, Some(amt_msat), None, payment_id, Retry::Attempts(0), params) - .unwrap(); + let mut params: OptionalOfferPaymentParams = Default::default(); + params.retry_strategy = Retry::Attempts(0); + nodes[0].node.pay_for_offer(&offer, Some(amt_msat), payment_id, params).unwrap(); let release_held_htlc_om = pass_async_payments_oms(static_invoice, &nodes[0], &nodes[1], &nodes[2], recipient_id).1; nodes[0] @@ -1850,11 +1812,7 @@ fn refresh_static_invoices_for_used_offers() { let offer = recipient.node.get_async_receive_offer().unwrap(); let amt_msat = 5000; let payment_id = PaymentId([1; 32]); - let params = RouteParametersConfig::default(); - sender - .node - .pay_for_offer(&offer, None, Some(amt_msat), None, payment_id, Retry::Attempts(0), params) - .unwrap(); + sender.node.pay_for_offer(&offer, Some(amt_msat), payment_id, Default::default()).unwrap(); let release_held_htlc_om = pass_async_payments_oms(updated_invoice.clone(), sender, server, recipient, recipient_id).1; @@ -2179,11 +2137,7 @@ fn invoice_server_is_not_channel_peer() { let offer = recipient.node.get_async_receive_offer().unwrap(); let amt_msat = 5000; let payment_id = PaymentId([1; 32]); - let params = RouteParametersConfig::default(); - sender - .node - .pay_for_offer(&offer, None, Some(amt_msat), None, payment_id, Retry::Attempts(0), params) - .unwrap(); + sender.node.pay_for_offer(&offer, Some(amt_msat), payment_id, Default::default()).unwrap(); // Do the held_htlc_available --> release_held_htlc dance. let release_held_htlc_om = diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index cfef0540a97..f67d0ee370d 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -99,7 +99,7 @@ use crate::offers::invoice::{ use crate::offers::invoice_error::InvoiceError; use crate::offers::invoice_request::InvoiceRequest; use crate::offers::nonce::Nonce; -use crate::offers::offer::Offer; +use crate::offers::offer::{Offer, OfferFromHrn}; use crate::offers::parse::Bolt12SemanticError; use crate::offers::refund::Refund; use crate::offers::signer; @@ -633,6 +633,42 @@ impl Readable for InterceptId { } } +/// Optional arguments to [`ChannelManager::pay_for_offer`] +#[cfg_attr( + feature = "dnssec", + doc = "and [`ChannelManager::pay_for_offer_from_human_readable_name`]" +)] +/// . +/// +/// These fields will often not need to be set, and the provided [`Self::default`] can be used. +pub struct OptionalOfferPaymentParams { + /// A note that is communicated to the recipient about this payment via + /// [`InvoiceRequest::payer_note`]. + pub payer_note: Option, + /// Pathfinding options which tweak how the path is constructed to the recipient. + pub route_params_config: RouteParametersConfig, + /// The number of tries or time during which we'll retry this payment if some paths to the + /// recipient fail. + /// + /// Once the retry limit is reached, further path failures will not be retried and the payment + /// will ultimately fail once all pending paths have failed (generating an + /// [`Event::PaymentFailed`]). + pub retry_strategy: Retry, +} + +impl Default for OptionalOfferPaymentParams { + fn default() -> Self { + Self { + payer_note: None, + route_params_config: Default::default(), + #[cfg(feature = "std")] + retry_strategy: Retry::Timeout(core::time::Duration::from_secs(2)), + #[cfg(not(feature = "std"))] + retry_strategy: Retry::Attempts(3), + } + } +} + #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] /// Uniquely describes an HTLC by its source. Just the guaranteed-unique subset of [`HTLCSource`]. pub(crate) enum SentHTLCId { @@ -2213,18 +2249,16 @@ where /// /// ``` /// # use lightning::events::{Event, EventsProvider}; -/// # use lightning::ln::channelmanager::{AChannelManager, PaymentId, RecentPaymentDetails, Retry}; +/// # use lightning::ln::channelmanager::{AChannelManager, PaymentId, RecentPaymentDetails}; /// # use lightning::offers::offer::Offer; -/// # use lightning::routing::router::RouteParametersConfig; /// # /// # fn example( -/// # channel_manager: T, offer: &Offer, quantity: Option, amount_msats: Option, -/// # payer_note: Option, retry: Retry, route_params_config: RouteParametersConfig +/// # channel_manager: T, offer: &Offer, amount_msats: 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, route_params_config +/// offer, amount_msats, payment_id, Default::default(), /// ) { /// Ok(()) => println!("Requesting invoice for offer"), /// Err(e) => println!("Unable to request invoice for offer: {:?}", e), @@ -5034,6 +5068,9 @@ where /// Sends a payment to the route found using the provided [`RouteParameters`], retrying failed /// payment paths based on the provided `Retry`. /// + /// You should likely prefer [`Self::pay_for_bolt11_invoice`] or [`Self::pay_for_offer`] in + /// general, however this method may allow for slightly more customization. + /// /// May generate [`UpdateHTLCs`] message(s) event on success, which should be relayed (e.g. via /// [`PeerManager::process_events`]). /// @@ -12217,16 +12254,12 @@ where /// /// 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`]. /// - /// # Custom Routing Parameters + /// `amount_msats` allows you to overpay what is required to satisfy the offer, or may be + /// required if the offer does not require a specific amount. /// - /// Users can customize routing parameters via [`RouteParametersConfig`]. - /// To use default settings, call the function with [`RouteParametersConfig::default`]. + /// If the [`Offer`] was built from a human readable name resolved using BIP 353, you *must* + /// instead call [`Self::pay_for_offer_from_hrn`]. /// /// # Payment /// @@ -12243,12 +12276,6 @@ where /// For payer privacy, uses a derived payer id and uses [`MessageRouter::create_blinded_paths`] /// to construct a [`BlindedMessagePath`] for the reply path. /// - /// # 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: @@ -12259,31 +12286,22 @@ where /// 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 /// [`BlindedMessagePath`]: crate::blinded_path::message::BlindedMessagePath /// [`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, - route_params_config: RouteParametersConfig, + &self, offer: &Offer, amount_msats: Option, payment_id: PaymentId, + optional_params: OptionalOfferPaymentParams, ) -> Result<(), Bolt12SemanticError> { - let create_pending_payment_fn = |invoice_request: &InvoiceRequest, nonce| { - let expiration = StaleExpiration::TimerTicks(1); - let retryable_invoice_request = RetryableInvoiceRequest { - invoice_request: invoice_request.clone(), - nonce, - needs_retry: true, - }; + let create_pending_payment_fn = |retryable_invoice_request: RetryableInvoiceRequest| { self.pending_outbound_payments .add_new_awaiting_invoice( payment_id, - expiration, - retry_strategy, - route_params_config, + StaleExpiration::TimerTicks(1), + optional_params.retry_strategy, + optional_params.route_params_config, Some(retryable_invoice_request), ) .map_err(|_| Bolt12SemanticError::DuplicatePaymentId) @@ -12291,9 +12309,80 @@ where self.pay_for_offer_intern( offer, - quantity, + if offer.expects_quantity() { Some(1) } else { None }, amount_msats, - payer_note, + optional_params.payer_note, + payment_id, + None, + create_pending_payment_fn, + ) + } + + /// Pays for an [`Offer`] which was built by resolving a human readable name. It is otherwise + /// identical to [`Self::pay_for_offer`]. + pub fn pay_for_offer_from_hrn( + &self, offer: &OfferFromHrn, amount_msats: u64, payment_id: PaymentId, + optional_params: OptionalOfferPaymentParams, + ) -> Result<(), Bolt12SemanticError> { + let create_pending_payment_fn = |retryable_invoice_request: RetryableInvoiceRequest| { + self.pending_outbound_payments + .add_new_awaiting_invoice( + payment_id, + StaleExpiration::TimerTicks(1), + optional_params.retry_strategy, + optional_params.route_params_config, + Some(retryable_invoice_request), + ) + .map_err(|_| Bolt12SemanticError::DuplicatePaymentId) + }; + + self.pay_for_offer_intern( + &offer.offer, + if offer.offer.expects_quantity() { Some(1) } else { None }, + Some(amount_msats), + optional_params.payer_note, + payment_id, + Some(offer.hrn), + create_pending_payment_fn, + ) + } + + /// Pays for an [`Offer`] using the given parameters, including a `quantity`, by creating an + /// [`InvoiceRequest`] and enqueuing it to be sent via an onion message. [`ChannelManager`] will + /// pay the actual [`Bolt12Invoice`] once it is received. + /// + /// This method is identical to [`Self::pay_for_offer`] with the one exception that it allows + /// you to specify the [`InvoiceRequest::quantity`]. We expect this to be rather seldomly used, + /// as the "quantity" feature of offers doesn't line up with common payment flows today. + /// + /// This method is otherwise identical to [`Self::pay_for_offer`] but will additionally fail if + /// the provided `quantity` does not meet the requirements described by + /// [`Offer::supported_quantity`]. + /// + /// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest + /// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice + /// [`InvoiceRequest::quantity`]: crate::offers::invoice_request::InvoiceRequest::quantity + pub fn pay_for_offer_with_quantity( + &self, offer: &Offer, amount_msats: Option, payment_id: PaymentId, + optional_params: OptionalOfferPaymentParams, quantity: u64, + ) -> Result<(), Bolt12SemanticError> { + let create_pending_payment_fn = |retryable_invoice_request: RetryableInvoiceRequest| { + self.pending_outbound_payments + .add_new_awaiting_invoice( + payment_id, + StaleExpiration::TimerTicks(1), + optional_params.retry_strategy, + optional_params.route_params_config, + Some(retryable_invoice_request), + ) + .map_err(|_| Bolt12SemanticError::DuplicatePaymentId) + }; + + self.pay_for_offer_intern( + offer, + Some(quantity), + amount_msats, + optional_params.payer_note, payment_id, None, create_pending_payment_fn, @@ -12301,7 +12390,7 @@ where } #[rustfmt::skip] - fn pay_for_offer_intern Result<(), Bolt12SemanticError>>( + 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, @@ -12338,7 +12427,13 @@ where self.get_peers_for_blinded_path() )?; - create_pending_payment(&invoice_request, nonce) + let retryable_invoice_request = RetryableInvoiceRequest { + invoice_request: invoice_request.clone(), + nonce, + needs_retry: true, + }; + + create_pending_payment(retryable_invoice_request) } /// Creates a [`Bolt12Invoice`] for a [`Refund`] and enqueues it to be sent via an onion @@ -12401,10 +12496,8 @@ where /// implementing [`DNSResolverMessageHandler`]) directly to look up a URI and then delegate to /// your normal URI handling. /// - /// # Custom Routing Parameters - /// - /// Users can customize routing parameters via [`RouteParametersConfig`]. - /// To use default settings, call the function with [`RouteParametersConfig::default`]. + /// Alternatively, the [`bitcoin-payment-instructions`] crate provides an implementation of + /// much of this logic, and may be useful to decode and resolve payment instructions generally. /// /// # Payment /// @@ -12422,22 +12515,15 @@ where /// For payer privacy, uses a derived payer id and uses [`MessageRouter::create_blinded_paths`] /// to construct a [`BlindedMessagePath`] for the reply path. /// - /// # Limitations - /// - /// Requires a direct connection to the given [`Destination`] as well as 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, + /// Errors if a duplicate `payment_id` is provided given the caveats in the aforementioned link. /// /// [BIP 353]: https://github.com/bitcoin/bips/blob/master/bip-0353.mediawiki /// [bLIP 32]: https://github.com/lightning/blips/blob/master/blip-0032.md - /// [`Bolt12Invoice::payment_paths`]: crate::offers::invoice::Bolt12Invoice::payment_paths /// [`OMNameResolver::resolve_name`]: crate::onion_message::dns_resolution::OMNameResolver::resolve_name /// [`OMNameResolver::handle_dnssec_proof_for_uri`]: crate::onion_message::dns_resolution::OMNameResolver::handle_dnssec_proof_for_uri + /// [`bitcoin-payment-instructions`]: https://docs.rs/bitcoin-payment-instructions/ /// [Avoiding Duplicate Payments]: #avoiding-duplicate-payments /// [`BlindedMessagePath`]: crate::blinded_path::message::BlindedMessagePath /// [`PaymentFailureReason::UserAbandoned`]: crate::events::PaymentFailureReason::UserAbandoned @@ -12445,8 +12531,7 @@ where #[cfg(feature = "dnssec")] pub fn pay_for_offer_from_human_readable_name( &self, name: HumanReadableName, amount_msats: u64, payment_id: PaymentId, - payer_note: Option, retry_strategy: Retry, - route_params_config: RouteParametersConfig, dns_resolvers: Vec, + optional_params: OptionalOfferPaymentParams, dns_resolvers: Vec, ) -> Result<(), ()> { let (onion_message, context) = self.flow.hrn_resolver.resolve_name(payment_id, name, &*self.entropy_source)?; @@ -12455,10 +12540,10 @@ where self.pending_outbound_payments.add_new_awaiting_offer( payment_id, expiration, - retry_strategy, - route_params_config, + optional_params.retry_strategy, + optional_params.route_params_config, amount_msats, - payer_note, + optional_params.payer_note, )?; self.flow @@ -14727,12 +14812,7 @@ where if let Ok((amt_msats, payer_note)) = self.pending_outbound_payments.params_for_payment_awaiting_offer(payment_id) { let offer_pay_res = self.pay_for_offer_intern(&offer, None, Some(amt_msats), payer_note, payment_id, Some(name), - |invoice_request, nonce| { - let retryable_invoice_request = RetryableInvoiceRequest { - invoice_request: invoice_request.clone(), - nonce, - needs_retry: true, - }; + |retryable_invoice_request| { self.pending_outbound_payments .received_offer(payment_id, Some(retryable_invoice_request)) .map_err(|_| Bolt12SemanticError::DuplicatePaymentId) diff --git a/lightning/src/ln/max_payment_path_len_tests.rs b/lightning/src/ln/max_payment_path_len_tests.rs index 177050baf90..8425e1928ad 100644 --- a/lightning/src/ln/max_payment_path_len_tests.rs +++ b/lightning/src/ln/max_payment_path_len_tests.rs @@ -17,7 +17,7 @@ use crate::blinded_path::payment::{ use crate::blinded_path::BlindedHop; use crate::events::Event; use crate::ln::blinded_payment_tests::get_blinded_route_parameters; -use crate::ln::channelmanager::PaymentId; +use crate::ln::channelmanager::{OptionalOfferPaymentParams, PaymentId}; use crate::ln::functional_test_utils::*; use crate::ln::msgs; use crate::ln::msgs::{BaseMessageHandler, OnionMessageHandler}; @@ -27,7 +27,7 @@ use crate::ln::outbound_payment::{RecipientOnionFields, Retry, RetryableSendFail use crate::offers::nonce::Nonce; use crate::prelude::*; use crate::routing::router::{ - PaymentParameters, RouteParameters, RouteParametersConfig, DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA, + PaymentParameters, RouteParameters, DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA, }; use crate::sign::NodeSigner; use crate::types::features::BlindedHopFeatures; @@ -519,10 +519,9 @@ fn bolt12_invoice_too_large_blinded_paths() { let offer = nodes[1].node.create_offer_builder().unwrap().build().unwrap(); let payment_id = PaymentId([1; 32]); - let route_config = RouteParametersConfig::default(); nodes[0] .node - .pay_for_offer(&offer, None, Some(5000), None, payment_id, Retry::Attempts(0), route_config) + .pay_for_offer(&offer, Some(5000), payment_id, OptionalOfferPaymentParams::default()) .unwrap(); let invreq_om = nodes[0] .onion_messenger diff --git a/lightning/src/ln/offers_tests.rs b/lightning/src/ln/offers_tests.rs index e2bdfc44e29..b7d64df4063 100644 --- a/lightning/src/ln/offers_tests.rs +++ b/lightning/src/ln/offers_tests.rs @@ -473,7 +473,7 @@ fn check_dummy_hop_pattern_in_offer() { } let payment_id = PaymentId([1; 32]); - bob.node.pay_for_offer(&compact_offer, None, None, None, payment_id, Retry::Attempts(0), RouteParametersConfig::default()).unwrap(); + bob.node.pay_for_offer(&compact_offer, None, payment_id, Default::default()).unwrap(); let onion_message = bob.onion_messenger.next_onion_message_for_peer(alice_id).unwrap(); let (invoice_request, reply_path) = extract_invoice_request(alice, &onion_message); @@ -495,7 +495,7 @@ fn check_dummy_hop_pattern_in_offer() { assert!(padded_offer.paths().iter().all(|path| path.blinded_hops().len() == PADDED_PATH_LENGTH)); let payment_id = PaymentId([2; 32]); - bob.node.pay_for_offer(&padded_offer, None, None, None, payment_id, Retry::Attempts(0), RouteParametersConfig::default()).unwrap(); + bob.node.pay_for_offer(&padded_offer, None, payment_id, Default::default()).unwrap(); let onion_message = bob.onion_messenger.next_onion_message_for_peer(alice_id).unwrap(); let (invoice_request, reply_path) = extract_invoice_request(alice, &onion_message); @@ -662,8 +662,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), RouteParametersConfig::default()) - .unwrap(); + david.node.pay_for_offer(&offer, None, payment_id, Default::default()).unwrap(); expect_recent_payment!(david, RecentPaymentDetails::AwaitingInvoice, payment_id); connect_peers(david, bob); @@ -828,7 +827,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), RouteParametersConfig::default()).unwrap(); + bob.node.pay_for_offer(&offer, None, payment_id, Default::default()).unwrap(); expect_recent_payment!(bob, RecentPaymentDetails::AwaitingInvoice, payment_id); let onion_message = bob.onion_messenger.next_onion_message_for_peer(alice_id).unwrap(); @@ -949,7 +948,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), RouteParametersConfig::default()).unwrap(); + bob.node.pay_for_offer(&offer, None, payment_id, Default::default()).unwrap(); expect_recent_payment!(bob, RecentPaymentDetails::AwaitingInvoice, payment_id); let onion_message = bob.onion_messenger.next_onion_message_for_peer(alice_id).unwrap(); @@ -1076,8 +1075,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), RouteParametersConfig::default()) - .unwrap(); + david.node.pay_for_offer(&offer, None, payment_id, Default::default()).unwrap(); expect_recent_payment!(david, RecentPaymentDetails::AwaitingInvoice, payment_id); connect_peers(david, bob); @@ -1210,7 +1208,7 @@ fn creates_and_pays_for_offer_with_retry() { assert!(check_compact_path_introduction_node(&path, bob, alice_id)); } let payment_id = PaymentId([1; 32]); - bob.node.pay_for_offer(&offer, None, None, None, payment_id, Retry::Attempts(0), RouteParametersConfig::default()).unwrap(); + bob.node.pay_for_offer(&offer, None, payment_id, Default::default()).unwrap(); expect_recent_payment!(bob, RecentPaymentDetails::AwaitingInvoice, payment_id); let _lost_onion_message = bob.onion_messenger.next_onion_message_for_peer(alice_id).unwrap(); @@ -1282,7 +1280,7 @@ fn pays_bolt12_invoice_asynchronously() { .build().unwrap(); let payment_id = PaymentId([1; 32]); - bob.node.pay_for_offer(&offer, None, None, None, payment_id, Retry::Attempts(0), RouteParametersConfig::default()).unwrap(); + bob.node.pay_for_offer(&offer, None, payment_id, Default::default()).unwrap(); expect_recent_payment!(bob, RecentPaymentDetails::AwaitingInvoice, payment_id); let onion_message = bob.onion_messenger.next_onion_message_for_peer(alice_id).unwrap(); @@ -1379,7 +1377,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), RouteParametersConfig::default()).unwrap(); + bob.node.pay_for_offer(&offer, None, payment_id, Default::default()).unwrap(); expect_recent_payment!(bob, RecentPaymentDetails::AwaitingInvoice, payment_id); let onion_message = bob.onion_messenger.next_onion_message_for_peer(alice_id).unwrap(); @@ -1520,8 +1518,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), RouteParametersConfig::default()) - .unwrap(); + david.node.pay_for_offer(&offer, None, payment_id, Default::default()).unwrap(); expect_recent_payment!(david, RecentPaymentDetails::AwaitingInvoice, payment_id); connect_peers(david, alice); @@ -1546,8 +1543,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), RouteParametersConfig::default()) - .unwrap(); + david.node.pay_for_offer(&offer, None, payment_id, Default::default()).unwrap(); expect_recent_payment!(david, RecentPaymentDetails::AwaitingInvoice, payment_id); match &mut david.node.flow.pending_offers_messages.lock().unwrap().first_mut().unwrap().1 { @@ -1623,8 +1619,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), RouteParametersConfig::default()) - .unwrap(); + david.node.pay_for_offer(&offer, None, payment_id, Default::default()).unwrap(); david.node.abandon_payment(payment_id); get_event!(david, Event::PaymentFailed); @@ -1640,8 +1635,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), RouteParametersConfig::default()) - .unwrap(); + david.node.pay_for_offer(&offer, None, payment_id, Default::default()).unwrap(); expect_recent_payment!(david, RecentPaymentDetails::AwaitingInvoice, payment_id); // Swap out the reply path to force authentication to fail when handling the invoice since it @@ -1825,7 +1819,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), RouteParametersConfig::default()) { + match david.node.pay_for_offer(&offer, None, payment_id, Default::default()) { Ok(_) => panic!("Expected error"), Err(e) => assert_eq!(e, Bolt12SemanticError::MissingPaths), } @@ -1836,12 +1830,7 @@ fn fails_creating_or_paying_for_offer_without_connected_peers() { args.send_channel_ready = (true, true); reconnect_nodes(args); - assert!( - david.node.pay_for_offer( - &offer, None, None, None, payment_id, Retry::Attempts(0), RouteParametersConfig::default() - ).is_ok() - ); - + assert!(david.node.pay_for_offer(&offer, None, payment_id, Default::default()).is_ok()); expect_recent_payment!(david, RecentPaymentDetails::AwaitingInvoice, payment_id); } @@ -1928,8 +1917,7 @@ fn fails_creating_invoice_request_for_unsupported_chain() { .chain(Network::Signet) .build().unwrap(); - let payment_id = PaymentId([1; 32]); - match bob.node.pay_for_offer(&offer, None, None, None, payment_id, Retry::Attempts(0), RouteParametersConfig::default()) { + match bob.node.pay_for_offer(&offer, None, PaymentId([1; 32]), Default::default()) { Ok(_) => panic!("Expected error"), Err(e) => assert_eq!(e, Bolt12SemanticError::UnsupportedChain), } @@ -1986,9 +1974,7 @@ fn fails_creating_invoice_request_without_blinded_reply_path() { .amount_msats(10_000_000) .build().unwrap(); - let payment_id = PaymentId([1; 32]); - - match david.node.pay_for_offer(&offer, None, None, None, payment_id, Retry::Attempts(0), RouteParametersConfig::default()) { + match david.node.pay_for_offer(&offer, None, PaymentId([1; 32]), Default::default()) { Ok(_) => panic!("Expected error"), Err(e) => assert_eq!(e, Bolt12SemanticError::MissingPaths), } @@ -2021,14 +2007,10 @@ fn fails_creating_invoice_request_with_duplicate_payment_id() { .build().unwrap(); let payment_id = PaymentId([1; 32]); - assert!( - david.node.pay_for_offer( - &offer, None, None, None, payment_id, Retry::Attempts(0), RouteParametersConfig::default() - ).is_ok() - ); + assert!(david.node.pay_for_offer( &offer, None, payment_id, Default::default()).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), RouteParametersConfig::default()) { + match david.node.pay_for_offer(&offer, None, payment_id, Default::default()) { Ok(_) => panic!("Expected error"), Err(e) => assert_eq!(e, Bolt12SemanticError::DuplicatePaymentId), } @@ -2107,8 +2089,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), RouteParametersConfig::default()) - .unwrap(); + david.node.pay_for_offer(&offer, None, payment_id, Default::default()).unwrap(); connect_peers(david, bob); @@ -2316,8 +2297,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), RouteParametersConfig::default()) - .unwrap(); + david.node.pay_for_offer(&offer, None, payment_id, Default::default()).unwrap(); connect_peers(david, bob); @@ -2393,7 +2373,7 @@ fn rejects_keysend_to_non_static_invoice_path() { let offer = nodes[1].node.create_offer_builder().unwrap().build().unwrap(); let amt_msat = 5000; let payment_id = PaymentId([1; 32]); - nodes[0].node.pay_for_offer(&offer, None, Some(amt_msat), None, payment_id, Retry::Attempts(1), RouteParametersConfig::default()).unwrap(); + nodes[0].node.pay_for_offer(&offer, Some(amt_msat), payment_id, Default::default()).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); let invoice_om = nodes[1].onion_messenger.next_onion_message_for_peer(nodes[0].node.get_our_node_id()).unwrap(); @@ -2478,7 +2458,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), RouteParametersConfig::default()).unwrap(); + nodes[0].node.pay_for_offer(&offer, None, payment_id, Default::default()).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/offer.rs b/lightning/src/offers/offer.rs index 7fd2c4e60e2..ff798388b19 100644 --- a/lightning/src/offers/offer.rs +++ b/lightning/src/offers/offer.rs @@ -86,6 +86,7 @@ use crate::offers::merkle::{TaggedHash, TlvRecord, TlvStream}; use crate::offers::nonce::Nonce; use crate::offers::parse::{Bech32Encode, Bolt12ParseError, Bolt12SemanticError, ParsedMessage}; use crate::offers::signer::{self, Metadata, MetadataMaterial}; +use crate::onion_message::dns_resolution::HumanReadableName; use crate::types::features::OfferFeatures; use crate::types::string::PrintableString; use crate::util::ser::{ @@ -577,6 +578,20 @@ impl<'a> From> } } +/// An [`Offer`] which was fetched from a human readable name, ie through BIP 353. +pub struct OfferFromHrn { + /// The offer itself. + /// + /// When you resolve this into an [`InvoiceRequestBuilder`] you *must* call + /// [`InvoiceRequestBuilder::sourced_from_human_readable_name`]. + /// + /// If you call [`Self::request_invoice`] rather than [`Offer::request_invoice`] this will be + /// handled for you. + pub offer: Offer, + /// The human readable name which was resolved to fetch the [`Self::offer`]. + pub hrn: HumanReadableName, +} + /// An `Offer` is a potentially long-lived proposal for payment of a good or service. /// /// An offer is a precursor to an [`InvoiceRequest`]. A merchant publishes an offer from which a @@ -750,7 +765,7 @@ impl Offer { } } -macro_rules! request_invoice_derived_signing_pubkey { ($self: ident, $builder: ty) => { +macro_rules! request_invoice_derived_signing_pubkey { ($self: ident, $offer: expr, $builder: ty, $hrn: expr) => { /// Creates an [`InvoiceRequestBuilder`] for the offer, which /// - derives the [`InvoiceRequest::payer_signing_pubkey`] such that a different key can be used /// for each request in order to protect the sender's privacy, @@ -779,24 +794,57 @@ macro_rules! request_invoice_derived_signing_pubkey { ($self: ident, $builder: t secp_ctx: &'b Secp256k1, payment_id: PaymentId ) -> Result<$builder, Bolt12SemanticError> { - if $self.offer_features().requires_unknown_bits() { + if $offer.offer_features().requires_unknown_bits() { return Err(Bolt12SemanticError::UnknownRequiredFeatures); } - Ok(<$builder>::deriving_signing_pubkey($self, expanded_key, nonce, secp_ctx, payment_id)) + let mut builder = <$builder>::deriving_signing_pubkey(&$offer, expanded_key, nonce, secp_ctx, payment_id); + if let Some(hrn) = $hrn { + #[cfg(c_bindings)] + { + builder.sourced_from_human_readable_name(hrn); + } + #[cfg(not(c_bindings))] + { + builder = builder.sourced_from_human_readable_name(hrn); + } + } + Ok(builder) } } } #[cfg(not(c_bindings))] impl Offer { - request_invoice_derived_signing_pubkey!(self, InvoiceRequestBuilder<'a, 'b, T>); + request_invoice_derived_signing_pubkey!(self, self, InvoiceRequestBuilder<'a, 'b, T>, None); +} + +#[cfg(not(c_bindings))] +impl OfferFromHrn { + request_invoice_derived_signing_pubkey!( + self, + self.offer, + InvoiceRequestBuilder<'a, 'b, T>, + Some(self.hrn) + ); } #[cfg(c_bindings)] impl Offer { request_invoice_derived_signing_pubkey!( self, - InvoiceRequestWithDerivedPayerSigningPubkeyBuilder<'a, 'b> + self, + InvoiceRequestWithDerivedPayerSigningPubkeyBuilder<'a, 'b>, + None + ); +} + +#[cfg(c_bindings)] +impl OfferFromHrn { + request_invoice_derived_signing_pubkey!( + self, + self.offer, + InvoiceRequestWithDerivedPayerSigningPubkeyBuilder<'a, 'b>, + Some(self.hrn.clone()) ); } diff --git a/lightning/src/onion_message/dns_resolution.rs b/lightning/src/onion_message/dns_resolution.rs index 9f88a7c56ff..7961828b1b2 100644 --- a/lightning/src/onion_message/dns_resolution.rs +++ b/lightning/src/onion_message/dns_resolution.rs @@ -201,7 +201,7 @@ const REQUIRED_EXTRA_LEN: usize = ".user._bitcoin-payment.".len() + 1; /// This struct can also be used for LN-Address recipients. /// /// [Homograph Attacks]: https://en.wikipedia.org/wiki/IDN_homograph_attack -#[derive(Clone, Debug, Hash, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] pub struct HumanReadableName { contents: [u8; 255 - REQUIRED_EXTRA_LEN], user_len: u8,