|
7 | 7 | // You may not use this file except in accordance with one or both of these |
8 | 8 | // licenses. |
9 | 9 |
|
10 | | -//! Convenient utilities for paying Lightning invoices. |
11 | | -
|
12 | | -use bitcoin::hashes::Hash; |
13 | | -use lightning_invoice::Bolt11Invoice; |
14 | | - |
15 | | -use crate::ln::channelmanager::RecipientOnionFields; |
16 | | -use crate::routing::router::{PaymentParameters, RouteParameters}; |
17 | | -use crate::types::payment::PaymentHash; |
18 | | - |
19 | | -/// Builds the necessary parameters to pay or pre-flight probe the given variable-amount |
20 | | -/// (also known as 'zero-amount') [`Bolt11Invoice`] using |
21 | | -/// [`ChannelManager::send_payment`] or [`ChannelManager::send_preflight_probes`]. |
22 | | -/// |
23 | | -/// Prior to paying, you must ensure that the [`Bolt11Invoice::payment_hash`] is unique and the |
24 | | -/// same [`PaymentHash`] has never been paid before. |
25 | | -/// |
26 | | -/// Will always succeed unless the invoice has an amount specified, in which case |
27 | | -/// [`payment_parameters_from_invoice`] should be used. |
28 | | -/// |
29 | | -/// [`ChannelManager::send_payment`]: crate::ln::channelmanager::ChannelManager::send_payment |
30 | | -/// [`ChannelManager::send_preflight_probes`]: crate::ln::channelmanager::ChannelManager::send_preflight_probes |
31 | | -pub fn payment_parameters_from_variable_amount_invoice( |
32 | | - invoice: &Bolt11Invoice, amount_msat: u64, |
33 | | -) -> Result<(PaymentHash, RecipientOnionFields, RouteParameters), ()> { |
34 | | - if invoice.amount_milli_satoshis().is_some() { |
35 | | - Err(()) |
36 | | - } else { |
37 | | - Ok(params_from_invoice(invoice, amount_msat)) |
38 | | - } |
39 | | -} |
40 | | - |
41 | | -/// Builds the necessary parameters to pay or pre-flight probe the given [`Bolt11Invoice`] using |
42 | | -/// [`ChannelManager::send_payment`] or [`ChannelManager::send_preflight_probes`]. |
43 | | -/// |
44 | | -/// Prior to paying, you must ensure that the [`Bolt11Invoice::payment_hash`] is unique and the |
45 | | -/// same [`PaymentHash`] has never been paid before. |
46 | | -/// |
47 | | -/// Will always succeed unless the invoice has no amount specified, in which case |
48 | | -/// [`payment_parameters_from_variable_amount_invoice`] should be used. |
49 | | -/// |
50 | | -/// [`ChannelManager::send_payment`]: crate::ln::channelmanager::ChannelManager::send_payment |
51 | | -/// [`ChannelManager::send_preflight_probes`]: crate::ln::channelmanager::ChannelManager::send_preflight_probes |
52 | | -pub fn payment_parameters_from_invoice( |
53 | | - invoice: &Bolt11Invoice, |
54 | | -) -> Result<(PaymentHash, RecipientOnionFields, RouteParameters), ()> { |
55 | | - if let Some(amount_msat) = invoice.amount_milli_satoshis() { |
56 | | - Ok(params_from_invoice(invoice, amount_msat)) |
57 | | - } else { |
58 | | - Err(()) |
59 | | - } |
60 | | -} |
61 | | - |
62 | | -fn params_from_invoice( |
63 | | - invoice: &Bolt11Invoice, amount_msat: u64, |
64 | | -) -> (PaymentHash, RecipientOnionFields, RouteParameters) { |
65 | | - let payment_hash = PaymentHash((*invoice.payment_hash()).to_byte_array()); |
66 | | - |
67 | | - let mut recipient_onion = RecipientOnionFields::secret_only(*invoice.payment_secret()); |
68 | | - recipient_onion.payment_metadata = invoice.payment_metadata().map(|v| v.clone()); |
69 | | - |
70 | | - let mut payment_params = PaymentParameters::from_node_id( |
71 | | - invoice.recover_payee_pub_key(), |
72 | | - invoice.min_final_cltv_expiry_delta() as u32, |
73 | | - ) |
74 | | - .with_route_hints(invoice.route_hints()) |
75 | | - .unwrap(); |
76 | | - if let Some(expiry) = invoice.expires_at() { |
77 | | - payment_params = payment_params.with_expiry_time(expiry.as_secs()); |
78 | | - } |
79 | | - if let Some(features) = invoice.features() { |
80 | | - payment_params = payment_params.with_bolt11_features(features.clone()).unwrap(); |
81 | | - } |
82 | | - |
83 | | - let route_params = RouteParameters::from_payment_params_and_value(payment_params, amount_msat); |
84 | | - (payment_hash, recipient_onion, route_params) |
85 | | -} |
| 10 | +//! Tests for verifying the correct end-to-end handling of BOLT11 payments, including metadata propagation. |
86 | 11 |
|
87 | 12 | #[cfg(test)] |
88 | 13 | mod tests { |
89 | | - use super::*; |
90 | 14 | use crate::events::Event; |
91 | 15 | use crate::ln::channelmanager::{PaymentId, Retry}; |
92 | 16 | use crate::ln::functional_test_utils::*; |
93 | 17 | use crate::ln::msgs::ChannelMessageHandler; |
94 | 18 | use crate::ln::outbound_payment::Bolt11PaymentError; |
95 | | - use crate::routing::router::{Payee, RouteParametersConfig}; |
| 19 | + use crate::routing::router::RouteParametersConfig; |
96 | 20 | use crate::sign::{NodeSigner, Recipient}; |
97 | | - use crate::types::payment::PaymentSecret; |
98 | 21 | use bitcoin::hashes::sha256::Hash as Sha256; |
99 | | - use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey}; |
100 | | - use lightning_invoice::{Currency, InvoiceBuilder}; |
| 22 | + use bitcoin::hashes::Hash; |
| 23 | + use lightning_invoice::{Bolt11Invoice, Currency, InvoiceBuilder}; |
101 | 24 | use std::time::SystemTime; |
102 | 25 |
|
103 | | - #[test] |
104 | | - fn invoice_test() { |
105 | | - let payment_hash = Sha256::hash(&[0; 32]); |
106 | | - let private_key = SecretKey::from_slice(&[42; 32]).unwrap(); |
107 | | - let secp_ctx = Secp256k1::new(); |
108 | | - let public_key = PublicKey::from_secret_key(&secp_ctx, &private_key); |
109 | | - |
110 | | - let timestamp = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap(); |
111 | | - let invoice = InvoiceBuilder::new(Currency::Bitcoin) |
112 | | - .description("test".into()) |
113 | | - .payment_hash(payment_hash) |
114 | | - .payment_secret(PaymentSecret([0; 32])) |
115 | | - .duration_since_epoch(timestamp) |
116 | | - .min_final_cltv_expiry_delta(144) |
117 | | - .amount_milli_satoshis(128) |
118 | | - .build_signed(|hash| secp_ctx.sign_ecdsa_recoverable(hash, &private_key)) |
119 | | - .unwrap(); |
120 | | - |
121 | | - assert!(payment_parameters_from_variable_amount_invoice(&invoice, 42).is_err()); |
122 | | - |
123 | | - let (hash, onion, params) = payment_parameters_from_invoice(&invoice).unwrap(); |
124 | | - assert_eq!(&hash.0[..], &payment_hash[..]); |
125 | | - assert_eq!(onion.payment_secret, Some(PaymentSecret([0; 32]))); |
126 | | - assert_eq!(params.final_value_msat, 128); |
127 | | - match params.payment_params.payee { |
128 | | - Payee::Clear { node_id, .. } => { |
129 | | - assert_eq!(node_id, public_key); |
130 | | - }, |
131 | | - _ => panic!(), |
132 | | - } |
133 | | - } |
134 | | - |
135 | | - #[test] |
136 | | - fn zero_value_invoice_test() { |
137 | | - let payment_hash = Sha256::hash(&[0; 32]); |
138 | | - let private_key = SecretKey::from_slice(&[42; 32]).unwrap(); |
139 | | - let secp_ctx = Secp256k1::new(); |
140 | | - let public_key = PublicKey::from_secret_key(&secp_ctx, &private_key); |
141 | | - |
142 | | - let timestamp = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap(); |
143 | | - let invoice = InvoiceBuilder::new(Currency::Bitcoin) |
144 | | - .description("test".into()) |
145 | | - .payment_hash(payment_hash) |
146 | | - .payment_secret(PaymentSecret([0; 32])) |
147 | | - .duration_since_epoch(timestamp) |
148 | | - .min_final_cltv_expiry_delta(144) |
149 | | - .build_signed(|hash| secp_ctx.sign_ecdsa_recoverable(hash, &private_key)) |
150 | | - .unwrap(); |
151 | | - |
152 | | - assert!(payment_parameters_from_invoice(&invoice).is_err()); |
153 | | - |
154 | | - let (hash, onion, params) = |
155 | | - payment_parameters_from_variable_amount_invoice(&invoice, 42).unwrap(); |
156 | | - assert_eq!(&hash.0[..], &payment_hash[..]); |
157 | | - assert_eq!(onion.payment_secret, Some(PaymentSecret([0; 32]))); |
158 | | - assert_eq!(params.final_value_msat, 42); |
159 | | - match params.payment_params.payee { |
160 | | - Payee::Clear { node_id, .. } => { |
161 | | - assert_eq!(node_id, public_key); |
162 | | - }, |
163 | | - _ => panic!(), |
164 | | - } |
165 | | - } |
166 | | - |
167 | 26 | #[test] |
168 | 27 | fn payment_metadata_end_to_end_for_invoice_with_amount() { |
169 | 28 | // Test that a payment metadata read from an invoice passed to `pay_invoice` makes it all |
|
0 commit comments