mirror of
https://github.com/lightningdevkit/rust-lightning.git
synced 2025-03-13 14:52:21 +01:00
Merge pull request #3617 from shaavan/i3262b
Simplify Bolt11 Payments and Remove Redundant Code
This commit is contained in:
commit
e6267d338c
7 changed files with 290 additions and 245 deletions
|
@ -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 <LICENSE-APACHE
|
||||
// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, 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"),
|
||||
}
|
||||
}
|
||||
}
|
159
lightning/src/ln/bolt11_payment_tests.rs
Normal file
159
lightning/src/ln/bolt11_payment_tests.rs
Normal file
|
@ -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 <LICENSE-APACHE
|
||||
// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, 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"),
|
||||
}
|
||||
}
|
|
@ -62,7 +62,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};
|
||||
|
@ -2023,25 +2023,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<T: AChannelManager>(
|
||||
/// # 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),
|
||||
|
@ -2364,7 +2362,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
|
||||
|
@ -4759,6 +4756,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<u64>,
|
||||
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
|
||||
|
|
|
@ -716,7 +716,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;
|
||||
|
@ -751,7 +751,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]);
|
||||
|
@ -785,17 +785,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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<R: Deref, ES: Deref, NS: Deref, IH, SP, L: Deref>(
|
||||
&self, invoice: &Bolt11Invoice, payment_id: PaymentId,
|
||||
amount_msats: Option<u64>,
|
||||
route_params_config: RouteParametersConfig,
|
||||
retry_strategy: Retry,
|
||||
router: &R,
|
||||
first_hops: Vec<ChannelDetails>, compute_inflight_htlcs: IH, entropy_source: &ES,
|
||||
node_signer: &NS, best_block_height: u32, logger: &L,
|
||||
pending_events: &Mutex<VecDeque<(events::Event, Option<EventCompletionAction>)>>, 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
|
||||
>(
|
||||
|
|
|
@ -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`].
|
||||
|
|
Loading…
Add table
Reference in a new issue