diff --git a/lightning/src/ln/bolt11_payment.rs b/lightning/src/ln/bolt11_payment.rs deleted file mode 100644 index 9b26569f2ef..00000000000 --- a/lightning/src/ln/bolt11_payment.rs +++ /dev/null @@ -1,218 +0,0 @@ -// 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. - -//! Convenient utilities for paying Lightning invoices. - -use bitcoin::hashes::Hash; -use lightning_invoice::Bolt11Invoice; - -use crate::ln::channelmanager::RecipientOnionFields; -use crate::routing::router::{PaymentParameters, RouteParameters}; -use crate::types::payment::PaymentHash; - -/// Builds the necessary parameters to pay or pre-flight probe the given variable-amount -/// (also known as 'zero-amount') [`Bolt11Invoice`] using -/// [`ChannelManager::send_payment`] or [`ChannelManager::send_preflight_probes`]. -/// -/// Prior to paying, you must ensure that the [`Bolt11Invoice::payment_hash`] is unique and the -/// same [`PaymentHash`] has never been paid before. -/// -/// Will always succeed unless the invoice has an amount specified, in which case -/// [`payment_parameters_from_invoice`] should be used. -/// -/// [`ChannelManager::send_payment`]: crate::ln::channelmanager::ChannelManager::send_payment -/// [`ChannelManager::send_preflight_probes`]: crate::ln::channelmanager::ChannelManager::send_preflight_probes -pub fn payment_parameters_from_variable_amount_invoice( - invoice: &Bolt11Invoice, amount_msat: u64, -) -> Result<(PaymentHash, RecipientOnionFields, RouteParameters), ()> { - if invoice.amount_milli_satoshis().is_some() { - Err(()) - } else { - Ok(params_from_invoice(invoice, amount_msat)) - } -} - -/// Builds the necessary parameters to pay or pre-flight probe the given [`Bolt11Invoice`] using -/// [`ChannelManager::send_payment`] or [`ChannelManager::send_preflight_probes`]. -/// -/// Prior to paying, you must ensure that the [`Bolt11Invoice::payment_hash`] is unique and the -/// same [`PaymentHash`] has never been paid before. -/// -/// Will always succeed unless the invoice has no amount specified, in which case -/// [`payment_parameters_from_variable_amount_invoice`] should be used. -/// -/// [`ChannelManager::send_payment`]: crate::ln::channelmanager::ChannelManager::send_payment -/// [`ChannelManager::send_preflight_probes`]: crate::ln::channelmanager::ChannelManager::send_preflight_probes -pub fn payment_parameters_from_invoice( - invoice: &Bolt11Invoice, -) -> Result<(PaymentHash, RecipientOnionFields, RouteParameters), ()> { - if let Some(amount_msat) = invoice.amount_milli_satoshis() { - Ok(params_from_invoice(invoice, amount_msat)) - } else { - Err(()) - } -} - -fn params_from_invoice( - invoice: &Bolt11Invoice, amount_msat: u64, -) -> (PaymentHash, RecipientOnionFields, RouteParameters) { - let payment_hash = PaymentHash((*invoice.payment_hash()).to_byte_array()); - - let mut recipient_onion = RecipientOnionFields::secret_only(*invoice.payment_secret()); - recipient_onion.payment_metadata = invoice.payment_metadata().map(|v| v.clone()); - - let mut payment_params = PaymentParameters::from_node_id( - invoice.recover_payee_pub_key(), - invoice.min_final_cltv_expiry_delta() as u32, - ) - .with_route_hints(invoice.route_hints()) - .unwrap(); - if let Some(expiry) = invoice.expires_at() { - payment_params = payment_params.with_expiry_time(expiry.as_secs()); - } - if let Some(features) = invoice.features() { - payment_params = payment_params.with_bolt11_features(features.clone()).unwrap(); - } - - let route_params = RouteParameters::from_payment_params_and_value(payment_params, amount_msat); - (payment_hash, recipient_onion, route_params) -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::routing::router::Payee; - use crate::sign::{NodeSigner, Recipient}; - use crate::types::payment::PaymentSecret; - use bitcoin::hashes::sha256::Hash as Sha256; - use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey}; - use lightning_invoice::{Currency, InvoiceBuilder}; - use std::time::SystemTime; - - #[test] - fn invoice_test() { - let payment_hash = Sha256::hash(&[0; 32]); - let private_key = SecretKey::from_slice(&[42; 32]).unwrap(); - let secp_ctx = Secp256k1::new(); - let public_key = PublicKey::from_secret_key(&secp_ctx, &private_key); - - let timestamp = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap(); - let invoice = InvoiceBuilder::new(Currency::Bitcoin) - .description("test".into()) - .payment_hash(payment_hash) - .payment_secret(PaymentSecret([0; 32])) - .duration_since_epoch(timestamp) - .min_final_cltv_expiry_delta(144) - .amount_milli_satoshis(128) - .build_signed(|hash| secp_ctx.sign_ecdsa_recoverable(hash, &private_key)) - .unwrap(); - - assert!(payment_parameters_from_variable_amount_invoice(&invoice, 42).is_err()); - - let (hash, onion, params) = payment_parameters_from_invoice(&invoice).unwrap(); - assert_eq!(&hash.0[..], &payment_hash[..]); - assert_eq!(onion.payment_secret, Some(PaymentSecret([0; 32]))); - assert_eq!(params.final_value_msat, 128); - match params.payment_params.payee { - Payee::Clear { node_id, .. } => { - assert_eq!(node_id, public_key); - }, - _ => panic!(), - } - } - - #[test] - fn zero_value_invoice_test() { - let payment_hash = Sha256::hash(&[0; 32]); - let private_key = SecretKey::from_slice(&[42; 32]).unwrap(); - let secp_ctx = Secp256k1::new(); - let public_key = PublicKey::from_secret_key(&secp_ctx, &private_key); - - let timestamp = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap(); - let invoice = InvoiceBuilder::new(Currency::Bitcoin) - .description("test".into()) - .payment_hash(payment_hash) - .payment_secret(PaymentSecret([0; 32])) - .duration_since_epoch(timestamp) - .min_final_cltv_expiry_delta(144) - .build_signed(|hash| secp_ctx.sign_ecdsa_recoverable(hash, &private_key)) - .unwrap(); - - assert!(payment_parameters_from_invoice(&invoice).is_err()); - - let (hash, onion, params) = - payment_parameters_from_variable_amount_invoice(&invoice, 42).unwrap(); - assert_eq!(&hash.0[..], &payment_hash[..]); - assert_eq!(onion.payment_secret, Some(PaymentSecret([0; 32]))); - assert_eq!(params.final_value_msat, 42); - match params.payment_params.payee { - Payee::Clear { node_id, .. } => { - assert_eq!(node_id, public_key); - }, - _ => panic!(), - } - } - - #[test] - fn payment_metadata_end_to_end() { - use crate::events::Event; - use crate::ln::channelmanager::{PaymentId, Retry}; - use crate::ln::functional_test_utils::*; - use crate::ln::msgs::ChannelMessageHandler; - - // Test that a payment metadata read from an invoice passed to `pay_invoice` makes it all - // the way out through the `PaymentClaimable` event. - let chanmon_cfgs = create_chanmon_cfgs(2); - let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); - let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); - let nodes = create_network(2, &node_cfgs, &node_chanmgrs); - create_announced_chan_between_nodes(&nodes, 0, 1); - - let payment_metadata = vec![42, 43, 44, 45, 46, 47, 48, 49, 42]; - - let (payment_hash, payment_secret) = - nodes[1].node.create_inbound_payment(None, 7200, None).unwrap(); - - let timestamp = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap(); - let invoice = InvoiceBuilder::new(Currency::Bitcoin) - .description("test".into()) - .payment_hash(Sha256::from_slice(&payment_hash.0).unwrap()) - .payment_secret(payment_secret) - .duration_since_epoch(timestamp) - .min_final_cltv_expiry_delta(144) - .amount_milli_satoshis(50_000) - .payment_metadata(payment_metadata.clone()) - .build_raw() - .unwrap(); - let sig = nodes[1].keys_manager.backing.sign_invoice(&invoice, Recipient::Node).unwrap(); - let invoice = invoice.sign::<_, ()>(|_| Ok(sig)).unwrap(); - let invoice = Bolt11Invoice::from_signed(invoice).unwrap(); - - let (hash, onion, params) = payment_parameters_from_invoice(&invoice).unwrap(); - nodes[0] - .node - .send_payment(hash, onion, PaymentId(hash.0), params, Retry::Attempts(0)) - .unwrap(); - check_added_monitors(&nodes[0], 1); - let send_event = SendEvent::from_node(&nodes[0]); - nodes[1].node.handle_update_add_htlc(nodes[0].node.get_our_node_id(), &send_event.msgs[0]); - commitment_signed_dance!(nodes[1], nodes[0], &send_event.commitment_msg, false); - - expect_pending_htlcs_forwardable!(nodes[1]); - - let mut events = nodes[1].node.get_and_clear_pending_events(); - assert_eq!(events.len(), 1); - match events.pop().unwrap() { - Event::PaymentClaimable { onion_fields, .. } => { - assert_eq!(Some(payment_metadata), onion_fields.unwrap().payment_metadata); - }, - _ => panic!("Unexpected event"), - } - } -} diff --git a/lightning/src/ln/bolt11_payment_tests.rs b/lightning/src/ln/bolt11_payment_tests.rs new file mode 100644 index 00000000000..3a18c719022 --- /dev/null +++ b/lightning/src/ln/bolt11_payment_tests.rs @@ -0,0 +1,159 @@ +// 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. + +//! Tests for verifying the correct end-to-end handling of BOLT11 payments, including metadata propagation. + +use crate::events::Event; +use crate::ln::channelmanager::{PaymentId, Retry}; +use crate::ln::functional_test_utils::*; +use crate::ln::msgs::ChannelMessageHandler; +use crate::ln::outbound_payment::Bolt11PaymentError; +use crate::routing::router::RouteParametersConfig; +use crate::sign::{NodeSigner, Recipient}; +use bitcoin::hashes::sha256::Hash as Sha256; +use bitcoin::hashes::Hash; +use lightning_invoice::{Bolt11Invoice, Currency, InvoiceBuilder}; +use std::time::SystemTime; + +#[test] +fn payment_metadata_end_to_end_for_invoice_with_amount() { + // Test that a payment metadata read from an invoice passed to `pay_invoice` makes it all + // the way out through the `PaymentClaimable` event. + let chanmon_cfgs = create_chanmon_cfgs(2); + let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); + let nodes = create_network(2, &node_cfgs, &node_chanmgrs); + create_announced_chan_between_nodes(&nodes, 0, 1); + + let payment_metadata = vec![42, 43, 44, 45, 46, 47, 48, 49, 42]; + + let (payment_hash, payment_secret) = + nodes[1].node.create_inbound_payment(None, 7200, None).unwrap(); + + let timestamp = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap(); + let invoice = InvoiceBuilder::new(Currency::Bitcoin) + .description("test".into()) + .payment_hash(Sha256::from_slice(&payment_hash.0).unwrap()) + .payment_secret(payment_secret) + .duration_since_epoch(timestamp) + .min_final_cltv_expiry_delta(144) + .amount_milli_satoshis(50_000) + .payment_metadata(payment_metadata.clone()) + .build_raw() + .unwrap(); + let sig = nodes[1].keys_manager.backing.sign_invoice(&invoice, Recipient::Node).unwrap(); + let invoice = invoice.sign::<_, ()>(|_| Ok(sig)).unwrap(); + let invoice = Bolt11Invoice::from_signed(invoice).unwrap(); + + match nodes[0].node.pay_for_bolt11_invoice( + &invoice, + PaymentId(payment_hash.0), + Some(100), + RouteParametersConfig::default(), + Retry::Attempts(0), + ) { + Err(Bolt11PaymentError::InvalidAmount) => (), + _ => panic!("Unexpected result"), + }; + + nodes[0] + .node + .pay_for_bolt11_invoice( + &invoice, + PaymentId(payment_hash.0), + None, + RouteParametersConfig::default(), + Retry::Attempts(0), + ) + .unwrap(); + + check_added_monitors(&nodes[0], 1); + let send_event = SendEvent::from_node(&nodes[0]); + nodes[1].node.handle_update_add_htlc(nodes[0].node.get_our_node_id(), &send_event.msgs[0]); + commitment_signed_dance!(nodes[1], nodes[0], &send_event.commitment_msg, false); + + expect_pending_htlcs_forwardable!(nodes[1]); + + let mut events = nodes[1].node.get_and_clear_pending_events(); + assert_eq!(events.len(), 1); + match events.pop().unwrap() { + Event::PaymentClaimable { onion_fields, .. } => { + assert_eq!(Some(payment_metadata), onion_fields.unwrap().payment_metadata); + }, + _ => panic!("Unexpected event"), + } +} + +#[test] +fn payment_metadata_end_to_end_for_invoice_with_no_amount() { + // Test that a payment metadata read from an invoice passed to `pay_invoice` makes it all + // the way out through the `PaymentClaimable` event. + let chanmon_cfgs = create_chanmon_cfgs(2); + let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); + let nodes = create_network(2, &node_cfgs, &node_chanmgrs); + create_announced_chan_between_nodes(&nodes, 0, 1); + + let payment_metadata = vec![42, 43, 44, 45, 46, 47, 48, 49, 42]; + + let (payment_hash, payment_secret) = + nodes[1].node.create_inbound_payment(None, 7200, None).unwrap(); + + let timestamp = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap(); + let invoice = InvoiceBuilder::new(Currency::Bitcoin) + .description("test".into()) + .payment_hash(Sha256::from_slice(&payment_hash.0).unwrap()) + .payment_secret(payment_secret) + .duration_since_epoch(timestamp) + .min_final_cltv_expiry_delta(144) + .payment_metadata(payment_metadata.clone()) + .build_raw() + .unwrap(); + let sig = nodes[1].keys_manager.backing.sign_invoice(&invoice, Recipient::Node).unwrap(); + let invoice = invoice.sign::<_, ()>(|_| Ok(sig)).unwrap(); + let invoice = Bolt11Invoice::from_signed(invoice).unwrap(); + + match nodes[0].node.pay_for_bolt11_invoice( + &invoice, + PaymentId(payment_hash.0), + None, + RouteParametersConfig::default(), + Retry::Attempts(0), + ) { + Err(Bolt11PaymentError::InvalidAmount) => (), + _ => panic!("Unexpected result"), + }; + + nodes[0] + .node + .pay_for_bolt11_invoice( + &invoice, + PaymentId(payment_hash.0), + Some(50_000), + RouteParametersConfig::default(), + Retry::Attempts(0), + ) + .unwrap(); + + check_added_monitors(&nodes[0], 1); + let send_event = SendEvent::from_node(&nodes[0]); + nodes[1].node.handle_update_add_htlc(nodes[0].node.get_our_node_id(), &send_event.msgs[0]); + commitment_signed_dance!(nodes[1], nodes[0], &send_event.commitment_msg, false); + + expect_pending_htlcs_forwardable!(nodes[1]); + + let mut events = nodes[1].node.get_and_clear_pending_events(); + assert_eq!(events.len(), 1); + match events.pop().unwrap() { + Event::PaymentClaimable { onion_fields, .. } => { + assert_eq!(Some(payment_metadata), onion_fields.unwrap().payment_metadata); + }, + _ => panic!("Unexpected event"), + } +} diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 00a536539ea..1f393d5b0e0 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -64,7 +64,7 @@ use crate::ln::onion_utils::{HTLCFailReason, INVALID_ONION_BLINDING}; use crate::ln::msgs::{BaseMessageHandler, ChannelMessageHandler, CommitmentUpdate, DecodeError, LightningError, MessageSendEvent}; #[cfg(test)] use crate::ln::outbound_payment; -use crate::ln::outbound_payment::{OutboundPayments, PendingOutboundPayment, RetryableInvoiceRequest, SendAlongPathArgs, StaleExpiration}; +use crate::ln::outbound_payment::{Bolt11PaymentError, 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_request::{InvoiceRequest, InvoiceRequestBuilder}; @@ -2038,25 +2038,23 @@ where /// # } /// ``` /// -/// For paying an invoice, see the [`bolt11_payment`] module with convenience functions for use with -/// [`send_payment`]. -/// /// ``` +/// # use bitcoin::hashes::Hash; /// # use lightning::events::{Event, EventsProvider}; /// # use lightning::types::payment::PaymentHash; -/// # use lightning::ln::channelmanager::{AChannelManager, PaymentId, RecentPaymentDetails, RecipientOnionFields, Retry}; -/// # use lightning::routing::router::RouteParameters; +/// # use lightning::ln::channelmanager::{AChannelManager, PaymentId, RecentPaymentDetails, Retry}; +/// # use lightning::routing::router::RouteParametersConfig; +/// # use lightning_invoice::Bolt11Invoice; /// # /// # fn example( -/// # channel_manager: T, payment_hash: PaymentHash, recipient_onion: RecipientOnionFields, -/// # route_params: RouteParameters, retry: Retry +/// # channel_manager: T, invoice: &Bolt11Invoice, route_params_config: RouteParametersConfig, +/// # retry: Retry /// # ) { /// # let channel_manager = channel_manager.get_cm(); -/// // let (payment_hash, recipient_onion, route_params) = -/// // payment::payment_parameters_from_invoice(&invoice); -/// let payment_id = PaymentId([42; 32]); -/// match channel_manager.send_payment( -/// payment_hash, recipient_onion, payment_id, route_params, retry +/// # let payment_id = PaymentId([42; 32]); +/// # let payment_hash = PaymentHash((*invoice.payment_hash()).to_byte_array()); +/// match channel_manager.pay_for_bolt11_invoice( +/// invoice, payment_id, None, route_params_config, retry /// ) { /// Ok(()) => println!("Sending payment with hash {}", payment_hash), /// Err(e) => println!("Failed sending payment with hash {}: {:?}", payment_hash, e), @@ -2379,7 +2377,6 @@ where /// [`create_bolt11_invoice`]: Self::create_bolt11_invoice /// [`create_inbound_payment`]: Self::create_inbound_payment /// [`create_inbound_payment_for_hash`]: Self::create_inbound_payment_for_hash -/// [`bolt11_payment`]: crate::ln::bolt11_payment /// [`claim_funds`]: Self::claim_funds /// [`send_payment`]: Self::send_payment /// [`offers`]: crate::offers @@ -4769,6 +4766,34 @@ where self.pending_outbound_payments.test_set_payment_metadata(payment_id, new_payment_metadata); } + /// Pays a [`Bolt11Invoice`] associated with the `payment_id`. See [`Self::send_payment`] for more info. + /// + /// # Payment Id + /// The invoice's `payment_hash().0` serves as a reliable choice for the `payment_id`. + /// + /// # Handling Invoice Amounts + /// Some invoices include a specific amount, while others require you to specify one. + /// - If the invoice **includes** an amount, user must not provide `amount_msats`. + /// - If the invoice **doesn't include** an amount, you'll need to specify `amount_msats`. + /// + /// If these conditions aren’t met, the function will return `Bolt11PaymentError::InvalidAmount`. + /// + /// # Custom Routing Parameters + /// Users can customize routing parameters via [`RouteParametersConfig`]. + /// To use default settings, call the function with `RouteParametersConfig::default()`. + pub fn pay_for_bolt11_invoice( + &self, invoice: &Bolt11Invoice, payment_id: PaymentId, amount_msats: Option, + route_params_config: RouteParametersConfig, retry_strategy: Retry + ) -> Result<(), Bolt11PaymentError> { + let best_block_height = self.best_block.read().unwrap().height; + let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self); + self.pending_outbound_payments + .pay_for_bolt11_invoice(invoice, payment_id, amount_msats, route_params_config, retry_strategy, + &self.router, self.list_usable_channels(), || self.compute_inflight_htlcs(), + &self.entropy_source, &self.node_signer, best_block_height, &self.logger, + &self.pending_events, |args| self.send_payment_along_path(args)) + } + /// 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 diff --git a/lightning/src/ln/invoice_utils.rs b/lightning/src/ln/invoice_utils.rs index 3c67b7c581f..31bf21bb37e 100644 --- a/lightning/src/ln/invoice_utils.rs +++ b/lightning/src/ln/invoice_utils.rs @@ -715,7 +715,7 @@ mod test { use crate::ln::channelmanager::{Bolt11InvoiceParameters, PhantomRouteHints, MIN_FINAL_CLTV_EXPIRY_DELTA, PaymentId, RecipientOnionFields, Retry}; use crate::ln::functional_test_utils::*; use crate::ln::msgs::{BaseMessageHandler, ChannelMessageHandler, MessageSendEvent}; - use crate::routing::router::{PaymentParameters, RouteParameters}; + use crate::routing::router::{PaymentParameters, RouteParameters, RouteParametersConfig}; use crate::util::test_utils; use crate::util::config::UserConfig; use std::collections::HashSet; @@ -750,7 +750,7 @@ mod test { #[test] - fn test_from_channelmanager() { + fn create_and_pay_for_bolt11_invoice() { let chanmon_cfgs = create_chanmon_cfgs(2); let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); @@ -784,17 +784,11 @@ mod test { assert_eq!(invoice.route_hints()[0].0[0].htlc_minimum_msat, chan.inbound_htlc_minimum_msat); assert_eq!(invoice.route_hints()[0].0[0].htlc_maximum_msat, chan.inbound_htlc_maximum_msat); - let payment_params = PaymentParameters::from_node_id(invoice.recover_payee_pub_key(), - invoice.min_final_cltv_expiry_delta() as u32) - .with_bolt11_features(invoice.features().unwrap().clone()).unwrap() - .with_route_hints(invoice.route_hints()).unwrap(); - let route_params = RouteParameters::from_payment_params_and_value( - payment_params, invoice.amount_milli_satoshis().unwrap()); let payment_event = { - let payment_hash = PaymentHash(invoice.payment_hash().to_byte_array()); - nodes[0].node.send_payment(payment_hash, - RecipientOnionFields::secret_only(*invoice.payment_secret()), - PaymentId(payment_hash.0), route_params, Retry::Attempts(0)).unwrap(); + nodes[0].node.pay_for_bolt11_invoice( + &invoice, PaymentId([42; 32]), None, RouteParametersConfig::default(), + Retry::Attempts(0) + ).unwrap(); check_added_monitors(&nodes[0], 1); let mut events = nodes[0].node.get_and_clear_pending_msg_events(); diff --git a/lightning/src/ln/mod.rs b/lightning/src/ln/mod.rs index da8efeb177a..e165d0673b9 100644 --- a/lightning/src/ln/mod.rs +++ b/lightning/src/ln/mod.rs @@ -28,7 +28,6 @@ pub mod types; // TODO: These modules were moved from lightning-invoice and need to be better integrated into this // crate now: pub mod invoice_utils; -pub mod bolt11_payment; #[cfg(fuzzing)] pub mod peer_channel_encryptor; @@ -52,6 +51,9 @@ pub use onion_utils::create_payment_onion; // without the node parameter being mut. This is incorrect, and thus newer rustcs will complain // about an unnecessary mut. Thus, we silence the unused_mut warning in two test modules below. +#[cfg(test)] +#[allow(unused_mut)] +pub mod bolt11_payment_tests; #[cfg(test)] #[allow(unused_mut)] mod blinded_payment_tests; diff --git a/lightning/src/ln/outbound_payment.rs b/lightning/src/ln/outbound_payment.rs index 15a370592c0..3033ffd3a2d 100644 --- a/lightning/src/ln/outbound_payment.rs +++ b/lightning/src/ln/outbound_payment.rs @@ -12,6 +12,7 @@ use bitcoin::hashes::Hash; use bitcoin::hashes::sha256::Hash as Sha256; use bitcoin::secp256k1::{self, Secp256k1, SecretKey}; +use lightning_invoice::Bolt11Invoice; use crate::blinded_path::{IntroductionNode, NodeIdLookUp}; use crate::events::{self, PaymentFailureReason}; @@ -570,6 +571,22 @@ pub(crate) enum PaymentSendFailure { }, } +/// An error when attempting to pay a [`Bolt11Invoice`]. +/// +/// [`Bolt11Invoice`]: lightning_invoice::Bolt11Invoice +#[derive(Debug)] +pub enum Bolt11PaymentError { + /// Incorrect amount was provided to [`ChannelManager::pay_for_bolt11_invoice`]. + /// This happens when an amount is specified when [`Bolt11Invoice`] already contains + /// an amount, or vice versa. + /// + /// [`Bolt11Invoice`]: lightning_invoice::Bolt11Invoice + /// [`ChannelManager::pay_for_bolt11_invoice`]: crate::ln::channelmanager::ChannelManager::pay_for_bolt11_invoice + InvalidAmount, + /// The invoice was valid for the corresponding [`PaymentId`], but sending the payment failed. + SendingFailed(RetryableSendFailure), +} + /// An error when attempting to pay a [`Bolt12Invoice`]. #[derive(Clone, Debug, PartialEq, Eq)] pub enum Bolt12PaymentError { @@ -843,6 +860,50 @@ impl OutboundPayments { .map(|()| payment_hash) } + pub(super) fn pay_for_bolt11_invoice( + &self, invoice: &Bolt11Invoice, payment_id: PaymentId, + amount_msats: Option, + route_params_config: RouteParametersConfig, + retry_strategy: Retry, + router: &R, + first_hops: Vec, compute_inflight_htlcs: IH, entropy_source: &ES, + node_signer: &NS, best_block_height: u32, logger: &L, + pending_events: &Mutex)>>, send_payment_along_path: SP, + ) -> Result<(), Bolt11PaymentError> + where + R::Target: Router, + ES::Target: EntropySource, + NS::Target: NodeSigner, + L::Target: Logger, + IH: Fn() -> InFlightHtlcs, + SP: Fn(SendAlongPathArgs) -> Result<(), APIError>, + { + let payment_hash = PaymentHash((*invoice.payment_hash()).to_byte_array()); + + let amount = match (invoice.amount_milli_satoshis(), amount_msats) { + (Some(amt), None) | (None, Some(amt)) => amt, + (None, None) | (Some(_), Some(_)) => return Err(Bolt11PaymentError::InvalidAmount), + }; + + let mut recipient_onion = RecipientOnionFields::secret_only(*invoice.payment_secret()); + recipient_onion.payment_metadata = invoice.payment_metadata().map(|v| v.clone()); + + let payment_params = PaymentParameters::from_bolt11_invoice(invoice) + .with_user_config_ignoring_fee_limit(route_params_config); + + let mut route_params = RouteParameters::from_payment_params_and_value(payment_params, 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); + } + + self.send_payment_internal(payment_id, payment_hash, recipient_onion, None, retry_strategy, route_params, + router, first_hops, compute_inflight_htlcs, + entropy_source, node_signer, best_block_height, logger, + pending_events, send_payment_along_path + ).map_err(|err| Bolt11PaymentError::SendingFailed(err)) + } + pub(super) fn send_payment_for_bolt12_invoice< R: Deref, ES: Deref, NS: Deref, NL: Deref, IH, SP, L: Deref >( diff --git a/lightning/src/routing/router.rs b/lightning/src/routing/router.rs index 017496f26cb..5516ca325f5 100644 --- a/lightning/src/routing/router.rs +++ b/lightning/src/routing/router.rs @@ -10,6 +10,7 @@ //! The router finds paths within a [`NetworkGraph`] for a payment. use bitcoin::secp256k1::{PublicKey, Secp256k1, self}; +use lightning_invoice::Bolt11Invoice; use crate::blinded_path::{BlindedHop, Direction, IntroductionNode}; use crate::blinded_path::payment::{BlindedPaymentPath, ForwardTlvs, PaymentConstraints, PaymentForwardNode, PaymentRelay, ReceiveTlvs}; @@ -910,6 +911,27 @@ impl PaymentParameters { .expect("PaymentParameters::from_node_id should always initialize the payee as unblinded") } + /// Creates parameters for paying to a blinded payee from the provided invoice. Sets + /// [`Payee::Blinded::route_hints`], [`Payee::Blinded::features`], and + /// [`PaymentParameters::expiry_time`]. + pub fn from_bolt11_invoice(invoice: &Bolt11Invoice) -> Self { + let mut payment_params = Self::from_node_id( + invoice.recover_payee_pub_key(), + invoice.min_final_cltv_expiry_delta() as u32, + ) + .with_route_hints(invoice.route_hints()) + .unwrap(); + + if let Some(expiry) = invoice.expires_at() { + payment_params = payment_params.with_expiry_time(expiry.as_secs()); + } + if let Some(features) = invoice.features() { + payment_params = payment_params.with_bolt11_features(features.clone()).unwrap(); + } + + payment_params + } + /// Creates parameters for paying to a blinded payee from the provided invoice. Sets /// [`Payee::Blinded::route_hints`], [`Payee::Blinded::features`], and /// [`PaymentParameters::expiry_time`].