From 0f8f48a345c13356e233db897e9c851e905c3ed1 Mon Sep 17 00:00:00 2001 From: Jeffrey Czyz Date: Sat, 9 Mar 2024 18:58:01 -0600 Subject: [PATCH 1/5] Refactor handling of InvoiceRequest In order to generate and InvoiceSent event, it would be cleaner to have one location where a Bolt12Invoice is successfully generated. Refactor the handling code to this end and clean-up line length by making some of the type conversions more streamlined. --- lightning/src/ln/channelmanager.rs | 48 +++++++++++---------------- lightning/src/offers/invoice_error.rs | 14 ++++++++ 2 files changed, 34 insertions(+), 28 deletions(-) diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 8dfd0c8fcaa..9e0bc851350 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -61,7 +61,6 @@ use crate::ln::wire::Encode; use crate::offers::invoice::{BlindedPayInfo, Bolt12Invoice, DEFAULT_RELATIVE_EXPIRY, DerivedSigningPubkey, ExplicitSigningPubkey, InvoiceBuilder, UnsignedBolt12Invoice}; use crate::offers::invoice_error::InvoiceError; use crate::offers::invoice_request::{DerivedPayerId, InvoiceRequestBuilder}; -use crate::offers::merkle::SignError; use crate::offers::offer::{Offer, OfferBuilder}; use crate::offers::parse::Bolt12SemanticError; use crate::offers::refund::{Refund, RefundBuilder}; @@ -9451,7 +9450,7 @@ where self.highest_seen_timestamp.load(Ordering::Acquire) as u64 ); - if invoice_request.keys.is_some() { + let response = if invoice_request.keys.is_some() { #[cfg(feature = "std")] let builder = invoice_request.respond_using_derived_keys( payment_paths, payment_hash @@ -9460,12 +9459,10 @@ where let builder = invoice_request.respond_using_derived_keys_no_std( payment_paths, payment_hash, created_at ); - let builder: Result, _> = - builder.map(|b| b.into()); - match builder.and_then(|b| b.allow_mpp().build_and_sign(secp_ctx)) { - Ok(invoice) => Some(OffersMessage::Invoice(invoice)), - Err(error) => Some(OffersMessage::InvoiceError(error.into())), - } + builder + .map(InvoiceBuilder::::from) + .and_then(|builder| builder.allow_mpp().build_and_sign(secp_ctx)) + .map_err(InvoiceError::from) } else { #[cfg(feature = "std")] let builder = invoice_request.respond_with(payment_paths, payment_hash); @@ -9473,29 +9470,24 @@ where let builder = invoice_request.respond_with_no_std( payment_paths, payment_hash, created_at ); - let builder: Result, _> = - builder.map(|b| b.into()); - let response = builder.and_then(|builder| builder.allow_mpp().build()) - .map_err(|e| OffersMessage::InvoiceError(e.into())) + builder + .map(InvoiceBuilder::::from) + .and_then(|builder| builder.allow_mpp().build()) + .map_err(InvoiceError::from) .and_then(|invoice| { #[cfg(c_bindings)] let mut invoice = invoice; - match invoice.sign(|invoice: &UnsignedBolt12Invoice| - self.node_signer.sign_bolt12_invoice(invoice) - ) { - Ok(invoice) => Ok(OffersMessage::Invoice(invoice)), - Err(SignError::Signing) => Err(OffersMessage::InvoiceError( - InvoiceError::from_string("Failed signing invoice".to_string()) - )), - Err(SignError::Verification(_)) => Err(OffersMessage::InvoiceError( - InvoiceError::from_string("Failed invoice signature verification".to_string()) - )), - } - }); - match response { - Ok(invoice) => Some(invoice), - Err(error) => Some(error), - } + invoice + .sign(|invoice: &UnsignedBolt12Invoice| + self.node_signer.sign_bolt12_invoice(invoice) + ) + .map_err(InvoiceError::from) + }) + }; + + match response { + Ok(invoice) => Some(OffersMessage::Invoice(invoice)), + Err(error) => Some(OffersMessage::InvoiceError(error.into())), } }, OffersMessage::Invoice(invoice) => { diff --git a/lightning/src/offers/invoice_error.rs b/lightning/src/offers/invoice_error.rs index 5476ad551b7..d5961349ff8 100644 --- a/lightning/src/offers/invoice_error.rs +++ b/lightning/src/offers/invoice_error.rs @@ -11,6 +11,7 @@ use crate::io; use crate::ln::msgs::DecodeError; +use crate::offers::merkle::SignError; use crate::offers::parse::Bolt12SemanticError; use crate::util::ser::{HighZeroBytesDroppedBigSize, Readable, WithoutLength, Writeable, Writer}; use crate::util::string::UntrustedString; @@ -112,6 +113,19 @@ impl From for InvoiceError { } } +impl From for InvoiceError { + fn from(error: SignError) -> Self { + let message = match error { + SignError::Signing => "Failed signing invoice", + SignError::Verification(_) => "Failed invoice signature verification", + }; + InvoiceError { + erroneous_field: None, + message: UntrustedString(message.to_string()), + } + } +} + #[cfg(test)] mod tests { use super::{ErroneousField, InvoiceError}; From eec68220e585d9a21eeaf3d55e9489512568c1cc Mon Sep 17 00:00:00 2001 From: Jeffrey Czyz Date: Mon, 11 Mar 2024 17:03:26 -0500 Subject: [PATCH 2/5] Refactor handling of Bolt12Invoice In order to provide an InvoiceGenerated event, it would be cleaner to have one location where a Bolt12Invoice is successfully created. Refactor the handling code to this end and clean-up line length by making some of the type conversions more streamlined. --- lightning/src/ln/channelmanager.rs | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 9e0bc851350..ad8b30ba92b 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -9491,21 +9491,25 @@ where } }, OffersMessage::Invoice(invoice) => { - match invoice.verify(expanded_key, secp_ctx) { - Err(()) => { - Some(OffersMessage::InvoiceError(InvoiceError::from_string("Unrecognized invoice".to_owned()))) - }, - Ok(_) if invoice.invoice_features().requires_unknown_bits_from(&self.bolt12_invoice_features()) => { - Some(OffersMessage::InvoiceError(Bolt12SemanticError::UnknownRequiredFeatures.into())) - }, - Ok(payment_id) => { - if let Err(e) = self.send_payment_for_bolt12_invoice(&invoice, payment_id) { - log_trace!(self.logger, "Failed paying invoice: {:?}", e); - Some(OffersMessage::InvoiceError(InvoiceError::from_string(format!("{:?}", e)))) + let response = invoice + .verify(expanded_key, secp_ctx) + .map_err(|()| InvoiceError::from_string("Unrecognized invoice".to_owned())) + .and_then(|payment_id| { + let features = self.bolt12_invoice_features(); + if invoice.invoice_features().requires_unknown_bits_from(&features) { + Err(InvoiceError::from(Bolt12SemanticError::UnknownRequiredFeatures)) } else { - None + self.send_payment_for_bolt12_invoice(&invoice, payment_id) + .map_err(|e| { + log_trace!(self.logger, "Failed paying invoice: {:?}", e); + InvoiceError::from_string(format!("{:?}", e)) + }) } - }, + }); + + match response { + Ok(()) => None, + Err(e) => Some(OffersMessage::InvoiceError(e)), } }, OffersMessage::InvoiceError(invoice_error) => { From 1be16301d06e3a0928ff49692d46923164078de9 Mon Sep 17 00:00:00 2001 From: Jeffrey Czyz Date: Mon, 11 Mar 2024 18:31:50 -0500 Subject: [PATCH 3/5] Implement Eq and Hash for Bolt12Invoice Bolt12Invoice will be added to a new Event::InvoiceGenerated variant. These traits along with PartialEq are required to be implemented for any type used in an Event. --- lightning/src/offers/invoice.rs | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/lightning/src/offers/invoice.rs b/lightning/src/offers/invoice.rs index fc837102348..078ecbb6c83 100644 --- a/lightning/src/offers/invoice.rs +++ b/lightning/src/offers/invoice.rs @@ -104,7 +104,6 @@ use bitcoin::blockdata::constants::ChainHash; use bitcoin::hash_types::{WPubkeyHash, WScriptHash}; -use bitcoin::hashes::Hash; use bitcoin::network::constants::Network; use bitcoin::secp256k1::{KeyPair, PublicKey, Secp256k1, self}; use bitcoin::secp256k1::schnorr::Signature; @@ -112,6 +111,7 @@ use bitcoin::address::{Address, Payload, WitnessProgram, WitnessVersion}; use bitcoin::key::TweakedPublicKey; use core::convert::{AsRef, TryFrom}; use core::time::Duration; +use core::hash::{Hash, Hasher}; use crate::io; use crate::blinded_path::BlindedPath; use crate::ln::PaymentHash; @@ -390,6 +390,7 @@ macro_rules! invoice_builder_methods { ( /// Successive calls to this method will add another address. Caller is responsible for not /// adding duplicate addresses and only calling if capable of receiving to P2WSH addresses. pub fn fallback_v0_p2wsh($($self_mut)* $self: $self_type, script_hash: &WScriptHash) -> $return_type { + use bitcoin::hashes::Hash; let address = FallbackAddress { version: WitnessVersion::V0.to_num(), program: Vec::from(script_hash.to_byte_array()), @@ -403,6 +404,7 @@ macro_rules! invoice_builder_methods { ( /// Successive calls to this method will add another address. Caller is responsible for not /// adding duplicate addresses and only calling if capable of receiving to P2WPKH addresses. pub fn fallback_v0_p2wpkh($($self_mut)* $self: $self_type, pubkey_hash: &WPubkeyHash) -> $return_type { + use bitcoin::hashes::Hash; let address = FallbackAddress { version: WitnessVersion::V0.to_num(), program: Vec::from(pubkey_hash.to_byte_array()), @@ -614,7 +616,6 @@ impl AsRef for UnsignedBolt12Invoice { /// [`Refund`]: crate::offers::refund::Refund /// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest #[derive(Clone, Debug)] -#[cfg_attr(test, derive(PartialEq))] pub struct Bolt12Invoice { bytes: Vec, contents: InvoiceContents, @@ -886,6 +887,20 @@ impl Bolt12Invoice { } } +impl PartialEq for Bolt12Invoice { + fn eq(&self, other: &Self) -> bool { + self.bytes.eq(&other.bytes) + } +} + +impl Eq for Bolt12Invoice {} + +impl Hash for Bolt12Invoice { + fn hash(&self, state: &mut H) { + self.bytes.hash(state); + } +} + impl InvoiceContents { /// Whether the original offer or refund has expired. #[cfg(feature = "std")] From 3a0d0ffd548a1e5e3d91e8cb3ec0252392953399 Mon Sep 17 00:00:00 2001 From: Jeffrey Czyz Date: Mon, 11 Mar 2024 18:35:55 -0500 Subject: [PATCH 4/5] Provide an InvoiceGenerated event for offers When responding to an InvoiceRequest for an Offer, provide an event when an invoice has been generated. This allows event handler to know when an inbound payment may occur along with information from the invoice such as metadata, payer_id, and payment_hash. --- lightning/src/events/mod.rs | 52 ++++++++++++++++++++++++++++++ lightning/src/ln/channelmanager.rs | 6 +++- lightning/src/ln/offers_tests.rs | 24 ++++++++++++++ 3 files changed, 81 insertions(+), 1 deletion(-) diff --git a/lightning/src/events/mod.rs b/lightning/src/events/mod.rs index 485a23e0292..10f1cb2e55a 100644 --- a/lightning/src/events/mod.rs +++ b/lightning/src/events/mod.rs @@ -25,6 +25,7 @@ use crate::ln::features::ChannelTypeFeatures; use crate::ln::msgs; use crate::ln::{ChannelId, PaymentPreimage, PaymentHash, PaymentSecret}; use crate::chain::transaction; +use crate::offers::invoice::Bolt12Invoice; use crate::routing::gossip::NetworkUpdate; use crate::util::errors::APIError; use crate::util::ser::{BigSize, FixedLengthReader, Writeable, Writer, MaybeReadable, Readable, RequiredWrapper, UpgradableRequired, WithoutLength}; @@ -582,6 +583,39 @@ pub enum Event { /// The `payment_id` to have been associated with payment for the requested invoice. payment_id: PaymentId, }, + /// Indicates that a [`Bolt12Invoice`] was generated in response to an [`InvoiceRequest`] and is + /// being prepared to be sent via an [`OnionMessage`]. + /// + /// Note that this doesn't necessarily mean that the invoice was sent and -- once sent -- it may + /// never reach its destination because of the unreliable nature of onion messages. Any of the + /// following scenarios may occur. + /// - Dropped by a node along the path to the destination + /// - Dropped upon node restart prior to being sent + /// - Buffered waiting to be sent by [`PeerManager`] + /// - Buffered waiting for an [`Event::ConnectionNeeded`] to be handled and peer connected + /// - Dropped waiting too long for such a peer connection + /// - Dropped because the onion message buffer was full + /// - Dropped because the [`MessageRouter`] failed to find an [`OnionMessagePath`] to the + /// destination + /// + /// Thus, this event is largely for informational purposes as the corresponding [`Offer`] and + /// [`InvoiceRequest`] fields are accessible from the invoice. In particular: + /// - [`Bolt12Invoice::metadata`] can help identify the corresponding [`Offer`] + /// - A common [`Bolt12Invoice::payer_id`] indicates the payer sent multiple requests for + /// redundancy, though in that case the [`Bolt12Invoice::payment_hash`] used may be different. + /// + /// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest + /// [`OnionMessage`]: crate::ln::msgs::OnionMessage + /// [`PeerManager`]: crate::ln::peer_handler::PeerManager + /// [`MessageRouter`]: crate::onion_message::messenger::MessageRouter + /// [`OnionMessagePath`]: crate::onion_message::messenger::OnionMessagePath + /// [`Offer`]: crate::offers::offer::Offer + InvoiceGenerated { + /// An invoice that was generated in response to an [`InvoiceRequest`]. + /// + /// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest + invoice: Bolt12Invoice, + }, /// Indicates an outbound payment we made succeeded (i.e. it made it all the way to its target /// and we got back the payment preimage for it). /// @@ -1262,6 +1296,12 @@ impl Writeable for Event { 35u8.write(writer)?; // Never write ConnectionNeeded events as buffered onion messages aren't serialized. }, + &Event::InvoiceGenerated { ref invoice } => { + 37u8.write(writer)?; + write_tlv_fields!(writer, { + (0, invoice, required), + }) + }, // Note that, going forward, all new events must only write data inside of // `write_tlv_fields`. Versions 0.0.101+ will ignore odd-numbered events that write // data via `write_tlv_fields`. @@ -1668,6 +1708,18 @@ impl MaybeReadable for Event { }, // Note that we do not write a length-prefixed TLV for ConnectionNeeded events. 35u8 => Ok(None), + 37u8 => { + let f = || { + let mut invoice_bytes = WithoutLength(Vec::new()); + read_tlv_fields!(reader, { + (0, invoice_bytes, required), + }); + let invoice = Bolt12Invoice::try_from(invoice_bytes.0) + .map_err(|_| msgs::DecodeError::InvalidValue)?; + Ok(Some(Event::InvoiceGenerated { invoice })) + }; + f() + }, // Versions prior to 0.0.100 did not ignore odd types, instead returning InvalidValue. // Version 0.0.100 failed to properly ignore odd types, possibly resulting in corrupt // reads. diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index ad8b30ba92b..0dbefa45451 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -9486,7 +9486,11 @@ where }; match response { - Ok(invoice) => Some(OffersMessage::Invoice(invoice)), + Ok(invoice) => { + let event = Event::InvoiceGenerated { invoice: invoice.clone() }; + self.pending_events.lock().unwrap().push_back((event, None)); + Some(OffersMessage::Invoice(invoice)) + }, Err(error) => Some(OffersMessage::InvoiceError(error.into())), } }, diff --git a/lightning/src/ln/offers_tests.rs b/lightning/src/ln/offers_tests.rs index e16c0ed515f..b61e80c35ff 100644 --- a/lightning/src/ln/offers_tests.rs +++ b/lightning/src/ln/offers_tests.rs @@ -211,6 +211,24 @@ fn extract_invoice_error<'a, 'b, 'c>( } } +fn expect_invoice_generated_event<'a, 'b, 'c, 'd>( + node: &'a Node<'b, 'c, 'd>, expected_invoice: &Bolt12Invoice +) { + use crate::io::Cursor; + use crate::util::ser::MaybeReadable; + use crate::util::ser::Writeable; + + let event = get_event!(node, Event::InvoiceGenerated); + match &event { + Event::InvoiceGenerated { invoice } => assert_eq!(invoice, expected_invoice), + _ => panic!(), + } + + let mut bytes = Vec::new(); + event.write(&mut bytes).unwrap(); + assert_eq!(Some(event), MaybeReadable::read(&mut Cursor::new(&bytes)).unwrap()); +} + /// Checks that blinded paths without Tor-only nodes are preferred when constructing an offer. #[test] fn prefers_non_tor_nodes_in_blinded_paths() { @@ -403,6 +421,8 @@ fn creates_and_pays_for_offer_using_two_hop_blinded_path() { david.onion_messenger.handle_onion_message(&charlie_id, &onion_message); let invoice = extract_invoice(david, &onion_message); + expect_invoice_generated_event(alice, &invoice); + assert_eq!(invoice.amount_msats(), 10_000_000); assert_ne!(invoice.signing_pubkey(), alice_id); assert!(!invoice.payment_paths().is_empty()); @@ -540,6 +560,8 @@ fn creates_and_pays_for_offer_using_one_hop_blinded_path() { bob.onion_messenger.handle_onion_message(&alice_id, &onion_message); let invoice = extract_invoice(bob, &onion_message); + expect_invoice_generated_event(alice, &invoice); + assert_eq!(invoice.amount_msats(), 10_000_000); assert_ne!(invoice.signing_pubkey(), alice_id); assert!(!invoice.payment_paths().is_empty()); @@ -644,6 +666,8 @@ fn pays_for_offer_without_blinded_paths() { bob.onion_messenger.handle_onion_message(&alice_id, &onion_message); let invoice = extract_invoice(bob, &onion_message); + expect_invoice_generated_event(alice, &invoice); + route_bolt12_payment(bob, &[alice], &invoice); expect_recent_payment!(bob, RecentPaymentDetails::Pending, payment_id); From 35c408fca8c2e6e8d13d77a549b2c2041ce4041e Mon Sep 17 00:00:00 2001 From: Jeffrey Czyz Date: Mon, 11 Mar 2024 19:00:40 -0500 Subject: [PATCH 5/5] Return the invoice when requesting a refund When sending an invoice for a refund, information from the invoice may be useful for caller. For instance, the payment_hash can be used to track whether the refund was paid. Return the invoice to facilitate this use case. --- lightning/src/events/mod.rs | 8 ++++++-- lightning/src/ln/channelmanager.rs | 10 ++++++---- lightning/src/ln/offers_tests.rs | 12 +++++++++--- 3 files changed, 21 insertions(+), 9 deletions(-) diff --git a/lightning/src/events/mod.rs b/lightning/src/events/mod.rs index 10f1cb2e55a..d0a8ee812f5 100644 --- a/lightning/src/events/mod.rs +++ b/lightning/src/events/mod.rs @@ -584,7 +584,9 @@ pub enum Event { payment_id: PaymentId, }, /// Indicates that a [`Bolt12Invoice`] was generated in response to an [`InvoiceRequest`] and is - /// being prepared to be sent via an [`OnionMessage`]. + /// being prepared to be sent via an [`OnionMessage`]. The event is provided only for invoices + /// corresponding to an [`Offer`], not for a [`Refund`]. For the latter, the invoice is returned + /// by [`ChannelManager::request_refund_payment`]. /// /// Note that this doesn't necessarily mean that the invoice was sent and -- once sent -- it may /// never reach its destination because of the unreliable nature of onion messages. Any of the @@ -606,10 +608,12 @@ pub enum Event { /// /// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest /// [`OnionMessage`]: crate::ln::msgs::OnionMessage + /// [`Offer`]: crate::offers::offer::Offer + /// [`Refund`]: crate::offers::refund::Refund + /// [`ChannelManager::request_refund_payment`]: crate::ln::channelmanager::ChannelManager::request_refund_payment /// [`PeerManager`]: crate::ln::peer_handler::PeerManager /// [`MessageRouter`]: crate::onion_message::messenger::MessageRouter /// [`OnionMessagePath`]: crate::onion_message::messenger::OnionMessagePath - /// [`Offer`]: crate::offers::offer::Offer InvoiceGenerated { /// An invoice that was generated in response to an [`InvoiceRequest`]. /// diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 0dbefa45451..6932a2eb1aa 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -7906,7 +7906,7 @@ where /// /// The resulting invoice uses a [`PaymentHash`] recognized by the [`ChannelManager`] and a /// [`BlindedPath`] containing the [`PaymentSecret`] needed to reconstruct the corresponding - /// [`PaymentPreimage`]. + /// [`PaymentPreimage`]. It is returned purely for informational purposes. /// /// # Limitations /// @@ -7921,7 +7921,9 @@ where /// path for the invoice. /// /// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice - pub fn request_refund_payment(&self, refund: &Refund) -> Result<(), Bolt12SemanticError> { + pub fn request_refund_payment( + &self, refund: &Refund + ) -> Result { let expanded_key = &self.inbound_payment_key; let entropy = &*self.entropy_source; let secp_ctx = &self.secp_ctx; @@ -7954,7 +7956,7 @@ where let mut pending_offers_messages = self.pending_offers_messages.lock().unwrap(); if refund.paths().is_empty() { let message = new_pending_onion_message( - OffersMessage::Invoice(invoice), + OffersMessage::Invoice(invoice.clone()), Destination::Node(refund.payer_id()), Some(reply_path), ); @@ -7970,7 +7972,7 @@ where } } - Ok(()) + Ok(invoice) }, Err(()) => Err(Bolt12SemanticError::InvalidAmount), } diff --git a/lightning/src/ln/offers_tests.rs b/lightning/src/ln/offers_tests.rs index b61e80c35ff..5143ac60e52 100644 --- a/lightning/src/ln/offers_tests.rs +++ b/lightning/src/ln/offers_tests.rs @@ -492,7 +492,7 @@ fn creates_and_pays_for_refund_using_two_hop_blinded_path() { } expect_recent_payment!(david, RecentPaymentDetails::AwaitingInvoice, payment_id); - alice.node.request_refund_payment(&refund).unwrap(); + let expected_invoice = alice.node.request_refund_payment(&refund).unwrap(); connect_peers(alice, charlie); @@ -503,6 +503,8 @@ fn creates_and_pays_for_refund_using_two_hop_blinded_path() { david.onion_messenger.handle_onion_message(&charlie_id, &onion_message); let invoice = extract_invoice(david, &onion_message); + assert_eq!(invoice, expected_invoice); + assert_eq!(invoice.amount_msats(), 10_000_000); assert_ne!(invoice.signing_pubkey(), alice_id); assert!(!invoice.payment_paths().is_empty()); @@ -610,12 +612,14 @@ fn creates_and_pays_for_refund_using_one_hop_blinded_path() { } expect_recent_payment!(bob, RecentPaymentDetails::AwaitingInvoice, payment_id); - alice.node.request_refund_payment(&refund).unwrap(); + let expected_invoice = alice.node.request_refund_payment(&refund).unwrap(); let onion_message = alice.onion_messenger.next_onion_message_for_peer(bob_id).unwrap(); bob.onion_messenger.handle_onion_message(&alice_id, &onion_message); let invoice = extract_invoice(bob, &onion_message); + assert_eq!(invoice, expected_invoice); + assert_eq!(invoice.amount_msats(), 10_000_000); assert_ne!(invoice.signing_pubkey(), alice_id); assert!(!invoice.payment_paths().is_empty()); @@ -704,12 +708,14 @@ fn pays_for_refund_without_blinded_paths() { assert!(refund.paths().is_empty()); expect_recent_payment!(bob, RecentPaymentDetails::AwaitingInvoice, payment_id); - alice.node.request_refund_payment(&refund).unwrap(); + let expected_invoice = alice.node.request_refund_payment(&refund).unwrap(); let onion_message = alice.onion_messenger.next_onion_message_for_peer(bob_id).unwrap(); bob.onion_messenger.handle_onion_message(&alice_id, &onion_message); let invoice = extract_invoice(bob, &onion_message); + assert_eq!(invoice, expected_invoice); + route_bolt12_payment(bob, &[alice], &invoice); expect_recent_payment!(bob, RecentPaymentDetails::Pending, payment_id);