Merge pull request #3001 from optout21/splicing-feature-bit-with-any
authorJeffrey Czyz <jkczyz@gmail.com>
Thu, 18 Apr 2024 19:24:00 +0000 (14:24 -0500)
committerGitHub <noreply@github.com>
Thu, 18 Apr 2024 19:24:00 +0000 (14:24 -0500)
Add splicing feature flag

1  2 
lightning/src/ln/channelmanager.rs
lightning/src/ln/msgs.rs

index ba768009450acf9a02a403469371ee921cb6b56d,b6453099a582e9224bf9c44d36bd63d86a4c6e21..49c4950b6020a5c27769ab9009cf4832f3a6fff4
@@@ -32,7 -32,7 +32,7 @@@ use bitcoin::secp256k1::Secp256k1
  use bitcoin::{secp256k1, Sequence};
  
  use crate::blinded_path::{BlindedPath, NodeIdLookUp};
 -use crate::blinded_path::payment::{PaymentConstraints, ReceiveTlvs};
 +use crate::blinded_path::payment::{Bolt12OfferContext, Bolt12RefundContext, PaymentConstraints, PaymentContext, ReceiveTlvs};
  use crate::chain;
  use crate::chain::{Confirm, ChannelMonitorUpdateStatus, Watch, BestBlock};
  use crate::chain::chaininterface::{BroadcasterInterface, ConfirmationTarget, FeeEstimator, LowerBoundedFeeEstimator};
@@@ -61,6 -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};
@@@ -155,11 -156,6 +155,11 @@@ pub enum PendingHTLCRouting 
                /// [`Event::PaymentClaimable::onion_fields`] as
                /// [`RecipientOnionFields::payment_metadata`].
                payment_metadata: Option<Vec<u8>>,
 +              /// The context of the payment included by the recipient in a blinded path, or `None` if a
 +              /// blinded path was not used.
 +              ///
 +              /// Used in part to determine the [`events::PaymentPurpose`].
 +              payment_context: Option<PaymentContext>,
                /// CLTV expiry of the received HTLC.
                ///
                /// Used to track when we should expire pending HTLCs that go unclaimed.
@@@ -357,11 -353,6 +357,11 @@@ enum OnionPayload 
                /// This is only here for backwards-compatibility in serialization, in the future it can be
                /// removed, breaking clients running 0.0.106 and earlier.
                _legacy_hop_data: Option<msgs::FinalOnionHopData>,
 +              /// The context of the payment included by the recipient in a blinded path, or `None` if a
 +              /// blinded path was not used.
 +              ///
 +              /// Used in part to determine the [`events::PaymentPurpose`].
 +              payment_context: Option<PaymentContext>,
        },
        /// Contains the payer-provided preimage.
        Spontaneous(PaymentPreimage),
@@@ -927,9 -918,9 +927,9 @@@ impl <SP: Deref> PeerState<SP> where SP
                        match phase {
                                ChannelPhase::Funded(_) | ChannelPhase::UnfundedOutboundV1(_) => true,
                                ChannelPhase::UnfundedInboundV1(_) => false,
-                               #[cfg(dual_funding)]
+                               #[cfg(any(dual_funding, splicing))]
                                ChannelPhase::UnfundedOutboundV2(_) => true,
-                               #[cfg(dual_funding)]
+                               #[cfg(any(dual_funding, splicing))]
                                ChannelPhase::UnfundedInboundV2(_) => false,
                        }
                )
