diff --git a/lightning-types/src/features.rs b/lightning-types/src/features.rs index 3aec5358d2b..54587dc980a 100644 --- a/lightning-types/src/features.rs +++ b/lightning-types/src/features.rs @@ -567,7 +567,7 @@ mod sealed { #[cfg(any(test, feature = "_test_utils"))] define_feature!( - 123456789, + 12345, UnknownFeature, [ NodeContext, @@ -1117,7 +1117,7 @@ mod tests { features.set_unknown_feature_required(); assert!(features.requires_unknown_bits()); assert!(features.supports_unknown_bits()); - assert_eq!(features.required_unknown_bits_from(&ChannelFeatures::empty()), vec![123456788]); + assert_eq!(features.required_unknown_bits_from(&ChannelFeatures::empty()), vec![12344]); let mut features = ChannelFeatures::empty(); features.set_unknown_feature_optional(); @@ -1127,17 +1127,17 @@ mod tests { let mut features = ChannelFeatures::empty(); features.set_unknown_feature_required(); - features.set_custom_bit(123456786).unwrap(); + features.set_custom_bit(12346).unwrap(); assert!(features.requires_unknown_bits()); assert!(features.supports_unknown_bits()); assert_eq!( features.required_unknown_bits_from(&ChannelFeatures::empty()), - vec![123456786, 123456788] + vec![12344, 12346] ); let mut limiter = ChannelFeatures::empty(); limiter.set_unknown_feature_optional(); - assert_eq!(features.required_unknown_bits_from(&limiter), vec![123456786]); + assert_eq!(features.required_unknown_bits_from(&limiter), vec![12346]); } #[test] diff --git a/lightning/src/blinded_path/message.rs b/lightning/src/blinded_path/message.rs index 47444eb900d..26019c0369d 100644 --- a/lightning/src/blinded_path/message.rs +++ b/lightning/src/blinded_path/message.rs @@ -16,6 +16,8 @@ use bitcoin::secp256k1::{self, PublicKey, Secp256k1, SecretKey}; #[allow(unused_imports)] use crate::prelude::*; +use bitcoin::hashes::hmac::Hmac; +use bitcoin::hashes::sha256::Hash as Sha256; use crate::blinded_path::{BlindedHop, BlindedPath, IntroductionNode, NextMessageHop, NodeIdLookUp}; use crate::blinded_path::utils; use crate::io; @@ -146,6 +148,12 @@ pub enum OffersContext { /// [`Refund`]: crate::offers::refund::Refund /// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest nonce: Nonce, + + /// Authentication code for the [`PaymentId`], which should be checked when the context is + /// used with an [`InvoiceError`]. + /// + /// [`InvoiceError`]: crate::offers::invoice_error::InvoiceError + hmac: Option>, }, /// Context used by a [`BlindedPath`] as a reply path for a [`Bolt12Invoice`]. /// @@ -173,6 +181,7 @@ impl_writeable_tlv_based_enum!(OffersContext, (1, OutboundPayment) => { (0, payment_id, required), (1, nonce, required), + (2, hmac, option), }, (2, InboundPayment) => { (0, payment_hash, required), diff --git a/lightning/src/events/mod.rs b/lightning/src/events/mod.rs index 3972ea470da..63f19ed222b 100644 --- a/lightning/src/events/mod.rs +++ b/lightning/src/events/mod.rs @@ -502,6 +502,12 @@ impl_writeable_tlv_based_enum!(InterceptNextHop, #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum PaymentFailureReason { /// The intended recipient rejected our payment. + /// + /// Also used for [`UnknownRequiredFeatures`] and [`InvoiceRequestRejected`] when downgrading to + /// version prior to 0.0.124. + /// + /// [`UnknownRequiredFeatures`]: Self::UnknownRequiredFeatures + /// [`InvoiceRequestRejected`]: Self::InvoiceRequestRejected RecipientRejected, /// The user chose to abandon this payment by calling [`ChannelManager::abandon_payment`]. /// @@ -517,7 +523,10 @@ pub enum PaymentFailureReason { /// The payment expired while retrying, based on the provided /// [`PaymentParameters::expiry_time`]. /// + /// Also used for [`InvoiceRequestExpired`] when downgrading to version prior to 0.0.124. + /// /// [`PaymentParameters::expiry_time`]: crate::routing::router::PaymentParameters::expiry_time + /// [`InvoiceRequestExpired`]: Self::InvoiceRequestExpired PaymentExpired, /// We failed to find a route while retrying the payment. /// @@ -528,12 +537,23 @@ pub enum PaymentFailureReason { /// This error should generally never happen. This likely means that there is a problem with /// your router. UnexpectedError, + /// An invoice was received that required unknown features. + UnknownRequiredFeatures, + /// A [`Bolt12Invoice`] was not received in a reasonable amount of time. + InvoiceRequestExpired, + /// An [`InvoiceRequest`] for the payment was rejected by the recipient. + /// + /// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest + InvoiceRequestRejected, } -impl_writeable_tlv_based_enum!(PaymentFailureReason, +impl_writeable_tlv_based_enum_upgradable!(PaymentFailureReason, (0, RecipientRejected) => {}, + (1, UnknownRequiredFeatures) => {}, (2, UserAbandoned) => {}, + (3, InvoiceRequestExpired) => {}, (4, RetriesExhausted) => {}, + (5, InvoiceRequestRejected) => {}, (6, PaymentExpired) => {}, (8, RouteNotFound) => {}, (10, UnexpectedError) => {}, @@ -769,22 +789,6 @@ pub enum Event { /// Sockets for connecting to the node. addresses: Vec, }, - /// Indicates a request for an invoice failed to yield a response in a reasonable amount of time - /// or was explicitly abandoned by [`ChannelManager::abandon_payment`]. This may be for an - /// [`InvoiceRequest`] sent for an [`Offer`] or for a [`Refund`] that hasn't been redeemed. - /// - /// # Failure Behavior and Persistence - /// This event will eventually be replayed after failures-to-handle (i.e., the event handler - /// returning `Err(ReplayEvent ())`) and will be persisted across restarts. - /// - /// [`ChannelManager::abandon_payment`]: crate::ln::channelmanager::ChannelManager::abandon_payment - /// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest - /// [`Offer`]: crate::offers::offer::Offer - /// [`Refund`]: crate::offers::refund::Refund - InvoiceRequestFailed { - /// The `payment_id` to have been associated with payment for the requested invoice. - payment_id: PaymentId, - }, /// Indicates a [`Bolt12Invoice`] in response to an [`InvoiceRequest`] or a [`Refund`] was /// received. /// @@ -876,12 +880,15 @@ pub enum Event { /// /// [`ChannelManager::send_payment`]: crate::ln::channelmanager::ChannelManager::send_payment payment_id: PaymentId, - /// The hash that was given to [`ChannelManager::send_payment`]. + /// The hash that was given to [`ChannelManager::send_payment`]. `None` if the payment failed + /// before receiving an invoice when paying a BOLT12 [`Offer`]. /// /// [`ChannelManager::send_payment`]: crate::ln::channelmanager::ChannelManager::send_payment - payment_hash: PaymentHash, + /// [`Offer`]: crate::offers::offer::Offer + payment_hash: Option, /// The reason the payment failed. This is only `None` for events generated or serialized - /// by versions prior to 0.0.115. + /// by versions prior to 0.0.115, or when downgrading to a version with a reason that was + /// added after. reason: Option, }, /// Indicates that a path for an outbound payment was successful. @@ -1552,10 +1559,34 @@ impl Writeable for Event { }, &Event::PaymentFailed { ref payment_id, ref payment_hash, ref reason } => { 15u8.write(writer)?; + let (payment_hash, invoice_received) = match payment_hash { + Some(payment_hash) => (payment_hash, true), + None => (&PaymentHash([0; 32]), false), + }; + let legacy_reason = match reason { + None => &None, + // Variants available prior to version 0.0.124. + Some(PaymentFailureReason::RecipientRejected) + | Some(PaymentFailureReason::UserAbandoned) + | Some(PaymentFailureReason::RetriesExhausted) + | Some(PaymentFailureReason::PaymentExpired) + | Some(PaymentFailureReason::RouteNotFound) + | Some(PaymentFailureReason::UnexpectedError) => reason, + // Variants introduced at version 0.0.124 or later. Prior versions fail to parse + // unknown variants, while versions 0.0.124 or later will use None. + Some(PaymentFailureReason::UnknownRequiredFeatures) => + &Some(PaymentFailureReason::RecipientRejected), + Some(PaymentFailureReason::InvoiceRequestExpired) => + &Some(PaymentFailureReason::RetriesExhausted), + Some(PaymentFailureReason::InvoiceRequestRejected) => + &Some(PaymentFailureReason::RecipientRejected), + }; write_tlv_fields!(writer, { (0, payment_id, required), - (1, reason, option), + (1, legacy_reason, option), (2, payment_hash, required), + (3, invoice_received, required), + (5, reason, option), }) }, &Event::OpenChannelRequest { .. } => { @@ -1634,12 +1665,6 @@ impl Writeable for Event { (8, funding_txo, required), }); }, - &Event::InvoiceRequestFailed { ref payment_id } => { - 33u8.write(writer)?; - write_tlv_fields!(writer, { - (0, payment_id, required), - }) - }, &Event::ConnectionNeeded { .. } => { 35u8.write(writer)?; // Never write ConnectionNeeded events as buffered onion messages aren't serialized. @@ -1929,15 +1954,24 @@ impl MaybeReadable for Event { let mut payment_hash = PaymentHash([0; 32]); let mut payment_id = PaymentId([0; 32]); let mut reason = None; + let mut legacy_reason = None; + let mut invoice_received: Option = None; read_tlv_fields!(reader, { (0, payment_id, required), - (1, reason, upgradable_option), + (1, legacy_reason, upgradable_option), (2, payment_hash, required), + (3, invoice_received, option), + (5, reason, upgradable_option), }); + let payment_hash = match invoice_received { + Some(invoice_received) => invoice_received.then(|| payment_hash), + None => (payment_hash != PaymentHash([0; 32])).then(|| payment_hash), + }; + let reason = reason.or(legacy_reason); Ok(Some(Event::PaymentFailed { payment_id, payment_hash, - reason, + reason: _init_tlv_based_struct_field!(reason, upgradable_option), })) }; f() @@ -2076,13 +2110,16 @@ impl MaybeReadable for Event { }; f() }, + // This was Event::InvoiceRequestFailed prior to version 0.0.124. 33u8 => { let mut f = || { _init_and_read_len_prefixed_tlv_fields!(reader, { (0, payment_id, required), }); - Ok(Some(Event::InvoiceRequestFailed { + Ok(Some(Event::PaymentFailed { payment_id: payment_id.0.unwrap(), + payment_hash: None, + reason: Some(PaymentFailureReason::InvoiceRequestExpired), })) }; f() diff --git a/lightning/src/ln/blinded_payment_tests.rs b/lightning/src/ln/blinded_payment_tests.rs index c1d65fd1a11..9d246f1a741 100644 --- a/lightning/src/ln/blinded_payment_tests.rs +++ b/lightning/src/ln/blinded_payment_tests.rs @@ -1068,7 +1068,7 @@ fn blinded_path_retries() { assert_eq!(evs.len(), 1); match evs[0] { Event::PaymentFailed { payment_hash: ev_payment_hash, reason, .. } => { - assert_eq!(ev_payment_hash, payment_hash); + assert_eq!(ev_payment_hash, Some(payment_hash)); // We have 1 retry attempt remaining, but we're out of blinded paths to try. assert_eq!(reason, Some(PaymentFailureReason::RouteNotFound)); }, diff --git a/lightning/src/ln/chanmon_update_fail_tests.rs b/lightning/src/ln/chanmon_update_fail_tests.rs index 9bc48f12f0a..52669637d00 100644 --- a/lightning/src/ln/chanmon_update_fail_tests.rs +++ b/lightning/src/ln/chanmon_update_fail_tests.rs @@ -1776,7 +1776,7 @@ fn test_monitor_update_on_pending_forwards() { } else { panic!("Unexpected event!"); } match events[2] { Event::PaymentFailed { payment_hash, .. } => { - assert_eq!(payment_hash, payment_hash_1); + assert_eq!(payment_hash, Some(payment_hash_1)); }, _ => panic!("Unexpected event"), } diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 4ce3ebc37fc..c6645956b18 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -68,6 +68,7 @@ use crate::offers::nonce::Nonce; use crate::offers::offer::{Offer, OfferBuilder}; use crate::offers::parse::Bolt12SemanticError; use crate::offers::refund::{Refund, RefundBuilder}; +use crate::offers::signer; use crate::onion_message::async_payments::{AsyncPaymentsMessage, HeldHtlcAvailable, ReleaseHeldHtlc, AsyncPaymentsMessageHandler}; use crate::onion_message::messenger::{new_pending_onion_message, Destination, MessageRouter, PendingOnionMessage, Responder, ResponseInstruction}; use crate::onion_message::offers::{OffersMessage, OffersMessageHandler}; @@ -1680,7 +1681,8 @@ where /// channel_manager.process_pending_events(&|event| { /// match event { /// Event::PaymentSent { payment_hash, .. } => println!("Paid {}", payment_hash), -/// Event::PaymentFailed { payment_hash, .. } => println!("Failed paying {}", payment_hash), +/// Event::PaymentFailed { payment_hash: Some(payment_hash), .. } => +/// println!("Failed paying {}", payment_hash), /// // ... /// # _ => {}, /// } @@ -1743,8 +1745,7 @@ where /// ``` /// /// Use [`pay_for_offer`] to initiated payment, which sends an [`InvoiceRequest`] for an [`Offer`] -/// and pays the [`Bolt12Invoice`] response. In addition to success and failure events, -/// [`ChannelManager`] may also generate an [`Event::InvoiceRequestFailed`]. +/// and pays the [`Bolt12Invoice`] response. /// /// ``` /// # use lightning::events::{Event, EventsProvider}; @@ -1786,7 +1787,6 @@ where /// match event { /// Event::PaymentSent { payment_id: Some(payment_id), .. } => println!("Paid {}", payment_id), /// Event::PaymentFailed { payment_id, .. } => println!("Failed paying {}", payment_id), -/// Event::InvoiceRequestFailed { payment_id, .. } => println!("Failed paying {}", payment_id), /// // ... /// # _ => {}, /// } @@ -4227,7 +4227,7 @@ where None if invoice.is_for_refund_without_paths() => { invoice.verify_using_metadata(expanded_key, secp_ctx) }, - Some(&OffersContext::OutboundPayment { payment_id, nonce }) => { + Some(&OffersContext::OutboundPayment { payment_id, nonce, .. }) => { invoice.verify_using_payer_data(payment_id, nonce, expanded_key, secp_ctx) }, _ => Err(()), @@ -4237,9 +4237,10 @@ where fn send_payment_for_verified_bolt12_invoice(&self, invoice: &Bolt12Invoice, payment_id: PaymentId) -> 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 .send_payment_for_bolt12_invoice( - invoice, payment_id, &self.router, self.list_usable_channels(), + invoice, payment_id, &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.logger, &self.pending_events, |args| self.send_payment_along_path(args) @@ -4262,20 +4263,22 @@ where /// # Requested Invoices /// /// In the case of paying a [`Bolt12Invoice`] via [`ChannelManager::pay_for_offer`], abandoning - /// the payment prior to receiving the invoice will result in an [`Event::InvoiceRequestFailed`] - /// and prevent any attempts at paying it once received. The other events may only be generated - /// once the invoice has been received. + /// the payment prior to receiving the invoice will result in an [`Event::PaymentFailed`] and + /// prevent any attempts at paying it once received. /// /// # Restart Behavior /// /// If an [`Event::PaymentFailed`] is generated and we restart without first persisting the - /// [`ChannelManager`], another [`Event::PaymentFailed`] may be generated; likewise for - /// [`Event::InvoiceRequestFailed`]. + /// [`ChannelManager`], another [`Event::PaymentFailed`] may be generated. /// /// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice pub fn abandon_payment(&self, payment_id: PaymentId) { + self.abandon_payment_with_reason(payment_id, PaymentFailureReason::UserAbandoned) + } + + fn abandon_payment_with_reason(&self, payment_id: PaymentId, reason: PaymentFailureReason) { let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self); - self.pending_outbound_payments.abandon_payment(payment_id, PaymentFailureReason::UserAbandoned, &self.pending_events); + self.pending_outbound_payments.abandon_payment(payment_id, reason, &self.pending_events); } /// Send a spontaneous payment, which is a payment that does not require the recipient to have @@ -8846,7 +8849,7 @@ macro_rules! create_refund_builder { ($self: ident, $builder: ty) => { /// /// To revoke the refund, use [`ChannelManager::abandon_payment`] prior to receiving the /// invoice. If abandoned, or an invoice isn't received before expiration, the payment will fail - /// with an [`Event::InvoiceRequestFailed`]. + /// with an [`Event::PaymentFailed`]. /// /// If `max_total_routing_fee_msat` is not specified, The default from /// [`RouteParameters::from_payment_params_and_value`] is applied. @@ -8886,7 +8889,7 @@ macro_rules! create_refund_builder { ($self: ident, $builder: ty) => { let secp_ctx = &$self.secp_ctx; let nonce = Nonce::from_entropy_source(entropy); - let context = OffersContext::OutboundPayment { payment_id, nonce }; + let context = OffersContext::OutboundPayment { payment_id, nonce, hmac: None }; let path = $self.create_blinded_paths_using_absolute_expiry(context, Some(absolute_expiry)) .and_then(|paths| paths.into_iter().next().ok_or(())) .map_err(|_| Bolt12SemanticError::MissingPaths)?; @@ -8962,7 +8965,7 @@ where /// /// To revoke the request, use [`ChannelManager::abandon_payment`] prior to receiving the /// invoice. If abandoned, or an invoice isn't received in a reasonable amount of time, the - /// payment will fail with an [`Event::InvoiceRequestFailed`]. + /// payment will fail with an [`Event::PaymentFailed`]. /// /// # Privacy /// @@ -9021,7 +9024,8 @@ where }; let invoice_request = builder.build_and_sign()?; - let context = OffersContext::OutboundPayment { payment_id, nonce }; + let hmac = signer::hmac_for_payment_id(payment_id, nonce, expanded_key); + let context = OffersContext::OutboundPayment { payment_id, nonce, hmac: Some(hmac) }; let reply_paths = self.create_blinded_paths(context) .map_err(|_| Bolt12SemanticError::MissingPaths)?; @@ -10726,15 +10730,6 @@ where let secp_ctx = &self.secp_ctx; let expanded_key = &self.inbound_payment_key; - let abandon_if_payment = |context| { - match context { - Some(OffersContext::OutboundPayment { payment_id, .. }) => { - self.abandon_payment(payment_id) - }, - _ => {}, - } - }; - match message { OffersMessage::InvoiceRequest(invoice_request) => { let responder = match responder { @@ -10848,42 +10843,38 @@ where &self.logger, None, None, Some(invoice.payment_hash()), ); - let result = { - let features = self.bolt12_invoice_features(); - if invoice.invoice_features().requires_unknown_bits_from(&features) { - Err(InvoiceError::from(Bolt12SemanticError::UnknownRequiredFeatures)) - } else if self.default_configuration.manually_handle_bolt12_invoices { - let event = Event::InvoiceReceived { - payment_id, invoice, context, responder, - }; - self.pending_events.lock().unwrap().push_back((event, None)); - return ResponseInstruction::NoResponse; - } else { - self.send_payment_for_verified_bolt12_invoice(&invoice, payment_id) - .map_err(|e| { - log_trace!(logger, "Failed paying invoice: {:?}", e); - InvoiceError::from_string(format!("{:?}", e)) - }) - } + if self.default_configuration.manually_handle_bolt12_invoices { + let event = Event::InvoiceReceived { + payment_id, invoice, context, responder, + }; + self.pending_events.lock().unwrap().push_back((event, None)); + return ResponseInstruction::NoResponse; + } + + let error = match self.send_payment_for_verified_bolt12_invoice( + &invoice, payment_id, + ) { + Err(Bolt12PaymentError::UnknownRequiredFeatures) => { + log_trace!( + logger, "Invoice requires unknown features: {:?}", + invoice.invoice_features() + ); + InvoiceError::from(Bolt12SemanticError::UnknownRequiredFeatures) + }, + Err(Bolt12PaymentError::SendingFailed(e)) => { + log_trace!(logger, "Failed paying invoice: {:?}", e); + InvoiceError::from_string(format!("{:?}", e)) + }, + Err(Bolt12PaymentError::UnexpectedInvoice) + | Err(Bolt12PaymentError::DuplicateInvoice) + | Ok(()) => return ResponseInstruction::NoResponse, }; - match result { - Ok(_) => ResponseInstruction::NoResponse, - Err(err) => match responder { - Some(responder) => { - abandon_if_payment(context); - responder.respond(OffersMessage::InvoiceError(err)) - }, - None => { - abandon_if_payment(context); - log_trace!( - logger, - "An error response was generated, but there is no reply_path specified \ - for sending the response. Error: {}", - err - ); - return ResponseInstruction::NoResponse; - }, + match responder { + Some(responder) => responder.respond(OffersMessage::InvoiceError(error)), + None => { + log_trace!(logger, "No reply path to send error: {:?}", error); + ResponseInstruction::NoResponse }, } }, @@ -10903,10 +10894,21 @@ where Some(OffersContext::InboundPayment { payment_hash }) => Some(payment_hash), _ => None, }; + let logger = WithContext::from(&self.logger, None, None, payment_hash); log_trace!(logger, "Received invoice_error: {}", invoice_error); - abandon_if_payment(context); + match context { + Some(OffersContext::OutboundPayment { payment_id, nonce, hmac: Some(hmac) }) => { + if signer::verify_payment_id(payment_id, hmac, nonce, expanded_key) { + self.abandon_payment_with_reason( + payment_id, PaymentFailureReason::InvoiceRequestRejected, + ); + } + }, + _ => {}, + } + ResponseInstruction::NoResponse }, } diff --git a/lightning/src/ln/functional_test_utils.rs b/lightning/src/ln/functional_test_utils.rs index 5f1b7123d5a..2845ddd9a41 100644 --- a/lightning/src/ln/functional_test_utils.rs +++ b/lightning/src/ln/functional_test_utils.rs @@ -2520,7 +2520,7 @@ pub fn expect_payment_failed_conditions_event<'a, 'b, 'c, 'd, 'e>( if !conditions.expected_mpp_parts_remain { match &payment_failed_events[1] { Event::PaymentFailed { ref payment_hash, ref payment_id, ref reason } => { - assert_eq!(*payment_hash, expected_payment_hash, "unexpected second payment_hash"); + assert_eq!(*payment_hash, Some(expected_payment_hash), "unexpected second payment_hash"); assert_eq!(*payment_id, expected_payment_id); assert_eq!(reason.unwrap(), if expected_payment_failed_permanently { PaymentFailureReason::RecipientRejected @@ -3162,7 +3162,7 @@ pub fn pass_failed_payment_back<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>, expe if i == expected_paths.len() - 1 { match events[1] { Event::PaymentFailed { ref payment_hash, ref payment_id, ref reason } => { - assert_eq!(*payment_hash, our_payment_hash, "unexpected second payment_hash"); + assert_eq!(*payment_hash, Some(our_payment_hash), "unexpected second payment_hash"); assert_eq!(*payment_id, expected_payment_id); assert_eq!(reason.unwrap(), expected_fail_reason); } diff --git a/lightning/src/ln/functional_tests.rs b/lightning/src/ln/functional_tests.rs index 99063334a28..617df3ce7cc 100644 --- a/lightning/src/ln/functional_tests.rs +++ b/lightning/src/ln/functional_tests.rs @@ -3380,7 +3380,7 @@ fn do_test_commitment_revoked_fail_backward_exhaustive(deliver_bs_raa: bool, use ))); assert!(events.iter().any(|ev| matches!( ev, - Event::PaymentFailed { ref payment_hash, .. } if *payment_hash == fourth_payment_hash + Event::PaymentFailed { ref payment_hash, .. } if *payment_hash == Some(fourth_payment_hash) ))); nodes[1].node.process_pending_htlc_forwards(); @@ -3442,7 +3442,7 @@ fn do_test_commitment_revoked_fail_backward_exhaustive(deliver_bs_raa: bool, use } match events[1] { Event::PaymentFailed { ref payment_hash, .. } => { - assert_eq!(*payment_hash, first_payment_hash); + assert_eq!(*payment_hash, Some(first_payment_hash)); }, _ => panic!("Unexpected event"), } @@ -3454,7 +3454,7 @@ fn do_test_commitment_revoked_fail_backward_exhaustive(deliver_bs_raa: bool, use } match events[3] { Event::PaymentFailed { ref payment_hash, .. } => { - assert_eq!(*payment_hash, second_payment_hash); + assert_eq!(*payment_hash, Some(second_payment_hash)); }, _ => panic!("Unexpected event"), } @@ -3466,7 +3466,7 @@ fn do_test_commitment_revoked_fail_backward_exhaustive(deliver_bs_raa: bool, use } match events[5] { Event::PaymentFailed { ref payment_hash, .. } => { - assert_eq!(*payment_hash, third_payment_hash); + assert_eq!(*payment_hash, Some(third_payment_hash)); }, _ => panic!("Unexpected event"), } @@ -3572,7 +3572,7 @@ fn fail_backward_pending_htlc_upon_channel_failure() { } match events[1] { Event::PaymentFailed { payment_hash, .. } => { - assert_eq!(payment_hash, failed_payment_hash); + assert_eq!(payment_hash, Some(failed_payment_hash)); }, _ => panic!("Unexpected event"), } @@ -3851,7 +3851,7 @@ fn test_simple_peer_disconnect() { } match events[3] { Event::PaymentFailed { payment_hash, .. } => { - assert_eq!(payment_hash, payment_hash_5); + assert_eq!(payment_hash, Some(payment_hash_5)); }, _ => panic!("Unexpected event"), } @@ -6007,7 +6007,7 @@ fn test_fail_holding_cell_htlc_upon_free() { } match &events[1] { &Event::PaymentFailed { ref payment_hash, .. } => { - assert_eq!(our_payment_hash.clone(), *payment_hash); + assert_eq!(Some(our_payment_hash), *payment_hash); }, _ => panic!("Unexpected event"), } @@ -6095,7 +6095,7 @@ fn test_free_and_fail_holding_cell_htlcs() { } match &events[1] { &Event::PaymentFailed { ref payment_hash, .. } => { - assert_eq!(payment_hash_2.clone(), *payment_hash); + assert_eq!(Some(payment_hash_2), *payment_hash); }, _ => panic!("Unexpected event"), } @@ -7048,7 +7048,7 @@ fn test_channel_failed_after_message_with_badonion_node_perm_bits_set() { } match events_5[1] { Event::PaymentFailed { payment_hash, .. } => { - assert_eq!(payment_hash, our_payment_hash); + assert_eq!(payment_hash, Some(our_payment_hash)); }, _ => panic!("Unexpected event"), } diff --git a/lightning/src/ln/max_payment_path_len_tests.rs b/lightning/src/ln/max_payment_path_len_tests.rs index fd027ea62b6..096bcf9633c 100644 --- a/lightning/src/ln/max_payment_path_len_tests.rs +++ b/lightning/src/ln/max_payment_path_len_tests.rs @@ -388,7 +388,7 @@ fn bolt12_invoice_too_large_blinded_paths() { let invoice_om = nodes[1].onion_messenger.next_onion_message_for_peer(nodes[0].node.get_our_node_id()).unwrap(); nodes[0].onion_messenger.handle_onion_message(&nodes[1].node.get_our_node_id(), &invoice_om); // TODO: assert on the invoice error once we support replying to invoice OMs with failure info - nodes[0].logger.assert_log_contains("lightning::ln::channelmanager", "Failed paying invoice: SendingFailed(OnionPacketSizeExceeded)", 1); + nodes[0].logger.assert_log_contains("lightning::ln::channelmanager", "Failed paying invoice: OnionPacketSizeExceeded", 1); let events = nodes[0].node.get_and_clear_pending_events(); assert_eq!(events.len(), 1); diff --git a/lightning/src/ln/offers_tests.rs b/lightning/src/ln/offers_tests.rs index a7fc92f527f..fd496b5f639 100644 --- a/lightning/src/ln/offers_tests.rs +++ b/lightning/src/ln/offers_tests.rs @@ -41,20 +41,24 @@ //! blinded paths are used. use bitcoin::network::Network; -use bitcoin::secp256k1::PublicKey; +use bitcoin::secp256k1::{PublicKey, Secp256k1}; use core::time::Duration; use crate::blinded_path::{BlindedPath, IntroductionNode}; use crate::blinded_path::payment::{Bolt12OfferContext, Bolt12RefundContext, PaymentContext}; -use crate::events::{Event, MessageSendEventsProvider, PaymentPurpose}; +use crate::blinded_path::message::{MessageContext, OffersContext}; +use crate::events::{Event, MessageSendEventsProvider, PaymentFailureReason, PaymentPurpose}; use crate::ln::channelmanager::{Bolt12PaymentError, MAX_SHORT_LIVED_RELATIVE_EXPIRY, PaymentId, RecentPaymentDetails, Retry, self}; +use crate::ln::features::Bolt12InvoiceFeatures; use crate::ln::functional_test_utils::*; +use crate::ln::inbound_payment::ExpandedKey; use crate::ln::msgs::{ChannelMessageHandler, Init, NodeAnnouncement, OnionMessage, OnionMessageHandler, RoutingMessageHandler, SocketAddress, UnsignedGossipMessage, UnsignedNodeAnnouncement}; use crate::ln::outbound_payment::IDEMPOTENCY_TIMEOUT_TICKS; use crate::offers::invoice::Bolt12Invoice; use crate::offers::invoice_error::InvoiceError; use crate::offers::invoice_request::{InvoiceRequest, InvoiceRequestFields}; +use crate::offers::nonce::Nonce; use crate::offers::parse::Bolt12SemanticError; -use crate::onion_message::messenger::{Destination, PeeledOnion}; +use crate::onion_message::messenger::{Destination, PeeledOnion, new_pending_onion_message}; use crate::onion_message::offers::OffersMessage; use crate::onion_message::packet::ParsedOnionMessageContents; use crate::routing::gossip::{NodeAlias, NodeId}; @@ -184,6 +188,15 @@ fn claim_bolt12_payment<'a, 'b, 'c>( claim_payment(node, path, payment_preimage); } +fn extract_offer_nonce<'a, 'b, 'c>(node: &Node<'a, 'b, 'c>, message: &OnionMessage) -> Nonce { + match node.onion_messenger.peel_onion_message(message) { + Ok(PeeledOnion::Receive(_, Some(MessageContext::Offers(OffersContext::InvoiceRequest { nonce })), _)) => nonce, + Ok(PeeledOnion::Receive(_, context, _)) => panic!("Unexpected onion message context: {:?}", context), + Ok(PeeledOnion::Forward(_, _)) => panic!("Unexpected onion message forward"), + Err(e) => panic!("Failed to process onion message {:?}", e), + } +} + fn extract_invoice_request<'a, 'b, 'c>( node: &Node<'a, 'b, 'c>, message: &OnionMessage ) -> (InvoiceRequest, BlindedPath) { @@ -1319,7 +1332,7 @@ fn fails_authentication_when_handling_invoice_request() { assert_eq!(alice.onion_messenger.next_onion_message_for_peer(charlie_id), None); david.node.abandon_payment(payment_id); - get_event!(david, Event::InvoiceRequestFailed); + get_event!(david, Event::PaymentFailed); // Send the invoice request to Alice using an invalid blinded path. let payment_id = PaymentId([2; 32]); @@ -1406,7 +1419,7 @@ fn fails_authentication_when_handling_invoice_for_offer() { david.node.pay_for_offer(&offer, None, None, None, payment_id, Retry::Attempts(0), None) .unwrap(); david.node.abandon_payment(payment_id); - get_event!(david, Event::InvoiceRequestFailed); + get_event!(david, Event::PaymentFailed); // Don't send the invoice request, but grab its reply path to use with a different request. let invalid_reply_path = { @@ -1534,7 +1547,7 @@ fn fails_authentication_when_handling_invoice_for_refund() { expect_recent_payment!(david, RecentPaymentDetails::AwaitingInvoice, payment_id); david.node.abandon_payment(payment_id); - get_event!(david, Event::InvoiceRequestFailed); + get_event!(david, Event::PaymentFailed); // Send the invoice to David using an invalid blinded path. let invalid_path = refund.paths().first().unwrap().clone(); @@ -1918,11 +1931,12 @@ fn fails_sending_invoice_without_blinded_payment_paths_for_offer() { assert_eq!(invoice_error, InvoiceError::from(Bolt12SemanticError::MissingPaths)); // Confirm that david drops this failed payment from his pending outbound payments. - match get_event!(david, Event::InvoiceRequestFailed) { - Event::InvoiceRequestFailed { payment_id: pay_id } => { - assert_eq!(pay_id, payment_id) + match get_event!(david, Event::PaymentFailed) { + Event::PaymentFailed { payment_id: actual_payment_id, reason, .. } => { + assert_eq!(payment_id, actual_payment_id); + assert_eq!(reason, Some(PaymentFailureReason::InvoiceRequestRejected)); }, - _ => panic!("No Event::InvoiceRequestFailed"), + _ => panic!("No Event::PaymentFailed"), } } @@ -2027,16 +2041,13 @@ fn fails_paying_invoice_more_than_once() { let onion_message = charlie.onion_messenger.next_onion_message_for_peer(david_id).unwrap(); david.onion_messenger.handle_onion_message(&charlie_id, &onion_message); - // David pays the first invoice + // David initiates paying the first invoice let payment_context = PaymentContext::Bolt12Refund(Bolt12RefundContext {}); let (invoice1, _) = extract_invoice(david, &onion_message); route_bolt12_payment(david, &[charlie, bob, alice], &invoice1); expect_recent_payment!(david, RecentPaymentDetails::Pending, payment_id); - claim_bolt12_payment(david, &[charlie, bob, alice], payment_context); - expect_recent_payment!(david, RecentPaymentDetails::Fulfilled, payment_id); - disconnect_peers(alice, &[charlie]); // Alice sends the second invoice @@ -2054,13 +2065,117 @@ fn fails_paying_invoice_more_than_once() { let (invoice2, _) = extract_invoice(david, &onion_message); assert_eq!(invoice1.payer_metadata(), invoice2.payer_metadata()); - // David sends an error instead of paying the second invoice + // David doesn't initiate paying the second invoice + assert!(david.onion_messenger.next_onion_message_for_peer(bob_id).is_none()); + assert!(david.node.get_and_clear_pending_msg_events().is_empty()); + + // Complete paying the first invoice + claim_bolt12_payment(david, &[charlie, bob, alice], payment_context); + expect_recent_payment!(david, RecentPaymentDetails::Fulfilled, payment_id); +} + +#[test] +fn fails_paying_invoice_with_unknown_required_features() { + let mut accept_forward_cfg = test_default_channel_config(); + accept_forward_cfg.accept_forwards_to_priv_channels = true; + + // Clearing route_blinding prevents forming any payment paths since the node is unannounced. + let mut features = channelmanager::provided_init_features(&accept_forward_cfg); + features.set_onion_messages_optional(); + features.set_route_blinding_optional(); + + let chanmon_cfgs = create_chanmon_cfgs(6); + let node_cfgs = create_node_cfgs(6, &chanmon_cfgs); + + *node_cfgs[1].override_init_features.borrow_mut() = Some(features); + + let node_chanmgrs = create_node_chanmgrs( + 6, &node_cfgs, &[None, Some(accept_forward_cfg), None, None, None, None] + ); + let nodes = create_network(6, &node_cfgs, &node_chanmgrs); + + create_unannounced_chan_between_nodes_with_value(&nodes, 0, 1, 10_000_000, 1_000_000_000); + create_unannounced_chan_between_nodes_with_value(&nodes, 2, 3, 10_000_000, 1_000_000_000); + create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 10_000_000, 1_000_000_000); + create_announced_chan_between_nodes_with_value(&nodes, 1, 4, 10_000_000, 1_000_000_000); + create_announced_chan_between_nodes_with_value(&nodes, 1, 5, 10_000_000, 1_000_000_000); + create_announced_chan_between_nodes_with_value(&nodes, 2, 4, 10_000_000, 1_000_000_000); + create_announced_chan_between_nodes_with_value(&nodes, 2, 5, 10_000_000, 1_000_000_000); + + let (alice, bob, charlie, david) = (&nodes[0], &nodes[1], &nodes[2], &nodes[3]); + let alice_id = alice.node.get_our_node_id(); + let bob_id = bob.node.get_our_node_id(); + let charlie_id = charlie.node.get_our_node_id(); + let david_id = david.node.get_our_node_id(); + + disconnect_peers(alice, &[charlie, david, &nodes[4], &nodes[5]]); + disconnect_peers(david, &[bob, &nodes[4], &nodes[5]]); + + let offer = alice.node + .create_offer_builder(None).unwrap() + .amount_msats(10_000_000) + .build().unwrap(); + + let payment_id = PaymentId([1; 32]); + david.node.pay_for_offer(&offer, None, None, None, payment_id, Retry::Attempts(0), None) + .unwrap(); + + connect_peers(david, bob); + let onion_message = david.onion_messenger.next_onion_message_for_peer(bob_id).unwrap(); bob.onion_messenger.handle_onion_message(&david_id, &onion_message); + connect_peers(alice, charlie); + 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_error = extract_invoice_error(alice, &onion_message); - assert_eq!(invoice_error, InvoiceError::from_string("DuplicateInvoice".to_string())); + let (invoice_request, reply_path) = extract_invoice_request(alice, &onion_message); + let nonce = extract_offer_nonce(alice, &onion_message); + + let onion_message = alice.onion_messenger.next_onion_message_for_peer(charlie_id).unwrap(); + charlie.onion_messenger.handle_onion_message(&alice_id, &onion_message); + + // Drop the invoice in favor for one with unknown required features. + let onion_message = charlie.onion_messenger.next_onion_message_for_peer(david_id).unwrap(); + let (invoice, _) = extract_invoice(david, &onion_message); + + let payment_paths = invoice.payment_paths().to_vec(); + let payment_hash = invoice.payment_hash(); + + let expanded_key = ExpandedKey::new(&alice.keys_manager.get_inbound_payment_key_material()); + let secp_ctx = Secp256k1::new(); + + let created_at = alice.node.duration_since_epoch(); + let invoice = invoice_request + .verify_using_recipient_data(nonce, &expanded_key, &secp_ctx).unwrap() + .respond_using_derived_keys_no_std(payment_paths, payment_hash, created_at).unwrap() + .features_unchecked(Bolt12InvoiceFeatures::unknown()) + .build_and_sign(&secp_ctx).unwrap(); + + // Enqueue an onion message containing the new invoice. + let pending_message = new_pending_onion_message( + OffersMessage::Invoice(invoice), Destination::BlindedPath(reply_path), None + ); + alice.node.pending_offers_messages.lock().unwrap().push(pending_message); + + let onion_message = alice.onion_messenger.next_onion_message_for_peer(charlie_id).unwrap(); + charlie.onion_messenger.handle_onion_message(&alice_id, &onion_message); + + let onion_message = charlie.onion_messenger.next_onion_message_for_peer(david_id).unwrap(); + david.onion_messenger.handle_onion_message(&charlie_id, &onion_message); + + // Confirm that david drops this failed payment from his pending outbound payments. + match get_event!(david, Event::PaymentFailed) { + Event::PaymentFailed { + payment_id: event_payment_id, + payment_hash: Some(event_payment_hash), + reason: Some(event_reason), + } => { + assert_eq!(event_payment_id, payment_id); + assert_eq!(event_payment_hash, payment_hash); + assert_eq!(event_reason, PaymentFailureReason::UnknownRequiredFeatures); + }, + _ => panic!("Expected Event::PaymentFailed with reason"), + } } diff --git a/lightning/src/ln/onion_route_tests.rs b/lightning/src/ln/onion_route_tests.rs index bd28f96988f..94c2cfd2ee4 100644 --- a/lightning/src/ln/onion_route_tests.rs +++ b/lightning/src/ln/onion_route_tests.rs @@ -215,7 +215,7 @@ fn run_onion_failure_test_with_fail_intercept( } match events[1] { Event::PaymentFailed { payment_hash: ev_payment_hash, payment_id: ev_payment_id, reason: ref ev_reason } => { - assert_eq!(*payment_hash, ev_payment_hash); + assert_eq!(Some(*payment_hash), ev_payment_hash); assert_eq!(payment_id, ev_payment_id); assert_eq!(if expected_retryable { PaymentFailureReason::RetriesExhausted diff --git a/lightning/src/ln/outbound_payment.rs b/lightning/src/ln/outbound_payment.rs index 859b3467296..c36f2c5c1a9 100644 --- a/lightning/src/ln/outbound_payment.rs +++ b/lightning/src/ln/outbound_payment.rs @@ -19,6 +19,7 @@ use crate::events::{self, PaymentFailureReason}; use crate::ln::types::{PaymentHash, PaymentPreimage, PaymentSecret}; use crate::ln::channel_state::ChannelDetails; use crate::ln::channelmanager::{EventCompletionAction, HTLCSource, PaymentId}; +use crate::ln::features::Bolt12InvoiceFeatures; use crate::ln::onion_utils; use crate::ln::onion_utils::{DecodedOnionFailure, HTLCFailReason}; use crate::offers::invoice::Bolt12Invoice; @@ -95,7 +96,8 @@ pub(crate) enum PendingOutboundPayment { Abandoned { session_privs: HashSet<[u8; 32]>, payment_hash: PaymentHash, - /// Will be `None` if the payment was serialized before 0.0.115. + /// Will be `None` if the payment was serialized before 0.0.115 or if downgrading to 0.0.124 + /// or later with a reason that was added after. reason: Option, }, } @@ -510,6 +512,8 @@ pub enum Bolt12PaymentError { UnexpectedInvoice, /// Payment for an invoice with the corresponding [`PaymentId`] was already initiated. DuplicateInvoice, + /// The invoice was valid for the corresponding [`PaymentId`], but required unknown features. + UnknownRequiredFeatures, /// The invoice was valid for the corresponding [`PaymentId`], but sending the payment failed. SendingFailed(RetryableSendFailure), } @@ -783,9 +787,9 @@ impl OutboundPayments { R: Deref, ES: Deref, NS: Deref, NL: Deref, IH, SP, L: Deref >( &self, invoice: &Bolt12Invoice, payment_id: PaymentId, 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, - logger: &L, + 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, logger: &L, pending_events: &Mutex)>>, send_payment_along_path: SP, ) -> Result<(), Bolt12PaymentError> @@ -819,6 +823,13 @@ impl OutboundPayments { hash_map::Entry::Vacant(_) => return Err(Bolt12PaymentError::UnexpectedInvoice), } + if invoice.invoice_features().requires_unknown_bits_from(&features) { + self.abandon_payment( + payment_id, PaymentFailureReason::UnknownRequiredFeatures, pending_events, + ); + return Err(Bolt12PaymentError::UnknownRequiredFeatures); + } + let mut payment_params = PaymentParameters::from_bolt12_invoice(&invoice); // Advance any blinded path where the introduction node is our node. @@ -951,7 +962,7 @@ impl OutboundPayments { if let PendingOutboundPayment::Abandoned { payment_hash, reason, .. } = pmt { pending_events.lock().unwrap().push_back((events::Event::PaymentFailed { payment_id: *pmt_id, - payment_hash: *payment_hash, + payment_hash: Some(*payment_hash), reason: *reason, }, None)); retain = false; @@ -1117,7 +1128,7 @@ impl OutboundPayments { if $payment.get().remaining_parts() == 0 { pending_events.lock().unwrap().push_back((events::Event::PaymentFailed { payment_id, - payment_hash, + payment_hash: Some(payment_hash), reason: *reason, }, None)); $payment.remove(); @@ -1698,9 +1709,12 @@ impl OutboundPayments { }, }; if is_stale { - pending_events.push_back( - (events::Event::InvoiceRequestFailed { payment_id: *payment_id }, None) - ); + let event = events::Event::PaymentFailed { + payment_id: *payment_id, + payment_hash: None, + reason: Some(PaymentFailureReason::InvoiceRequestExpired), + }; + pending_events.push_back((event, None)); false } else { true @@ -1785,7 +1799,7 @@ impl OutboundPayments { if !payment_is_probe { full_failure_ev = Some(events::Event::PaymentFailed { payment_id: *payment_id, - payment_hash: *payment_hash, + payment_hash: Some(*payment_hash), reason: *reason, }); } @@ -1854,14 +1868,16 @@ impl OutboundPayments { if payment.get().remaining_parts() == 0 { pending_events.lock().unwrap().push_back((events::Event::PaymentFailed { payment_id, - payment_hash: *payment_hash, + payment_hash: Some(*payment_hash), reason: *reason, }, None)); payment.remove(); } } else if let PendingOutboundPayment::AwaitingInvoice { .. } = payment.get() { - pending_events.lock().unwrap().push_back((events::Event::InvoiceRequestFailed { + pending_events.lock().unwrap().push_back((events::Event::PaymentFailed { payment_id, + payment_hash: None, + reason: Some(reason), }, None)); payment.remove(); } @@ -1925,7 +1941,7 @@ impl_writeable_tlv_based_enum_upgradable!(PendingOutboundPayment, }, (3, Abandoned) => { (0, session_privs, required), - (1, reason, option), + (1, reason, upgradable_option), (2, payment_hash, required), }, (5, AwaitingInvoice) => { @@ -1951,7 +1967,7 @@ mod tests { use crate::events::{Event, PathFailure, PaymentFailureReason}; use crate::ln::types::PaymentHash; use crate::ln::channelmanager::{PaymentId, RecipientOnionFields}; - use crate::ln::features::{ChannelFeatures, NodeFeatures}; + use crate::ln::features::{Bolt12InvoiceFeatures, ChannelFeatures, NodeFeatures}; use crate::ln::msgs::{ErrorAction, LightningError}; use crate::ln::outbound_payment::{Bolt12PaymentError, OutboundPayments, Retry, RetryableSendFailure, StaleExpiration}; #[cfg(feature = "std")] @@ -2190,7 +2206,11 @@ mod tests { assert!(!pending_events.lock().unwrap().is_empty()); assert_eq!( pending_events.lock().unwrap().pop_front(), - Some((Event::InvoiceRequestFailed { payment_id }, None)), + Some((Event::PaymentFailed { + payment_id, + payment_hash: None, + reason: Some(PaymentFailureReason::InvoiceRequestExpired), + }, None)), ); assert!(pending_events.lock().unwrap().is_empty()); @@ -2239,7 +2259,11 @@ mod tests { assert!(!pending_events.lock().unwrap().is_empty()); assert_eq!( pending_events.lock().unwrap().pop_front(), - Some((Event::InvoiceRequestFailed { payment_id }, None)), + Some((Event::PaymentFailed { + payment_id, + payment_hash: None, + reason: Some(PaymentFailureReason::InvoiceRequestExpired), + }, None)), ); assert!(pending_events.lock().unwrap().is_empty()); @@ -2279,7 +2303,9 @@ mod tests { assert!(!pending_events.lock().unwrap().is_empty()); assert_eq!( pending_events.lock().unwrap().pop_front(), - Some((Event::InvoiceRequestFailed { payment_id }, None)), + Some((Event::PaymentFailed { + payment_id, payment_hash: None, reason: Some(PaymentFailureReason::UserAbandoned), + }, None)), ); assert!(pending_events.lock().unwrap().is_empty()); } @@ -2319,15 +2345,15 @@ mod tests { assert_eq!( outbound_payments.send_payment_for_bolt12_invoice( - &invoice, payment_id, &&router, vec![], || InFlightHtlcs::new(), &&keys_manager, - &&keys_manager, &EmptyNodeIdLookUp {}, &secp_ctx, 0, &&logger, &pending_events, - |_| panic!() + &invoice, payment_id, &&router, vec![], Bolt12InvoiceFeatures::empty(), + || InFlightHtlcs::new(), &&keys_manager, &&keys_manager, &EmptyNodeIdLookUp {}, + &secp_ctx, 0, &&logger, &pending_events, |_| panic!() ), Err(Bolt12PaymentError::SendingFailed(RetryableSendFailure::PaymentExpired)), ); assert!(!outbound_payments.has_pending_payments()); - let payment_hash = invoice.payment_hash(); + let payment_hash = Some(invoice.payment_hash()); let reason = Some(PaymentFailureReason::PaymentExpired); assert!(!pending_events.lock().unwrap().is_empty()); @@ -2380,15 +2406,15 @@ mod tests { assert_eq!( outbound_payments.send_payment_for_bolt12_invoice( - &invoice, payment_id, &&router, vec![], || InFlightHtlcs::new(), &&keys_manager, - &&keys_manager, &EmptyNodeIdLookUp {}, &secp_ctx, 0, &&logger, &pending_events, - |_| panic!() + &invoice, payment_id, &&router, vec![], Bolt12InvoiceFeatures::empty(), + || InFlightHtlcs::new(), &&keys_manager, &&keys_manager, &EmptyNodeIdLookUp {}, + &secp_ctx, 0, &&logger, &pending_events, |_| panic!() ), Err(Bolt12PaymentError::SendingFailed(RetryableSendFailure::RouteNotFound)), ); assert!(!outbound_payments.has_pending_payments()); - let payment_hash = invoice.payment_hash(); + let payment_hash = Some(invoice.payment_hash()); let reason = Some(PaymentFailureReason::RouteNotFound); assert!(!pending_events.lock().unwrap().is_empty()); @@ -2454,9 +2480,9 @@ mod tests { assert!(!outbound_payments.has_pending_payments()); assert_eq!( outbound_payments.send_payment_for_bolt12_invoice( - &invoice, payment_id, &&router, vec![], || InFlightHtlcs::new(), &&keys_manager, - &&keys_manager, &EmptyNodeIdLookUp {}, &secp_ctx, 0, &&logger, &pending_events, - |_| panic!() + &invoice, payment_id, &&router, vec![], Bolt12InvoiceFeatures::empty(), + || InFlightHtlcs::new(), &&keys_manager, &&keys_manager, &EmptyNodeIdLookUp {}, + &secp_ctx, 0, &&logger, &pending_events, |_| panic!() ), Err(Bolt12PaymentError::UnexpectedInvoice), ); @@ -2472,9 +2498,9 @@ mod tests { assert_eq!( outbound_payments.send_payment_for_bolt12_invoice( - &invoice, payment_id, &&router, vec![], || InFlightHtlcs::new(), &&keys_manager, - &&keys_manager, &EmptyNodeIdLookUp {}, &secp_ctx, 0, &&logger, &pending_events, - |_| Ok(()) + &invoice, payment_id, &&router, vec![], Bolt12InvoiceFeatures::empty(), + || InFlightHtlcs::new(), &&keys_manager, &&keys_manager, &EmptyNodeIdLookUp {}, + &secp_ctx, 0, &&logger, &pending_events, |_| Ok(()) ), Ok(()), ); @@ -2483,9 +2509,9 @@ mod tests { assert_eq!( outbound_payments.send_payment_for_bolt12_invoice( - &invoice, payment_id, &&router, vec![], || InFlightHtlcs::new(), &&keys_manager, - &&keys_manager, &EmptyNodeIdLookUp {}, &secp_ctx, 0, &&logger, &pending_events, - |_| panic!() + &invoice, payment_id, &&router, vec![], Bolt12InvoiceFeatures::empty(), + || InFlightHtlcs::new(), &&keys_manager, &&keys_manager, &EmptyNodeIdLookUp {}, + &secp_ctx, 0, &&logger, &pending_events, |_| panic!() ), Err(Bolt12PaymentError::DuplicateInvoice), ); diff --git a/lightning/src/ln/payment_tests.rs b/lightning/src/ln/payment_tests.rs index 3ab055d2ada..083c32e5393 100644 --- a/lightning/src/ln/payment_tests.rs +++ b/lightning/src/ln/payment_tests.rs @@ -2268,7 +2268,7 @@ fn do_automatic_retries(test: AutoRetry) { } else { match events[1] { Event::PaymentFailed { payment_hash: ev_payment_hash, .. } => { - assert_eq!(payment_hash, ev_payment_hash); + assert_eq!(Some(payment_hash), ev_payment_hash); }, _ => panic!("Unexpected event"), } @@ -2351,7 +2351,7 @@ fn do_automatic_retries(test: AutoRetry) { assert_eq!(events.len(), 1); match events[0] { Event::PaymentFailed { payment_hash: ref ev_payment_hash, payment_id: ref ev_payment_id, reason: ref ev_reason } => { - assert_eq!(payment_hash, *ev_payment_hash); + assert_eq!(Some(payment_hash), *ev_payment_hash); assert_eq!(PaymentId(payment_hash.0), *ev_payment_id); assert_eq!(PaymentFailureReason::RetriesExhausted, ev_reason.unwrap()); }, @@ -2388,7 +2388,7 @@ fn do_automatic_retries(test: AutoRetry) { assert_eq!(events.len(), 1); match events[0] { Event::PaymentFailed { payment_hash: ref ev_payment_hash, payment_id: ref ev_payment_id, reason: ref ev_reason } => { - assert_eq!(payment_hash, *ev_payment_hash); + assert_eq!(Some(payment_hash), *ev_payment_hash); assert_eq!(PaymentId(payment_hash.0), *ev_payment_id); assert_eq!(PaymentFailureReason::RetriesExhausted, ev_reason.unwrap()); }, @@ -2409,7 +2409,7 @@ fn do_automatic_retries(test: AutoRetry) { assert_eq!(events.len(), 1); match events[0] { Event::PaymentFailed { payment_hash: ref ev_payment_hash, payment_id: ref ev_payment_id, reason: ref ev_reason } => { - assert_eq!(payment_hash, *ev_payment_hash); + assert_eq!(Some(payment_hash), *ev_payment_hash); assert_eq!(PaymentId(payment_hash.0), *ev_payment_id); assert_eq!(PaymentFailureReason::RouteNotFound, ev_reason.unwrap()); }, @@ -3099,7 +3099,7 @@ fn no_extra_retries_on_back_to_back_fail() { } match events[1] { Event::PaymentFailed { payment_hash: ref ev_payment_hash, payment_id: ref ev_payment_id, reason: ref ev_reason } => { - assert_eq!(payment_hash, *ev_payment_hash); + assert_eq!(Some(payment_hash), *ev_payment_hash); assert_eq!(PaymentId(payment_hash.0), *ev_payment_id); assert_eq!(PaymentFailureReason::RetriesExhausted, ev_reason.unwrap()); }, diff --git a/lightning/src/offers/invoice.rs b/lightning/src/offers/invoice.rs index 835a077f294..3fd2e73aa5b 100644 --- a/lightning/src/offers/invoice.rs +++ b/lightning/src/offers/invoice.rs @@ -117,6 +117,8 @@ use crate::ln::features::{BlindedHopFeatures, Bolt12InvoiceFeatures, InvoiceRequ use crate::ln::inbound_payment::{ExpandedKey, IV_LEN}; use crate::ln::msgs::DecodeError; use crate::offers::invoice_macros::{invoice_accessors_common, invoice_builder_methods_common}; +#[cfg(test)] +use crate::offers::invoice_macros::invoice_builder_methods_test; use crate::offers::invoice_request::{INVOICE_REQUEST_PAYER_ID_TYPE, INVOICE_REQUEST_TYPES, IV_BYTES as INVOICE_REQUEST_IV_BYTES, InvoiceRequest, InvoiceRequestContents, InvoiceRequestTlvStream, InvoiceRequestTlvStreamRef}; use crate::offers::merkle::{SignError, SignFn, SignatureTlvStream, SignatureTlvStreamRef, TaggedHash, TlvStream, WithoutSignatures, self}; use crate::offers::nonce::Nonce; @@ -384,35 +386,40 @@ impl<'a> InvoiceBuilder<'a, DerivedSigningPubkey> { impl<'a, S: SigningPubkeyStrategy> InvoiceBuilder<'a, S> { invoice_builder_methods!(self, Self, Self, self, S, mut); - invoice_builder_methods_common!(self, Self, self.invoice.fields_mut(), Self, self, S, Bolt12Invoice, mut); + invoice_builder_methods_common!(self, Self, self.invoice.fields_mut(), Self, self, Bolt12Invoice, mut); + + #[cfg(test)] + invoice_builder_methods_test!(self, Self, self.invoice.fields_mut(), Self, self, mut); } #[cfg(all(c_bindings, not(test)))] impl<'a> InvoiceWithExplicitSigningPubkeyBuilder<'a> { invoice_explicit_signing_pubkey_builder_methods!(self, &mut Self); invoice_builder_methods!(self, &mut Self, (), (), ExplicitSigningPubkey); - invoice_builder_methods_common!(self, &mut Self, self.invoice.fields_mut(), (), (), ExplicitSigningPubkey, Bolt12Invoice); + invoice_builder_methods_common!(self, &mut Self, self.invoice.fields_mut(), (), (), Bolt12Invoice); } #[cfg(all(c_bindings, test))] impl<'a> InvoiceWithExplicitSigningPubkeyBuilder<'a> { invoice_explicit_signing_pubkey_builder_methods!(self, &mut Self); invoice_builder_methods!(self, &mut Self, &mut Self, self, ExplicitSigningPubkey); - invoice_builder_methods_common!(self, &mut Self, self.invoice.fields_mut(), &mut Self, self, ExplicitSigningPubkey, Bolt12Invoice); + invoice_builder_methods_common!(self, &mut Self, self.invoice.fields_mut(), &mut Self, self, Bolt12Invoice); + invoice_builder_methods_test!(self, &mut Self, self.invoice.fields_mut(), &mut Self, self); } #[cfg(all(c_bindings, not(test)))] impl<'a> InvoiceWithDerivedSigningPubkeyBuilder<'a> { invoice_derived_signing_pubkey_builder_methods!(self, &mut Self); invoice_builder_methods!(self, &mut Self, (), (), DerivedSigningPubkey); - invoice_builder_methods_common!(self, &mut Self, self.invoice.fields_mut(), (), (), DerivedSigningPubkey, Bolt12Invoice); + invoice_builder_methods_common!(self, &mut Self, self.invoice.fields_mut(), (), (), Bolt12Invoice); } #[cfg(all(c_bindings, test))] impl<'a> InvoiceWithDerivedSigningPubkeyBuilder<'a> { invoice_derived_signing_pubkey_builder_methods!(self, &mut Self); invoice_builder_methods!(self, &mut Self, &mut Self, self, DerivedSigningPubkey); - invoice_builder_methods_common!(self, &mut Self, self.invoice.fields_mut(), &mut Self, self, DerivedSigningPubkey, Bolt12Invoice); + invoice_builder_methods_common!(self, &mut Self, self.invoice.fields_mut(), &mut Self, self, Bolt12Invoice); + invoice_builder_methods_test!(self, &mut Self, self.invoice.fields_mut(), &mut Self, self); } #[cfg(c_bindings)] diff --git a/lightning/src/offers/invoice_macros.rs b/lightning/src/offers/invoice_macros.rs index b79bb8c9e88..c603c352ce9 100644 --- a/lightning/src/offers/invoice_macros.rs +++ b/lightning/src/offers/invoice_macros.rs @@ -11,7 +11,7 @@ macro_rules! invoice_builder_methods_common { ( $self: ident, $self_type: ty, $invoice_fields: expr, $return_type: ty, $return_value: expr, - $type_param: ty, $invoice_type: ty $(, $self_mut: tt)? + $invoice_type: ty $(, $self_mut: tt)? ) => { #[doc = concat!("Sets the [`", stringify!($invoice_type), "::relative_expiry`]")] #[doc = concat!("as seconds since [`", stringify!($invoice_type), "::created_at`].")] @@ -82,6 +82,21 @@ macro_rules! invoice_builder_methods_common { ( } } } +#[cfg(test)] +macro_rules! invoice_builder_methods_test { ( + $self: ident, $self_type: ty, $invoice_fields: expr, $return_type: ty, $return_value: expr + $(, $self_mut: tt)? +) => { + #[cfg_attr(c_bindings, allow(dead_code))] + pub(crate) fn features_unchecked( + $($self_mut)* $self: $self_type, features: Bolt12InvoiceFeatures + ) -> $return_type { + $invoice_fields.features = features; + $return_value + } + +} } + macro_rules! invoice_accessors_common { ($self: ident, $contents: expr, $invoice_type: ty) => { /// Paths to the recipient originating from publicly reachable nodes, including information /// needed for routing payments across them. @@ -133,3 +148,5 @@ macro_rules! invoice_accessors_common { ($self: ident, $contents: expr, $invoice pub(super) use invoice_accessors_common; pub(super) use invoice_builder_methods_common; +#[cfg(test)] +pub(super) use invoice_builder_methods_test; diff --git a/lightning/src/offers/signer.rs b/lightning/src/offers/signer.rs index c15b94d4996..0aa51cd3338 100644 --- a/lightning/src/offers/signer.rs +++ b/lightning/src/offers/signer.rs @@ -36,6 +36,9 @@ const DERIVED_METADATA_AND_KEYS_HMAC_INPUT: &[u8; 16] = &[2; 16]; const WITHOUT_ENCRYPTED_PAYMENT_ID_HMAC_INPUT: &[u8; 16] = &[3; 16]; const WITH_ENCRYPTED_PAYMENT_ID_HMAC_INPUT: &[u8; 16] = &[4; 16]; +// HMAC input for a `PaymentId`. The HMAC is used in `OffersContext::OutboundPayment`. +const PAYMENT_ID_HMAC_INPUT: &[u8; 16] = &[5; 16]; + /// Message metadata which possibly is derived from [`MetadataMaterial`] such that it can be /// verified. #[derive(Clone)] @@ -391,3 +394,22 @@ fn hmac_for_message<'a>( Ok(hmac) } + +pub(crate) fn hmac_for_payment_id( + payment_id: PaymentId, nonce: Nonce, expanded_key: &ExpandedKey, +) -> Hmac { + const IV_BYTES: &[u8; IV_LEN] = b"LDK Payment ID ~"; + let mut hmac = expanded_key.hmac_for_offer(); + hmac.input(IV_BYTES); + hmac.input(&nonce.0); + hmac.input(PAYMENT_ID_HMAC_INPUT); + hmac.input(&payment_id.0); + + Hmac::from_engine(hmac) +} + +pub(crate) fn verify_payment_id( + payment_id: PaymentId, hmac: Hmac, nonce: Nonce, expanded_key: &ExpandedKey, +) -> bool { + hmac_for_payment_id(payment_id, nonce, expanded_key) == hmac +} diff --git a/lightning/src/offers/static_invoice.rs b/lightning/src/offers/static_invoice.rs index 614903bdfcc..c91496e17a1 100644 --- a/lightning/src/offers/static_invoice.rs +++ b/lightning/src/offers/static_invoice.rs @@ -156,7 +156,7 @@ impl<'a> StaticInvoiceBuilder<'a> { Ok(invoice) } - invoice_builder_methods_common!(self, Self, self.invoice, Self, self, S, StaticInvoice, mut); + invoice_builder_methods_common!(self, Self, self.invoice, Self, self, StaticInvoice, mut); } /// A semantically valid [`StaticInvoice`] that hasn't been signed. diff --git a/lightning/src/util/ser.rs b/lightning/src/util/ser.rs index 29c52f6a08b..5f203b74d97 100644 --- a/lightning/src/util/ser.rs +++ b/lightning/src/util/ser.rs @@ -33,7 +33,9 @@ use bitcoin::script::{self, ScriptBuf}; use bitcoin::transaction::{OutPoint, Transaction, TxOut}; use bitcoin::{consensus, Witness}; use bitcoin::consensus::Encodable; +use bitcoin::hashes::hmac::Hmac; use bitcoin::hashes::sha256d::Hash as Sha256dHash; +use bitcoin::hashes::sha256::Hash as Sha256; use bitcoin::hash_types::{Txid, BlockHash}; use core::time::Duration; use crate::chain::ClaimId; @@ -1033,6 +1035,21 @@ impl Readable for PartialSignatureWithNonce { } } +impl Writeable for Hmac { + fn write(&self, w: &mut W) -> Result<(), io::Error> { + w.write_all(&self[..]) + } +} + +impl Readable for Hmac { + fn read(r: &mut R) -> Result { + use bitcoin::hashes::Hash; + + let buf: [u8; 32] = Readable::read(r)?; + Ok(Hmac::::from_byte_array(buf)) + } +} + impl Writeable for Sha256dHash { fn write(&self, w: &mut W) -> Result<(), io::Error> { w.write_all(&self[..]) diff --git a/pending_changelog/3192-invoice-request-failed-event.txt b/pending_changelog/3192-invoice-request-failed-event.txt new file mode 100644 index 00000000000..26d736c0e41 --- /dev/null +++ b/pending_changelog/3192-invoice-request-failed-event.txt @@ -0,0 +1,18 @@ +## API Updates + * `Event::PaymentFailed` now uses an `Option` for its payment hash, which will + be `None` if the invoice hasn't been received yet for a BOLT12 payment + (#3192). + * `Event::PaymentFailed` is now generated instead `Event::InvoiceRequestFailed`, + which has been removed. When deserialized the latter will be converted to the + former with `None` for the payment hash (#3192). + +## Backwards Compatibility + * Any `Event::PaymentFailed` generated without a payment hash will deserialize + with `PaymentHash([0; 32])` when downgrading. This can be treated like an + `Event::InvoiceRequestFailed` (#3192). + * An `Event::PaymentFailed` generated with one of the following + `PaymentFailureReason`s will deserialize with the corresponding reason after + downgrading to a version prior to 0.0.124: + - `UnknownRequiredFeatures` to `RecipientRejected`, + - `InvoiceRequestExpired` to `RetriesExhausted`, and + - `InvoiceRequestRejected` to `RecipientRejected` (#3192).