diff --git a/lightning/src/ln/async_payments_tests.rs b/lightning/src/ln/async_payments_tests.rs index bd07d13c13d..194215b2aed 100644 --- a/lightning/src/ln/async_payments_tests.rs +++ b/lightning/src/ln/async_payments_tests.rs @@ -868,6 +868,7 @@ fn ignore_unexpected_static_invoice() { } #[test] +#[allow(deprecated)] // Tests the deprecated send_payment_for_bolt12_invoice. fn ignore_duplicate_invoice() { // When a sender tries to pay an async recipient it could potentially end up receiving two // invoices: one static invoice that it received from always-online node and a fresh invoice diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index a3c33b8320f..5f12784b5ba 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -758,6 +758,41 @@ impl Default for OptionalBolt11PaymentParams { } } +/// Optional arguments to [`ChannelManager::pay_for_bolt12_invoice`]. +/// +/// These fields will often not need to be set, and the provided [`Self::default`] can be used. +pub struct OptionalBolt12PaymentParams { + /// The amount this node contributes to the payment. Set to `None` to pay the full invoice + /// amount. + /// + /// For payments split across multiple senders, provide a partial amount here — the onion total + /// is always set to the full invoice amount so that the recipient can correctly validate the + /// payment. + pub amount_msats: 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 OptionalBolt12PaymentParams { + fn default() -> Self { + Self { + amount_msats: 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), + } + } +} + /// Optional arguments to [`ChannelManager::pay_for_offer`]. /// /// These fields will often not need to be set, and the provided [`Self::default`] can be used. @@ -5824,6 +5859,10 @@ impl< /// whether or not the payment was successful. /// /// [timer tick]: Self::timer_tick_occurred + #[deprecated( + since = "0.3.0", + note = "Use ChannelManager::pay_for_bolt12_invoice instead, providing a fresh payment_id and verifying the invoice yourself." + )] pub fn send_payment_for_bolt12_invoice( &self, invoice: &Bolt12Invoice, context: Option<&OffersContext>, ) -> Result<(), Bolt12PaymentError> { @@ -5857,6 +5896,47 @@ impl< ) } + /// Pays a [`Bolt12Invoice`] without requiring it to have been requested through LDK. + /// + /// Unlike [`ChannelManager::send_payment_for_bolt12_invoice`], this method does not verify + /// that the invoice was previously requested. The caller is responsible for invoice + /// verification and for providing a unique `payment_id`. + /// + /// The amount this node contributes to the payment can be set via + /// [`OptionalBolt12PaymentParams::amount_msats`], which defaults to the full invoice amount. + /// + /// Returns [`Bolt12PaymentError::DuplicateInvoice`] if a payment with the given `payment_id` + /// is already pending, or [`Bolt12PaymentError::InvalidAmount`] if the requested amount is zero + /// or exceeds the invoice amount. + /// + /// Either [`Event::PaymentSent`] or [`Event::PaymentFailed`] will be generated once the + /// payment completes. + pub fn pay_for_bolt12_invoice( + &self, invoice: &Bolt12Invoice, payment_id: PaymentId, + optional_params: OptionalBolt12PaymentParams, + ) -> 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.pay_for_bolt12_invoice( + invoice, + payment_id, + optional_params, + &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.pending_events, + |args| self.send_payment_along_path(args), + &WithContext::for_payment(&self.logger, None, None, None, payment_id), + ) + } + fn check_refresh_async_receive_offer_cache(&self, timer_tick_occurred: bool) { let peers = self.get_peers_for_blinded_path(); let channels = self.list_usable_channels(); @@ -17059,6 +17139,11 @@ impl< log_trace!($logger, "{}", err_msg); InvoiceError::from_string(err_msg.to_string()) }, + Err(Bolt12PaymentError::InvalidAmount) => { + debug_assert!(false, "Got InvalidAmount paying internally-sourced invoice; this shouldn't happen"); + log_error!($logger, "Got InvalidAmount paying internally-sourced invoice; this shouldn't happen"); + return None + }, Err(Bolt12PaymentError::UnexpectedInvoice) | Err(Bolt12PaymentError::DuplicateInvoice) | Ok(()) => return None, diff --git a/lightning/src/ln/offers_tests.rs b/lightning/src/ln/offers_tests.rs index de08af5d276..fa332a9fff3 100644 --- a/lightning/src/ln/offers_tests.rs +++ b/lightning/src/ln/offers_tests.rs @@ -1303,6 +1303,7 @@ fn creates_and_pays_for_offer_with_retry() { /// Checks that a deferred invoice can be paid asynchronously from an Event::InvoiceReceived. #[test] +#[allow(deprecated)] // Tests the deprecated send_payment_for_bolt12_invoice. fn pays_bolt12_invoice_asynchronously() { let mut manually_pay_cfg = test_default_channel_config(); manually_pay_cfg.manually_handle_bolt12_invoices = true; @@ -2667,3 +2668,261 @@ fn creates_and_pays_for_phantom_offer() { assert!(nodes[0].onion_messenger.next_onion_message_for_peer(node_c_id).is_none()); } } + +/// Checks that a BOLT 12 invoice can be paid via [`ChannelManager::pay_for_bolt12_invoice`] +/// without requiring a prior LDK-managed payment request. +#[test] +fn pay_for_bolt12_invoice_with_fresh_payment_id() { + 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().unwrap() + .amount_msats(10_000_000) + .build().unwrap(); + + // Use the standard offer flow to obtain an invoice, but pay it via the new API with a + // fresh payment_id rather than the one from the original request. + let orig_payment_id = PaymentId([1; 32]); + bob.node.pay_for_offer(&offer, None, orig_payment_id, Default::default()).unwrap(); + + 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 = match get_event!(bob, Event::InvoiceReceived) { + Event::InvoiceReceived { invoice, .. } => invoice, + _ => panic!("Expected InvoiceReceived"), + }; + + // Abandon the original payment since we're paying via a fresh payment_id below. + bob.node.abandon_payment(orig_payment_id); + get_event!(bob, Event::PaymentFailed); + + let payment_id = PaymentId([2; 32]); + bob.node.pay_for_bolt12_invoice(&invoice, payment_id, Default::default()).unwrap(); + expect_recent_payment!(bob, RecentPaymentDetails::Pending, payment_id); + + route_bolt12_payment(bob, &[alice], &invoice); + claim_bolt12_payment(bob, &[alice], payment_context, &invoice); + expect_recent_payment!(bob, RecentPaymentDetails::Fulfilled, payment_id); +} + +/// Checks error cases for [`ChannelManager::pay_for_bolt12_invoice`]: +/// zero amount and overpaying return [`Bolt12PaymentError::InvalidAmount`], re-using a +/// payment_id returns [`Bolt12PaymentError::DuplicateInvoice`]. +#[test] +fn pay_for_bolt12_invoice_error_cases() { + 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().unwrap() + .amount_msats(10_000_000) + .build().unwrap(); + + let orig_payment_id = PaymentId([1; 32]); + bob.node.pay_for_offer(&offer, None, orig_payment_id, Default::default()).unwrap(); + + let invoice_request_onion_message = bob.onion_messenger.next_onion_message_for_peer(alice_id).unwrap(); + alice.onion_messenger.handle_onion_message(bob_id, &invoice_request_onion_message); + + let (invoice_request, _) = extract_invoice_request(alice, &invoice_request_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 = match get_event!(bob, Event::InvoiceReceived) { + Event::InvoiceReceived { invoice, .. } => invoice, + _ => panic!("Expected InvoiceReceived"), + }; + + bob.node.abandon_payment(orig_payment_id); + get_event!(bob, Event::PaymentFailed); + + let payment_id = PaymentId([2; 32]); + + // Zero amount is rejected. + let zero_amount_params = channelmanager::OptionalBolt12PaymentParams { + amount_msats: Some(0), + ..Default::default() + }; + assert_eq!( + bob.node.pay_for_bolt12_invoice(&invoice, payment_id, zero_amount_params), + Err(Bolt12PaymentError::InvalidAmount), + ); + + // Overpaying is rejected before any state is inserted. + let overpay_params = channelmanager::OptionalBolt12PaymentParams { + amount_msats: Some(invoice.amount_msats() + 1), + ..Default::default() + }; + assert_eq!( + bob.node.pay_for_bolt12_invoice(&invoice, payment_id, overpay_params), + Err(Bolt12PaymentError::InvalidAmount), + ); + + // First call succeeds and starts the payment. + bob.node.pay_for_bolt12_invoice(&invoice, payment_id, Default::default()).unwrap(); + + // Re-using the same payment_id is rejected. + assert_eq!( + bob.node.pay_for_bolt12_invoice(&invoice, payment_id, Default::default()), + Err(Bolt12PaymentError::DuplicateInvoice), + ); + + // Creating an invoice with unknown required features should be rejected. + let expanded_key = alice.keys_manager.get_expanded_key(); + let secp_ctx = Secp256k1::new(); + let created_at = alice.node.duration_since_epoch(); + let nonce = extract_offer_nonce(alice, &invoice_request_onion_message); + let verified_invoice_request = invoice_request + .verify_using_recipient_data(nonce, &expanded_key, &secp_ctx).unwrap(); + + let unknown_features_invoice = match verified_invoice_request { + InvoiceRequestVerifiedFromOffer::DerivedKeys(request) => { + request.respond_using_derived_keys_no_std(invoice.payment_paths().to_vec(), invoice.payment_hash(), created_at).unwrap() + .features_unchecked(Bolt12InvoiceFeatures::unknown()) + .build_and_sign(&secp_ctx).unwrap() + }, + InvoiceRequestVerifiedFromOffer::ExplicitKeys(_) => { + panic!("Expected invoice request with keys"); + }, + }; + + let unknown_features_payment_id = PaymentId([3; 32]); + assert_eq!( + bob.node.pay_for_bolt12_invoice(&unknown_features_invoice, unknown_features_payment_id, Default::default()), + Err(Bolt12PaymentError::UnknownRequiredFeatures), + ); + + route_bolt12_payment(bob, &[alice], &invoice); + claim_bolt12_payment(bob, &[alice], payment_context, &invoice); + expect_recent_payment!(bob, RecentPaymentDetails::Fulfilled, payment_id); +} + +/// Checks that pay_for_bolt12_invoice with a partial amount routes an HTLC for the partial +/// amount while setting total_mpp_amount_msat to the full invoice amount in the onion, so the +/// recipient holds the HTLC awaiting additional parts until the full amount arrives. +#[test] +fn pay_for_bolt12_invoice_partial_amount() { + 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 invoice_amount = 10_000_000u64; + let partial_amount = 5_000_000u64; + + let offer = alice.node + .create_offer_builder().unwrap() + .amount_msats(invoice_amount) + .build().unwrap(); + + let orig_payment_id = PaymentId([1; 32]); + bob.node.pay_for_offer(&offer, None, orig_payment_id, Default::default()).unwrap(); + + 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 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 = match get_event!(bob, Event::InvoiceReceived) { + Event::InvoiceReceived { invoice, .. } => invoice, + _ => panic!("Expected InvoiceReceived"), + }; + + bob.node.abandon_payment(orig_payment_id); + get_event!(bob, Event::PaymentFailed); + + let payment_hash = invoice.payment_hash(); + let payment_id = PaymentId([2; 32]); + + let params = channelmanager::OptionalBolt12PaymentParams { + amount_msats: Some(partial_amount), + retry_strategy: Retry::Attempts(0), + ..Default::default() + }; + bob.node.pay_for_bolt12_invoice(&invoice, payment_id, params).unwrap(); + expect_recent_payment!(bob, RecentPaymentDetails::Pending, payment_id); + + check_added_monitors(bob, 1); + let mut events = bob.node.get_and_clear_pending_msg_events(); + assert_eq!(events.len(), 1); + let ev = remove_first_msg_event_to_node(&alice_id, &mut events); + + // The HTLC carries the partial amount, not the full invoice amount. + if let crate::ln::msgs::MessageSendEvent::UpdateHTLCs { ref updates, .. } = ev { + assert_eq!(updates.update_add_htlcs[0].amount_msat, partial_amount); + } else { + panic!("Expected UpdateHTLCs"); + } + + do_pass_along_path( + PassAlongPathArgs::new(bob, &[alice], partial_amount, payment_hash, ev) + .without_clearing_recipient_events() + .without_claimable_event() + .with_dummy_tlvs(&[DummyTlvs::default(); DEFAULT_PAYMENT_DUMMY_HOPS]) + ); + + // Alice has not emitted PaymentClaimable: total_mpp_amount_msat in the onion equals the + // full invoice amount (10M), so she waits for the remaining 5M before settling. + assert!(alice.node.get_and_clear_pending_events().is_empty()); +} diff --git a/lightning/src/ln/outbound_payment.rs b/lightning/src/ln/outbound_payment.rs index 7259f60796f..0294161b069 100644 --- a/lightning/src/ln/outbound_payment.rs +++ b/lightning/src/ln/outbound_payment.rs @@ -18,8 +18,8 @@ use crate::blinded_path::{IntroductionNode, NodeIdLookUp}; use crate::events::{self, PaidBolt12Invoice, PaymentFailureReason}; use crate::ln::channel_state::ChannelDetails; use crate::ln::channelmanager::{ - EventCompletionAction, HTLCSource, OptionalBolt11PaymentParams, PaymentCompleteUpdate, - PaymentId, + EventCompletionAction, HTLCSource, OptionalBolt11PaymentParams, OptionalBolt12PaymentParams, + PaymentCompleteUpdate, PaymentId, }; use crate::ln::msgs::DecodeError; use crate::ln::onion_utils; @@ -657,6 +657,12 @@ pub enum Bolt12PaymentError { DuplicateInvoice, /// The invoice was valid for the corresponding [`PaymentId`], but required unknown features. UnknownRequiredFeatures, + /// Incorrect amount was provided to [`ChannelManager::pay_for_bolt12_invoice`]. + /// + /// This occurs when `amount_msats` is zero or exceeds the invoice amount. + /// + /// [`ChannelManager::pay_for_bolt12_invoice`]: crate::ln::channelmanager::ChannelManager::pay_for_bolt12_invoice + InvalidAmount, /// The invoice was valid for the corresponding [`PaymentId`], but sending the payment failed. SendingFailed(RetryableSendFailure), /// Failed to create a blinded path back to ourselves. @@ -1124,12 +1130,73 @@ impl OutboundPayments { } let invoice = PaidBolt12Invoice::Bolt12Invoice(invoice.clone()); self.send_payment_for_bolt12_invoice_internal( - payment_id, payment_hash, None, None, invoice, route_params, retry_strategy, false, router, - first_hops, inflight_htlcs, entropy_source, node_signer, node_id_lookup, secp_ctx, + payment_id, payment_hash, None, None, invoice, route_params, retry_strategy, false, None, + router, first_hops, inflight_htlcs, entropy_source, node_signer, node_id_lookup, secp_ctx, best_block_height, pending_events, send_payment_along_path, logger, ) } + #[rustfmt::skip] + pub(super) fn pay_for_bolt12_invoice< + R: Router, ES: EntropySource, NS: NodeSigner, NL: NodeIdLookUp, IH, SP, L: Logger, + >( + &self, invoice: &Bolt12Invoice, payment_id: PaymentId, + optional_params: OptionalBolt12PaymentParams, + router: &R, first_hops: Vec, features: Bolt12InvoiceFeatures, inflight_htlcs: IH, + entropy_source: &ES, node_signer: &NS, node_id_lookup: &NL, + secp_ctx: &Secp256k1, best_block_height: u32, + pending_events: &Mutex)>>, + send_payment_along_path: SP, logger: &WithContext, + ) -> Result<(), Bolt12PaymentError> + where + IH: Fn() -> InFlightHtlcs, + SP: Fn(SendAlongPathArgs) -> Result<(), APIError>, + { + let OptionalBolt12PaymentParams { amount_msats, retry_strategy, route_params_config } = optional_params; + + let invoice_amount = invoice.amount_msats(); + let send_amount = amount_msats.unwrap_or(invoice_amount); + + if send_amount == 0 || send_amount > invoice_amount { + return Err(Bolt12PaymentError::InvalidAmount); + } + + if invoice.invoice_features().requires_unknown_bits_from(&features) { + return Err(Bolt12PaymentError::UnknownRequiredFeatures); + } + + let payment_hash = invoice.payment_hash(); + + match self.pending_outbound_payments.lock().unwrap().entry(payment_id) { + hash_map::Entry::Occupied(_) => return Err(Bolt12PaymentError::DuplicateInvoice), + hash_map::Entry::Vacant(entry) => { + entry.insert(PendingOutboundPayment::InvoiceReceived { + payment_hash, + retry_strategy, + route_params_config, + }); + }, + } + + let mut route_params = RouteParameters::from_payment_params_and_value( + PaymentParameters::from_bolt12_invoice(invoice) + .with_user_config_ignoring_fee_limit(route_params_config), + send_amount, + ); + if let Some(max_fee_msat) = route_params_config.max_total_routing_fee_msat { + route_params.max_total_routing_fee_msat = Some(max_fee_msat); + } + // The onion total must always reflect the full invoice amount so that the recipient can + // correctly validate MPP payments, including when this node pays only a partial amount. + let invoice = PaidBolt12Invoice::Bolt12Invoice(invoice.clone()); + self.send_payment_for_bolt12_invoice_internal( + payment_id, payment_hash, None, None, invoice, route_params, retry_strategy, + false, Some(invoice_amount), router, first_hops, inflight_htlcs, + entropy_source, node_signer, node_id_lookup, secp_ctx, best_block_height, + pending_events, send_payment_along_path, logger, + ) + } + #[rustfmt::skip] fn send_payment_for_bolt12_invoice_internal< R: Router, ES: EntropySource, NS: NodeSigner, NL: NodeIdLookUp, IH, SP, L: Logger, @@ -1137,7 +1204,8 @@ impl OutboundPayments { &self, payment_id: PaymentId, payment_hash: PaymentHash, keysend_preimage: Option, invoice_request: Option<&InvoiceRequest>, bolt12_invoice: PaidBolt12Invoice, - mut route_params: RouteParameters, retry_strategy: Retry, hold_htlcs_at_next_hop: bool, router: &R, + mut route_params: RouteParameters, retry_strategy: Retry, hold_htlcs_at_next_hop: bool, + total_mpp_amount_msat_override: Option, router: &R, first_hops: Vec, inflight_htlcs: IH, entropy_source: &ES, node_signer: &NS, node_id_lookup: &NL, secp_ctx: &Secp256k1, best_block_height: u32, pending_events: &Mutex)>>, @@ -1169,7 +1237,7 @@ impl OutboundPayments { payment_secret: None, payment_metadata: None, custom_tlvs: vec![], - total_mpp_amount_msat: route_params.final_value_msat, + total_mpp_amount_msat: total_mpp_amount_msat_override.unwrap_or(route_params.final_value_msat), }; let route = match self.find_initial_route( payment_id, payment_hash, &recipient_onion, keysend_preimage, invoice_request, @@ -1405,6 +1473,7 @@ impl OutboundPayments { route_params, retry_strategy, hold_htlcs_at_next_hop, + None, router, first_hops, inflight_htlcs,