@@@ -1464,12 -1455,12 +1464,12 @@@ wher
  /// // On the event processing thread
  /// channel_manager.process_pending_events(&|event| match event {
  ///     Event::PaymentClaimable { payment_hash, purpose, .. } => match purpose {
 -///         PaymentPurpose::InvoicePayment { payment_preimage: Some(payment_preimage), .. } => {
 +///         PaymentPurpose::Bolt11InvoicePayment { payment_preimage: Some(payment_preimage), .. } => {
  ///             assert_eq!(payment_hash, known_payment_hash);
  ///             println!("Claiming payment {}", payment_hash);
  ///             channel_manager.claim_funds(payment_preimage);
  ///         },
 -///         PaymentPurpose::InvoicePayment { payment_preimage: None, .. } => {
 +///         PaymentPurpose::Bolt11InvoicePayment { payment_preimage: None, .. } => {
  ///             println!("Unknown payment hash: {}", payment_hash);
  ///         },
  ///         PaymentPurpose::SpontaneousPayment(payment_preimage) => {
  ///             println!("Claiming spontaneous payment {}", payment_hash);
  ///             channel_manager.claim_funds(payment_preimage);
  ///         },
 +///         // ...
 +/// #         _ => {},
  ///     },
  ///     Event::PaymentClaimed { payment_hash, amount_msat, .. } => {
  ///         assert_eq!(payment_hash, known_payment_hash);
  /// // On the event processing thread
  /// channel_manager.process_pending_events(&|event| match event {
  ///     Event::PaymentClaimable { payment_hash, purpose, .. } => match purpose {
 -///         PaymentPurpose::InvoicePayment { payment_preimage: Some(payment_preimage), .. } => {
 +///         PaymentPurpose::Bolt12OfferPayment { payment_preimage: Some(payment_preimage), .. } => {
  ///             println!("Claiming payment {}", payment_hash);
  ///             channel_manager.claim_funds(payment_preimage);
  ///         },
 -///         PaymentPurpose::InvoicePayment { payment_preimage: None, .. } => {
 +///         PaymentPurpose::Bolt12OfferPayment { payment_preimage: None, .. } => {
  ///             println!("Unknown payment hash: {}", payment_hash);
  ///         },
  ///         // ...
  /// #
  /// # fn example<T: AChannelManager>(channel_manager: T, refund: &Refund) {
  /// # let channel_manager = channel_manager.get_cm();
 -/// match channel_manager.request_refund_payment(refund) {
 -///     Ok(()) => println!("Requesting payment for refund"),
 -///     Err(e) => println!("Unable to request payment for refund: {:?}", e),
 -/// }
 +/// let known_payment_hash = match channel_manager.request_refund_payment(refund) {
 +///     Ok(invoice) => {
 +///         let payment_hash = invoice.payment_hash();
 +///         println!("Requesting refund payment {}", payment_hash);
 +///         payment_hash
 +///     },
 +///     Err(e) => panic!("Unable to request payment for refund: {:?}", e),
 +/// };
  ///
  /// // On the event processing thread
  /// channel_manager.process_pending_events(&|event| match event {
  ///     Event::PaymentClaimable { payment_hash, purpose, .. } => match purpose {
 -///           PaymentPurpose::InvoicePayment { payment_preimage: Some(payment_preimage), .. } => {
 +///           PaymentPurpose::Bolt12RefundPayment { payment_preimage: Some(payment_preimage), .. } => {
 +///             assert_eq!(payment_hash, known_payment_hash);
  ///             println!("Claiming payment {}", payment_hash);
  ///             channel_manager.claim_funds(payment_preimage);
  ///         },
 -///           PaymentPurpose::InvoicePayment { payment_preimage: None, .. } => {
 +///           PaymentPurpose::Bolt12RefundPayment { payment_preimage: None, .. } => {
  ///             println!("Unknown payment hash: {}", payment_hash);
  ///           },
  ///         // ...
  /// #         _ => {},
  ///     },
  ///     Event::PaymentClaimed { payment_hash, amount_msat, .. } => {
 +///         assert_eq!(payment_hash, known_payment_hash);
  ///         println!("Claimed {} msats", amount_msat);
  ///     },
  ///     // ...
@@@ -2791,11 -2774,11 +2791,11 @@@ macro_rules! convert_chan_phase_err 
                        ChannelPhase::UnfundedInboundV1(channel) => {
                                convert_chan_phase_err!($self, $err, channel, $channel_id, UNFUNDED_CHANNEL)
                        },
-                       #[cfg(dual_funding)]
+                       #[cfg(any(dual_funding, splicing))]
                        ChannelPhase::UnfundedOutboundV2(channel) => {
                                convert_chan_phase_err!($self, $err, channel, $channel_id, UNFUNDED_CHANNEL)
                        },
