X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=blobdiff_plain;f=lightning-invoice%2Fsrc%2Fpayment.rs;h=42408540ee41e7a9e1f9d8ba6c711c6def7b565d;hb=8e2b70d91a0f7322042d1b0935527c90c59afe45;hp=981fd2e6ba49225c0e41fb2923ee56e480aea0b7;hpb=d355ce1b78164d34a59c93b36c064f96462696a4;p=rust-lightning diff --git a/lightning-invoice/src/payment.rs b/lightning-invoice/src/payment.rs index 981fd2e6..42408540 100644 --- a/lightning-invoice/src/payment.rs +++ b/lightning-invoice/src/payment.rs @@ -9,15 +9,15 @@ //! Convenient utilities for paying Lightning invoices and sending spontaneous payments. -use crate::Invoice; +use crate::Bolt11Invoice; use bitcoin_hashes::Hash; use lightning::chain; use lightning::chain::chaininterface::{BroadcasterInterface, FeeEstimator}; -use lightning::chain::keysinterface::{NodeSigner, SignerProvider, EntropySource}; -use lightning::ln::{PaymentHash, PaymentSecret}; -use lightning::ln::channelmanager::{ChannelManager, PaymentId, Retry, RetryableSendFailure}; +use lightning::sign::{NodeSigner, SignerProvider, EntropySource}; +use lightning::ln::PaymentHash; +use lightning::ln::channelmanager::{ChannelManager, PaymentId, Retry, RetryableSendFailure, RecipientOnionFields}; use lightning::routing::router::{PaymentParameters, RouteParameters, Router}; use lightning::util::logger::Logger; @@ -25,15 +25,15 @@ use core::fmt::Debug; use core::ops::Deref; use core::time::Duration; -/// Pays the given [`Invoice`], retrying if needed based on [`Retry`]. +/// Pays the given [`Bolt11Invoice`], retrying if needed based on [`Retry`]. /// -/// [`Invoice::payment_hash`] is used as the [`PaymentId`], which ensures idempotency as long -/// as the payment is still pending. Once the payment completes or fails, you must ensure that -/// a second payment with the same [`PaymentHash`] is never sent. +/// [`Bolt11Invoice::payment_hash`] is used as the [`PaymentId`], which ensures idempotency as long +/// as the payment is still pending. If the payment succeeds, you must ensure that a second payment +/// with the same [`PaymentHash`] is never sent. /// /// If you wish to use a different payment idempotency token, see [`pay_invoice_with_id`]. pub fn pay_invoice( - invoice: &Invoice, retry_strategy: Retry, + invoice: &Bolt11Invoice, retry_strategy: Retry, channelmanager: &ChannelManager ) -> Result where @@ -51,17 +51,18 @@ where .map(|()| payment_id) } -/// Pays the given [`Invoice`] with a custom idempotency key, retrying if needed based on [`Retry`]. +/// Pays the given [`Bolt11Invoice`] with a custom idempotency key, retrying if needed based on +/// [`Retry`]. /// /// Note that idempotency is only guaranteed as long as the payment is still pending. Once the /// payment completes or fails, no idempotency guarantees are made. /// -/// You should ensure that the [`Invoice::payment_hash`] is unique and the same [`PaymentHash`] -/// has never been paid before. +/// You should ensure that the [`Bolt11Invoice::payment_hash`] is unique and the same +/// [`PaymentHash`] has never been paid before. /// /// See [`pay_invoice`] for a variant which uses the [`PaymentHash`] for the idempotency token. pub fn pay_invoice_with_id( - invoice: &Invoice, payment_id: PaymentId, retry_strategy: Retry, + invoice: &Bolt11Invoice, payment_id: PaymentId, retry_strategy: Retry, channelmanager: &ChannelManager ) -> Result<(), PaymentError> where @@ -78,17 +79,17 @@ where pay_invoice_using_amount(invoice, amt_msat, payment_id, retry_strategy, channelmanager) } -/// Pays the given zero-value [`Invoice`] using the given amount, retrying if needed based on +/// Pays the given zero-value [`Bolt11Invoice`] using the given amount, retrying if needed based on /// [`Retry`]. /// -/// [`Invoice::payment_hash`] is used as the [`PaymentId`], which ensures idempotency as long -/// as the payment is still pending. Once the payment completes or fails, you must ensure that -/// a second payment with the same [`PaymentHash`] is never sent. +/// [`Bolt11Invoice::payment_hash`] is used as the [`PaymentId`], which ensures idempotency as long +/// as the payment is still pending. If the payment succeeds, you must ensure that a second payment +/// with the same [`PaymentHash`] is never sent. /// /// If you wish to use a different payment idempotency token, see /// [`pay_zero_value_invoice_with_id`]. pub fn pay_zero_value_invoice( - invoice: &Invoice, amount_msats: u64, retry_strategy: Retry, + invoice: &Bolt11Invoice, amount_msats: u64, retry_strategy: Retry, channelmanager: &ChannelManager ) -> Result where @@ -107,19 +108,19 @@ where .map(|()| payment_id) } -/// Pays the given zero-value [`Invoice`] using the given amount and custom idempotency key, -/// , retrying if needed based on [`Retry`]. +/// Pays the given zero-value [`Bolt11Invoice`] using the given amount and custom idempotency key, +/// retrying if needed based on [`Retry`]. /// /// Note that idempotency is only guaranteed as long as the payment is still pending. Once the /// payment completes or fails, no idempotency guarantees are made. /// -/// You should ensure that the [`Invoice::payment_hash`] is unique and the same [`PaymentHash`] -/// has never been paid before. +/// You should ensure that the [`Bolt11Invoice::payment_hash`] is unique and the same +/// [`PaymentHash`] has never been paid before. /// /// See [`pay_zero_value_invoice`] for a variant which uses the [`PaymentHash`] for the /// idempotency token. pub fn pay_zero_value_invoice_with_id( - invoice: &Invoice, amount_msats: u64, payment_id: PaymentId, retry_strategy: Retry, + invoice: &Bolt11Invoice, amount_msats: u64, payment_id: PaymentId, retry_strategy: Retry, channelmanager: &ChannelManager ) -> Result<(), PaymentError> where @@ -141,40 +142,43 @@ where } fn pay_invoice_using_amount( - invoice: &Invoice, amount_msats: u64, payment_id: PaymentId, retry_strategy: Retry, + invoice: &Bolt11Invoice, amount_msats: u64, payment_id: PaymentId, retry_strategy: Retry, payer: P ) -> Result<(), PaymentError> where P::Target: Payer { - let payment_hash = PaymentHash(invoice.payment_hash().clone().into_inner()); - let payment_secret = Some(invoice.payment_secret().clone()); + let payment_hash = PaymentHash((*invoice.payment_hash()).into_inner()); + let recipient_onion = RecipientOnionFields { + payment_secret: Some(*invoice.payment_secret()), + 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_expiry_time(expiry_time_from_unix_epoch(&invoice).as_secs()) - .with_route_hints(invoice.route_hints()); + .with_expiry_time(expiry_time_from_unix_epoch(invoice).as_secs()) + .with_route_hints(invoice.route_hints()).unwrap(); if let Some(features) = invoice.features() { - payment_params = payment_params.with_features(features.clone()); + payment_params = payment_params.with_bolt11_features(features.clone()).unwrap(); } let route_params = RouteParameters { payment_params, final_value_msat: amount_msats, }; - payer.send_payment(payment_hash, &payment_secret, payment_id, route_params, retry_strategy) + payer.send_payment(payment_hash, recipient_onion, payment_id, route_params, retry_strategy) } -fn expiry_time_from_unix_epoch(invoice: &Invoice) -> Duration { +fn expiry_time_from_unix_epoch(invoice: &Bolt11Invoice) -> Duration { invoice.signed_invoice.raw_invoice.data.timestamp.0 + invoice.expiry_time() } /// An error that may occur when making a payment. -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq, Eq)] pub enum PaymentError { - /// An error resulting from the provided [`Invoice`] or payment hash. + /// An error resulting from the provided [`Bolt11Invoice`] or payment hash. Invoice(&'static str), /// An error occurring when sending a payment. Sending(RetryableSendFailure), } -/// A trait defining behavior of an [`Invoice`] payer. +/// A trait defining behavior of a [`Bolt11Invoice`] payer. /// /// Useful for unit testing internal methods. trait Payer { @@ -182,7 +186,7 @@ trait Payer { /// /// [`Route`]: lightning::routing::router::Route fn send_payment( - &self, payment_hash: PaymentHash, payment_secret: &Option, + &self, payment_hash: PaymentHash, recipient_onion: RecipientOnionFields, payment_id: PaymentId, route_params: RouteParameters, retry_strategy: Retry ) -> Result<(), PaymentError>; } @@ -199,11 +203,11 @@ where L::Target: Logger, { fn send_payment( - &self, payment_hash: PaymentHash, payment_secret: &Option, + &self, payment_hash: PaymentHash, recipient_onion: RecipientOnionFields, payment_id: PaymentId, route_params: RouteParameters, retry_strategy: Retry ) -> Result<(), PaymentError> { - self.send_payment_with_retry(payment_hash, payment_secret, payment_id, route_params, retry_strategy) - .map_err(|e| PaymentError::Sending(e)) + self.send_payment(payment_hash, recipient_onion, payment_id, route_params, retry_strategy) + .map_err(PaymentError::Sending) } } @@ -212,7 +216,9 @@ mod tests { use super::*; use crate::{InvoiceBuilder, Currency}; use bitcoin_hashes::sha256::Hash as Sha256; - use lightning::ln::PaymentPreimage; + use lightning::events::Event; + use lightning::ln::msgs::ChannelMessageHandler; + use lightning::ln::{PaymentPreimage, PaymentSecret}; use lightning::ln::functional_test_utils::*; use secp256k1::{SecretKey, Secp256k1}; use std::collections::VecDeque; @@ -249,7 +255,7 @@ mod tests { impl Payer for TestPayer { fn send_payment( - &self, _payment_hash: PaymentHash, _payment_secret: &Option, + &self, _payment_hash: PaymentHash, _recipient_onion: RecipientOnionFields, _payment_id: PaymentId, route_params: RouteParameters, _retry_strategy: Retry ) -> Result<(), PaymentError> { self.check_value_msats(Amount(route_params.final_value_msat)); @@ -278,7 +284,7 @@ mod tests { duration_since_epoch } - fn invoice(payment_preimage: PaymentPreimage) -> Invoice { + fn invoice(payment_preimage: PaymentPreimage) -> Bolt11Invoice { let payment_hash = Sha256::hash(&payment_preimage.0); let private_key = SecretKey::from_slice(&[42; 32]).unwrap(); @@ -295,7 +301,7 @@ mod tests { .unwrap() } - fn zero_value_invoice(payment_preimage: PaymentPreimage) -> Invoice { + fn zero_value_invoice(payment_preimage: PaymentPreimage) -> Bolt11Invoice { let payment_hash = Sha256::hash(&payment_preimage.0); let private_key = SecretKey::from_slice(&[42; 32]).unwrap(); @@ -344,9 +350,57 @@ mod tests { let invoice = invoice(payment_preimage); let amt_msat = 10_000; - match pay_zero_value_invoice(&invoice, amt_msat, Retry::Attempts(0), &nodes[0].node) { + match pay_zero_value_invoice(&invoice, amt_msat, Retry::Attempts(0), nodes[0].node) { Err(PaymentError::Invoice("amount unexpected")) => {}, _ => panic!() } } + + #[test] + #[cfg(feature = "std")] + fn payment_metadata_end_to_end() { + // 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 invoice = InvoiceBuilder::new(Currency::Bitcoin) + .description("test".into()) + .payment_hash(Sha256::from_slice(&payment_hash.0).unwrap()) + .payment_secret(payment_secret) + .current_timestamp() + .min_final_cltv_expiry_delta(144) + .amount_milli_satoshis(50_000) + .payment_metadata(payment_metadata.clone()) + .build_signed(|hash| { + Secp256k1::new().sign_ecdsa_recoverable(hash, + &nodes[1].keys_manager.backing.get_node_secret_key()) + }) + .unwrap(); + + pay_invoice(&invoice, Retry::Attempts(0), nodes[0].node).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") + } + } }