diff --git a/lightning/src/events/mod.rs b/lightning/src/events/mod.rs index 485a23e0292..d0a8ee812f5 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,43 @@ 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`]. 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 + /// 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 + /// [`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 + 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 +1300,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 +1712,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 8dfd0c8fcaa..6932a2eb1aa 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}; @@ -7907,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 /// @@ -7922,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; @@ -7955,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), ); @@ -7971,7 +7972,7 @@ where } } - Ok(()) + Ok(invoice) }, Err(()) => Err(Bolt12SemanticError::InvalidAmount), } @@ -9451,7 +9452,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 +9461,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,47 +9472,50 @@ 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) => { + 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())), } }, 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) => { diff --git a/lightning/src/ln/offers_tests.rs b/lightning/src/ln/offers_tests.rs index e16c0ed515f..5143ac60e52 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()); @@ -472,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); @@ -483,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()); @@ -540,6 +562,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()); @@ -588,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()); @@ -644,6 +670,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); @@ -680,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); 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")] 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};