-                       #[cfg(dual_funding)]
+                       #[cfg(any(dual_funding, splicing))]
                        ChannelPhase::UnfundedInboundV2(channel) => {
                                convert_chan_phase_err!($self, $err, channel, $channel_id, UNFUNDED_CHANNEL)
                        },
@@@ -3670,8 -3653,8 +3670,8 @@@ wher
                                                // Unfunded channel has no update
                                                (None, chan_phase.context().get_counterparty_node_id())
                                        },
-                                       // TODO(dual_funding): Combine this match arm with above once #[cfg(dual_funding)] is removed.
-                                       #[cfg(dual_funding)]
+                                       // TODO(dual_funding): Combine this match arm with above once #[cfg(any(dual_funding, splicing))] is removed.
+                                       #[cfg(any(dual_funding, splicing))]
                                        ChannelPhase::UnfundedOutboundV2(_) | ChannelPhase::UnfundedInboundV2(_) => {
                                                self.finish_close_channel(chan_phase.context_mut().force_shutdown(false, closure_reason));
                                                // Unfunded channel has no update
                                                                let blinded_failure = routing.blinded_failure();
                                                                let (cltv_expiry, onion_payload, payment_data, phantom_shared_secret, mut onion_fields) = match routing {
                                                                        PendingHTLCRouting::Receive {
 -                                                                              payment_data, payment_metadata, incoming_cltv_expiry, phantom_shared_secret,
 -                                                                              custom_tlvs, requires_blinded_error: _
 +                                                                              payment_data, payment_metadata, payment_context,
 +                                                                              incoming_cltv_expiry, phantom_shared_secret, custom_tlvs,
 +                                                                              requires_blinded_error: _
                                                                        } => {
                                                                                let _legacy_hop_data = Some(payment_data.clone());
                                                                                let onion_fields = RecipientOnionFields { payment_secret: Some(payment_data.payment_secret),
                                                                                                payment_metadata, custom_tlvs };
 -                                                                              (incoming_cltv_expiry, OnionPayload::Invoice { _legacy_hop_data },
 +                                                                              (incoming_cltv_expiry, OnionPayload::Invoice { _legacy_hop_data, payment_context },
                                                                                        Some(payment_data), phantom_shared_secret, onion_fields)
                                                                        },
                                                                        PendingHTLCRouting::ReceiveKeysend {
                                                                macro_rules! check_total_value {
                                                                        ($purpose: expr) => {{
                                                                                let mut payment_claimable_generated = false;
 -                                                                              let is_keysend = match $purpose {
 -                                                                                      events::PaymentPurpose::SpontaneousPayment(_) => true,
 -                                                                                      events::PaymentPurpose::InvoicePayment { .. } => false,
 -                                                                              };
 +                                                                              let is_keysend = $purpose.is_keysend();
                                                                                let mut claimable_payments = self.claimable_payments.lock().unwrap();
                                                                                if claimable_payments.pending_claiming_payments.contains_key(&payment_hash) {
                                                                                        fail_htlc!(claimable_htlc, payment_hash);
                                                                match payment_secrets.entry(payment_hash) {
                                                                        hash_map::Entry::Vacant(_) => {
                                                                                match claimable_htlc.onion_payload {
 -                                                                                      OnionPayload::Invoice { .. } => {
 +                                                                                      OnionPayload::Invoice { ref payment_context, .. } => {
                                                                                                let payment_data = payment_data.unwrap();
                                                                                                let (payment_preimage, min_final_cltv_expiry_delta) = match inbound_payment::verify(payment_hash, &payment_data, self.highest_seen_timestamp.load(Ordering::Acquire) as u64, &self.inbound_payment_key, &self.logger) {
                                                                                                        Ok(result) => result,
                                                                                                                fail_htlc!(claimable_htlc, payment_hash);
                                                                                                        }
                                                                                                }
 -                                                                                              let purpose = events::PaymentPurpose::InvoicePayment {
 -                                                                                                      payment_preimage: payment_preimage.clone(),
 -                                                                                                      payment_secret: payment_data.payment_secret,
 -                                                                                              };
 +                                                                                              let purpose = events::PaymentPurpose::from_parts(
 +                                                                                                      payment_preimage.clone(),
 +                                                                                                      payment_data.payment_secret,
 +                                                                                                      payment_context.clone(),
 +                                                                                              );
                                                                                                check_total_value!(purpose);
                                                                                        },
                                                                                        OnionPayload::Spontaneous(preimage) => {
                                                                                }
                                                                        },
                                                                        hash_map::Entry::Occupied(inbound_payment) => {
 -                                                                              if let OnionPayload::Spontaneous(_) = claimable_htlc.onion_payload {
 -                                                                                      log_trace!(self.logger, "Failing new keysend HTLC with payment_hash {} because we already have an inbound payment with the same payment hash", &payment_hash);
 -                                                                                      fail_htlc!(claimable_htlc, payment_hash);
 -                                                                              }
 +                                                                              let payment_context = match claimable_htlc.onion_payload {
 +                                                                                      OnionPayload::Spontaneous(_) => {
 +                                                                                              log_trace!(self.logger, "Failing new keysend HTLC with payment_hash {} because we already have an inbound payment with the same payment hash", &payment_hash);
 +                                                                                              fail_htlc!(claimable_htlc, payment_hash);
 +                                                                                      },
 +                                                                                      OnionPayload::Invoice { ref payment_context, .. } => payment_context,
 +                                                                              };
                                                                                let payment_data = payment_data.unwrap();
                                                                                if inbound_payment.get().payment_secret != payment_data.payment_secret {
                                                                                        log_trace!(self.logger, "Failing new HTLC with payment_hash {} as it didn't match our expected payment secret.", &payment_hash);
                                                                                                &payment_hash, payment_data.total_msat, inbound_payment.get().min_value_msat.unwrap());
                                                                                        fail_htlc!(claimable_htlc, payment_hash);
                                                                                } else {
 -                                                                                      let purpose = events::PaymentPurpose::InvoicePayment {
 -                                                                                              payment_preimage: inbound_payment.get().payment_preimage,
 -                                                                                              payment_secret: payment_data.payment_secret,
 -                                                                                      };
 +                                                                                      let purpose = events::PaymentPurpose::from_parts(
 +                                                                                              inbound_payment.get().payment_preimage,
 +                                                                                              payment_data.payment_secret,
 +                                                                                              payment_context.clone(),
 +                                                                                      );
                                                                                        let payment_claimable_generated = check_total_value!(purpose);
                                                                                        if payment_claimable_generated {
                                                                                                inbound_payment.remove_entry();
                                                                process_unfunded_channel_tick(chan_id, &mut chan.context, &mut chan.unfunded_context,
                                                                        pending_msg_events, counterparty_node_id)
                                                        },
-                                                       #[cfg(dual_funding)]
+                                                       #[cfg(any(dual_funding, splicing))]
                                                        ChannelPhase::UnfundedInboundV2(chan) => {
                                                                process_unfunded_channel_tick(chan_id, &mut chan.context, &mut chan.unfunded_context,
                                                                        pending_msg_events, counterparty_node_id)
                                                        },
-                                                       #[cfg(dual_funding)]
+                                                       #[cfg(any(dual_funding, splicing))]
                                                        ChannelPhase::UnfundedOutboundV2(chan) => {
                                                                process_unfunded_channel_tick(chan_id, &mut chan.context, &mut chan.unfunded_context,
                                                                        pending_msg_events, counterparty_node_id)
                                                num_unfunded_channels += 1;
                                        }
                                },
-                               // TODO(dual_funding): Combine this match arm with above once #[cfg(dual_funding)] is removed.
-                               #[cfg(dual_funding)]
+                               // TODO(dual_funding): Combine this match arm with above once #[cfg(any(dual_funding, splicing))] is removed.
+                               #[cfg(any(dual_funding, splicing))]
                                ChannelPhase::UnfundedInboundV2(chan) => {
                                        // Only inbound V2 channels that are not 0conf and that we do not contribute to will be
                                        // included in the unfunded count.
                                        // Outbound channels don't contribute to the unfunded count in the DoS context.
                                        continue;
                                },
-                               // TODO(dual_funding): Combine this match arm with above once #[cfg(dual_funding)] is removed.
-                               #[cfg(dual_funding)]
+                               // TODO(dual_funding): Combine this match arm with above once #[cfg(any(dual_funding, splicing))] is removed.
+                               #[cfg(any(dual_funding, splicing))]
                                ChannelPhase::UnfundedOutboundV2(_) => {
                                        // Outbound channels don't contribute to the unfunded count in the DoS context.
                                        continue;
                                                finish_shutdown = Some(chan.context_mut().force_shutdown(false, ClosureReason::CounterpartyCoopClosedUnfundedChannel));
                                        },
                                        // TODO(dual_funding): Combine this match arm with above.
-                                       #[cfg(dual_funding)]
+                                       #[cfg(any(dual_funding, splicing))]
                                        ChannelPhase::UnfundedInboundV2(_) | ChannelPhase::UnfundedOutboundV2(_) => {
                                                let context = phase.context_mut();
                                                log_error!(self.logger, "Immediately closing unfunded channel {} as peer asked to cooperatively shut it down (which is unnecessary)", &msg.channel_id);
@@@ -8795,7 -8775,7 +8795,7 @@@ wher
        ///
        /// 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
        ///
        ///   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<Bolt12Invoice, Bolt12SemanticError> {
                let expanded_key = &self.inbound_payment_key;
                let entropy = &*self.entropy_source;
                let secp_ctx = &self.secp_ctx;
  
                match self.create_inbound_payment(Some(amount_msats), relative_expiry, None) {
                        Ok((payment_hash, payment_secret)) => {
 -                              let payment_paths = self.create_blinded_payment_paths(amount_msats, payment_secret)
 +                              let payment_context = PaymentContext::Bolt12Refund(Bolt12RefundContext {});
 +                              let payment_paths = self.create_blinded_payment_paths(
 +                                      amount_msats, payment_secret, payment_context
 +                              )
                                        .map_err(|_| Bolt12SemanticError::MissingPaths)?;
  
                                #[cfg(feature = "std")]
                                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),
                                        );
                                        }
                                }
  
 -                              Ok(())
 +                              Ok(invoice)
                        },
                        Err(()) => Err(Bolt12SemanticError::InvalidAmount),
                }
        /// This differs from [`create_inbound_payment_for_hash`] only in that it generates the
        /// [`PaymentHash`] and [`PaymentPreimage`] for you.
        ///
 -      /// The [`PaymentPreimage`] will ultimately be returned to you in the [`PaymentClaimable`], which
 -      /// will have the [`PaymentClaimable::purpose`] be [`PaymentPurpose::InvoicePayment`] with
 -      /// its [`PaymentPurpose::InvoicePayment::payment_preimage`] field filled in. That should then be
 -      /// passed directly to [`claim_funds`].
 +      /// The [`PaymentPreimage`] will ultimately be returned to you in the [`PaymentClaimable`] event, which
 +      /// will have the [`PaymentClaimable::purpose`] return `Some` for [`PaymentPurpose::preimage`]. That
 +      /// should then be passed directly to [`claim_funds`].
        ///
        /// See [`create_inbound_payment_for_hash`] for detailed documentation on behavior and requirements.
        ///
        /// [`claim_funds`]: Self::claim_funds
        /// [`PaymentClaimable`]: events::Event::PaymentClaimable
        /// [`PaymentClaimable::purpose`]: events::Event::PaymentClaimable::purpose
 -      /// [`PaymentPurpose::InvoicePayment`]: events::PaymentPurpose::InvoicePayment
 -      /// [`PaymentPurpose::InvoicePayment::payment_preimage`]: events::PaymentPurpose::InvoicePayment::payment_preimage
 +      /// [`PaymentPurpose::preimage`]: events::PaymentPurpose::preimage
        /// [`create_inbound_payment_for_hash`]: Self::create_inbound_payment_for_hash
        pub fn create_inbound_payment(&self, min_value_msat: Option<u64>, invoice_expiry_delta_secs: u32,
                min_final_cltv_expiry_delta: Option<u16>) -> Result<(PaymentHash, PaymentSecret), ()> {
        /// Creates multi-hop blinded payment paths for the given `amount_msats` by delegating to
        /// [`Router::create_blinded_payment_paths`].
        fn create_blinded_payment_paths(
 -              &self, amount_msats: u64, payment_secret: PaymentSecret
 +              &self, amount_msats: u64, payment_secret: PaymentSecret, payment_context: PaymentContext
        ) -> Result<Vec<(BlindedPayInfo, BlindedPath)>, ()> {
                let secp_ctx = &self.secp_ctx;
  
                                max_cltv_expiry,
                                htlc_minimum_msat: 1,
                        },
 +                      payment_context,
                };
                self.router.create_blinded_payment_paths(
                        payee_node_id, first_hops, payee_tlvs, amount_msats, secp_ctx
@@@ -9474,7 -9450,7 +9474,7 @@@ wher
                                                // Retain unfunded channels.
                                                ChannelPhase::UnfundedOutboundV1(_) | ChannelPhase::UnfundedInboundV1(_) => true,
                                                // TODO(dual_funding): Combine this match arm with above.
-                                               #[cfg(dual_funding)]
+                                               #[cfg(any(dual_funding, splicing))]
                                                ChannelPhase::UnfundedOutboundV2(_) | ChannelPhase::UnfundedInboundV2(_) => true,
                                                ChannelPhase::Funded(channel) => {
                                                        let res = f(channel);
@@@ -9780,21 -9756,21 +9780,21 @@@ wher
                         msg.channel_id.clone())), *counterparty_node_id);
        }
  
-       #[cfg(dual_funding)]
+       #[cfg(splicing)]
        fn handle_splice(&self, counterparty_node_id: &PublicKey, msg: &msgs::Splice) {
                let _: Result<(), _> = handle_error!(self, Err(MsgHandleErrInternal::send_err_msg_no_close(
                        "Splicing not supported".to_owned(),
                         msg.channel_id.clone())), *counterparty_node_id);
        }
  
-       #[cfg(dual_funding)]
+       #[cfg(splicing)]
        fn handle_splice_ack(&self, counterparty_node_id: &PublicKey, msg: &msgs::SpliceAck) {
                let _: Result<(), _> = handle_error!(self, Err(MsgHandleErrInternal::send_err_msg_no_close(
                        "Splicing not supported (splice_ack)".to_owned(),
                         msg.channel_id.clone())), *counterparty_node_id);
        }
  
-       #[cfg(dual_funding)]
+       #[cfg(splicing)]
        fn handle_splice_locked(&self, counterparty_node_id: &PublicKey, msg: &msgs::SpliceLocked) {
                let _: Result<(), _> = handle_error!(self, Err(MsgHandleErrInternal::send_err_msg_no_close(
                        "Splicing not supported (splice_locked)".to_owned(),
                                                ChannelPhase::UnfundedInboundV1(chan) => {
                                                        &mut chan.context
                                                },
-                                               #[cfg(dual_funding)]
+                                               #[cfg(any(dual_funding, splicing))]
                                                ChannelPhase::UnfundedOutboundV2(chan) => {
                                                        &mut chan.context
                                                },
-                                               #[cfg(dual_funding)]
+                                               #[cfg(any(dual_funding, splicing))]
                                                ChannelPhase::UnfundedInboundV2(chan) => {
                                                        &mut chan.context
                                                },
                                                        });
                                                }
  
-                                               // TODO(dual_funding): Combine this match arm with above once #[cfg(dual_funding)] is removed.
-                                               #[cfg(dual_funding)]
+                                               // TODO(dual_funding): Combine this match arm with above once #[cfg(any(dual_funding, splicing))] is removed.
+                                               #[cfg(any(dual_funding, splicing))]
                                                ChannelPhase::UnfundedOutboundV2(chan) => {
                                                        pending_msg_events.push(events::MessageSendEvent::SendOpenChannelV2 {
                                                                node_id: chan.context.get_counterparty_node_id(),
                                                        debug_assert!(false);
                                                }
  
-                                               // TODO(dual_funding): Combine this match arm with above once #[cfg(dual_funding)] is removed.
-                                               #[cfg(dual_funding)]
+                                               // TODO(dual_funding): Combine this match arm with above once #[cfg(any(dual_funding, splicing))] is removed.
+                                               #[cfg(any(dual_funding, splicing))]
                                                ChannelPhase::UnfundedInboundV2(channel) => {
                                                        // Since unfunded inbound channel maps are cleared upon disconnecting a peer,
                                                        // they are not persisted and won't be recovered after a crash.
                                                        return;
                                                }
                                        },
-                                       #[cfg(dual_funding)]
+                                       #[cfg(any(dual_funding, splicing))]
                                        Some(ChannelPhase::UnfundedOutboundV2(ref mut chan)) => {
                                                if let Ok(msg) = chan.maybe_handle_error_without_close(self.chain_hash, &self.fee_estimator) {
                                                        peer_state.pending_msg_events.push(events::MessageSendEvent::SendOpenChannelV2 {
                                                }
                                        },
                                        None | Some(ChannelPhase::UnfundedInboundV1(_) | ChannelPhase::Funded(_)) => (),
-                                       #[cfg(dual_funding)]
+                                       #[cfg(any(dual_funding, splicing))]
                                        Some(ChannelPhase::UnfundedInboundV2(_)) => (),
                                }
                        }
                                        },
                                };
  
 +                              let payment_context = PaymentContext::Bolt12Offer(Bolt12OfferContext {
 +                                      offer_id: invoice_request.offer_id,
 +                                      invoice_request: invoice_request.fields(),
 +                              });
                                let payment_paths = match self.create_blinded_payment_paths(
 -                                      amount_msats, payment_secret
 +                                      amount_msats, payment_secret, payment_context
                                ) {
                                        Ok(payment_paths) => payment_paths,
                                        Err(()) => {
                                        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
                                        let builder = invoice_request.respond_using_derived_keys_no_std(
                                                payment_paths, payment_hash, created_at
                                        );
 -                                      let builder: Result<InvoiceBuilder<DerivedSigningPubkey>, _> =
 -                                              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::<DerivedSigningPubkey>::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);
                                        let builder = invoice_request.respond_with_no_std(
                                                payment_paths, payment_hash, created_at
                                        );
 -                                      let builder: Result<InvoiceBuilder<ExplicitSigningPubkey>, _> =
 -                                              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::<ExplicitSigningPubkey>::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) => {
 -                              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) => {
@@@ -10700,7 -10675,6 +10700,7 @@@ impl_writeable_tlv_based_enum!(PendingH
                (3, payment_metadata, option),
                (5, custom_tlvs, optional_vec),
                (7, requires_blinded_error, (default_value, false)),
 +              (9, payment_context, option),
        },
        (2, ReceiveKeysend) => {
                (0, payment_preimage, required),
@@@ -10815,11 -10789,9 +10815,11 @@@ impl_writeable_tlv_based!(HTLCPreviousH
  
  impl Writeable for ClaimableHTLC {
        fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
 -              let (payment_data, keysend_preimage) = match &self.onion_payload {
 -                      OnionPayload::Invoice { _legacy_hop_data } => (_legacy_hop_data.as_ref(), None),
 -                      OnionPayload::Spontaneous(preimage) => (None, Some(preimage)),
 +              let (payment_data, keysend_preimage, payment_context) = match &self.onion_payload {
 +                      OnionPayload::Invoice { _legacy_hop_data, payment_context } => {
 +                              (_legacy_hop_data.as_ref(), None, payment_context.as_ref())
 +                      },
 +                      OnionPayload::Spontaneous(preimage) => (None, Some(preimage), None),
                };
                write_tlv_fields!(writer, {
                        (0, self.prev_hop, required),
                        (6, self.cltv_expiry, required),
                        (8, keysend_preimage, option),
                        (10, self.counterparty_skimmed_fee_msat, option),
 +                      (11, payment_context, option),
                });
                Ok(())
        }
@@@ -10849,7 -10820,6 +10849,7 @@@ impl Readable for ClaimableHTLC 
                        (6, cltv_expiry, required),
                        (8, keysend_preimage, option),
                        (10, counterparty_skimmed_fee_msat, option),
 +                      (11, payment_context, option),
                });
                let payment_data: Option<msgs::FinalOnionHopData> = payment_data_opt;
                let value = value_ser.0.unwrap();
                                        }
                                        total_msat = Some(payment_data.as_ref().unwrap().total_msat);
                                }
 -                              OnionPayload::Invoice { _legacy_hop_data: payment_data }
 +                              OnionPayload::Invoice { _legacy_hop_data: payment_data, payment_context }
                        },
                };
                Ok(Self {
@@@ -12107,9 -12077,9 +12107,9 @@@ wher
                                        return Err(DecodeError::InvalidValue);
                                }
                                let purpose = match &htlcs[0].onion_payload {
 -                                      OnionPayload::Invoice { _legacy_hop_data } => {
 +                                      OnionPayload::Invoice { _legacy_hop_data, payment_context: _ } => {
                                                if let Some(hop_data) = _legacy_hop_data {
 -                                                      events::PaymentPurpose::InvoicePayment {
 +                                                      events::PaymentPurpose::Bolt11InvoicePayment {
                                                                payment_preimage: match pending_inbound_payments.get(&payment_hash) {
                                                                        Some(inbound_payment) => inbound_payment.payment_preimage,
                                                                        None => match inbound_payment::verify(payment_hash, &hop_data, 0, &expanded_inbound_key, &args.logger) {
diff --combined lightning/src/ln/msgs.rs
index 39e23ca4ec1b61c032514a7b82bc3649f2db700d,d42807842f2957361f8c9da08cba87d7a28aaef6..136ed4d317b35bfdc43618a0109e7c3ed1a729a1
@@@ -1462,13 -1462,13 +1462,13 @@@ pub trait ChannelMessageHandler : Messa
  
        // Splicing
        /// Handle an incoming `splice` message from the given peer.
-       #[cfg(dual_funding)]
+       #[cfg(splicing)]
        fn handle_splice(&self, their_node_id: &PublicKey, msg: &Splice);
        /// Handle an incoming `splice_ack` message from the given peer.
-       #[cfg(dual_funding)]
+       #[cfg(splicing)]
        fn handle_splice_ack(&self, their_node_id: &PublicKey, msg: &SpliceAck);
        /// Handle an incoming `splice_locked` message from the given peer.
-       #[cfg(dual_funding)]
+       #[cfg(splicing)]
        fn handle_splice_locked(&self, their_node_id: &PublicKey, msg: &SpliceLocked);
  
        // Interactive channel construction
@@@ -1677,7 -1677,7 +1677,7 @@@ pub struct FinalOnionHopData 
  
  mod fuzzy_internal_msgs {
        use bitcoin::secp256k1::PublicKey;
 -      use crate::blinded_path::payment::{PaymentConstraints, PaymentRelay};
 +      use crate::blinded_path::payment::{PaymentConstraints, PaymentContext, PaymentRelay};
        use crate::ln::{PaymentPreimage, PaymentSecret};
        use crate::ln::features::BlindedHopFeatures;
        use super::{FinalOnionHopData, TrampolineOnionPacket};
                        cltv_expiry_height: u32,
                        payment_secret: PaymentSecret,
                        payment_constraints: PaymentConstraints,
 +                      payment_context: PaymentContext,
                        intro_node_blinding_point: Option<PublicKey>,
                        keysend_preimage: Option<PaymentPreimage>,
                        custom_tlvs: Vec<(u64, Vec<u8>)>,
@@@ -2721,7 -2720,7 +2721,7 @@@ impl<NS: Deref> ReadableArgs<(Option<Pu
                                        })
                                },
                                ChaChaPolyReadAdapter { readable: BlindedPaymentTlvs::Receive(ReceiveTlvs {
 -                                      payment_secret, payment_constraints
 +                                      payment_secret, payment_constraints, payment_context
                                })} => {
                                        if total_msat.unwrap_or(0) > MAX_VALUE_MSAT { return Err(DecodeError::InvalidValue) }
                                        Ok(Self::BlindedReceive {
                                                cltv_expiry_height: cltv_value.ok_or(DecodeError::InvalidValue)?,
                                                payment_secret,
                                                payment_constraints,
 +                                              payment_context,
                                                intro_node_blinding_point,
                                                keysend_preimage,
                                                custom_tlvs,