OffersMessageHandler impl for ChannelManager
authorJeffrey Czyz <jkczyz@gmail.com>
Fri, 1 Sep 2023 19:04:27 +0000 (14:04 -0500)
committerJeffrey Czyz <jkczyz@gmail.com>
Fri, 20 Oct 2023 14:49:57 +0000 (09:49 -0500)
Define the BOLT 12 message flow in ChannelManager's
OffersMessageHandler implementation.
- An invoice_request message results in responding with an invoice
  message if it can be verified that the request is for a valid offer.
- An invoice is paid if it can be verified to have originated from a
  sent invoice_request or a refund.
- An invoice_error is sent in some failure cases.
- Initial messages enqueued for sending are released to OnionMessenger

lightning/src/ln/channelmanager.rs
lightning/src/offers/invoice.rs
lightning/src/offers/invoice_error.rs

index 7861078ff9522797cc54986174651aed3415c0fe..064f6a3529af349481e5d139cc045b21202c5447 100644 (file)
@@ -55,13 +55,15 @@ use crate::ln::onion_utils::HTLCFailReason;
 use crate::ln::msgs::{ChannelMessageHandler, DecodeError, LightningError};
 #[cfg(test)]
 use crate::ln::outbound_payment;
-use crate::ln::outbound_payment::{OutboundPayments, PaymentAttempts, PendingOutboundPayment, SendAlongPathArgs, StaleExpiration};
+use crate::ln::outbound_payment::{Bolt12PaymentError, OutboundPayments, PaymentAttempts, PendingOutboundPayment, SendAlongPathArgs, StaleExpiration};
 use crate::ln::wire::Encode;
-use crate::offers::invoice::{BlindedPayInfo, DEFAULT_RELATIVE_EXPIRY};
+use crate::offers::invoice::{BlindedPayInfo, Bolt12Invoice, DEFAULT_RELATIVE_EXPIRY, DerivedSigningPubkey, InvoiceBuilder};
+use crate::offers::invoice_error::InvoiceError;
+use crate::offers::merkle::SignError;
 use crate::offers::offer::{DerivedMetadata, Offer, OfferBuilder};
 use crate::offers::parse::Bolt12SemanticError;
 use crate::offers::refund::{Refund, RefundBuilder};
-use crate::onion_message::{Destination, OffersMessage, PendingOnionMessage};
+use crate::onion_message::{Destination, OffersMessage, OffersMessageHandler, PendingOnionMessage};
 use crate::sign::{EntropySource, KeysManager, NodeSigner, Recipient, SignerProvider, WriteableEcdsaChannelSigner};
 use crate::util::config::{UserConfig, ChannelConfig, ChannelConfigUpdate};
 use crate::util::wakers::{Future, Notifier};
@@ -3579,6 +3581,17 @@ where
                self.pending_outbound_payments.test_set_payment_metadata(payment_id, new_payment_metadata);
        }
 
+       pub(super) fn send_payment_for_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);
+               self.pending_outbound_payments
+                       .send_payment_for_bolt12_invoice(
+                               invoice, payment_id, &self.router, self.list_usable_channels(),
+                               || self.compute_inflight_htlcs(), &self.entropy_source, &self.node_signer,
+                               best_block_height, &self.logger, &self.pending_events,
+                               |args| self.send_payment_along_path(args)
+                       )
+       }
 
        /// Signals that no further attempts for the given payment should occur. Useful if you have a
        /// pending outbound payment with retries remaining, but wish to stop retrying the payment before
@@ -8809,6 +8822,127 @@ where
        }
 }
 
+impl<M: Deref, T: Deref, ES: Deref, NS: Deref, SP: Deref, F: Deref, R: Deref, L: Deref>
+OffersMessageHandler for ChannelManager<M, T, ES, NS, SP, F, R, L>
+where
+       M::Target: chain::Watch<<SP::Target as SignerProvider>::Signer>,
+       T::Target: BroadcasterInterface,
+       ES::Target: EntropySource,
+       NS::Target: NodeSigner,
+       SP::Target: SignerProvider,
+       F::Target: FeeEstimator,
+       R::Target: Router,
+       L::Target: Logger,
+{
+       fn handle_message(&self, message: OffersMessage) -> Option<OffersMessage> {
+               let secp_ctx = &self.secp_ctx;
+               let expanded_key = &self.inbound_payment_key;
+
+               match message {
+                       OffersMessage::InvoiceRequest(invoice_request) => {
+                               let amount_msats = match InvoiceBuilder::<DerivedSigningPubkey>::amount_msats(
+                                       &invoice_request
+                               ) {
+                                       Ok(amount_msats) => Some(amount_msats),
+                                       Err(error) => return Some(OffersMessage::InvoiceError(error.into())),
+                               };
+                               let invoice_request = match invoice_request.verify(expanded_key, secp_ctx) {
+                                       Ok(invoice_request) => invoice_request,
+                                       Err(()) => {
+                                               let error = Bolt12SemanticError::InvalidMetadata;
+                                               return Some(OffersMessage::InvoiceError(error.into()));
+                                       },
+                               };
+                               let relative_expiry = DEFAULT_RELATIVE_EXPIRY.as_secs() as u32;
+
+                               match self.create_inbound_payment(amount_msats, relative_expiry, None) {
+                                       Ok((payment_hash, payment_secret)) if invoice_request.keys.is_some() => {
+                                               let payment_paths = vec![
+                                                       self.create_one_hop_blinded_payment_path(payment_secret),
+                                               ];
+                                               #[cfg(not(feature = "no-std"))]
+                                               let builder = invoice_request.respond_using_derived_keys(
+                                                       payment_paths, payment_hash
+                                               );
+                                               #[cfg(feature = "no-std")]
+                                               let created_at = Duration::from_secs(
+                                                       self.highest_seen_timestamp.load(Ordering::Acquire) as u64
+                                               );
+                                               #[cfg(feature = "no-std")]
+                                               let builder = invoice_request.respond_using_derived_keys_no_std(
+                                                       payment_paths, payment_hash, created_at
+                                               );
+                                               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())),
+                                               }
+                                       },
+                                       Ok((payment_hash, payment_secret)) => {
+                                               let payment_paths = vec![
+                                                       self.create_one_hop_blinded_payment_path(payment_secret),
+                                               ];
+                                               #[cfg(not(feature = "no-std"))]
+                                               let builder = invoice_request.respond_with(payment_paths, payment_hash);
+                                               #[cfg(feature = "no-std")]
+                                               let created_at = Duration::from_secs(
+                                                       self.highest_seen_timestamp.load(Ordering::Acquire) as u64
+                                               );
+                                               #[cfg(feature = "no-std")]
+                                               let builder = invoice_request.respond_with_no_std(
+                                                       payment_paths, payment_hash, created_at
+                                               );
+                                               let response = builder.and_then(|builder| builder.allow_mpp().build())
+                                                       .map_err(|e| OffersMessage::InvoiceError(e.into()))
+                                                       .and_then(|invoice|
+                                                               match invoice.sign(|invoice| self.node_signer.sign_bolt12_invoice(invoice)) {
+                                                                       Ok(invoice) => Ok(OffersMessage::Invoice(invoice)),
+                                                                       Err(SignError::Signing(())) => Err(OffersMessage::InvoiceError(
+                                                                                       InvoiceError::from_str("Failed signing invoice")
+                                                                       )),
+                                                                       Err(SignError::Verification(_)) => Err(OffersMessage::InvoiceError(
+                                                                                       InvoiceError::from_str("Failed invoice signature verification")
+                                                                       )),
+                                                               });
+                                               match response {
+                                                       Ok(invoice) => Some(invoice),
+                                                       Err(error) => Some(error),
+                                               }
+                                       },
+                                       Err(()) => {
+                                               Some(OffersMessage::InvoiceError(Bolt12SemanticError::InvalidAmount.into()))
+                                       },
+                               }
+                       },
+                       OffersMessage::Invoice(invoice) => {
+                               match invoice.verify(expanded_key, secp_ctx) {
+                                       Err(()) => {
+                                               Some(OffersMessage::InvoiceError(InvoiceError::from_str("Unrecognized invoice")))
+                                       },
+                                       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_str(&format!("{:?}", e))))
+                                               } else {
+                                                       None
+                                               }
+                                       },
+                               }
+                       },
+                       OffersMessage::InvoiceError(invoice_error) => {
+                               log_trace!(self.logger, "Received invoice_error: {}", invoice_error);
+                               None
+                       },
+               }
+       }
+
+       fn release_pending_messages(&self) -> Vec<PendingOnionMessage<OffersMessage>> {
+               core::mem::take(&mut self.pending_offers_messages.lock().unwrap())
+       }
+}
+
 /// Fetches the set of [`NodeFeatures`] flags that are provided by or required by
 /// [`ChannelManager`].
 pub(crate) fn provided_node_features(config: &UserConfig) -> NodeFeatures {
index 1165cfd39a50dd7dcd9d01f883c62663d7f2e882..c6fd5d056491560b5b3f630564fe97af93bea372 100644 (file)
@@ -174,7 +174,7 @@ impl<'a> InvoiceBuilder<'a, ExplicitSigningPubkey> {
                invoice_request: &'a InvoiceRequest, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>,
                created_at: Duration, payment_hash: PaymentHash
        ) -> Result<Self, Bolt12SemanticError> {
-               let amount_msats = Self::check_amount_msats(invoice_request)?;
+               let amount_msats = Self::amount_msats(invoice_request)?;
                let signing_pubkey = invoice_request.contents.inner.offer.signing_pubkey();
                let contents = InvoiceContents::ForOffer {
                        invoice_request: invoice_request.contents.clone(),
@@ -207,7 +207,7 @@ impl<'a> InvoiceBuilder<'a, DerivedSigningPubkey> {
                invoice_request: &'a InvoiceRequest, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>,
                created_at: Duration, payment_hash: PaymentHash, keys: KeyPair
        ) -> Result<Self, Bolt12SemanticError> {
-               let amount_msats = Self::check_amount_msats(invoice_request)?;
+               let amount_msats = Self::amount_msats(invoice_request)?;
                let signing_pubkey = invoice_request.contents.inner.offer.signing_pubkey();
                let contents = InvoiceContents::ForOffer {
                        invoice_request: invoice_request.contents.clone(),
@@ -237,7 +237,9 @@ impl<'a> InvoiceBuilder<'a, DerivedSigningPubkey> {
 }
 
 impl<'a, S: SigningPubkeyStrategy> InvoiceBuilder<'a, S> {
-       fn check_amount_msats(invoice_request: &InvoiceRequest) -> Result<u64, Bolt12SemanticError> {
+       pub(crate) fn amount_msats(
+               invoice_request: &InvoiceRequest
+       ) -> Result<u64, Bolt12SemanticError> {
                match invoice_request.amount_msats() {
                        Some(amount_msats) => Ok(amount_msats),
                        None => match invoice_request.contents.inner.offer.amount() {
index f587b271d12d84e97031391044025f91df0dab65..441dae265cbb4fb9c9ed8d527e6b9b65c1e61ad1 100644 (file)
@@ -48,6 +48,16 @@ pub struct ErroneousField {
        pub suggested_value: Option<Vec<u8>>,
 }
 
+impl InvoiceError {
+       /// Creates an [`InvoiceError`] with the given message.
+       pub fn from_str(s: &str) -> Self {
+               Self {
+                       erroneous_field: None,
+                       message: UntrustedString(s.to_string()),
+               }
+       }
+}
+
 impl core::fmt::Display for InvoiceError {
        fn fmt(&self, f: &mut core::fmt::Formatter) -> Result<(), core::fmt::Error> {
                self.message.fmt(f)