Merge pull request #2361 from arik-so/2023-06-anchor-channel-type-features
authorMatt Corallo <649246+TheBlueMatt@users.noreply.github.com>
Fri, 23 Jun 2023 19:12:15 +0000 (19:12 +0000)
committerGitHub <noreply@github.com>
Fri, 23 Jun 2023 19:12:15 +0000 (19:12 +0000)
Replace `opt_anchors` with `ChannelTypeFeatures`

20 files changed:
fuzz/src/invoice_request_deser.rs
fuzz/src/refund_deser.rs
lightning/src/chain/chainmonitor.rs
lightning/src/events/mod.rs
lightning/src/lib.rs
lightning/src/ln/channel.rs
lightning/src/ln/channelmanager.rs
lightning/src/ln/functional_test_utils.rs
lightning/src/ln/functional_tests.rs
lightning/src/ln/msgs.rs
lightning/src/ln/payment_tests.rs
lightning/src/offers/invoice.rs
lightning/src/offers/invoice_request.rs
lightning/src/offers/refund.rs
lightning/src/offers/test_utils.rs
lightning/src/routing/router.rs
lightning/src/util/config.rs
lightning/src/util/mod.rs
lightning/src/util/test_utils.rs
pending_changelog/forward-underpaying-htlc.txt [new file with mode: 0644]

index 394d57fcebc04c79d76a3894fda2ea7a8e5308e3..f8fe34f2b8b5d647140af18a53558f7ba07b6fb2 100644 (file)
@@ -97,7 +97,7 @@ fn build_response<'a, T: secp256k1::Signing + secp256k1::Verification>(
                },
        ];
 
-       let payment_paths = paths.into_iter().zip(payinfo.into_iter()).collect();
+       let payment_paths = payinfo.into_iter().zip(paths.into_iter()).collect();
        let payment_hash = PaymentHash([42; 32]);
        invoice_request.respond_with(payment_paths, payment_hash)?.build()
 }
index 359bbcc739dcc8567b4bc3adb4f05f72679dde52..618b129f9a1e5517fa5dcddb09f739308feee436 100644 (file)
@@ -86,7 +86,7 @@ fn build_response<'a, T: secp256k1::Signing + secp256k1::Verification>(
                },
        ];
 
-       let payment_paths = paths.into_iter().zip(payinfo.into_iter()).collect();
+       let payment_paths = payinfo.into_iter().zip(paths.into_iter()).collect();
        let payment_hash = PaymentHash([42; 32]);
        refund.respond_with(payment_paths, payment_hash, signing_pubkey)?.build()
 }
index 261e5593b5b4600ad661142a4579308b68903316..8ce59597d846b960d5bb11d843ace21b20d0ee53 100644 (file)
@@ -502,7 +502,7 @@ where C::Target: chain::Filter,
                self.event_notifier.notify();
        }
 
-       #[cfg(any(test, fuzzing, feature = "_test_utils"))]
+       #[cfg(any(test, feature = "_test_utils"))]
        pub fn get_and_clear_pending_events(&self) -> Vec<events::Event> {
                use crate::events::EventsProvider;
                let events = core::cell::RefCell::new(Vec::new());
index fa4c86cd0ddea84b6970536455737517756edf52..f4c4af7e90ff02bf5cf59c22994a7728ded4fcf5 100644 (file)
@@ -385,8 +385,25 @@ pub enum Event {
                ///
                /// Payments received on LDK versions prior to 0.0.115 will have this field unset.
                onion_fields: Option<RecipientOnionFields>,
-               /// The value, in thousandths of a satoshi, that this payment is for.
+               /// The value, in thousandths of a satoshi, that this payment is claimable for. May be greater
+               /// than the invoice amount.
+               ///
+               /// May be less than the invoice amount if [`ChannelConfig::accept_underpaying_htlcs`] is set
+               /// and the previous hop took an extra fee.
+               ///
+               /// # Note
+               /// If [`ChannelConfig::accept_underpaying_htlcs`] is set and you claim without verifying this
+               /// field, you may lose money!
+               ///
+               /// [`ChannelConfig::accept_underpaying_htlcs`]: crate::util::config::ChannelConfig::accept_underpaying_htlcs
                amount_msat: u64,
+               /// The value, in thousands of a satoshi, that was skimmed off of this payment as an extra fee
+               /// taken by our channel counterparty.
+               ///
+               /// Will always be 0 unless [`ChannelConfig::accept_underpaying_htlcs`] is set.
+               ///
+               /// [`ChannelConfig::accept_underpaying_htlcs`]: crate::util::config::ChannelConfig::accept_underpaying_htlcs
+               counterparty_skimmed_fee_msat: u64,
                /// Information for claiming this received payment, based on whether the purpose of the
                /// payment is to pay an invoice or to send a spontaneous payment.
                purpose: PaymentPurpose,
@@ -428,7 +445,8 @@ pub enum Event {
                /// The payment hash of the claimed payment. Note that LDK will not stop you from
                /// registering duplicate payment hashes for inbound payments.
                payment_hash: PaymentHash,
-               /// The value, in thousandths of a satoshi, that this payment is for.
+               /// The value, in thousandths of a satoshi, that this payment is for. May be greater than the
+               /// invoice amount.
                amount_msat: u64,
                /// The purpose of the claimed payment, i.e. whether the payment was for an invoice or a
                /// spontaneous payment.
@@ -621,6 +639,7 @@ pub enum Event {
                inbound_amount_msat: u64,
                /// How many msats the payer intended to route to the next node. Depending on the reason you are
                /// intercepting this payment, you might take a fee by forwarding less than this amount.
+               /// Forwarding less than this amount may break compatibility with LDK versions prior to 0.0.116.
                ///
                /// Note that LDK will NOT check that expected fees were factored into this value. You MUST
                /// check that whatever fee you want has been included here or subtract it as required. Further,
@@ -830,8 +849,8 @@ impl Writeable for Event {
                                // We never write out FundingGenerationReady events as, upon disconnection, peers
                                // drop any channels which have not yet exchanged funding_signed.
                        },
-                       &Event::PaymentClaimable { ref payment_hash, ref amount_msat, ref purpose,
-                               ref receiver_node_id, ref via_channel_id, ref via_user_channel_id,
+                       &Event::PaymentClaimable { ref payment_hash, ref amount_msat, counterparty_skimmed_fee_msat,
+                               ref purpose, ref receiver_node_id, ref via_channel_id, ref via_user_channel_id,
                                ref claim_deadline, ref onion_fields
                        } => {
                                1u8.write(writer)?;
@@ -846,6 +865,8 @@ impl Writeable for Event {
                                                payment_preimage = Some(*preimage);
                                        }
                                }
+                               let skimmed_fee_opt = if counterparty_skimmed_fee_msat == 0 { None }
+                                       else { Some(counterparty_skimmed_fee_msat) };
                                write_tlv_fields!(writer, {
                                        (0, payment_hash, required),
                                        (1, receiver_node_id, option),
@@ -857,6 +878,7 @@ impl Writeable for Event {
                                        (7, claim_deadline, option),
                                        (8, payment_preimage, option),
                                        (9, onion_fields, option),
+                                       (10, skimmed_fee_opt, option),
                                });
                        },
                        &Event::PaymentSent { ref payment_id, ref payment_preimage, ref payment_hash, ref fee_paid_msat } => {
@@ -1056,6 +1078,7 @@ impl MaybeReadable for Event {
                                        let mut payment_preimage = None;
                                        let mut payment_secret = None;
                                        let mut amount_msat = 0;
+                                       let mut counterparty_skimmed_fee_msat_opt = None;
                                        let mut receiver_node_id = None;
                                        let mut _user_payment_id = None::<u64>; // Used in 0.0.103 and earlier, no longer written in 0.0.116+.
                                        let mut via_channel_id = None;
@@ -1073,6 +1096,7 @@ impl MaybeReadable for Event {
                                                (7, claim_deadline, option),
                                                (8, payment_preimage, option),
                                                (9, onion_fields, option),
+                                               (10, counterparty_skimmed_fee_msat_opt, option),
                                        });
                                        let purpose = match payment_secret {
                                                Some(secret) => PaymentPurpose::InvoicePayment {
@@ -1086,6 +1110,7 @@ impl MaybeReadable for Event {
                                                receiver_node_id,
                                                payment_hash,
                                                amount_msat,
+                                               counterparty_skimmed_fee_msat: counterparty_skimmed_fee_msat_opt.unwrap_or(0),
                                                purpose,
                                                via_channel_id,
                                                via_user_channel_id,
index cea15b21ad2fba1b6f1937b030d59551bede682c..cf0a04aab081f458b586e1c9f87c0ab7cc305e55 100644 (file)
@@ -38,7 +38,7 @@
 //!     * `max_level_trace`
 
 #![cfg_attr(not(any(test, fuzzing, feature = "_test_utils")), deny(missing_docs))]
-#![cfg_attr(not(any(test, fuzzing, feature = "_test_utils")), forbid(unsafe_code))]
+#![cfg_attr(not(any(test, feature = "_test_utils")), forbid(unsafe_code))]
 
 // Prefix these with `rustdoc::` when we update our MSRV to be >= 1.52 to remove warnings.
 #![deny(broken_intra_doc_links)]
@@ -67,7 +67,7 @@ extern crate bitcoin;
 extern crate core;
 
 #[cfg(any(test, feature = "_test_utils"))] extern crate hex;
-#[cfg(any(test, fuzzing, feature = "_test_utils"))] extern crate regex;
+#[cfg(any(test, feature = "_test_utils"))] extern crate regex;
 
 #[cfg(not(feature = "std"))] extern crate core2;
 
index bf1ac638c44645473402c848088fbd3727d389f0..664448f56f6a7dfbee8c23595293ad722bd3a107 100644 (file)
@@ -224,6 +224,7 @@ struct OutboundHTLCOutput {
        payment_hash: PaymentHash,
        state: OutboundHTLCState,
        source: HTLCSource,
+       skimmed_fee_msat: Option<u64>,
 }
 
 /// See AwaitingRemoteRevoke ChannelState for more info
@@ -235,6 +236,8 @@ enum HTLCUpdateAwaitingACK {
                payment_hash: PaymentHash,
                source: HTLCSource,
                onion_routing_packet: msgs::OnionPacket,
+               // The extra fee we're skimming off the top of this HTLC.
+               skimmed_fee_msat: Option<u64>,
        },
        ClaimHTLC {
                payment_preimage: PaymentPreimage,
@@ -3047,8 +3050,13 @@ impl<Signer: WriteableEcdsaChannelSigner> Channel<Signer> {
                                // handling this case better and maybe fulfilling some of the HTLCs while attempting
                                // to rebalance channels.
                                match &htlc_update {
-                                       &HTLCUpdateAwaitingACK::AddHTLC {amount_msat, cltv_expiry, ref payment_hash, ref source, ref onion_routing_packet, ..} => {
-                                               match self.send_htlc(amount_msat, *payment_hash, cltv_expiry, source.clone(), onion_routing_packet.clone(), false, logger) {
+                                       &HTLCUpdateAwaitingACK::AddHTLC {
+                                               amount_msat, cltv_expiry, ref payment_hash, ref source, ref onion_routing_packet,
+                                               skimmed_fee_msat, ..
+                                       } => {
+                                               match self.send_htlc(amount_msat, *payment_hash, cltv_expiry, source.clone(),
+                                                       onion_routing_packet.clone(), false, skimmed_fee_msat, logger)
+                                               {
                                                        Ok(update_add_msg_option) => update_add_htlcs.push(update_add_msg_option.unwrap()),
                                                        Err(e) => {
                                                                match e {
@@ -3690,6 +3698,7 @@ impl<Signer: WriteableEcdsaChannelSigner> Channel<Signer> {
                                        payment_hash: htlc.payment_hash,
                                        cltv_expiry: htlc.cltv_expiry,
                                        onion_routing_packet: (**onion_packet).clone(),
+                                       skimmed_fee_msat: htlc.skimmed_fee_msat,
                                });
                        }
                }
@@ -5037,11 +5046,13 @@ impl<Signer: WriteableEcdsaChannelSigner> Channel<Signer> {
        /// commitment update.
        ///
        /// `Err`s will only be [`ChannelError::Ignore`].
-       pub fn queue_add_htlc<L: Deref>(&mut self, amount_msat: u64, payment_hash: PaymentHash, cltv_expiry: u32, source: HTLCSource,
-               onion_routing_packet: msgs::OnionPacket, logger: &L)
-       -> Result<(), ChannelError> where L::Target: Logger {
+       pub fn queue_add_htlc<L: Deref>(
+               &mut self, amount_msat: u64, payment_hash: PaymentHash, cltv_expiry: u32, source: HTLCSource,
+               onion_routing_packet: msgs::OnionPacket, skimmed_fee_msat: Option<u64>, logger: &L
+       ) -> Result<(), ChannelError> where L::Target: Logger {
                self
-                       .send_htlc(amount_msat, payment_hash, cltv_expiry, source, onion_routing_packet, true, logger)
+                       .send_htlc(amount_msat, payment_hash, cltv_expiry, source, onion_routing_packet, true,
+                               skimmed_fee_msat, logger)
                        .map(|msg_opt| assert!(msg_opt.is_none(), "We forced holding cell?"))
                        .map_err(|err| {
                                if let ChannelError::Ignore(_) = err { /* fine */ }
@@ -5066,9 +5077,11 @@ impl<Signer: WriteableEcdsaChannelSigner> Channel<Signer> {
        /// on this [`Channel`] if `force_holding_cell` is false.
        ///
        /// `Err`s will only be [`ChannelError::Ignore`].
-       fn send_htlc<L: Deref>(&mut self, amount_msat: u64, payment_hash: PaymentHash, cltv_expiry: u32, source: HTLCSource,
-               onion_routing_packet: msgs::OnionPacket, mut force_holding_cell: bool, logger: &L)
-       -> Result<Option<msgs::UpdateAddHTLC>, ChannelError> where L::Target: Logger {
+       fn send_htlc<L: Deref>(
+               &mut self, amount_msat: u64, payment_hash: PaymentHash, cltv_expiry: u32, source: HTLCSource,
+               onion_routing_packet: msgs::OnionPacket, mut force_holding_cell: bool,
+               skimmed_fee_msat: Option<u64>, logger: &L
+       ) -> Result<Option<msgs::UpdateAddHTLC>, ChannelError> where L::Target: Logger {
                if (self.context.channel_state & (ChannelState::ChannelReady as u32 | BOTH_SIDES_SHUTDOWN_MASK)) != (ChannelState::ChannelReady as u32) {
                        return Err(ChannelError::Ignore("Cannot send HTLC until channel is fully established and we haven't started shutting down".to_owned()));
                }
@@ -5120,6 +5133,7 @@ impl<Signer: WriteableEcdsaChannelSigner> Channel<Signer> {
                                cltv_expiry,
                                source,
                                onion_routing_packet,
+                               skimmed_fee_msat,
                        });
                        return Ok(None);
                }
@@ -5131,6 +5145,7 @@ impl<Signer: WriteableEcdsaChannelSigner> Channel<Signer> {
                        cltv_expiry,
                        state: OutboundHTLCState::LocalAnnounced(Box::new(onion_routing_packet.clone())),
                        source,
+                       skimmed_fee_msat,
                });
 
                let res = msgs::UpdateAddHTLC {
@@ -5140,6 +5155,7 @@ impl<Signer: WriteableEcdsaChannelSigner> Channel<Signer> {
                        payment_hash,
                        cltv_expiry,
                        onion_routing_packet,
+                       skimmed_fee_msat,
                };
                self.context.next_holder_htlc_id += 1;
 
@@ -5278,8 +5294,12 @@ impl<Signer: WriteableEcdsaChannelSigner> Channel<Signer> {
        ///
        /// Shorthand for calling [`Self::send_htlc`] followed by a commitment update, see docs on
        /// [`Self::send_htlc`] and [`Self::build_commitment_no_state_update`] for more info.
-       pub fn send_htlc_and_commit<L: Deref>(&mut self, amount_msat: u64, payment_hash: PaymentHash, cltv_expiry: u32, source: HTLCSource, onion_routing_packet: msgs::OnionPacket, logger: &L) -> Result<Option<&ChannelMonitorUpdate>, ChannelError> where L::Target: Logger {
-               let send_res = self.send_htlc(amount_msat, payment_hash, cltv_expiry, source, onion_routing_packet, false, logger);
+       pub fn send_htlc_and_commit<L: Deref>(
+               &mut self, amount_msat: u64, payment_hash: PaymentHash, cltv_expiry: u32, source: HTLCSource,
+               onion_routing_packet: msgs::OnionPacket, skimmed_fee_msat: Option<u64>, logger: &L
+       ) -> Result<Option<&ChannelMonitorUpdate>, ChannelError> where L::Target: Logger {
+               let send_res = self.send_htlc(amount_msat, payment_hash, cltv_expiry, source,
+                       onion_routing_packet, false, skimmed_fee_msat, logger);
                if let Err(e) = &send_res { if let ChannelError::Ignore(_) = e {} else { debug_assert!(false, "Sending cannot trigger channel failure"); } }
                match send_res? {
                        Some(_) => {
@@ -6602,9 +6622,10 @@ impl<Signer: WriteableEcdsaChannelSigner> Writeable for Channel<Signer> {
                }
 
                let mut preimages: Vec<&Option<PaymentPreimage>> = vec![];
+               let mut pending_outbound_skimmed_fees: Vec<Option<u64>> = Vec::new();
 
                (self.context.pending_outbound_htlcs.len() as u64).write(writer)?;
-               for htlc in self.context.pending_outbound_htlcs.iter() {
+               for (idx, htlc) in self.context.pending_outbound_htlcs.iter().enumerate() {
                        htlc.htlc_id.write(writer)?;
                        htlc.amount_msat.write(writer)?;
                        htlc.cltv_expiry.write(writer)?;
@@ -6640,18 +6661,37 @@ impl<Signer: WriteableEcdsaChannelSigner> Writeable for Channel<Signer> {
                                        reason.write(writer)?;
                                }
                        }
+                       if let Some(skimmed_fee) = htlc.skimmed_fee_msat {
+                               if pending_outbound_skimmed_fees.is_empty() {
+                                       for _ in 0..idx { pending_outbound_skimmed_fees.push(None); }
+                               }
+                               pending_outbound_skimmed_fees.push(Some(skimmed_fee));
+                       } else if !pending_outbound_skimmed_fees.is_empty() {
+                               pending_outbound_skimmed_fees.push(None);
+                       }
                }
 
+               let mut holding_cell_skimmed_fees: Vec<Option<u64>> = Vec::new();
                (self.context.holding_cell_htlc_updates.len() as u64).write(writer)?;
-               for update in self.context.holding_cell_htlc_updates.iter() {
+               for (idx, update) in self.context.holding_cell_htlc_updates.iter().enumerate() {
                        match update {
-                               &HTLCUpdateAwaitingACK::AddHTLC { ref amount_msat, ref cltv_expiry, ref payment_hash, ref source, ref onion_routing_packet } => {
+                               &HTLCUpdateAwaitingACK::AddHTLC {
+                                       ref amount_msat, ref cltv_expiry, ref payment_hash, ref source, ref onion_routing_packet,
+                                       skimmed_fee_msat,
+                               } => {
                                        0u8.write(writer)?;
                                        amount_msat.write(writer)?;
                                        cltv_expiry.write(writer)?;
                                        payment_hash.write(writer)?;
                                        source.write(writer)?;
                                        onion_routing_packet.write(writer)?;
+
+                                       if let Some(skimmed_fee) = skimmed_fee_msat {
+                                               if holding_cell_skimmed_fees.is_empty() {
+                                                       for _ in 0..idx { holding_cell_skimmed_fees.push(None); }
+                                               }
+                                               holding_cell_skimmed_fees.push(Some(skimmed_fee));
+                                       } else if !holding_cell_skimmed_fees.is_empty() { holding_cell_skimmed_fees.push(None); }
                                },
                                &HTLCUpdateAwaitingACK::ClaimHTLC { ref payment_preimage, ref htlc_id } => {
                                        1u8.write(writer)?;
@@ -6818,6 +6858,8 @@ impl<Signer: WriteableEcdsaChannelSigner> Writeable for Channel<Signer> {
                        (29, self.context.temporary_channel_id, option),
                        (31, channel_pending_event_emitted, option),
                        (33, self.context.pending_monitor_updates, vec_type),
+                       (35, pending_outbound_skimmed_fees, optional_vec),
+                       (37, holding_cell_skimmed_fees, optional_vec),
                });
 
                Ok(())
@@ -6928,6 +6970,7 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, u32, &'c Ch
                                        },
                                        _ => return Err(DecodeError::InvalidValue),
                                },
+                               skimmed_fee_msat: None,
                        });
                }
 
@@ -6941,6 +6984,7 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, u32, &'c Ch
                                        payment_hash: Readable::read(reader)?,
                                        source: Readable::read(reader)?,
                                        onion_routing_packet: Readable::read(reader)?,
+                                       skimmed_fee_msat: None,
                                },
                                1 => HTLCUpdateAwaitingACK::ClaimHTLC {
                                        payment_preimage: Readable::read(reader)?,
@@ -7096,6 +7140,9 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, u32, &'c Ch
 
                let mut pending_monitor_updates = Some(Vec::new());
 
+               let mut pending_outbound_skimmed_fees_opt: Option<Vec<Option<u64>>> = None;
+               let mut holding_cell_skimmed_fees_opt: Option<Vec<Option<u64>>> = None;
+
                read_tlv_fields!(reader, {
                        (0, announcement_sigs, option),
                        (1, minimum_depth, option),
@@ -7119,6 +7166,8 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, u32, &'c Ch
                        (29, temporary_channel_id, option),
                        (31, channel_pending_event_emitted, option),
                        (33, pending_monitor_updates, vec_type),
+                       (35, pending_outbound_skimmed_fees_opt, optional_vec),
+                       (37, holding_cell_skimmed_fees_opt, optional_vec),
                });
 
                let (channel_keys_id, holder_signer) = if let Some(channel_keys_id) = channel_keys_id {
@@ -7177,6 +7226,25 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, u32, &'c Ch
 
                let holder_max_accepted_htlcs = holder_max_accepted_htlcs.unwrap_or(DEFAULT_MAX_HTLCS);
 
+               if let Some(skimmed_fees) = pending_outbound_skimmed_fees_opt {
+                       let mut iter = skimmed_fees.into_iter();
+                       for htlc in pending_outbound_htlcs.iter_mut() {
+                               htlc.skimmed_fee_msat = iter.next().ok_or(DecodeError::InvalidValue)?;
+                       }
+                       // We expect all skimmed fees to be consumed above
+                       if iter.next().is_some() { return Err(DecodeError::InvalidValue) }
+               }
+               if let Some(skimmed_fees) = holding_cell_skimmed_fees_opt {
+                       let mut iter = skimmed_fees.into_iter();
+                       for htlc in holding_cell_htlc_updates.iter_mut() {
+                               if let HTLCUpdateAwaitingACK::AddHTLC { ref mut skimmed_fee_msat, .. } = htlc {
+                                       *skimmed_fee_msat = iter.next().ok_or(DecodeError::InvalidValue)?;
+                               }
+                       }
+                       // We expect all skimmed fees to be consumed above
+                       if iter.next().is_some() { return Err(DecodeError::InvalidValue) }
+               }
+
                Ok(Channel {
                        context: ChannelContext {
                                user_id,
@@ -7519,7 +7587,8 @@ mod tests {
                                session_priv: SecretKey::from_slice(&hex::decode("0fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff").unwrap()[..]).unwrap(),
                                first_hop_htlc_msat: 548,
                                payment_id: PaymentId([42; 32]),
-                       }
+                       },
+                       skimmed_fee_msat: None,
                });
 
                // Make sure when Node A calculates their local commitment transaction, none of the HTLCs pass
@@ -8076,6 +8145,7 @@ mod tests {
                                payment_hash: PaymentHash([0; 32]),
                                state: OutboundHTLCState::Committed,
                                source: HTLCSource::dummy(),
+                               skimmed_fee_msat: None,
                        };
                        out.payment_hash.0 = Sha256::hash(&hex::decode("0202020202020202020202020202020202020202020202020202020202020202").unwrap()).into_inner();
                        out
@@ -8088,6 +8158,7 @@ mod tests {
                                payment_hash: PaymentHash([0; 32]),
                                state: OutboundHTLCState::Committed,
                                source: HTLCSource::dummy(),
+                               skimmed_fee_msat: None,
                        };
                        out.payment_hash.0 = Sha256::hash(&hex::decode("0303030303030303030303030303030303030303030303030303030303030303").unwrap()).into_inner();
                        out
@@ -8498,6 +8569,7 @@ mod tests {
                                payment_hash: PaymentHash([0; 32]),
                                state: OutboundHTLCState::Committed,
                                source: HTLCSource::dummy(),
+                               skimmed_fee_msat: None,
                        };
                        out.payment_hash.0 = Sha256::hash(&hex::decode("0505050505050505050505050505050505050505050505050505050505050505").unwrap()).into_inner();
                        out
@@ -8510,6 +8582,7 @@ mod tests {
                                payment_hash: PaymentHash([0; 32]),
                                state: OutboundHTLCState::Committed,
                                source: HTLCSource::dummy(),
+                               skimmed_fee_msat: None,
                        };
                        out.payment_hash.0 = Sha256::hash(&hex::decode("0505050505050505050505050505050505050505050505050505050505050505").unwrap()).into_inner();
                        out
index 595fad60ff2a321d773455c4a9cb54b8c4471bf8..eb682c34e01e9ad0e5484c1b2c8b8e3a1a81e15a 100644 (file)
@@ -131,6 +131,9 @@ pub(super) struct PendingHTLCInfo {
        /// may overshoot this in either case)
        pub(super) outgoing_amt_msat: u64,
        pub(super) outgoing_cltv_value: u32,
+       /// The fee being skimmed off the top of this HTLC. If this is a forward, it'll be the fee we are
+       /// skimming. If we're receiving this HTLC, it's the fee that our counterparty skimmed.
+       pub(super) skimmed_fee_msat: Option<u64>,
 }
 
 #[derive(Clone)] // See Channel::revoke_and_ack for why, tl;dr: Rust bug
@@ -210,6 +213,8 @@ struct ClaimableHTLC {
        total_value_received: Option<u64>,
        /// The sender intended sum total of all MPP parts specified in the onion
        total_msat: u64,
+       /// The extra fee our counterparty skimmed off the top of this HTLC.
+       counterparty_skimmed_fee_msat: Option<u64>,
 }
 
 /// A payment identifier used to uniquely identify a payment to LDK.
@@ -2521,9 +2526,11 @@ where
                }
        }
 
-       fn construct_recv_pending_htlc_info(&self, hop_data: msgs::OnionHopData, shared_secret: [u8; 32],
-               payment_hash: PaymentHash, amt_msat: u64, cltv_expiry: u32, phantom_shared_secret: Option<[u8; 32]>) -> Result<PendingHTLCInfo, ReceiveError>
-       {
+       fn construct_recv_pending_htlc_info(
+               &self, hop_data: msgs::OnionHopData, shared_secret: [u8; 32], payment_hash: PaymentHash,
+               amt_msat: u64, cltv_expiry: u32, phantom_shared_secret: Option<[u8; 32]>, allow_underpay: bool,
+               counterparty_skimmed_fee_msat: Option<u64>,
+       ) -> Result<PendingHTLCInfo, ReceiveError> {
                // final_incorrect_cltv_expiry
                if hop_data.outgoing_cltv_value > cltv_expiry {
                        return Err(ReceiveError {
@@ -2549,7 +2556,10 @@ where
                                msg: "The final CLTV expiry is too soon to handle",
                        });
                }
-               if hop_data.amt_to_forward > amt_msat {
+               if (!allow_underpay && hop_data.amt_to_forward > amt_msat) ||
+                       (allow_underpay && hop_data.amt_to_forward >
+                        amt_msat.saturating_add(counterparty_skimmed_fee_msat.unwrap_or(0)))
+               {
                        return Err(ReceiveError {
                                err_code: 19,
                                err_data: amt_msat.to_be_bytes().to_vec(),
@@ -2616,15 +2626,18 @@ where
                        incoming_amt_msat: Some(amt_msat),
                        outgoing_amt_msat: hop_data.amt_to_forward,
                        outgoing_cltv_value: hop_data.outgoing_cltv_value,
+                       skimmed_fee_msat: counterparty_skimmed_fee_msat,
                })
        }
 
-       fn decode_update_add_htlc_onion(&self, msg: &msgs::UpdateAddHTLC) -> PendingHTLCStatus {
+       fn decode_update_add_htlc_onion(
+               &self, msg: &msgs::UpdateAddHTLC
+       ) -> Result<(onion_utils::Hop, [u8; 32], Option<Result<PublicKey, secp256k1::Error>>), HTLCFailureMsg> {
                macro_rules! return_malformed_err {
                        ($msg: expr, $err_code: expr) => {
                                {
                                        log_info!(self.logger, "Failed to accept/forward incoming HTLC: {}", $msg);
-                                       return PendingHTLCStatus::Fail(HTLCFailureMsg::Malformed(msgs::UpdateFailMalformedHTLC {
+                                       return Err(HTLCFailureMsg::Malformed(msgs::UpdateFailMalformedHTLC {
                                                channel_id: msg.channel_id,
                                                htlc_id: msg.htlc_id,
                                                sha256_of_onion: Sha256::hash(&msg.onion_routing_packet.hop_data).into_inner(),
@@ -2655,7 +2668,7 @@ where
                        ($msg: expr, $err_code: expr, $data: expr) => {
                                {
                                        log_info!(self.logger, "Failed to accept/forward incoming HTLC: {}", $msg);
-                                       return PendingHTLCStatus::Fail(HTLCFailureMsg::Relay(msgs::UpdateFailHTLC {
+                                       return Err(HTLCFailureMsg::Relay(msgs::UpdateFailHTLC {
                                                channel_id: msg.channel_id,
                                                htlc_id: msg.htlc_id,
                                                reason: HTLCFailReason::reason($err_code, $data.to_vec())
@@ -2674,11 +2687,186 @@ where
                                return_err!(err_msg, err_code, &[0; 0]);
                        },
                };
+               let (outgoing_scid, outgoing_amt_msat, outgoing_cltv_value, next_packet_pk_opt) = match next_hop {
+                       onion_utils::Hop::Forward {
+                               next_hop_data: msgs::OnionHopData {
+                                       format: msgs::OnionHopDataFormat::NonFinalNode { short_channel_id }, amt_to_forward,
+                                       outgoing_cltv_value,
+                               }, ..
+                       } => {
+                               let next_pk = onion_utils::next_hop_packet_pubkey(&self.secp_ctx,
+                                       msg.onion_routing_packet.public_key.unwrap(), &shared_secret);
+                               (short_channel_id, amt_to_forward, outgoing_cltv_value, Some(next_pk))
+                       },
+                       // We'll do receive checks in [`Self::construct_pending_htlc_info`] so we have access to the
+                       // inbound channel's state.
+                       onion_utils::Hop::Receive { .. } => return Ok((next_hop, shared_secret, None)),
+                       onion_utils::Hop::Forward {
+                               next_hop_data: msgs::OnionHopData { format: msgs::OnionHopDataFormat::FinalNode { .. }, .. }, ..
+                       } => {
+                               return_err!("Final Node OnionHopData provided for us as an intermediary node", 0x4000 | 22, &[0; 0]);
+                       }
+               };
+
+               // Perform outbound checks here instead of in [`Self::construct_pending_htlc_info`] because we
+               // can't hold the outbound peer state lock at the same time as the inbound peer state lock.
+               if let Some((err, mut code, chan_update)) = loop {
+                       let id_option = self.short_to_chan_info.read().unwrap().get(&outgoing_scid).cloned();
+                       let forwarding_chan_info_opt = match id_option {
+                               None => { // unknown_next_peer
+                                       // Note that this is likely a timing oracle for detecting whether an scid is a
+                                       // phantom or an intercept.
+                                       if (self.default_configuration.accept_intercept_htlcs &&
+                                               fake_scid::is_valid_intercept(&self.fake_scid_rand_bytes, outgoing_scid, &self.genesis_hash)) ||
+                                               fake_scid::is_valid_phantom(&self.fake_scid_rand_bytes, outgoing_scid, &self.genesis_hash)
+                                       {
+                                               None
+                                       } else {
+                                               break Some(("Don't have available channel for forwarding as requested.", 0x4000 | 10, None));
+                                       }
+                               },
+                               Some((cp_id, id)) => Some((cp_id.clone(), id.clone())),
+                       };
+                       let chan_update_opt = if let Some((counterparty_node_id, forwarding_id)) = forwarding_chan_info_opt {
+                               let per_peer_state = self.per_peer_state.read().unwrap();
+                               let peer_state_mutex_opt = per_peer_state.get(&counterparty_node_id);
+                               if peer_state_mutex_opt.is_none() {
+                                       break Some(("Don't have available channel for forwarding as requested.", 0x4000 | 10, None));
+                               }
+                               let mut peer_state_lock = peer_state_mutex_opt.unwrap().lock().unwrap();
+                               let peer_state = &mut *peer_state_lock;
+                               let chan = match peer_state.channel_by_id.get_mut(&forwarding_id) {
+                                       None => {
+                                               // Channel was removed. The short_to_chan_info and channel_by_id maps
+                                               // have no consistency guarantees.
+                                               break Some(("Don't have available channel for forwarding as requested.", 0x4000 | 10, None));
+                                       },
+                                       Some(chan) => chan
+                               };
+                               if !chan.context.should_announce() && !self.default_configuration.accept_forwards_to_priv_channels {
+                                       // Note that the behavior here should be identical to the above block - we
+                                       // should NOT reveal the existence or non-existence of a private channel if
+                                       // we don't allow forwards outbound over them.
+                                       break Some(("Refusing to forward to a private channel based on our config.", 0x4000 | 10, None));
+                               }
+                               if chan.context.get_channel_type().supports_scid_privacy() && outgoing_scid != chan.context.outbound_scid_alias() {
+                                       // `option_scid_alias` (referred to in LDK as `scid_privacy`) means
+                                       // "refuse to forward unless the SCID alias was used", so we pretend
+                                       // we don't have the channel here.
+                                       break Some(("Refusing to forward over real channel SCID as our counterparty requested.", 0x4000 | 10, None));
+                               }
+                               let chan_update_opt = self.get_channel_update_for_onion(outgoing_scid, chan).ok();
+
+                               // Note that we could technically not return an error yet here and just hope
+                               // that the connection is reestablished or monitor updated by the time we get
+                               // around to doing the actual forward, but better to fail early if we can and
+                               // hopefully an attacker trying to path-trace payments cannot make this occur
+                               // on a small/per-node/per-channel scale.
+                               if !chan.context.is_live() { // channel_disabled
+                                       // If the channel_update we're going to return is disabled (i.e. the
+                                       // peer has been disabled for some time), return `channel_disabled`,
+                                       // otherwise return `temporary_channel_failure`.
+                                       if chan_update_opt.as_ref().map(|u| u.contents.flags & 2 == 2).unwrap_or(false) {
+                                               break Some(("Forwarding channel has been disconnected for some time.", 0x1000 | 20, chan_update_opt));
+                                       } else {
+                                               break Some(("Forwarding channel is not in a ready state.", 0x1000 | 7, chan_update_opt));
+                                       }
+                               }
+                               if outgoing_amt_msat < chan.context.get_counterparty_htlc_minimum_msat() { // amount_below_minimum
+                                       break Some(("HTLC amount was below the htlc_minimum_msat", 0x1000 | 11, chan_update_opt));
+                               }
+                               if let Err((err, code)) = chan.htlc_satisfies_config(&msg, outgoing_amt_msat, outgoing_cltv_value) {
+                                       break Some((err, code, chan_update_opt));
+                               }
+                               chan_update_opt
+                       } else {
+                               if (msg.cltv_expiry as u64) < (outgoing_cltv_value) as u64 + MIN_CLTV_EXPIRY_DELTA as u64 {
+                                       // We really should set `incorrect_cltv_expiry` here but as we're not
+                                       // forwarding over a real channel we can't generate a channel_update
+                                       // for it. Instead we just return a generic temporary_node_failure.
+                                       break Some((
+                                                       "Forwarding node has tampered with the intended HTLC values or origin node has an obsolete cltv_expiry_delta",
+                                                       0x2000 | 2, None,
+                                       ));
+                               }
+                               None
+                       };
+
+                       let cur_height = self.best_block.read().unwrap().height() + 1;
+                       // Theoretically, channel counterparty shouldn't send us a HTLC expiring now,
+                       // but we want to be robust wrt to counterparty packet sanitization (see
+                       // HTLC_FAIL_BACK_BUFFER rationale).
+                       if msg.cltv_expiry <= cur_height + HTLC_FAIL_BACK_BUFFER as u32 { // expiry_too_soon
+                               break Some(("CLTV expiry is too close", 0x1000 | 14, chan_update_opt));
+                       }
+                       if msg.cltv_expiry > cur_height + CLTV_FAR_FAR_AWAY as u32 { // expiry_too_far
+                               break Some(("CLTV expiry is too far in the future", 21, None));
+                       }
+                       // If the HTLC expires ~now, don't bother trying to forward it to our
+                       // counterparty. They should fail it anyway, but we don't want to bother with
+                       // the round-trips or risk them deciding they definitely want the HTLC and
+                       // force-closing to ensure they get it if we're offline.
+                       // We previously had a much more aggressive check here which tried to ensure
+                       // our counterparty receives an HTLC which has *our* risk threshold met on it,
+                       // but there is no need to do that, and since we're a bit conservative with our
+                       // risk threshold it just results in failing to forward payments.
+                       if (outgoing_cltv_value) as u64 <= (cur_height + LATENCY_GRACE_PERIOD_BLOCKS) as u64 {
+                               break Some(("Outgoing CLTV value is too soon", 0x1000 | 14, chan_update_opt));
+                       }
+
+                       break None;
+               }
+               {
+                       let mut res = VecWriter(Vec::with_capacity(chan_update.serialized_length() + 2 + 8 + 2));
+                       if let Some(chan_update) = chan_update {
+                               if code == 0x1000 | 11 || code == 0x1000 | 12 {
+                                       msg.amount_msat.write(&mut res).expect("Writes cannot fail");
+                               }
+                               else if code == 0x1000 | 13 {
+                                       msg.cltv_expiry.write(&mut res).expect("Writes cannot fail");
+                               }
+                               else if code == 0x1000 | 20 {
+                                       // TODO: underspecified, follow https://github.com/lightning/bolts/issues/791
+                                       0u16.write(&mut res).expect("Writes cannot fail");
+                               }
+                               (chan_update.serialized_length() as u16 + 2).write(&mut res).expect("Writes cannot fail");
+                               msgs::ChannelUpdate::TYPE.write(&mut res).expect("Writes cannot fail");
+                               chan_update.write(&mut res).expect("Writes cannot fail");
+                       } else if code & 0x1000 == 0x1000 {
+                               // If we're trying to return an error that requires a `channel_update` but
+                               // we're forwarding to a phantom or intercept "channel" (i.e. cannot
+                               // generate an update), just use the generic "temporary_node_failure"
+                               // instead.
+                               code = 0x2000 | 2;
+                       }
+                       return_err!(err, code, &res.0[..]);
+               }
+               Ok((next_hop, shared_secret, next_packet_pk_opt))
+       }
 
-               let pending_forward_info = match next_hop {
+       fn construct_pending_htlc_status<'a>(
+               &self, msg: &msgs::UpdateAddHTLC, shared_secret: [u8; 32], decoded_hop: onion_utils::Hop,
+               allow_underpay: bool, next_packet_pubkey_opt: Option<Result<PublicKey, secp256k1::Error>>
+       ) -> PendingHTLCStatus {
+               macro_rules! return_err {
+                       ($msg: expr, $err_code: expr, $data: expr) => {
+                               {
+                                       log_info!(self.logger, "Failed to accept/forward incoming HTLC: {}", $msg);
+                                       return PendingHTLCStatus::Fail(HTLCFailureMsg::Relay(msgs::UpdateFailHTLC {
+                                               channel_id: msg.channel_id,
+                                               htlc_id: msg.htlc_id,
+                                               reason: HTLCFailReason::reason($err_code, $data.to_vec())
+                                                       .get_encrypted_failure_packet(&shared_secret, &None),
+                                       }));
+                               }
+                       }
+               }
+               match decoded_hop {
                        onion_utils::Hop::Receive(next_hop_data) => {
                                // OUR PAYMENT!
-                               match self.construct_recv_pending_htlc_info(next_hop_data, shared_secret, msg.payment_hash, msg.amount_msat, msg.cltv_expiry, None) {
+                               match self.construct_recv_pending_htlc_info(next_hop_data, shared_secret, msg.payment_hash,
+                                       msg.amount_msat, msg.cltv_expiry, None, allow_underpay, msg.skimmed_fee_msat)
+                               {
                                        Ok(info) => {
                                                // Note that we could obviously respond immediately with an update_fulfill_htlc
                                                // message, however that would leak that we are the recipient of this payment, so
@@ -2690,10 +2878,10 @@ where
                                }
                        },
                        onion_utils::Hop::Forward { next_hop_data, next_hop_hmac, new_packet_bytes } => {
-                               let new_pubkey = msg.onion_routing_packet.public_key.unwrap();
+                               debug_assert!(next_packet_pubkey_opt.is_some());
                                let outgoing_packet = msgs::OnionPacket {
                                        version: 0,
-                                       public_key: onion_utils::next_hop_packet_pubkey(&self.secp_ctx, new_pubkey, &shared_secret),
+                                       public_key: next_packet_pubkey_opt.unwrap_or(Err(secp256k1::Error::InvalidPublicKey)),
                                        hop_data: new_packet_bytes,
                                        hmac: next_hop_hmac.clone(),
                                };
@@ -2715,150 +2903,10 @@ where
                                        incoming_amt_msat: Some(msg.amount_msat),
                                        outgoing_amt_msat: next_hop_data.amt_to_forward,
                                        outgoing_cltv_value: next_hop_data.outgoing_cltv_value,
+                                       skimmed_fee_msat: None,
                                })
                        }
-               };
-
-               if let &PendingHTLCStatus::Forward(PendingHTLCInfo { ref routing, ref outgoing_amt_msat, ref outgoing_cltv_value, .. }) = &pending_forward_info {
-                       // If short_channel_id is 0 here, we'll reject the HTLC as there cannot be a channel
-                       // with a short_channel_id of 0. This is important as various things later assume
-                       // short_channel_id is non-0 in any ::Forward.
-                       if let &PendingHTLCRouting::Forward { ref short_channel_id, .. } = routing {
-                               if let Some((err, mut code, chan_update)) = loop {
-                                       let id_option = self.short_to_chan_info.read().unwrap().get(short_channel_id).cloned();
-                                       let forwarding_chan_info_opt = match id_option {
-                                               None => { // unknown_next_peer
-                                                       // Note that this is likely a timing oracle for detecting whether an scid is a
-                                                       // phantom or an intercept.
-                                                       if (self.default_configuration.accept_intercept_htlcs &&
-                                                          fake_scid::is_valid_intercept(&self.fake_scid_rand_bytes, *short_channel_id, &self.genesis_hash)) ||
-                                                          fake_scid::is_valid_phantom(&self.fake_scid_rand_bytes, *short_channel_id, &self.genesis_hash)
-                                                       {
-                                                               None
-                                                       } else {
-                                                               break Some(("Don't have available channel for forwarding as requested.", 0x4000 | 10, None));
-                                                       }
-                                               },
-                                               Some((cp_id, id)) => Some((cp_id.clone(), id.clone())),
-                                       };
-                                       let chan_update_opt = if let Some((counterparty_node_id, forwarding_id)) = forwarding_chan_info_opt {
-                                               let per_peer_state = self.per_peer_state.read().unwrap();
-                                               let peer_state_mutex_opt = per_peer_state.get(&counterparty_node_id);
-                                               if peer_state_mutex_opt.is_none() {
-                                                       break Some(("Don't have available channel for forwarding as requested.", 0x4000 | 10, None));
-                                               }
-                                               let mut peer_state_lock = peer_state_mutex_opt.unwrap().lock().unwrap();
-                                               let peer_state = &mut *peer_state_lock;
-                                               let chan = match peer_state.channel_by_id.get_mut(&forwarding_id) {
-                                                       None => {
-                                                               // Channel was removed. The short_to_chan_info and channel_by_id maps
-                                                               // have no consistency guarantees.
-                                                               break Some(("Don't have available channel for forwarding as requested.", 0x4000 | 10, None));
-                                                       },
-                                                       Some(chan) => chan
-                                               };
-                                               if !chan.context.should_announce() && !self.default_configuration.accept_forwards_to_priv_channels {
-                                                       // Note that the behavior here should be identical to the above block - we
-                                                       // should NOT reveal the existence or non-existence of a private channel if
-                                                       // we don't allow forwards outbound over them.
-                                                       break Some(("Refusing to forward to a private channel based on our config.", 0x4000 | 10, None));
-                                               }
-                                               if chan.context.get_channel_type().supports_scid_privacy() && *short_channel_id != chan.context.outbound_scid_alias() {
-                                                       // `option_scid_alias` (referred to in LDK as `scid_privacy`) means
-                                                       // "refuse to forward unless the SCID alias was used", so we pretend
-                                                       // we don't have the channel here.
-                                                       break Some(("Refusing to forward over real channel SCID as our counterparty requested.", 0x4000 | 10, None));
-                                               }
-                                               let chan_update_opt = self.get_channel_update_for_onion(*short_channel_id, chan).ok();
-
-                                               // Note that we could technically not return an error yet here and just hope
-                                               // that the connection is reestablished or monitor updated by the time we get
-                                               // around to doing the actual forward, but better to fail early if we can and
-                                               // hopefully an attacker trying to path-trace payments cannot make this occur
-                                               // on a small/per-node/per-channel scale.
-                                               if !chan.context.is_live() { // channel_disabled
-                                                       // If the channel_update we're going to return is disabled (i.e. the
-                                                       // peer has been disabled for some time), return `channel_disabled`,
-                                                       // otherwise return `temporary_channel_failure`.
-                                                       if chan_update_opt.as_ref().map(|u| u.contents.flags & 2 == 2).unwrap_or(false) {
-                                                               break Some(("Forwarding channel has been disconnected for some time.", 0x1000 | 20, chan_update_opt));
-                                                       } else {
-                                                               break Some(("Forwarding channel is not in a ready state.", 0x1000 | 7, chan_update_opt));
-                                                       }
-                                               }
-                                               if *outgoing_amt_msat < chan.context.get_counterparty_htlc_minimum_msat() { // amount_below_minimum
-                                                       break Some(("HTLC amount was below the htlc_minimum_msat", 0x1000 | 11, chan_update_opt));
-                                               }
-                                               if let Err((err, code)) = chan.htlc_satisfies_config(&msg, *outgoing_amt_msat, *outgoing_cltv_value) {
-                                                       break Some((err, code, chan_update_opt));
-                                               }
-                                               chan_update_opt
-                                       } else {
-                                               if (msg.cltv_expiry as u64) < (*outgoing_cltv_value) as u64 + MIN_CLTV_EXPIRY_DELTA as u64 {
-                                                       // We really should set `incorrect_cltv_expiry` here but as we're not
-                                                       // forwarding over a real channel we can't generate a channel_update
-                                                       // for it. Instead we just return a generic temporary_node_failure.
-                                                       break Some((
-                                                               "Forwarding node has tampered with the intended HTLC values or origin node has an obsolete cltv_expiry_delta",
-                                                               0x2000 | 2, None,
-                                                       ));
-                                               }
-                                               None
-                                       };
-
-                                       let cur_height = self.best_block.read().unwrap().height() + 1;
-                                       // Theoretically, channel counterparty shouldn't send us a HTLC expiring now,
-                                       // but we want to be robust wrt to counterparty packet sanitization (see
-                                       // HTLC_FAIL_BACK_BUFFER rationale).
-                                       if msg.cltv_expiry <= cur_height + HTLC_FAIL_BACK_BUFFER as u32 { // expiry_too_soon
-                                               break Some(("CLTV expiry is too close", 0x1000 | 14, chan_update_opt));
-                                       }
-                                       if msg.cltv_expiry > cur_height + CLTV_FAR_FAR_AWAY as u32 { // expiry_too_far
-                                               break Some(("CLTV expiry is too far in the future", 21, None));
-                                       }
-                                       // If the HTLC expires ~now, don't bother trying to forward it to our
-                                       // counterparty. They should fail it anyway, but we don't want to bother with
-                                       // the round-trips or risk them deciding they definitely want the HTLC and
-                                       // force-closing to ensure they get it if we're offline.
-                                       // We previously had a much more aggressive check here which tried to ensure
-                                       // our counterparty receives an HTLC which has *our* risk threshold met on it,
-                                       // but there is no need to do that, and since we're a bit conservative with our
-                                       // risk threshold it just results in failing to forward payments.
-                                       if (*outgoing_cltv_value) as u64 <= (cur_height + LATENCY_GRACE_PERIOD_BLOCKS) as u64 {
-                                               break Some(("Outgoing CLTV value is too soon", 0x1000 | 14, chan_update_opt));
-                                       }
-
-                                       break None;
-                               }
-                               {
-                                       let mut res = VecWriter(Vec::with_capacity(chan_update.serialized_length() + 2 + 8 + 2));
-                                       if let Some(chan_update) = chan_update {
-                                               if code == 0x1000 | 11 || code == 0x1000 | 12 {
-                                                       msg.amount_msat.write(&mut res).expect("Writes cannot fail");
-                                               }
-                                               else if code == 0x1000 | 13 {
-                                                       msg.cltv_expiry.write(&mut res).expect("Writes cannot fail");
-                                               }
-                                               else if code == 0x1000 | 20 {
-                                                       // TODO: underspecified, follow https://github.com/lightning/bolts/issues/791
-                                                       0u16.write(&mut res).expect("Writes cannot fail");
-                                               }
-                                               (chan_update.serialized_length() as u16 + 2).write(&mut res).expect("Writes cannot fail");
-                                               msgs::ChannelUpdate::TYPE.write(&mut res).expect("Writes cannot fail");
-                                               chan_update.write(&mut res).expect("Writes cannot fail");
-                                       } else if code & 0x1000 == 0x1000 {
-                                               // If we're trying to return an error that requires a `channel_update` but
-                                               // we're forwarding to a phantom or intercept "channel" (i.e. cannot
-                                               // generate an update), just use the generic "temporary_node_failure"
-                                               // instead.
-                                               code = 0x2000 | 2;
-                                       }
-                                       return_err!(err, code, &res.0[..]);
-                               }
-                       }
                }
-
-               pending_forward_info
        }
 
        /// Gets the current [`channel_update`] for the given channel. This first checks if the channel is
@@ -2984,7 +3032,7 @@ where
                                                session_priv: session_priv.clone(),
                                                first_hop_htlc_msat: htlc_msat,
                                                payment_id,
-                                       }, onion_packet, &self.logger);
+                                       }, onion_packet, None, &self.logger);
                                match break_chan_entry!(self, send_res, chan) {
                                        Some(monitor_update) => {
                                                let update_id = monitor_update.update_id;
@@ -3451,13 +3499,16 @@ where
        /// [`ChannelManager::fail_intercepted_htlc`] MUST be called in response to the event.
        ///
        /// Note that LDK does not enforce fee requirements in `amt_to_forward_msat`, and will not stop
-       /// you from forwarding more than you received.
+       /// you from forwarding more than you received. See
+       /// [`HTLCIntercepted::expected_outbound_amount_msat`] for more on forwarding a different amount
+       /// than expected.
        ///
        /// Errors if the event was not handled in time, in which case the HTLC was automatically failed
        /// backwards.
        ///
        /// [`UserConfig::accept_intercept_htlcs`]: crate::util::config::UserConfig::accept_intercept_htlcs
        /// [`HTLCIntercepted`]: events::Event::HTLCIntercepted
+       /// [`HTLCIntercepted::expected_outbound_amount_msat`]: events::Event::HTLCIntercepted::expected_outbound_amount_msat
        // TODO: when we move to deciding the best outbound channel at forward time, only take
        // `next_node_id` and not `next_hop_channel_id`
        pub fn forward_intercepted_htlc(&self, intercept_id: InterceptId, next_hop_channel_id: &[u8; 32], next_node_id: PublicKey, amt_to_forward_msat: u64) -> Result<(), APIError> {
@@ -3496,7 +3547,10 @@ where
                        },
                        _ => unreachable!() // Only `PendingHTLCRouting::Forward`s are intercepted
                };
+               let skimmed_fee_msat =
+                       payment.forward_info.outgoing_amt_msat.saturating_sub(amt_to_forward_msat);
                let pending_htlc_info = PendingHTLCInfo {
+                       skimmed_fee_msat: if skimmed_fee_msat == 0 { None } else { Some(skimmed_fee_msat) },
                        outgoing_amt_msat: amt_to_forward_msat, routing, ..payment.forward_info
                };
 
@@ -3566,7 +3620,7 @@ where
                                                                                prev_short_channel_id, prev_htlc_id, prev_funding_outpoint, prev_user_channel_id,
                                                                                forward_info: PendingHTLCInfo {
                                                                                        routing, incoming_shared_secret, payment_hash, outgoing_amt_msat,
-                                                                                       outgoing_cltv_value, incoming_amt_msat: _
+                                                                                       outgoing_cltv_value, ..
                                                                                }
                                                                        }) => {
                                                                                macro_rules! failure_handler {
@@ -3628,7 +3682,10 @@ where
                                                                                                };
                                                                                                match next_hop {
                                                                                                        onion_utils::Hop::Receive(hop_data) => {
-                                                                                                               match self.construct_recv_pending_htlc_info(hop_data, incoming_shared_secret, payment_hash, outgoing_amt_msat, outgoing_cltv_value, Some(phantom_shared_secret)) {
+                                                                                                               match self.construct_recv_pending_htlc_info(hop_data,
+                                                                                                                       incoming_shared_secret, payment_hash, outgoing_amt_msat,
+                                                                                                                       outgoing_cltv_value, Some(phantom_shared_secret), false, None)
+                                                                                                               {
                                                                                                                        Ok(info) => phantom_receives.push((prev_short_channel_id, prev_funding_outpoint, prev_user_channel_id, vec![(info, prev_htlc_id)])),
                                                                                                                        Err(ReceiveError { err_code, err_data, msg }) => failed_payment!(msg, err_code, err_data, Some(phantom_shared_secret))
                                                                                                                }
@@ -3679,7 +3736,7 @@ where
                                                                                prev_short_channel_id, prev_htlc_id, prev_funding_outpoint, prev_user_channel_id: _,
                                                                                forward_info: PendingHTLCInfo {
                                                                                        incoming_shared_secret, payment_hash, outgoing_amt_msat, outgoing_cltv_value,
-                                                                                       routing: PendingHTLCRouting::Forward { onion_packet, .. }, incoming_amt_msat: _,
+                                                                                       routing: PendingHTLCRouting::Forward { onion_packet, .. }, skimmed_fee_msat, ..
                                                                                },
                                                                        }) => {
                                                                                log_trace!(self.logger, "Adding HTLC from short id {} with payment_hash {} to channel with short id {} after delay", prev_short_channel_id, log_bytes!(payment_hash.0), short_chan_id);
@@ -3693,7 +3750,7 @@ where
                                                                                });
                                                                                if let Err(e) = chan.get_mut().queue_add_htlc(outgoing_amt_msat,
                                                                                        payment_hash, outgoing_cltv_value, htlc_source.clone(),
-                                                                                       onion_packet, &self.logger)
+                                                                                       onion_packet, skimmed_fee_msat, &self.logger)
                                                                                {
                                                                                        if let ChannelError::Ignore(msg) = e {
                                                                                                log_trace!(self.logger, "Failed to forward HTLC with payment_hash {}: {}", log_bytes!(payment_hash.0), msg);
@@ -3737,7 +3794,8 @@ where
                                                        HTLCForwardInfo::AddHTLC(PendingAddHTLCInfo {
                                                                prev_short_channel_id, prev_htlc_id, prev_funding_outpoint, prev_user_channel_id,
                                                                forward_info: PendingHTLCInfo {
-                                                                       routing, incoming_shared_secret, payment_hash, incoming_amt_msat, outgoing_amt_msat, ..
+                                                                       routing, incoming_shared_secret, payment_hash, incoming_amt_msat, outgoing_amt_msat,
+                                                                       skimmed_fee_msat, ..
                                                                }
                                                        }) => {
                                                                let (cltv_expiry, onion_payload, payment_data, phantom_shared_secret, mut onion_fields) = match routing {
@@ -3778,6 +3836,7 @@ where
                                                                        total_msat: if let Some(data) = &payment_data { data.total_msat } else { outgoing_amt_msat },
                                                                        cltv_expiry,
                                                                        onion_payload,
+                                                                       counterparty_skimmed_fee_msat: skimmed_fee_msat,
                                                                };
 
                                                                let mut committed_to_claimable = false;
@@ -3874,11 +3933,16 @@ where
                                                                                        htlcs.push(claimable_htlc);
                                                                                        let amount_msat = htlcs.iter().map(|htlc| htlc.value).sum();
                                                                                        htlcs.iter_mut().for_each(|htlc| htlc.total_value_received = Some(amount_msat));
+                                                                                       let counterparty_skimmed_fee_msat = htlcs.iter()
+                                                                                               .map(|htlc| htlc.counterparty_skimmed_fee_msat.unwrap_or(0)).sum();
+                                                                                       debug_assert!(total_value.saturating_sub(amount_msat) <=
+                                                                                               counterparty_skimmed_fee_msat);
                                                                                        new_events.push_back((events::Event::PaymentClaimable {
                                                                                                receiver_node_id: Some(receiver_node_id),
                                                                                                payment_hash,
                                                                                                purpose: $purpose,
                                                                                                amount_msat,
+                                                                                               counterparty_skimmed_fee_msat,
                                                                                                via_channel_id: Some(prev_channel_id),
                                                                                                via_user_channel_id: Some(prev_user_channel_id),
                                                                                                claim_deadline: Some(earliest_expiry - HTLC_FAIL_BACK_BUFFER),
@@ -5358,7 +5422,7 @@ where
                //encrypted with the same key. It's not immediately obvious how to usefully exploit that,
                //but we should prevent it anyway.
 
-               let pending_forward_info = self.decode_update_add_htlc_onion(msg);
+               let decoded_hop_res = self.decode_update_add_htlc_onion(msg);
                let per_peer_state = self.per_peer_state.read().unwrap();
                let peer_state_mutex = per_peer_state.get(counterparty_node_id)
                        .ok_or_else(|| {
@@ -5370,6 +5434,12 @@ where
                match peer_state.channel_by_id.entry(msg.channel_id) {
                        hash_map::Entry::Occupied(mut chan) => {
 
+                               let pending_forward_info = match decoded_hop_res {
+                                       Ok((next_hop, shared_secret, next_packet_pk_opt)) =>
+                                               self.construct_pending_htlc_status(msg, shared_secret, next_hop,
+                                                       chan.get().context.config().accept_underpaying_htlcs, next_packet_pk_opt),
+                                       Err(e) => PendingHTLCStatus::Fail(e)
+                               };
                                let create_pending_htlc_status = |chan: &Channel<<SP::Target as SignerProvider>::Signer>, pending_forward_info: PendingHTLCStatus, error_code: u16| {
                                        // If the update_add is completely bogus, the call will Err and we will close,
                                        // but if we've sent a shutdown and they haven't acknowledged it yet, we just
@@ -6171,7 +6241,7 @@ where
                inflight_htlcs
        }
 
-       #[cfg(any(test, fuzzing, feature = "_test_utils"))]
+       #[cfg(any(test, feature = "_test_utils"))]
        pub fn get_and_clear_pending_events(&self) -> Vec<events::Event> {
                let events = core::cell::RefCell::new(Vec::new());
                let event_handler = |event: events::Event| events.borrow_mut().push(event);
@@ -7347,6 +7417,7 @@ impl_writeable_tlv_based!(PendingHTLCInfo, {
        (6, outgoing_amt_msat, required),
        (8, outgoing_cltv_value, required),
        (9, incoming_amt_msat, option),
+       (10, skimmed_fee_msat, option),
 });
 
 
@@ -7445,6 +7516,7 @@ impl Writeable for ClaimableHTLC {
                        (5, self.total_value_received, option),
                        (6, self.cltv_expiry, required),
                        (8, keysend_preimage, option),
+                       (10, self.counterparty_skimmed_fee_msat, option),
                });
                Ok(())
        }
@@ -7452,24 +7524,19 @@ impl Writeable for ClaimableHTLC {
 
 impl Readable for ClaimableHTLC {
        fn read<R: Read>(reader: &mut R) -> Result<Self, DecodeError> {
-               let mut prev_hop = crate::util::ser::RequiredWrapper(None);
-               let mut value = 0;
-               let mut sender_intended_value = None;
-               let mut payment_data: Option<msgs::FinalOnionHopData> = None;
-               let mut cltv_expiry = 0;
-               let mut total_value_received = None;
-               let mut total_msat = None;
-               let mut keysend_preimage: Option<PaymentPreimage> = None;
-               read_tlv_fields!(reader, {
+               _init_and_read_tlv_fields!(reader, {
                        (0, prev_hop, required),
                        (1, total_msat, option),
-                       (2, value, required),
+                       (2, value_ser, required),
                        (3, sender_intended_value, option),
-                       (4, payment_data, option),
+                       (4, payment_data_opt, option),
                        (5, total_value_received, option),
                        (6, cltv_expiry, required),
-                       (8, keysend_preimage, option)
+                       (8, keysend_preimage, option),
+                       (10, counterparty_skimmed_fee_msat, option),
                });
+               let payment_data: Option<msgs::FinalOnionHopData> = payment_data_opt;
+               let value = value_ser.0.unwrap();
                let onion_payload = match keysend_preimage {
                        Some(p) => {
                                if payment_data.is_some() {
@@ -7498,7 +7565,8 @@ impl Readable for ClaimableHTLC {
                        total_value_received,
                        total_msat: total_msat.unwrap(),
                        onion_payload,
-                       cltv_expiry,
+                       cltv_expiry: cltv_expiry.0.unwrap(),
+                       counterparty_skimmed_fee_msat,
                })
        }
 }
@@ -9619,6 +9687,50 @@ mod tests {
                get_event_msg!(nodes[1], MessageSendEvent::SendAcceptChannel, last_random_pk);
        }
 
+       #[test]
+       fn reject_excessively_underpaying_htlcs() {
+               let chanmon_cfg = create_chanmon_cfgs(1);
+               let node_cfg = create_node_cfgs(1, &chanmon_cfg);
+               let node_chanmgr = create_node_chanmgrs(1, &node_cfg, &[None]);
+               let node = create_network(1, &node_cfg, &node_chanmgr);
+               let sender_intended_amt_msat = 100;
+               let extra_fee_msat = 10;
+               let hop_data = msgs::OnionHopData {
+                       amt_to_forward: 100,
+                       outgoing_cltv_value: 42,
+                       format: msgs::OnionHopDataFormat::FinalNode {
+                               keysend_preimage: None,
+                               payment_metadata: None,
+                               payment_data: Some(msgs::FinalOnionHopData {
+                                       payment_secret: PaymentSecret([0; 32]), total_msat: sender_intended_amt_msat,
+                               }),
+                       }
+               };
+               // Check that if the amount we received + the penultimate hop extra fee is less than the sender
+               // intended amount, we fail the payment.
+               if let Err(crate::ln::channelmanager::ReceiveError { err_code, .. }) =
+                       node[0].node.construct_recv_pending_htlc_info(hop_data, [0; 32], PaymentHash([0; 32]),
+                               sender_intended_amt_msat - extra_fee_msat - 1, 42, None, true, Some(extra_fee_msat))
+               {
+                       assert_eq!(err_code, 19);
+               } else { panic!(); }
+
+               // If amt_received + extra_fee is equal to the sender intended amount, we're fine.
+               let hop_data = msgs::OnionHopData { // This is the same hop_data as above, OnionHopData doesn't implement Clone
+                       amt_to_forward: 100,
+                       outgoing_cltv_value: 42,
+                       format: msgs::OnionHopDataFormat::FinalNode {
+                               keysend_preimage: None,
+                               payment_metadata: None,
+                               payment_data: Some(msgs::FinalOnionHopData {
+                                       payment_secret: PaymentSecret([0; 32]), total_msat: sender_intended_amt_msat,
+                               }),
+                       }
+               };
+               assert!(node[0].node.construct_recv_pending_htlc_info(hop_data, [0; 32], PaymentHash([0; 32]),
+                       sender_intended_amt_msat - extra_fee_msat, 42, None, true, Some(extra_fee_msat)).is_ok());
+       }
+
        #[cfg(anchors)]
        #[test]
        fn test_anchors_zero_fee_htlc_tx_fallback() {
index 00054a83b27ae4cd0d7fe5dd00ecabb2ba66ca1f..27002679ed78e914b533efe2147efbff123fb1f9 100644 (file)
@@ -2124,7 +2124,7 @@ pub fn do_pass_along_path<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>, expected_p
                                match &events_2[0] {
                                        Event::PaymentClaimable { ref payment_hash, ref purpose, amount_msat,
                                                receiver_node_id, ref via_channel_id, ref via_user_channel_id,
-                                               claim_deadline, onion_fields,
+                                               claim_deadline, onion_fields, ..
                                        } => {
                                                assert_eq!(our_payment_hash, *payment_hash);
                                                assert_eq!(node.node.get_our_node_id(), receiver_node_id.unwrap());
@@ -2186,7 +2186,20 @@ pub fn send_along_route<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>, route: Route
        (our_payment_preimage, our_payment_hash, our_payment_secret, payment_id)
 }
 
-pub fn do_claim_payment_along_route<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>, expected_paths: &[&[&Node<'a, 'b, 'c>]], skip_last: bool, our_payment_preimage: PaymentPreimage) -> u64 {
+pub fn do_claim_payment_along_route<'a, 'b, 'c>(
+       origin_node: &Node<'a, 'b, 'c>, expected_paths: &[&[&Node<'a, 'b, 'c>]], skip_last: bool,
+       our_payment_preimage: PaymentPreimage
+) -> u64 {
+       let extra_fees = vec![0; expected_paths.len()];
+       do_claim_payment_along_route_with_extra_penultimate_hop_fees(origin_node, expected_paths,
+               &extra_fees[..], skip_last, our_payment_preimage)
+}
+
+pub fn do_claim_payment_along_route_with_extra_penultimate_hop_fees<'a, 'b, 'c>(
+       origin_node: &Node<'a, 'b, 'c>, expected_paths: &[&[&Node<'a, 'b, 'c>]], expected_extra_fees:
+       &[u32], skip_last: bool, our_payment_preimage: PaymentPreimage
+) -> u64 {
+       assert_eq!(expected_paths.len(), expected_extra_fees.len());
        for path in expected_paths.iter() {
                assert_eq!(path.last().unwrap().node.get_our_node_id(), expected_paths[0].last().unwrap().node.get_our_node_id());
        }
@@ -2236,7 +2249,7 @@ pub fn do_claim_payment_along_route<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>,
                }
        }
 
-       for (expected_route, (path_msgs, next_hop)) in expected_paths.iter().zip(per_path_msgs.drain(..)) {
+       for (i, (expected_route, (path_msgs, next_hop))) in expected_paths.iter().zip(per_path_msgs.drain(..)).enumerate() {
                let mut next_msgs = Some(path_msgs);
                let mut expected_next_node = next_hop;
 
@@ -2251,10 +2264,10 @@ pub fn do_claim_payment_along_route<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>,
                        }
                }
                macro_rules! mid_update_fulfill_dance {
-                       ($node: expr, $prev_node: expr, $next_node: expr, $new_msgs: expr) => {
+                       ($idx: expr, $node: expr, $prev_node: expr, $next_node: expr, $new_msgs: expr) => {
                                {
                                        $node.node.handle_update_fulfill_htlc(&$prev_node.node.get_our_node_id(), &next_msgs.as_ref().unwrap().0);
-                                       let fee = {
+                                       let mut fee = {
                                                let per_peer_state = $node.node.per_peer_state.read().unwrap();
                                                let peer_state = per_peer_state.get(&$prev_node.node.get_our_node_id())
                                                        .unwrap().lock().unwrap();
@@ -2265,6 +2278,7 @@ pub fn do_claim_payment_along_route<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>,
                                                        channel.context.config().forwarding_fee_base_msat
                                                }
                                        };
+                                       if $idx == 1 { fee += expected_extra_fees[i]; }
                                        expect_payment_forwarded!($node, $next_node, $prev_node, Some(fee as u64), false, false);
                                        expected_total_fee_msat += fee as u64;
                                        check_added_monitors!($node, 1);
@@ -2296,7 +2310,7 @@ pub fn do_claim_payment_along_route<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>,
                                } else {
                                        next_node = expected_route[expected_route.len() - 1 - idx - 1];
                                }
-                               mid_update_fulfill_dance!(node, prev_node, next_node, update_next_msgs);
+                               mid_update_fulfill_dance!(idx, node, prev_node, next_node, update_next_msgs);
                        } else {
                                assert!(!update_next_msgs);
                                assert!(node.node.get_and_clear_pending_msg_events().is_empty());
index cba17b5eb38361ada8b790d5982e2bd5495d57eb..7d8d52427f251e7ce16cba67bf948889c35ccd11 100644 (file)
@@ -1393,6 +1393,7 @@ fn test_fee_spike_violation_fails_htlc() {
                payment_hash: payment_hash,
                cltv_expiry: htlc_cltv,
                onion_routing_packet: onion_packet,
+               skimmed_fee_msat: None,
        };
 
        nodes[1].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &msg);
@@ -1582,6 +1583,7 @@ fn test_chan_reserve_violation_inbound_htlc_outbound_channel() {
                payment_hash: payment_hash,
                cltv_expiry: htlc_cltv,
                onion_routing_packet: onion_packet,
+               skimmed_fee_msat: None,
        };
 
        nodes[0].node.handle_update_add_htlc(&nodes[1].node.get_our_node_id(), &msg);
@@ -1758,6 +1760,7 @@ fn test_chan_reserve_violation_inbound_htlc_inbound_chan() {
                payment_hash: our_payment_hash_1,
                cltv_expiry: htlc_cltv,
                onion_routing_packet: onion_packet,
+               skimmed_fee_msat: None,
        };
 
        nodes[1].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &msg);
@@ -3410,6 +3413,7 @@ fn fail_backward_pending_htlc_upon_channel_failure() {
                        payment_hash,
                        cltv_expiry,
                        onion_routing_packet,
+                       skimmed_fee_msat: None,
                };
                nodes[0].node.handle_update_add_htlc(&nodes[1].node.get_our_node_id(), &update_add_htlc);
        }
@@ -6259,6 +6263,7 @@ fn test_update_add_htlc_bolt2_receiver_check_max_htlc_limit() {
                payment_hash: our_payment_hash,
                cltv_expiry: htlc_cltv,
                onion_routing_packet: onion_packet.clone(),
+               skimmed_fee_msat: None,
        };
 
        for i in 0..50 {
index 3dd4a6da70ccda8bde8a2e22f50db9c12e12d9cd..672c6ae6e96e8387cd840ae97f19080a5d4d8016 100644 (file)
@@ -610,6 +610,11 @@ pub struct UpdateAddHTLC {
        pub payment_hash: PaymentHash,
        /// The expiry height of the HTLC
        pub cltv_expiry: u32,
+       /// The extra fee skimmed by the sender of this message. See
+       /// [`ChannelConfig::accept_underpaying_htlcs`].
+       ///
+       /// [`ChannelConfig::accept_underpaying_htlcs`]: crate::util::config::ChannelConfig::accept_underpaying_htlcs
+       pub skimmed_fee_msat: Option<u64>,
        pub(crate) onion_routing_packet: OnionPacket,
 }
 
@@ -1903,8 +1908,10 @@ impl_writeable_msg!(UpdateAddHTLC, {
        amount_msat,
        payment_hash,
        cltv_expiry,
-       onion_routing_packet
-}, {});
+       onion_routing_packet,
+}, {
+       (65537, skimmed_fee_msat, option)
+});
 
 impl Readable for OnionMessage {
        fn read<R: Read>(r: &mut R) -> Result<Self, DecodeError> {
@@ -3330,7 +3337,8 @@ mod tests {
                        amount_msat: 3608586615801332854,
                        payment_hash: PaymentHash([1; 32]),
                        cltv_expiry: 821716,
-                       onion_routing_packet
+                       onion_routing_packet,
+                       skimmed_fee_msat: None,
                };
                let encoded_value = update_add_htlc.encode();
                let target_value = hex::decode("020202020202020202020202020202020202020202020202020202020202020200083a840000034d32144668701144760101010101010101010101010101010101010101010101010101010101010101000c89d4ff031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010202020202020202020202020202020202020202020202020202020202020202").unwrap();
index 90c7ad7625ceb85dd4cbc318a755e5925ef55390..fa607f680981f3d68d81229834ad1becb6ed8b66 100644 (file)
@@ -1736,6 +1736,133 @@ fn do_test_intercepted_payment(test: InterceptTest) {
        }
 }
 
+#[test]
+fn accept_underpaying_htlcs_config() {
+       do_accept_underpaying_htlcs_config(1);
+       do_accept_underpaying_htlcs_config(2);
+       do_accept_underpaying_htlcs_config(3);
+}
+
+fn do_accept_underpaying_htlcs_config(num_mpp_parts: usize) {
+       let chanmon_cfgs = create_chanmon_cfgs(3);
+       let node_cfgs = create_node_cfgs(3, &chanmon_cfgs);
+       let mut intercept_forwards_config = test_default_channel_config();
+       intercept_forwards_config.accept_intercept_htlcs = true;
+       let mut underpay_config = test_default_channel_config();
+       underpay_config.channel_config.accept_underpaying_htlcs = true;
+       let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, Some(intercept_forwards_config), Some(underpay_config)]);
+       let nodes = create_network(3, &node_cfgs, &node_chanmgrs);
+
+       let mut chan_ids = Vec::new();
+       for _ in 0..num_mpp_parts {
+               let _ = create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 10_000, 0);
+               let channel_id = create_unannounced_chan_between_nodes_with_value(&nodes, 1, 2, 2_000_000, 0).0.channel_id;
+               chan_ids.push(channel_id);
+       }
+
+       // Send the initial payment.
+       let amt_msat = 900_000;
+       let skimmed_fee_msat = 20;
+       let mut route_hints = Vec::new();
+       for _ in 0..num_mpp_parts {
+               route_hints.push(RouteHint(vec![RouteHintHop {
+                       src_node_id: nodes[1].node.get_our_node_id(),
+                       short_channel_id: nodes[1].node.get_intercept_scid(),
+                       fees: RoutingFees {
+                               base_msat: 1000,
+                               proportional_millionths: 0,
+                       },
+                       cltv_expiry_delta: MIN_CLTV_EXPIRY_DELTA,
+                       htlc_minimum_msat: None,
+                       htlc_maximum_msat: Some(amt_msat / num_mpp_parts as u64 + 5),
+               }]));
+       }
+       let payment_params = PaymentParameters::from_node_id(nodes[2].node.get_our_node_id(), TEST_FINAL_CLTV)
+               .with_route_hints(route_hints).unwrap()
+               .with_bolt11_features(nodes[2].node.invoice_features()).unwrap();
+       let route_params = RouteParameters {
+               payment_params,
+               final_value_msat: amt_msat,
+       };
+       let (payment_hash, payment_secret) = nodes[2].node.create_inbound_payment(Some(amt_msat), 60 * 60, None).unwrap();
+       nodes[0].node.send_payment(payment_hash, RecipientOnionFields::secret_only(payment_secret),
+               PaymentId(payment_hash.0), route_params, Retry::Attempts(0)).unwrap();
+       check_added_monitors!(nodes[0], num_mpp_parts); // one monitor per path
+       let mut events: Vec<SendEvent> = nodes[0].node.get_and_clear_pending_msg_events().into_iter().map(|e| SendEvent::from_event(e)).collect();
+       assert_eq!(events.len(), num_mpp_parts);
+
+       // Forward the intercepted payments.
+       for (idx, ev) in events.into_iter().enumerate() {
+               nodes[1].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &ev.msgs[0]);
+               do_commitment_signed_dance(&nodes[1], &nodes[0], &ev.commitment_msg, false, true);
+
+               let events = nodes[1].node.get_and_clear_pending_events();
+               assert_eq!(events.len(), 1);
+               let (intercept_id, expected_outbound_amt_msat) = match events[0] {
+                       crate::events::Event::HTLCIntercepted {
+                               intercept_id, expected_outbound_amount_msat, payment_hash: pmt_hash, ..
+                       } => {
+                               assert_eq!(pmt_hash, payment_hash);
+                               (intercept_id, expected_outbound_amount_msat)
+                       },
+                       _ => panic!()
+               };
+               nodes[1].node.forward_intercepted_htlc(intercept_id, &chan_ids[idx],
+                       nodes[2].node.get_our_node_id(), expected_outbound_amt_msat - skimmed_fee_msat).unwrap();
+               expect_pending_htlcs_forwardable!(nodes[1]);
+               let payment_event = {
+                       {
+                               let mut added_monitors = nodes[1].chain_monitor.added_monitors.lock().unwrap();
+                               assert_eq!(added_monitors.len(), 1);
+                               added_monitors.clear();
+                       }
+                       let mut events = nodes[1].node.get_and_clear_pending_msg_events();
+                       assert_eq!(events.len(), 1);
+                       SendEvent::from_event(events.remove(0))
+               };
+               nodes[2].node.handle_update_add_htlc(&nodes[1].node.get_our_node_id(), &payment_event.msgs[0]);
+               do_commitment_signed_dance(&nodes[2], &nodes[1], &payment_event.commitment_msg, false, true);
+               if idx == num_mpp_parts - 1 {
+                       expect_pending_htlcs_forwardable!(nodes[2]);
+               }
+       }
+
+       // Claim the payment and check that the skimmed fee is as expected.
+       let payment_preimage = nodes[2].node.get_payment_preimage(payment_hash, payment_secret).unwrap();
+       let events = nodes[2].node.get_and_clear_pending_events();
+       assert_eq!(events.len(), 1);
+       match events[0] {
+               crate::events::Event::PaymentClaimable {
+                       ref payment_hash, ref purpose, amount_msat, counterparty_skimmed_fee_msat, receiver_node_id, ..
+               } => {
+                       assert_eq!(payment_hash, payment_hash);
+                       assert_eq!(amt_msat - skimmed_fee_msat * num_mpp_parts as u64, amount_msat);
+                       assert_eq!(skimmed_fee_msat * num_mpp_parts as u64, counterparty_skimmed_fee_msat);
+                       assert_eq!(nodes[2].node.get_our_node_id(), receiver_node_id.unwrap());
+                       match purpose {
+                               crate::events::PaymentPurpose::InvoicePayment { payment_preimage: ev_payment_preimage,
+                                       payment_secret: ev_payment_secret, .. } =>
+                               {
+                                       assert_eq!(payment_preimage, ev_payment_preimage.unwrap());
+                                       assert_eq!(payment_secret, *ev_payment_secret);
+                               },
+                               _ => panic!(),
+                       }
+               },
+               _ => panic!("Unexpected event"),
+       }
+       let mut expected_paths_vecs = Vec::new();
+       let mut expected_paths = Vec::new();
+       for _ in 0..num_mpp_parts { expected_paths_vecs.push(vec!(&nodes[1], &nodes[2])); }
+       for i in 0..num_mpp_parts { expected_paths.push(&expected_paths_vecs[i][..]); }
+       let total_fee_msat = do_claim_payment_along_route_with_extra_penultimate_hop_fees(
+               &nodes[0], &expected_paths[..], &vec![skimmed_fee_msat as u32; num_mpp_parts][..], false,
+               payment_preimage);
+       // The sender doesn't know that the penultimate hop took an extra fee.
+       expect_payment_sent(&nodes[0], payment_preimage,
+               Some(Some(total_fee_msat - skimmed_fee_msat * num_mpp_parts as u64)), true);
+}
+
 #[derive(PartialEq)]
 enum AutoRetry {
        Success,
index 4d1398644ce0d24483205b0ac04e428ba3d52482..fb1f78fd64445c406d582830eebd97d4c107c16b 100644 (file)
@@ -31,7 +31,7 @@
 //! # use lightning::offers::invoice::BlindedPayInfo;
 //! # use lightning::blinded_path::BlindedPath;
 //! #
-//! # fn create_payment_paths() -> Vec<(BlindedPath, BlindedPayInfo)> { unimplemented!() }
+//! # fn create_payment_paths() -> Vec<(BlindedPayInfo, BlindedPath)> { unimplemented!() }
 //! # fn create_payment_hash() -> PaymentHash { unimplemented!() }
 //! #
 //! # fn parse_invoice_request(bytes: Vec<u8>) -> Result<(), lightning::offers::parse::ParseError> {
@@ -166,7 +166,7 @@ impl SigningPubkeyStrategy for DerivedSigningPubkey {}
 
 impl<'a> InvoiceBuilder<'a, ExplicitSigningPubkey> {
        pub(super) fn for_offer(
-               invoice_request: &'a InvoiceRequest, payment_paths: Vec<(BlindedPath, BlindedPayInfo)>,
+               invoice_request: &'a InvoiceRequest, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>,
                created_at: Duration, payment_hash: PaymentHash
        ) -> Result<Self, SemanticError> {
                let amount_msats = Self::check_amount_msats(invoice_request)?;
@@ -182,7 +182,7 @@ impl<'a> InvoiceBuilder<'a, ExplicitSigningPubkey> {
        }
 
        pub(super) fn for_refund(
-               refund: &'a Refund, payment_paths: Vec<(BlindedPath, BlindedPayInfo)>, created_at: Duration,
+               refund: &'a Refund, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, created_at: Duration,
                payment_hash: PaymentHash, signing_pubkey: PublicKey
        ) -> Result<Self, SemanticError> {
                let amount_msats = refund.amount_msats();
@@ -199,7 +199,7 @@ impl<'a> InvoiceBuilder<'a, ExplicitSigningPubkey> {
 
 impl<'a> InvoiceBuilder<'a, DerivedSigningPubkey> {
        pub(super) fn for_offer_using_keys(
-               invoice_request: &'a InvoiceRequest, payment_paths: Vec<(BlindedPath, BlindedPayInfo)>,
+               invoice_request: &'a InvoiceRequest, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>,
                created_at: Duration, payment_hash: PaymentHash, keys: KeyPair
        ) -> Result<Self, SemanticError> {
                let amount_msats = Self::check_amount_msats(invoice_request)?;
@@ -215,7 +215,7 @@ impl<'a> InvoiceBuilder<'a, DerivedSigningPubkey> {
        }
 
        pub(super) fn for_refund_using_keys(
-               refund: &'a Refund, payment_paths: Vec<(BlindedPath, BlindedPayInfo)>, created_at: Duration,
+               refund: &'a Refund, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, created_at: Duration,
                payment_hash: PaymentHash, keys: KeyPair,
        ) -> Result<Self, SemanticError> {
                let amount_msats = refund.amount_msats();
@@ -247,7 +247,7 @@ impl<'a, S: SigningPubkeyStrategy> InvoiceBuilder<'a, S> {
        }
 
        fn fields(
-               payment_paths: Vec<(BlindedPath, BlindedPayInfo)>, created_at: Duration,
+               payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, created_at: Duration,
                payment_hash: PaymentHash, amount_msats: u64, signing_pubkey: PublicKey
        ) -> InvoiceFields {
                InvoiceFields {
@@ -454,7 +454,7 @@ enum InvoiceContents {
 /// Invoice-specific fields for an `invoice` message.
 #[derive(Clone, Debug, PartialEq)]
 struct InvoiceFields {
-       payment_paths: Vec<(BlindedPath, BlindedPayInfo)>,
+       payment_paths: Vec<(BlindedPayInfo, BlindedPath)>,
        created_at: Duration,
        relative_expiry: Option<Duration>,
        payment_hash: PaymentHash,
@@ -476,7 +476,7 @@ impl Invoice {
        ///
        /// Blinded paths provide recipient privacy by obfuscating its node id. Note, however, that this
        /// privacy is lost if a public node id is used for [`Invoice::signing_pubkey`].
-       pub fn payment_paths(&self) -> &[(BlindedPath, BlindedPayInfo)] {
+       pub fn payment_paths(&self) -> &[(BlindedPayInfo, BlindedPath)] {
                &self.contents.fields().payment_paths[..]
        }
 
@@ -703,8 +703,8 @@ impl InvoiceFields {
                };
 
                InvoiceTlvStreamRef {
-                       paths: Some(Iterable(self.payment_paths.iter().map(|(path, _)| path))),
-                       blindedpay: Some(Iterable(self.payment_paths.iter().map(|(_, payinfo)| payinfo))),
+                       paths: Some(Iterable(self.payment_paths.iter().map(|(_, path)| path))),
+                       blindedpay: Some(Iterable(self.payment_paths.iter().map(|(payinfo, _)| payinfo))),
                        created_at: Some(self.created_at.as_secs()),
                        relative_expiry: self.relative_expiry.map(|duration| duration.as_secs() as u32),
                        payment_hash: Some(&self.payment_hash),
@@ -750,13 +750,13 @@ tlv_stream!(InvoiceTlvStream, InvoiceTlvStreamRef, 160..240, {
 });
 
 type BlindedPathIter<'a> = core::iter::Map<
-       core::slice::Iter<'a, (BlindedPath, BlindedPayInfo)>,
-       for<'r> fn(&'r (BlindedPath, BlindedPayInfo)) -> &'r BlindedPath,
+       core::slice::Iter<'a, (BlindedPayInfo, BlindedPath)>,
+       for<'r> fn(&'r (BlindedPayInfo, BlindedPath)) -> &'r BlindedPath,
 >;
 
 type BlindedPayInfoIter<'a> = core::iter::Map<
-       core::slice::Iter<'a, (BlindedPath, BlindedPayInfo)>,
-       for<'r> fn(&'r (BlindedPath, BlindedPayInfo)) -> &'r BlindedPayInfo,
+       core::slice::Iter<'a, (BlindedPayInfo, BlindedPath)>,
+       for<'r> fn(&'r (BlindedPayInfo, BlindedPath)) -> &'r BlindedPayInfo,
 >;
 
 /// Information needed to route a payment across a [`BlindedPath`].
@@ -878,15 +878,15 @@ impl TryFrom<PartialInvoiceTlvStream> for InvoiceContents {
                        },
                ) = tlv_stream;
 
-               let payment_paths = match (paths, blindedpay) {
-                       (None, _) => return Err(SemanticError::MissingPaths),
-                       (_, None) => return Err(SemanticError::InvalidPayInfo),
-                       (Some(paths), _) if paths.is_empty() => return Err(SemanticError::MissingPaths),
-                       (Some(paths), Some(blindedpay)) if paths.len() != blindedpay.len() => {
+               let payment_paths = match (blindedpay, paths) {
+                       (_, None) => return Err(SemanticError::MissingPaths),
+                       (None, _) => return Err(SemanticError::InvalidPayInfo),
+                       (_, Some(paths)) if paths.is_empty() => return Err(SemanticError::MissingPaths),
+                       (Some(blindedpay), Some(paths)) if paths.len() != blindedpay.len() => {
                                return Err(SemanticError::InvalidPayInfo);
                        },
-                       (Some(paths), Some(blindedpay)) => {
-                               paths.into_iter().zip(blindedpay.into_iter()).collect::<Vec<_>>()
+                       (Some(blindedpay), Some(paths)) => {
+                               blindedpay.into_iter().zip(paths.into_iter()).collect::<Vec<_>>()
                        },
                };
 
@@ -1052,8 +1052,8 @@ mod tests {
                                        payer_note: None,
                                },
                                InvoiceTlvStreamRef {
-                                       paths: Some(Iterable(payment_paths.iter().map(|(path, _)| path))),
-                                       blindedpay: Some(Iterable(payment_paths.iter().map(|(_, payinfo)| payinfo))),
+                                       paths: Some(Iterable(payment_paths.iter().map(|(_, path)| path))),
+                                       blindedpay: Some(Iterable(payment_paths.iter().map(|(payinfo, _)| payinfo))),
                                        created_at: Some(now.as_secs()),
                                        relative_expiry: None,
                                        payment_hash: Some(&payment_hash),
@@ -1130,8 +1130,8 @@ mod tests {
                                        payer_note: None,
                                },
                                InvoiceTlvStreamRef {
-                                       paths: Some(Iterable(payment_paths.iter().map(|(path, _)| path))),
-                                       blindedpay: Some(Iterable(payment_paths.iter().map(|(_, payinfo)| payinfo))),
+                                       paths: Some(Iterable(payment_paths.iter().map(|(_, path)| path))),
+                                       blindedpay: Some(Iterable(payment_paths.iter().map(|(payinfo, _)| payinfo))),
                                        created_at: Some(now.as_secs()),
                                        relative_expiry: None,
                                        payment_hash: Some(&payment_hash),
@@ -1516,7 +1516,7 @@ mod tests {
 
                let empty_payment_paths = vec![];
                let mut tlv_stream = invoice.as_tlv_stream();
-               tlv_stream.3.paths = Some(Iterable(empty_payment_paths.iter().map(|(path, _)| path)));
+               tlv_stream.3.paths = Some(Iterable(empty_payment_paths.iter().map(|(_, path)| path)));
 
                match Invoice::try_from(tlv_stream.to_bytes()) {
                        Ok(_) => panic!("expected error"),
@@ -1526,7 +1526,7 @@ mod tests {
                let mut payment_paths = payment_paths();
                payment_paths.pop();
                let mut tlv_stream = invoice.as_tlv_stream();
-               tlv_stream.3.blindedpay = Some(Iterable(payment_paths.iter().map(|(_, payinfo)| payinfo)));
+               tlv_stream.3.blindedpay = Some(Iterable(payment_paths.iter().map(|(payinfo, _)| payinfo)));
 
                match Invoice::try_from(tlv_stream.to_bytes()) {
                        Ok(_) => panic!("expected error"),
index ed884848f4a015a85f365a8801998afc10801224..f51d2526781270d9e17b059b2a71fdc3bd1b04ad 100644 (file)
@@ -480,7 +480,7 @@ impl InvoiceRequest {
        /// [`Duration`]: core::time::Duration
        #[cfg(feature = "std")]
        pub fn respond_with(
-               &self, payment_paths: Vec<(BlindedPath, BlindedPayInfo)>, payment_hash: PaymentHash
+               &self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash
        ) -> Result<InvoiceBuilder<ExplicitSigningPubkey>, SemanticError> {
                let created_at = std::time::SystemTime::now()
                        .duration_since(std::time::SystemTime::UNIX_EPOCH)
@@ -509,7 +509,7 @@ impl InvoiceRequest {
        ///
        /// [`Invoice::created_at`]: crate::offers::invoice::Invoice::created_at
        pub fn respond_with_no_std(
-               &self, payment_paths: Vec<(BlindedPath, BlindedPayInfo)>, payment_hash: PaymentHash,
+               &self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash,
                created_at: core::time::Duration
        ) -> Result<InvoiceBuilder<ExplicitSigningPubkey>, SemanticError> {
                if self.features().requires_unknown_bits() {
@@ -530,7 +530,7 @@ impl InvoiceRequest {
        /// [`Invoice`]: crate::offers::invoice::Invoice
        #[cfg(feature = "std")]
        pub fn verify_and_respond_using_derived_keys<T: secp256k1::Signing>(
-               &self, payment_paths: Vec<(BlindedPath, BlindedPayInfo)>, payment_hash: PaymentHash,
+               &self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash,
                expanded_key: &ExpandedKey, secp_ctx: &Secp256k1<T>
        ) -> Result<InvoiceBuilder<DerivedSigningPubkey>, SemanticError> {
                let created_at = std::time::SystemTime::now()
@@ -552,7 +552,7 @@ impl InvoiceRequest {
        ///
        /// [`Invoice`]: crate::offers::invoice::Invoice
        pub fn verify_and_respond_using_derived_keys_no_std<T: secp256k1::Signing>(
-               &self, payment_paths: Vec<(BlindedPath, BlindedPayInfo)>, payment_hash: PaymentHash,
+               &self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash,
                created_at: core::time::Duration, expanded_key: &ExpandedKey, secp_ctx: &Secp256k1<T>
        ) -> Result<InvoiceBuilder<DerivedSigningPubkey>, SemanticError> {
                if self.features().requires_unknown_bits() {
index 8fbc47d122cc88983694d0c5235b58e33e970169..07e759917acc54d5973c613e3e8d9826297f805c 100644 (file)
@@ -394,7 +394,7 @@ impl Refund {
        /// [`Duration`]: core::time::Duration
        #[cfg(feature = "std")]
        pub fn respond_with(
-               &self, payment_paths: Vec<(BlindedPath, BlindedPayInfo)>, payment_hash: PaymentHash,
+               &self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash,
                signing_pubkey: PublicKey,
        ) -> Result<InvoiceBuilder<ExplicitSigningPubkey>, SemanticError> {
                let created_at = std::time::SystemTime::now()
@@ -427,7 +427,7 @@ impl Refund {
        ///
        /// [`Invoice::created_at`]: crate::offers::invoice::Invoice::created_at
        pub fn respond_with_no_std(
-               &self, payment_paths: Vec<(BlindedPath, BlindedPayInfo)>, payment_hash: PaymentHash,
+               &self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash,
                signing_pubkey: PublicKey, created_at: Duration
        ) -> Result<InvoiceBuilder<ExplicitSigningPubkey>, SemanticError> {
                if self.features().requires_unknown_bits() {
@@ -447,7 +447,7 @@ impl Refund {
        /// [`Invoice`]: crate::offers::invoice::Invoice
        #[cfg(feature = "std")]
        pub fn respond_using_derived_keys<ES: Deref>(
-               &self, payment_paths: Vec<(BlindedPath, BlindedPayInfo)>, payment_hash: PaymentHash,
+               &self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash,
                expanded_key: &ExpandedKey, entropy_source: ES
        ) -> Result<InvoiceBuilder<DerivedSigningPubkey>, SemanticError>
        where
@@ -471,7 +471,7 @@ impl Refund {
        ///
        /// [`Invoice`]: crate::offers::invoice::Invoice
        pub fn respond_using_derived_keys_no_std<ES: Deref>(
-               &self, payment_paths: Vec<(BlindedPath, BlindedPayInfo)>, payment_hash: PaymentHash,
+               &self, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, payment_hash: PaymentHash,
                created_at: core::time::Duration, expanded_key: &ExpandedKey, entropy_source: ES
        ) -> Result<InvoiceBuilder<DerivedSigningPubkey>, SemanticError>
        where
index 26c0d051223a6c26787055257a187fdfd95f1da7..230c6aa1628d96a180276d228d5742369062886f 100644 (file)
@@ -58,7 +58,7 @@ pub(super) fn privkey(byte: u8) -> SecretKey {
        SecretKey::from_slice(&[byte; 32]).unwrap()
 }
 
-pub(super) fn payment_paths() -> Vec<(BlindedPath, BlindedPayInfo)> {
+pub(super) fn payment_paths() -> Vec<(BlindedPayInfo, BlindedPath)> {
        let paths = vec![
                BlindedPath {
                        introduction_node_id: pubkey(40),
@@ -97,7 +97,7 @@ pub(super) fn payment_paths() -> Vec<(BlindedPath, BlindedPayInfo)> {
                },
        ];
 
-       paths.into_iter().zip(payinfo.into_iter()).collect()
+       payinfo.into_iter().zip(paths.into_iter()).collect()
 }
 
 pub(super) fn payment_hash() -> PaymentHash {
index f7ed173b901a90a9dbe0d81166dfb188b77ccec9..7342e813a9ed906d4f6a45412203106a7445c59e 100644 (file)
@@ -18,7 +18,7 @@ use crate::ln::PaymentHash;
 use crate::ln::channelmanager::{ChannelDetails, PaymentId};
 use crate::ln::features::{Bolt12InvoiceFeatures, ChannelFeatures, InvoiceFeatures, NodeFeatures};
 use crate::ln::msgs::{DecodeError, ErrorAction, LightningError, MAX_VALUE_MSAT};
-use crate::offers::invoice::BlindedPayInfo;
+use crate::offers::invoice::{BlindedPayInfo, Invoice as Bolt12Invoice};
 use crate::routing::gossip::{DirectedChannelInfo, EffectiveCapacity, ReadOnlyNetworkGraph, NetworkGraph, NodeId, RoutingFees};
 use crate::routing::scoring::{ChannelUsage, LockableScore, Score};
 use crate::util::ser::{Writeable, Readable, ReadableArgs, Writer};
@@ -481,6 +481,8 @@ pub const DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA: u32 = 1008;
 // limits, but for now more than 10 paths likely carries too much one-path failure.
 pub const DEFAULT_MAX_PATH_COUNT: u8 = 10;
 
+const DEFAULT_MAX_CHANNEL_SATURATION_POW_HALF: u8 = 2;
+
 // The median hop CLTV expiry delta currently seen in the network.
 const MEDIAN_HOP_CLTV_EXPIRY_DELTA: u32 = 40;
 
@@ -567,7 +569,7 @@ impl ReadableArgs<u32> for PaymentParameters {
                        (2, features, (option: ReadableArgs, payee_pubkey.is_some())),
                        (3, max_path_count, (default_value, DEFAULT_MAX_PATH_COUNT)),
                        (4, route_hints, vec_type),
-                       (5, max_channel_saturation_power_of_half, (default_value, 2)),
+                       (5, max_channel_saturation_power_of_half, (default_value, DEFAULT_MAX_CHANNEL_SATURATION_POW_HALF)),
                        (6, expiry_time, option),
                        (7, previously_failed_channels, vec_type),
                        (8, blinded_route_hints, optional_vec),
@@ -612,7 +614,7 @@ impl PaymentParameters {
                        expiry_time: None,
                        max_total_cltv_expiry_delta: DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA,
                        max_path_count: DEFAULT_MAX_PATH_COUNT,
-                       max_channel_saturation_power_of_half: 2,
+                       max_channel_saturation_power_of_half: DEFAULT_MAX_CHANNEL_SATURATION_POW_HALF,
                        previously_failed_channels: Vec::new(),
                }
        }
@@ -634,8 +636,40 @@ impl PaymentParameters {
                        .expect("PaymentParameters::from_node_id should always initialize the payee as unblinded")
        }
 
-       /// Includes the payee's features. Errors if the parameters were initialized with blinded payment
-       /// paths.
+       /// Creates parameters for paying to a blinded payee from the provided invoice. Sets
+       /// [`Payee::Blinded::route_hints`], [`Payee::Blinded::features`], and
+       /// [`PaymentParameters::expiry_time`].
+       pub fn from_bolt12_invoice(invoice: &Bolt12Invoice) -> Self {
+               Self::blinded(invoice.payment_paths().to_vec())
+                       .with_bolt12_features(invoice.features().clone()).unwrap()
+                       .with_expiry_time(invoice.created_at().as_secs().saturating_add(invoice.relative_expiry().as_secs()))
+       }
+
+       fn blinded(blinded_route_hints: Vec<(BlindedPayInfo, BlindedPath)>) -> Self {
+               Self {
+                       payee: Payee::Blinded { route_hints: blinded_route_hints, features: None },
+                       expiry_time: None,
+                       max_total_cltv_expiry_delta: DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA,
+                       max_path_count: DEFAULT_MAX_PATH_COUNT,
+                       max_channel_saturation_power_of_half: DEFAULT_MAX_CHANNEL_SATURATION_POW_HALF,
+                       previously_failed_channels: Vec::new(),
+               }
+       }
+
+       /// Includes the payee's features. Errors if the parameters were not initialized with
+       /// [`PaymentParameters::from_bolt12_invoice`].
+       ///
+       /// This is not exported to bindings users since bindings don't support move semantics
+       pub fn with_bolt12_features(self, features: Bolt12InvoiceFeatures) -> Result<Self, ()> {
+               match self.payee {
+                       Payee::Clear { .. } => Err(()),
+                       Payee::Blinded { route_hints, .. } =>
+                               Ok(Self { payee: Payee::Blinded { route_hints, features: Some(features) }, ..self })
+               }
+       }
+
+       /// Includes the payee's features. Errors if the parameters were initialized with
+       /// [`PaymentParameters::from_bolt12_invoice`].
        ///
        /// This is not exported to bindings users since bindings don't support move semantics
        pub fn with_bolt11_features(self, features: InvoiceFeatures) -> Result<Self, ()> {
@@ -651,7 +685,7 @@ impl PaymentParameters {
        }
 
        /// Includes hints for routing to the payee. Errors if the parameters were initialized with
-       /// blinded payment paths.
+       /// [`PaymentParameters::from_bolt12_invoice`].
        ///
        /// This is not exported to bindings users since bindings don't support move semantics
        pub fn with_route_hints(self, route_hints: Vec<RouteHint>) -> Result<Self, ()> {
@@ -687,7 +721,8 @@ impl PaymentParameters {
                Self { max_path_count, ..self }
        }
 
-       /// Includes a limit for the maximum number of payment paths that may be used.
+       /// Includes a limit for the maximum share of a channel's total capacity that can be sent over, as
+       /// a power of 1/2. See [`PaymentParameters::max_channel_saturation_power_of_half`].
        ///
        /// This is not exported to bindings users since bindings don't support move semantics
        pub fn with_max_channel_saturation_power_of_half(self, max_channel_saturation_power_of_half: u8) -> Self {
@@ -760,6 +795,19 @@ impl Payee {
                        _ => None,
                }
        }
+       fn blinded_route_hints(&self) -> &[(BlindedPayInfo, BlindedPath)] {
+               match self {
+                       Self::Blinded { route_hints, .. } => &route_hints[..],
+                       Self::Clear { .. } => &[]
+               }
+       }
+
+       fn unblinded_route_hints(&self) -> &[RouteHint] {
+               match self {
+                       Self::Blinded { .. } => &[],
+                       Self::Clear { route_hints, .. } => &route_hints[..]
+               }
+       }
 }
 
 enum FeaturesRef<'a> {
@@ -904,18 +952,34 @@ enum CandidateRouteHop<'a> {
                info: DirectedChannelInfo<'a>,
                short_channel_id: u64,
        },
-       /// A hop to the payee found in the payment invoice, though not necessarily a direct channel.
+       /// A hop to the payee found in the BOLT 11 payment invoice, though not necessarily a direct
+       /// channel.
        PrivateHop {
                hint: &'a RouteHintHop,
-       }
+       },
+       /// The payee's identity is concealed behind blinded paths provided in a BOLT 12 invoice.
+       Blinded {
+               hint: &'a (BlindedPayInfo, BlindedPath),
+               hint_idx: usize,
+       },
+       /// Similar to [`Self::Blinded`], but the path here has 1 blinded hop. `BlindedPayInfo` provided
+       /// for 1-hop blinded paths is ignored because it is meant to apply to the hops *between* the
+       /// introduction node and the destination. Useful for tracking that we need to include a blinded
+       /// path at the end of our [`Route`].
+       OneHopBlinded {
+               hint: &'a (BlindedPayInfo, BlindedPath),
+               hint_idx: usize,
+       },
 }
 
 impl<'a> CandidateRouteHop<'a> {
-       fn short_channel_id(&self) -> u64 {
+       fn short_channel_id(&self) -> Option<u64> {
                match self {
-                       CandidateRouteHop::FirstHop { details } => details.get_outbound_payment_scid().unwrap(),
-                       CandidateRouteHop::PublicHop { short_channel_id, .. } => *short_channel_id,
-                       CandidateRouteHop::PrivateHop { hint } => hint.short_channel_id,
+                       CandidateRouteHop::FirstHop { details } => Some(details.get_outbound_payment_scid().unwrap()),
+                       CandidateRouteHop::PublicHop { short_channel_id, .. } => Some(*short_channel_id),
+                       CandidateRouteHop::PrivateHop { hint } => Some(hint.short_channel_id),
+                       CandidateRouteHop::Blinded { .. } => None,
+                       CandidateRouteHop::OneHopBlinded { .. } => None,
                }
        }
 
@@ -925,6 +989,8 @@ impl<'a> CandidateRouteHop<'a> {
                        CandidateRouteHop::FirstHop { details } => details.counterparty.features.to_context(),
                        CandidateRouteHop::PublicHop { info, .. } => info.channel().features.clone(),
                        CandidateRouteHop::PrivateHop { .. } => ChannelFeatures::empty(),
+                       CandidateRouteHop::Blinded { .. } => ChannelFeatures::empty(),
+                       CandidateRouteHop::OneHopBlinded { .. } => ChannelFeatures::empty(),
                }
        }
 
@@ -933,6 +999,8 @@ impl<'a> CandidateRouteHop<'a> {
                        CandidateRouteHop::FirstHop { .. } => 0,
                        CandidateRouteHop::PublicHop { info, .. } => info.direction().cltv_expiry_delta as u32,
                        CandidateRouteHop::PrivateHop { hint } => hint.cltv_expiry_delta as u32,
+                       CandidateRouteHop::Blinded { hint, .. } => hint.0.cltv_expiry_delta as u32,
+                       CandidateRouteHop::OneHopBlinded { .. } => 0,
                }
        }
 
@@ -941,6 +1009,8 @@ impl<'a> CandidateRouteHop<'a> {
                        CandidateRouteHop::FirstHop { details } => details.next_outbound_htlc_minimum_msat,
                        CandidateRouteHop::PublicHop { info, .. } => info.direction().htlc_minimum_msat,
                        CandidateRouteHop::PrivateHop { hint } => hint.htlc_minimum_msat.unwrap_or(0),
+                       CandidateRouteHop::Blinded { hint, .. } => hint.0.htlc_minimum_msat,
+                       CandidateRouteHop::OneHopBlinded { .. } => 0,
                }
        }
 
@@ -951,6 +1021,14 @@ impl<'a> CandidateRouteHop<'a> {
                        },
                        CandidateRouteHop::PublicHop { info, .. } => info.direction().fees,
                        CandidateRouteHop::PrivateHop { hint } => hint.fees,
+                       CandidateRouteHop::Blinded { hint, .. } => {
+                               RoutingFees {
+                                       base_msat: hint.0.fee_base_msat,
+                                       proportional_millionths: hint.0.fee_proportional_millionths
+                               }
+                       },
+                       CandidateRouteHop::OneHopBlinded { .. } =>
+                               RoutingFees { base_msat: 0, proportional_millionths: 0 },
                }
        }
 
@@ -964,8 +1042,35 @@ impl<'a> CandidateRouteHop<'a> {
                                EffectiveCapacity::HintMaxHTLC { amount_msat: *max },
                        CandidateRouteHop::PrivateHop { hint: RouteHintHop { htlc_maximum_msat: None, .. }} =>
                                EffectiveCapacity::Infinite,
+                       CandidateRouteHop::Blinded { hint, .. } =>
+                               EffectiveCapacity::HintMaxHTLC { amount_msat: hint.0.htlc_maximum_msat },
+                       CandidateRouteHop::OneHopBlinded { .. } => EffectiveCapacity::Infinite,
+               }
+       }
+
+       fn id(&self, channel_direction: bool /* src_node_id < target_node_id */) -> CandidateHopId {
+               match self {
+                       CandidateRouteHop::Blinded { hint_idx, .. } => CandidateHopId::Blinded(*hint_idx),
+                       CandidateRouteHop::OneHopBlinded { hint_idx, .. } => CandidateHopId::Blinded(*hint_idx),
+                       _ => CandidateHopId::Clear((self.short_channel_id().unwrap(), channel_direction)),
                }
        }
+       fn blinded_path(&self) -> Option<&'a BlindedPath> {
+               match self {
+                       CandidateRouteHop::Blinded { hint, .. } | CandidateRouteHop::OneHopBlinded { hint, .. } => {
+                               Some(&hint.1)
+                       },
+                       _ => None,
+               }
+       }
+}
+
+#[derive(Clone, Copy, Eq, Hash, Ord, PartialOrd, PartialEq)]
+enum CandidateHopId {
+       /// Contains (scid, src_node_id < target_node_id)
+       Clear((u64, bool)),
+       /// Index of the blinded route hint in [`Payee::Blinded::route_hints`].
+       Blinded(usize),
 }
 
 #[inline]
@@ -1207,9 +1312,27 @@ impl fmt::Display for LoggedPayeePubkey {
        }
 }
 
+struct LoggedCandidateHop<'a>(&'a CandidateRouteHop<'a>);
+impl<'a> fmt::Display for LoggedCandidateHop<'a> {
+       fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+               match self.0 {
+                       CandidateRouteHop::Blinded { hint, .. } | CandidateRouteHop::OneHopBlinded { hint, .. } => {
+                               "blinded route hint with introduction node id ".fmt(f)?;
+                               hint.1.introduction_node_id.fmt(f)?;
+                               " and blinding point ".fmt(f)?;
+                               hint.1.blinding_point.fmt(f)
+                       },
+                       _ => {
+                               "SCID ".fmt(f)?;
+                               self.0.short_channel_id().unwrap().fmt(f)
+                       },
+               }
+       }
+}
+
 #[inline]
 fn sort_first_hop_channels(
-       channels: &mut Vec<&ChannelDetails>, used_channel_liquidities: &HashMap<(u64, bool), u64>,
+       channels: &mut Vec<&ChannelDetails>, used_liquidities: &HashMap<CandidateHopId, u64>,
        recommended_value_msat: u64, our_node_pubkey: &PublicKey
 ) {
        // Sort the first_hops channels to the same node(s) in priority order of which channel we'd
@@ -1227,11 +1350,11 @@ fn sort_first_hop_channels(
        // Available outbound balances factor in liquidity already reserved for previously found paths.
        channels.sort_unstable_by(|chan_a, chan_b| {
                let chan_a_outbound_limit_msat = chan_a.next_outbound_htlc_limit_msat
-                       .saturating_sub(*used_channel_liquidities.get(&(chan_a.get_outbound_payment_scid().unwrap(),
-                       our_node_pubkey < &chan_a.counterparty.node_id)).unwrap_or(&0));
+                       .saturating_sub(*used_liquidities.get(&CandidateHopId::Clear((chan_a.get_outbound_payment_scid().unwrap(),
+                       our_node_pubkey < &chan_a.counterparty.node_id))).unwrap_or(&0));
                let chan_b_outbound_limit_msat = chan_b.next_outbound_htlc_limit_msat
-                       .saturating_sub(*used_channel_liquidities.get(&(chan_b.get_outbound_payment_scid().unwrap(),
-                       our_node_pubkey < &chan_b.counterparty.node_id)).unwrap_or(&0));
+                       .saturating_sub(*used_liquidities.get(&CandidateHopId::Clear((chan_b.get_outbound_payment_scid().unwrap(),
+                       our_node_pubkey < &chan_b.counterparty.node_id))).unwrap_or(&0));
                if chan_b_outbound_limit_msat < recommended_value_msat || chan_a_outbound_limit_msat < recommended_value_msat {
                        // Sort in descending order
                        chan_b_outbound_limit_msat.cmp(&chan_a_outbound_limit_msat)
@@ -1295,7 +1418,7 @@ where L::Target: Logger {
        // unblinded payee id as an option. We also need a non-optional "payee id" for path construction,
        // so use a dummy id for this in the blinded case.
        let payee_node_id_opt = payment_params.payee.node_id().map(|pk| NodeId::from_pubkey(&pk));
-       const DUMMY_BLINDED_PAYEE_ID: [u8; 33] = [42u8; 33];
+       const DUMMY_BLINDED_PAYEE_ID: [u8; 33] = [2; 33];
        let maybe_dummy_payee_pk = payment_params.payee.node_id().unwrap_or_else(|| PublicKey::from_slice(&DUMMY_BLINDED_PAYEE_ID).unwrap());
        let maybe_dummy_payee_node_id = NodeId::from_pubkey(&maybe_dummy_payee_pk);
        let our_node_id = NodeId::from_pubkey(&our_node_pubkey);
@@ -1322,8 +1445,23 @@ where L::Target: Logger {
                                }
                        }
                },
-               _ => return Err(LightningError{err: "Routing to blinded paths isn't supported yet".to_owned(), action: ErrorAction::IgnoreError}),
-
+               Payee::Blinded { route_hints, .. } => {
+                       if route_hints.iter().all(|(_, path)| &path.introduction_node_id == our_node_pubkey) {
+                               return Err(LightningError{err: "Cannot generate a route to blinded paths if we are the introduction node to all of them".to_owned(), action: ErrorAction::IgnoreError});
+                       }
+                       for (_, blinded_path) in route_hints.iter() {
+                               if blinded_path.blinded_hops.len() == 0 {
+                                       return Err(LightningError{err: "0-hop blinded path provided".to_owned(), action: ErrorAction::IgnoreError});
+                               } else if &blinded_path.introduction_node_id == our_node_pubkey {
+                                       log_info!(logger, "Got blinded path with ourselves as the introduction node, ignoring");
+                               } else if blinded_path.blinded_hops.len() == 1 &&
+                                       route_hints.iter().any( |(_, p)| p.blinded_hops.len() == 1
+                                               && p.introduction_node_id != blinded_path.introduction_node_id)
+                               {
+                                       return Err(LightningError{err: format!("1-hop blinded paths must all have matching introduction node ids"), action: ErrorAction::IgnoreError});
+                               }
+                       }
+               }
        }
        let final_cltv_expiry_delta = payment_params.payee.final_cltv_expiry_delta().unwrap_or(0);
        if payment_params.max_total_cltv_expiry_delta <= final_cltv_expiry_delta {
@@ -1475,11 +1613,12 @@ where L::Target: Logger {
        // drop the requirement by setting this to 0.
        let mut channel_saturation_pow_half = payment_params.max_channel_saturation_power_of_half;
 
-       // Keep track of how much liquidity has been used in selected channels. Used to determine
-       // if the channel can be used by additional MPP paths or to inform path finding decisions. It is
-       // aware of direction *only* to ensure that the correct htlc_maximum_msat value is used. Hence,
-       // liquidity used in one direction will not offset any used in the opposite direction.
-       let mut used_channel_liquidities: HashMap<(u64, bool), u64> =
+       // Keep track of how much liquidity has been used in selected channels or blinded paths. Used to
+       // determine if the channel can be used by additional MPP paths or to inform path finding
+       // decisions. It is aware of direction *only* to ensure that the correct htlc_maximum_msat value
+       // is used. Hence, liquidity used in one direction will not offset any used in the opposite
+       // direction.
+       let mut used_liquidities: HashMap<CandidateHopId, u64> =
                HashMap::with_capacity(network_nodes.len());
 
        // Keeping track of how much value we already collected across other paths. Helps to decide
@@ -1487,7 +1626,7 @@ where L::Target: Logger {
        let mut already_collected_value_msat = 0;
 
        for (_, channels) in first_hop_targets.iter_mut() {
-               sort_first_hop_channels(channels, &used_channel_liquidities, recommended_value_msat,
+               sort_first_hop_channels(channels, &used_liquidities, recommended_value_msat,
                        our_node_pubkey);
        }
 
@@ -1510,7 +1649,7 @@ where L::Target: Logger {
                        // - for regular channels at channel announcement (TODO)
                        // - for first and last hops early in get_route
                        if $src_node_id != $dest_node_id {
-                               let short_channel_id = $candidate.short_channel_id();
+                               let scid_opt = $candidate.short_channel_id();
                                let effective_capacity = $candidate.effective_capacity();
                                let htlc_maximum_msat = max_htlc_from_capacity(effective_capacity, channel_saturation_pow_half);
 
@@ -1522,8 +1661,8 @@ where L::Target: Logger {
                                // if the amount being transferred over this path is lower.
                                // We do this for now, but this is a subject for removal.
                                if let Some(mut available_value_contribution_msat) = htlc_maximum_msat.checked_sub($next_hops_fee_msat) {
-                                       let used_liquidity_msat = used_channel_liquidities
-                                               .get(&(short_channel_id, $src_node_id < $dest_node_id))
+                                       let used_liquidity_msat = used_liquidities
+                                               .get(&$candidate.id($src_node_id < $dest_node_id))
                                                .map_or(0, |used_liquidity_msat| {
                                                        available_value_contribution_msat = available_value_contribution_msat
                                                                .saturating_sub(*used_liquidity_msat);
@@ -1565,8 +1704,8 @@ where L::Target: Logger {
                                                 (amount_to_transfer_over_msat < $next_hops_path_htlc_minimum_msat &&
                                                  recommended_value_msat > $next_hops_path_htlc_minimum_msat));
 
-                                       let payment_failed_on_this_channel =
-                                               payment_params.previously_failed_channels.contains(&short_channel_id);
+                                       let payment_failed_on_this_channel = scid_opt.map_or(false,
+                                               |scid| payment_params.previously_failed_channels.contains(&scid));
 
                                        // If HTLC minimum is larger than the amount we're going to transfer, we shouldn't
                                        // bother considering this channel. If retrying with recommended_value_msat may
@@ -1635,9 +1774,9 @@ where L::Target: Logger {
                                                                inflight_htlc_msat: used_liquidity_msat,
                                                                effective_capacity,
                                                        };
-                                                       let channel_penalty_msat = scorer.channel_penalty_msat(
-                                                               short_channel_id, &$src_node_id, &$dest_node_id, channel_usage, score_params
-                                                       );
+                                                       let channel_penalty_msat = scid_opt.map_or(0,
+                                                               |scid| scorer.channel_penalty_msat(scid, &$src_node_id, &$dest_node_id,
+                                                                       channel_usage, score_params));
                                                        let path_penalty_msat = $next_hops_path_penalty_msat
                                                                .saturating_add(channel_penalty_msat);
                                                        let new_graph_node = RouteGraphNode {
@@ -1794,7 +1933,7 @@ where L::Target: Logger {
 
        // TODO: diversify by nodes (so that all paths aren't doomed if one node is offline).
        'paths_collection: loop {
-               // For every new path, start from scratch, except for used_channel_liquidities, which
+               // For every new path, start from scratch, except for used_liquidities, which
                // helps to avoid reusing previously selected paths in future iterations.
                targets.clear();
                dist.clear();
@@ -1807,8 +1946,8 @@ where L::Target: Logger {
                                let candidate = CandidateRouteHop::FirstHop { details };
                                let added = add_entry!(candidate, our_node_id, payee, 0, path_value_msat,
                                                                        0, 0u64, 0, 0).is_some();
-                               log_trace!(logger, "{} direct route to payee via SCID {}",
-                                               if added { "Added" } else { "Skipped" }, candidate.short_channel_id());
+                               log_trace!(logger, "{} direct route to payee via {}",
+                                               if added { "Added" } else { "Skipped" }, LoggedCandidateHop(&candidate));
                        }
                }));
 
@@ -1829,11 +1968,37 @@ where L::Target: Logger {
                // If a caller provided us with last hops, add them to routing targets. Since this happens
                // earlier than general path finding, they will be somewhat prioritized, although currently
                // it matters only if the fees are exactly the same.
-               let route_hints = match &payment_params.payee {
-                       Payee::Clear { route_hints, .. } => route_hints,
-                       _ => return Err(LightningError{err: "Routing to blinded paths isn't supported yet".to_owned(), action: ErrorAction::IgnoreError}),
-               };
-               for route in route_hints.iter().filter(|route| !route.0.is_empty()) {
+               for (hint_idx, hint) in payment_params.payee.blinded_route_hints().iter().enumerate() {
+                       let intro_node_id = NodeId::from_pubkey(&hint.1.introduction_node_id);
+                       let have_intro_node_in_graph =
+                               // Only add the hops in this route to our candidate set if either
+                               // we have a direct channel to the first hop or the first hop is
+                               // in the regular network graph.
+                               first_hop_targets.get(&intro_node_id).is_some() ||
+                               network_nodes.get(&intro_node_id).is_some();
+                       if !have_intro_node_in_graph { continue }
+                       let candidate = if hint.1.blinded_hops.len() == 1 {
+                               CandidateRouteHop::OneHopBlinded { hint, hint_idx }
+                       } else { CandidateRouteHop::Blinded { hint, hint_idx } };
+                       let mut path_contribution_msat = path_value_msat;
+                       if let Some(hop_used_msat) = add_entry!(candidate, intro_node_id, maybe_dummy_payee_node_id,
+                               0, path_contribution_msat, 0, 0_u64, 0, 0)
+                       {
+                               path_contribution_msat = hop_used_msat;
+                       } else { continue }
+                       if let Some(first_channels) = first_hop_targets.get_mut(&NodeId::from_pubkey(&hint.1.introduction_node_id)) {
+                               sort_first_hop_channels(first_channels, &used_liquidities, recommended_value_msat,
+                                       our_node_pubkey);
+                               for details in first_channels {
+                                       let first_hop_candidate = CandidateRouteHop::FirstHop { details };
+                                       add_entry!(first_hop_candidate, our_node_id, intro_node_id, 0, path_contribution_msat, 0,
+                                               0_u64, 0, 0);
+                               }
+                       }
+               }
+               for route in payment_params.payee.unblinded_route_hints().iter()
+                       .filter(|route| !route.0.is_empty())
+               {
                        let first_hop_in_route = &(route.0)[0];
                        let have_hop_src_in_graph =
                                // Only add the hops in this route to our candidate set if either
@@ -1880,8 +2045,9 @@ where L::Target: Logger {
                                                hop_used = false;
                                        }
 
-                                       let used_liquidity_msat = used_channel_liquidities
-                                               .get(&(hop.short_channel_id, source < target)).copied().unwrap_or(0);
+                                       let used_liquidity_msat = used_liquidities
+                                               .get(&candidate.id(source < target)).copied()
+                                               .unwrap_or(0);
                                        let channel_usage = ChannelUsage {
                                                amount_msat: final_value_msat + aggregate_next_hops_fee_msat,
                                                inflight_htlc_msat: used_liquidity_msat,
@@ -1901,7 +2067,7 @@ where L::Target: Logger {
 
                                        // Searching for a direct channel between last checked hop and first_hop_targets
                                        if let Some(first_channels) = first_hop_targets.get_mut(&NodeId::from_pubkey(&prev_hop_id)) {
-                                               sort_first_hop_channels(first_channels, &used_channel_liquidities,
+                                               sort_first_hop_channels(first_channels, &used_liquidities,
                                                        recommended_value_msat, our_node_pubkey);
                                                for details in first_channels {
                                                        let first_hop_candidate = CandidateRouteHop::FirstHop { details };
@@ -1942,7 +2108,7 @@ where L::Target: Logger {
                                                // always assumes that the third argument is a node to which we have a
                                                // path.
                                                if let Some(first_channels) = first_hop_targets.get_mut(&NodeId::from_pubkey(&hop.src_node_id)) {
-                                                       sort_first_hop_channels(first_channels, &used_channel_liquidities,
+                                                       sort_first_hop_channels(first_channels, &used_liquidities,
                                                                recommended_value_msat, our_node_pubkey);
                                                        for details in first_channels {
                                                                let first_hop_candidate = CandidateRouteHop::FirstHop { details };
@@ -1988,10 +2154,12 @@ where L::Target: Logger {
                                        let mut features_set = false;
                                        if let Some(first_channels) = first_hop_targets.get(&ordered_hops.last().unwrap().0.node_id) {
                                                for details in first_channels {
-                                                       if details.get_outbound_payment_scid().unwrap() == ordered_hops.last().unwrap().0.candidate.short_channel_id() {
-                                                               ordered_hops.last_mut().unwrap().1 = details.counterparty.features.to_context();
-                                                               features_set = true;
-                                                               break;
+                                                       if let Some(scid) = ordered_hops.last().unwrap().0.candidate.short_channel_id() {
+                                                               if details.get_outbound_payment_scid().unwrap() == scid {
+                                                                       ordered_hops.last_mut().unwrap().1 = details.counterparty.features.to_context();
+                                                                       features_set = true;
+                                                                       break;
+                                                               }
                                                        }
                                                }
                                        }
@@ -2060,8 +2228,8 @@ where L::Target: Logger {
                                        .chain(payment_path.hops.iter().map(|(hop, _)| &hop.node_id));
                                for (prev_hop, (hop, _)) in prev_hop_iter.zip(payment_path.hops.iter()) {
                                        let spent_on_hop_msat = value_contribution_msat + hop.next_hops_fee_msat;
-                                       let used_liquidity_msat = used_channel_liquidities
-                                               .entry((hop.candidate.short_channel_id(), *prev_hop < hop.node_id))
+                                       let used_liquidity_msat = used_liquidities
+                                               .entry(hop.candidate.id(*prev_hop < hop.node_id))
                                                .and_modify(|used_liquidity_msat| *used_liquidity_msat += spent_on_hop_msat)
                                                .or_insert(spent_on_hop_msat);
                                        let hop_capacity = hop.candidate.effective_capacity();
@@ -2077,11 +2245,12 @@ where L::Target: Logger {
                                        // If we weren't capped by hitting a liquidity limit on a channel in the path,
                                        // we'll probably end up picking the same path again on the next iteration.
                                        // Decrease the available liquidity of a hop in the middle of the path.
-                                       let victim_scid = payment_path.hops[(payment_path.hops.len()) / 2].0.candidate.short_channel_id();
+                                       let victim_candidate = &payment_path.hops[(payment_path.hops.len()) / 2].0.candidate;
                                        let exhausted = u64::max_value();
-                                       log_trace!(logger, "Disabling channel {} for future path building iterations to avoid duplicates.", victim_scid);
-                                       *used_channel_liquidities.entry((victim_scid, false)).or_default() = exhausted;
-                                       *used_channel_liquidities.entry((victim_scid, true)).or_default() = exhausted;
+                                       log_trace!(logger, "Disabling route candidate {} for future path building iterations to
+                                               avoid duplicates.", LoggedCandidateHop(victim_candidate));
+                                       *used_liquidities.entry(victim_candidate.id(false)).or_default() = exhausted;
+                                       *used_liquidities.entry(victim_candidate.id(true)).or_default() = exhausted;
                                }
 
                                // Track the total amount all our collected paths allow to send so that we know
@@ -2209,63 +2378,68 @@ where L::Target: Logger {
        // compare both SCIDs and NodeIds as individual nodes may use random aliases causing collisions
        // across nodes.
        selected_route.sort_unstable_by_key(|path| {
-               let mut key = [0u64; MAX_PATH_LENGTH_ESTIMATE as usize];
+               let mut key = [CandidateHopId::Clear((42, true)) ; MAX_PATH_LENGTH_ESTIMATE as usize];
                debug_assert!(path.hops.len() <= key.len());
-               for (scid, key) in path.hops.iter().map(|h| h.0.candidate.short_channel_id()).zip(key.iter_mut()) {
+               for (scid, key) in path.hops.iter() .map(|h| h.0.candidate.id(true)).zip(key.iter_mut()) {
                        *key = scid;
                }
                key
        });
        for idx in 0..(selected_route.len() - 1) {
                if idx + 1 >= selected_route.len() { break; }
-               if iter_equal(selected_route[idx    ].hops.iter().map(|h| (h.0.candidate.short_channel_id(), h.0.node_id)),
-                             selected_route[idx + 1].hops.iter().map(|h| (h.0.candidate.short_channel_id(), h.0.node_id))) {
+               if iter_equal(selected_route[idx    ].hops.iter().map(|h| (h.0.candidate.id(true), h.0.node_id)),
+                             selected_route[idx + 1].hops.iter().map(|h| (h.0.candidate.id(true), h.0.node_id))) {
                        let new_value = selected_route[idx].get_value_msat() + selected_route[idx + 1].get_value_msat();
                        selected_route[idx].update_value_and_recompute_fees(new_value);
                        selected_route.remove(idx + 1);
                }
        }
 
-       let mut selected_paths = Vec::<Vec<Result<RouteHop, LightningError>>>::new();
+       let mut paths = Vec::new();
        for payment_path in selected_route {
-               let mut path = payment_path.hops.iter().map(|(payment_hop, node_features)| {
-                       Ok(RouteHop {
-                               pubkey: PublicKey::from_slice(payment_hop.node_id.as_slice()).map_err(|_| LightningError{err: format!("Public key {:?} is invalid", &payment_hop.node_id), action: ErrorAction::IgnoreAndLog(Level::Trace)})?,
+               let mut hops = Vec::with_capacity(payment_path.hops.len());
+               for (hop, node_features) in payment_path.hops.iter()
+                       .filter(|(h, _)| h.candidate.short_channel_id().is_some())
+               {
+                       hops.push(RouteHop {
+                               pubkey: PublicKey::from_slice(hop.node_id.as_slice()).map_err(|_| LightningError{err: format!("Public key {:?} is invalid", &hop.node_id), action: ErrorAction::IgnoreAndLog(Level::Trace)})?,
                                node_features: node_features.clone(),
-                               short_channel_id: payment_hop.candidate.short_channel_id(),
-                               channel_features: payment_hop.candidate.features(),
-                               fee_msat: payment_hop.fee_msat,
-                               cltv_expiry_delta: payment_hop.candidate.cltv_expiry_delta(),
-                       })
-               }).collect::<Vec<_>>();
+                               short_channel_id: hop.candidate.short_channel_id().unwrap(),
+                               channel_features: hop.candidate.features(),
+                               fee_msat: hop.fee_msat,
+                               cltv_expiry_delta: hop.candidate.cltv_expiry_delta(),
+                       });
+               }
+               let mut final_cltv_delta = final_cltv_expiry_delta;
+               let blinded_tail = payment_path.hops.last().and_then(|(h, _)| {
+                       if let Some(blinded_path) = h.candidate.blinded_path() {
+                               final_cltv_delta = h.candidate.cltv_expiry_delta();
+                               Some(BlindedTail {
+                                       hops: blinded_path.blinded_hops.clone(),
+                                       blinding_point: blinded_path.blinding_point,
+                                       excess_final_cltv_expiry_delta: 0,
+                                       final_value_msat: h.fee_msat,
+                               })
+                       } else { None }
+               });
                // Propagate the cltv_expiry_delta one hop backwards since the delta from the current hop is
                // applicable for the previous hop.
-               path.iter_mut().rev().fold(final_cltv_expiry_delta, |prev_cltv_expiry_delta, hop| {
-                       core::mem::replace(&mut hop.as_mut().unwrap().cltv_expiry_delta, prev_cltv_expiry_delta)
+               hops.iter_mut().rev().fold(final_cltv_delta, |prev_cltv_expiry_delta, hop| {
+                       core::mem::replace(&mut hop.cltv_expiry_delta, prev_cltv_expiry_delta)
                });
-               selected_paths.push(path);
+
+               paths.push(Path { hops, blinded_tail });
        }
        // Make sure we would never create a route with more paths than we allow.
-       debug_assert!(selected_paths.len() <= payment_params.max_path_count.into());
+       debug_assert!(paths.len() <= payment_params.max_path_count.into());
 
        if let Some(node_features) = payment_params.payee.node_features() {
-               for path in selected_paths.iter_mut() {
-                       if let Ok(route_hop) = path.last_mut().unwrap() {
-                               route_hop.node_features = node_features.clone();
-                       }
+               for path in paths.iter_mut() {
+                       path.hops.last_mut().unwrap().node_features = node_features.clone();
                }
        }
 
-       let mut paths: Vec<Path> = Vec::new();
-       for results_vec in selected_paths {
-               let mut hops = Vec::with_capacity(results_vec.len());
-               for res in results_vec { hops.push(res?); }
-               paths.push(Path { hops, blinded_tail: None });
-       }
-       let route = Route {
-               paths,
-               payment_params: Some(payment_params.clone()),
-       };
+       let route = Route { paths, payment_params: Some(payment_params.clone()) };
        log_info!(logger, "Got route: {}", log_route!(route));
        Ok(route)
 }
@@ -2451,9 +2625,10 @@ mod tests {
        use crate::routing::test_utils::{add_channel, add_or_update_node, build_graph, build_line_graph, id_to_feature_flags, get_nodes, update_channel};
        use crate::chain::transaction::OutPoint;
        use crate::sign::EntropySource;
-       use crate::ln::features::{ChannelFeatures, InitFeatures, NodeFeatures};
+       use crate::ln::features::{BlindedHopFeatures, Bolt12InvoiceFeatures, ChannelFeatures, InitFeatures, NodeFeatures};
        use crate::ln::msgs::{ErrorAction, LightningError, UnsignedChannelUpdate, MAX_VALUE_MSAT};
        use crate::ln::channelmanager;
+       use crate::offers::invoice::BlindedPayInfo;
        use crate::util::config::UserConfig;
        use crate::util::test_utils as ln_test_utils;
        use crate::util::chacha20::ChaCha20;
@@ -4120,14 +4295,66 @@ mod tests {
 
        #[test]
        fn simple_mpp_route_test() {
+               let (secp_ctx, _, _, _, _) = build_graph();
+               let (_, _, _, nodes) = get_nodes(&secp_ctx);
+               let config = UserConfig::default();
+               let clear_payment_params = PaymentParameters::from_node_id(nodes[2], 42)
+                       .with_bolt11_features(channelmanager::provided_invoice_features(&config)).unwrap();
+               do_simple_mpp_route_test(clear_payment_params);
+
+               // MPP to a 1-hop blinded path for nodes[2]
+               let bolt12_features: Bolt12InvoiceFeatures = channelmanager::provided_invoice_features(&config).to_context();
+               let blinded_path = BlindedPath {
+                       introduction_node_id: nodes[2],
+                       blinding_point: ln_test_utils::pubkey(42),
+                       blinded_hops: vec![BlindedHop { blinded_node_id: ln_test_utils::pubkey(42 as u8), encrypted_payload: Vec::new() }],
+               };
+               let blinded_payinfo = BlindedPayInfo { // These fields are ignored for 1-hop blinded paths
+                       fee_base_msat: 0,
+                       fee_proportional_millionths: 0,
+                       htlc_minimum_msat: 0,
+                       htlc_maximum_msat: 0,
+                       cltv_expiry_delta: 0,
+                       features: BlindedHopFeatures::empty(),
+               };
+               let one_hop_blinded_payment_params = PaymentParameters::blinded(vec![(blinded_payinfo.clone(), blinded_path.clone())])
+                       .with_bolt12_features(bolt12_features.clone()).unwrap();
+               do_simple_mpp_route_test(one_hop_blinded_payment_params.clone());
+
+               // MPP to 3 2-hop blinded paths
+               let mut blinded_path_node_0 = blinded_path.clone();
+               blinded_path_node_0.introduction_node_id = nodes[0];
+               blinded_path_node_0.blinded_hops.push(blinded_path.blinded_hops[0].clone());
+               let mut node_0_payinfo = blinded_payinfo.clone();
+               node_0_payinfo.htlc_maximum_msat = 50_000;
+
+               let mut blinded_path_node_7 = blinded_path_node_0.clone();
+               blinded_path_node_7.introduction_node_id = nodes[7];
+               let mut node_7_payinfo = blinded_payinfo.clone();
+               node_7_payinfo.htlc_maximum_msat = 60_000;
+
+               let mut blinded_path_node_1 = blinded_path_node_0.clone();
+               blinded_path_node_1.introduction_node_id = nodes[1];
+               let mut node_1_payinfo = blinded_payinfo.clone();
+               node_1_payinfo.htlc_maximum_msat = 180_000;
+
+               let two_hop_blinded_payment_params = PaymentParameters::blinded(
+                       vec![
+                               (node_0_payinfo, blinded_path_node_0),
+                               (node_7_payinfo, blinded_path_node_7),
+                               (node_1_payinfo, blinded_path_node_1)
+                       ])
+                       .with_bolt12_features(bolt12_features).unwrap();
+               do_simple_mpp_route_test(two_hop_blinded_payment_params);
+       }
+
+
+       fn do_simple_mpp_route_test(payment_params: PaymentParameters) {
                let (secp_ctx, network_graph, gossip_sync, _, logger) = build_graph();
                let (our_privkey, our_id, privkeys, nodes) = get_nodes(&secp_ctx);
                let scorer = ln_test_utils::TestScorer::new();
                let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
                let random_seed_bytes = keys_manager.get_secure_random_bytes();
-               let config = UserConfig::default();
-               let payment_params = PaymentParameters::from_node_id(nodes[2], 42)
-                       .with_bolt11_features(channelmanager::provided_invoice_features(&config)).unwrap();
 
                // We need a route consisting of 3 paths:
                // From our node to node2 via node0, node7, node1 (three paths one hop each).
@@ -4256,8 +4483,12 @@ mod tests {
                        assert_eq!(route.paths.len(), 3);
                        let mut total_amount_paid_msat = 0;
                        for path in &route.paths {
-                               assert_eq!(path.hops.len(), 2);
-                               assert_eq!(path.hops.last().unwrap().pubkey, nodes[2]);
+                               if let Some(bt) = &path.blinded_tail {
+                                       assert_eq!(path.hops.len() + if bt.hops.len() == 1 { 0 } else { 1 }, 2);
+                               } else {
+                                       assert_eq!(path.hops.len(), 2);
+                                       assert_eq!(path.hops.last().unwrap().pubkey, nodes[2]);
+                               }
                                total_amount_paid_msat += path.final_value_msat();
                        }
                        assert_eq!(total_amount_paid_msat, 250_000);
@@ -4270,8 +4501,22 @@ mod tests {
                        assert_eq!(route.paths.len(), 3);
                        let mut total_amount_paid_msat = 0;
                        for path in &route.paths {
-                               assert_eq!(path.hops.len(), 2);
-                               assert_eq!(path.hops.last().unwrap().pubkey, nodes[2]);
+                               if payment_params.payee.blinded_route_hints().len() != 0 {
+                                       assert!(path.blinded_tail.is_some()) } else { assert!(path.blinded_tail.is_none()) }
+                               if let Some(bt) = &path.blinded_tail {
+                                       assert_eq!(path.hops.len() + if bt.hops.len() == 1 { 0 } else { 1 }, 2);
+                                       if bt.hops.len() > 1 {
+                                               assert_eq!(path.hops.last().unwrap().pubkey,
+                                                       payment_params.payee.blinded_route_hints().iter()
+                                                               .find(|(p, _)| p.htlc_maximum_msat == path.final_value_msat())
+                                                               .map(|(_, p)| p.introduction_node_id).unwrap());
+                                       } else {
+                                               assert_eq!(path.hops.last().unwrap().pubkey, nodes[2]);
+                                       }
+                               } else {
+                                       assert_eq!(path.hops.len(), 2);
+                                       assert_eq!(path.hops.last().unwrap().pubkey, nodes[2]);
+                               }
                                total_amount_paid_msat += path.final_value_msat();
                        }
                        assert_eq!(total_amount_paid_msat, 290_000);
@@ -6053,6 +6298,36 @@ mod tests {
                assert!(route.paths[0].hops.last().unwrap().fee_msat <= max_htlc_msat);
                assert!(route.paths[1].hops.last().unwrap().fee_msat <= max_htlc_msat);
                assert_eq!(route.get_total_amount(), amt_msat);
+
+               // Make sure this works for blinded route hints.
+               let blinded_path = BlindedPath {
+                       introduction_node_id: intermed_node_id,
+                       blinding_point: ln_test_utils::pubkey(42),
+                       blinded_hops: vec![
+                               BlindedHop { blinded_node_id: ln_test_utils::pubkey(42), encrypted_payload: vec![] },
+                               BlindedHop { blinded_node_id: ln_test_utils::pubkey(43), encrypted_payload: vec![] },
+                       ],
+               };
+               let blinded_payinfo = BlindedPayInfo {
+                       fee_base_msat: 100,
+                       fee_proportional_millionths: 0,
+                       htlc_minimum_msat: 1,
+                       htlc_maximum_msat: max_htlc_msat,
+                       cltv_expiry_delta: 10,
+                       features: BlindedHopFeatures::empty(),
+               };
+               let bolt12_features: Bolt12InvoiceFeatures = channelmanager::provided_invoice_features(&config).to_context();
+               let payment_params = PaymentParameters::blinded(vec![
+                       (blinded_payinfo.clone(), blinded_path.clone()),
+                       (blinded_payinfo.clone(), blinded_path.clone())])
+                       .with_bolt12_features(bolt12_features).unwrap();
+               let route = get_route(&our_node_id, &payment_params, &network_graph.read_only(),
+                       Some(&first_hops.iter().collect::<Vec<_>>()), amt_msat, Arc::clone(&logger), &scorer, &(),
+                       &random_seed_bytes).unwrap();
+               assert_eq!(route.paths.len(), 2);
+               assert!(route.paths[0].hops.last().unwrap().fee_msat <= max_htlc_msat);
+               assert!(route.paths[1].hops.last().unwrap().fee_msat <= max_htlc_msat);
+               assert_eq!(route.get_total_amount(), amt_msat);
        }
 
        #[test]
@@ -6199,6 +6474,190 @@ mod tests {
                assert_eq!(route.paths[0].blinded_tail.as_ref().unwrap().excess_final_cltv_expiry_delta, 40);
                assert_eq!(route.paths[0].hops.last().unwrap().cltv_expiry_delta, 40);
        }
+
+       #[test]
+       fn simple_blinded_route_hints() {
+               do_simple_blinded_route_hints(1);
+               do_simple_blinded_route_hints(2);
+               do_simple_blinded_route_hints(3);
+       }
+
+       fn do_simple_blinded_route_hints(num_blinded_hops: usize) {
+               // Check that we can generate a route to a blinded path with the expected hops.
+               let (secp_ctx, network, _, _, logger) = build_graph();
+               let (_, our_id, _, nodes) = get_nodes(&secp_ctx);
+               let network_graph = network.read_only();
+
+               let scorer = ln_test_utils::TestScorer::new();
+               let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
+               let random_seed_bytes = keys_manager.get_secure_random_bytes();
+
+               let mut blinded_path = BlindedPath {
+                       introduction_node_id: nodes[2],
+                       blinding_point: ln_test_utils::pubkey(42),
+                       blinded_hops: Vec::with_capacity(num_blinded_hops),
+               };
+               for i in 0..num_blinded_hops {
+                       blinded_path.blinded_hops.push(
+                               BlindedHop { blinded_node_id: ln_test_utils::pubkey(42 + i as u8), encrypted_payload: Vec::new() },
+                       );
+               }
+               let blinded_payinfo = BlindedPayInfo {
+                       fee_base_msat: 100,
+                       fee_proportional_millionths: 500,
+                       htlc_minimum_msat: 1000,
+                       htlc_maximum_msat: 100_000_000,
+                       cltv_expiry_delta: 15,
+                       features: BlindedHopFeatures::empty(),
+               };
+
+               let final_amt_msat = 1001;
+               let payment_params = PaymentParameters::blinded(vec![(blinded_payinfo.clone(), blinded_path.clone())]);
+               let route = get_route(&our_id, &payment_params, &network_graph, None, final_amt_msat , Arc::clone(&logger),
+                       &scorer, &(), &random_seed_bytes).unwrap();
+               assert_eq!(route.paths.len(), 1);
+               assert_eq!(route.paths[0].hops.len(), 2);
+
+               let tail = route.paths[0].blinded_tail.as_ref().unwrap();
+               assert_eq!(tail.hops, blinded_path.blinded_hops);
+               assert_eq!(tail.excess_final_cltv_expiry_delta, 0);
+               assert_eq!(tail.final_value_msat, 1001);
+
+               let final_hop = route.paths[0].hops.last().unwrap();
+               assert_eq!(final_hop.pubkey, blinded_path.introduction_node_id);
+               if tail.hops.len() > 1 {
+                       assert_eq!(final_hop.fee_msat,
+                               blinded_payinfo.fee_base_msat as u64 + blinded_payinfo.fee_proportional_millionths as u64 * tail.final_value_msat / 1000000);
+                       assert_eq!(final_hop.cltv_expiry_delta, blinded_payinfo.cltv_expiry_delta as u32);
+               } else {
+                       assert_eq!(final_hop.fee_msat, 0);
+                       assert_eq!(final_hop.cltv_expiry_delta, 0);
+               }
+       }
+
+       #[test]
+       fn blinded_path_routing_errors() {
+               // Check that we can generate a route to a blinded path with the expected hops.
+               let (secp_ctx, network, _, _, logger) = build_graph();
+               let (_, our_id, _, nodes) = get_nodes(&secp_ctx);
+               let network_graph = network.read_only();
+
+               let scorer = ln_test_utils::TestScorer::new();
+               let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
+               let random_seed_bytes = keys_manager.get_secure_random_bytes();
+
+               let mut invalid_blinded_path = BlindedPath {
+                       introduction_node_id: nodes[2],
+                       blinding_point: ln_test_utils::pubkey(42),
+                       blinded_hops: vec![
+                               BlindedHop { blinded_node_id: ln_test_utils::pubkey(43), encrypted_payload: vec![0; 43] },
+                       ],
+               };
+               let blinded_payinfo = BlindedPayInfo {
+                       fee_base_msat: 100,
+                       fee_proportional_millionths: 500,
+                       htlc_minimum_msat: 1000,
+                       htlc_maximum_msat: 100_000_000,
+                       cltv_expiry_delta: 15,
+                       features: BlindedHopFeatures::empty(),
+               };
+
+               let mut invalid_blinded_path_2 = invalid_blinded_path.clone();
+               invalid_blinded_path_2.introduction_node_id = ln_test_utils::pubkey(45);
+               let payment_params = PaymentParameters::blinded(vec![
+                       (blinded_payinfo.clone(), invalid_blinded_path.clone()),
+                       (blinded_payinfo.clone(), invalid_blinded_path_2)]);
+               match get_route(&our_id, &payment_params, &network_graph, None, 1001, Arc::clone(&logger),
+                       &scorer, &(), &random_seed_bytes)
+               {
+                       Err(LightningError { err, .. }) => {
+                               assert_eq!(err, "1-hop blinded paths must all have matching introduction node ids");
+                       },
+                       _ => panic!("Expected error")
+               }
+
+               invalid_blinded_path.introduction_node_id = our_id;
+               let payment_params = PaymentParameters::blinded(vec![(blinded_payinfo.clone(), invalid_blinded_path.clone())]);
+               match get_route(&our_id, &payment_params, &network_graph, None, 1001, Arc::clone(&logger),
+                       &scorer, &(), &random_seed_bytes)
+               {
+                       Err(LightningError { err, .. }) => {
+                               assert_eq!(err, "Cannot generate a route to blinded paths if we are the introduction node to all of them");
+                       },
+                       _ => panic!("Expected error")
+               }
+
+               invalid_blinded_path.introduction_node_id = ln_test_utils::pubkey(46);
+               invalid_blinded_path.blinded_hops.clear();
+               let payment_params = PaymentParameters::blinded(vec![(blinded_payinfo, invalid_blinded_path)]);
+               match get_route(&our_id, &payment_params, &network_graph, None, 1001, Arc::clone(&logger),
+                       &scorer, &(), &random_seed_bytes)
+               {
+                       Err(LightningError { err, .. }) => {
+                               assert_eq!(err, "0-hop blinded path provided");
+                       },
+                       _ => panic!("Expected error")
+               }
+       }
+
+       #[test]
+       fn matching_intro_node_paths_provided() {
+               // Check that if multiple blinded paths with the same intro node are provided in payment
+               // parameters, we'll return the correct paths in the resulting MPP route.
+               let (secp_ctx, network, _, _, logger) = build_graph();
+               let (_, our_id, _, nodes) = get_nodes(&secp_ctx);
+               let network_graph = network.read_only();
+
+               let scorer = ln_test_utils::TestScorer::new();
+               let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
+               let random_seed_bytes = keys_manager.get_secure_random_bytes();
+               let config = UserConfig::default();
+
+               let bolt12_features: Bolt12InvoiceFeatures = channelmanager::provided_invoice_features(&config).to_context();
+               let blinded_path_1 = BlindedPath {
+                       introduction_node_id: nodes[2],
+                       blinding_point: ln_test_utils::pubkey(42),
+                       blinded_hops: vec![
+                               BlindedHop { blinded_node_id: ln_test_utils::pubkey(42 as u8), encrypted_payload: Vec::new() },
+                               BlindedHop { blinded_node_id: ln_test_utils::pubkey(42 as u8), encrypted_payload: Vec::new() }
+                       ],
+               };
+               let blinded_payinfo_1 = BlindedPayInfo {
+                       fee_base_msat: 0,
+                       fee_proportional_millionths: 0,
+                       htlc_minimum_msat: 0,
+                       htlc_maximum_msat: 30_000,
+                       cltv_expiry_delta: 0,
+                       features: BlindedHopFeatures::empty(),
+               };
+
+               let mut blinded_path_2 = blinded_path_1.clone();
+               blinded_path_2.blinding_point = ln_test_utils::pubkey(43);
+               let mut blinded_payinfo_2 = blinded_payinfo_1.clone();
+               blinded_payinfo_2.htlc_maximum_msat = 70_000;
+
+               let blinded_hints = vec![
+                       (blinded_payinfo_1.clone(), blinded_path_1.clone()),
+                       (blinded_payinfo_2.clone(), blinded_path_2.clone()),
+               ];
+               let payment_params = PaymentParameters::blinded(blinded_hints.clone())
+                       .with_bolt12_features(bolt12_features.clone()).unwrap();
+
+               let route = get_route(&our_id, &payment_params, &network_graph, None,
+                       100_000, Arc::clone(&logger), &scorer, &(), &random_seed_bytes).unwrap();
+               assert_eq!(route.paths.len(), 2);
+               let mut total_amount_paid_msat = 0;
+               for path in route.paths.into_iter() {
+                       assert_eq!(path.hops.last().unwrap().pubkey, nodes[2]);
+                       if let Some(bt) = &path.blinded_tail {
+                               assert_eq!(bt.blinding_point,
+                                       blinded_hints.iter().find(|(p, _)| p.htlc_maximum_msat == path.final_value_msat())
+                                               .map(|(_, bp)| bp.blinding_point).unwrap());
+                       } else { panic!(); }
+                       total_amount_paid_msat += path.final_value_msat();
+               }
+               assert_eq!(total_amount_paid_msat, 100_000);
+       }
 }
 
 #[cfg(all(any(test, ldk_bench), not(feature = "no-std")))]
index 8f1f77b32aadd2f29b76be8fb6efc6290f2cb55a..1faf595ac8fabc7440729f24c14bc6e2cc34b6e7 100644 (file)
@@ -397,6 +397,38 @@ pub struct ChannelConfig {
        /// [`Normal`]: crate::chain::chaininterface::ConfirmationTarget::Normal
        /// [`Background`]: crate::chain::chaininterface::ConfirmationTarget::Background
        pub force_close_avoidance_max_fee_satoshis: u64,
+       /// If set, allows this channel's counterparty to skim an additional fee off this node's inbound
+       /// HTLCs. Useful for liquidity providers to offload on-chain channel costs to end users.
+       ///
+       /// Usage:
+       /// - The payee will set this option and set its invoice route hints to use [intercept scids]
+       ///   generated by this channel's counterparty.
+       /// - The counterparty will get an [`HTLCIntercepted`] event upon payment forward, and call
+       ///   [`forward_intercepted_htlc`] with less than the amount provided in
+       ///   [`HTLCIntercepted::expected_outbound_amount_msat`]. The difference between the expected and
+       ///   actual forward amounts is their fee.
+       // TODO: link to LSP JIT channel invoice generation spec when it's merged
+       ///
+       /// # Note
+       /// It's important for payee wallet software to verify that [`PaymentClaimable::amount_msat`] is
+       /// as-expected if this feature is activated, otherwise they may lose money!
+       /// [`PaymentClaimable::counterparty_skimmed_fee_msat`] provides the fee taken by the
+       /// counterparty.
+       ///
+       /// # Note
+       /// Switching this config flag on may break compatibility with versions of LDK prior to 0.0.116.
+       /// Unsetting this flag between restarts may lead to payment receive failures.
+       ///
+       /// Default value: false.
+       ///
+       /// [intercept scids]: crate::ln::channelmanager::ChannelManager::get_intercept_scid
+       /// [`forward_intercepted_htlc`]: crate::ln::channelmanager::ChannelManager::forward_intercepted_htlc
+       /// [`HTLCIntercepted`]: crate::events::Event::HTLCIntercepted
+       /// [`HTLCIntercepted::expected_outbound_amount_msat`]: crate::events::Event::HTLCIntercepted::expected_outbound_amount_msat
+       /// [`PaymentClaimable::amount_msat`]: crate::events::Event::PaymentClaimable::amount_msat
+       /// [`PaymentClaimable::counterparty_skimmed_fee_msat`]: crate::events::Event::PaymentClaimable::counterparty_skimmed_fee_msat
+       //  TODO: link to bLIP when it's merged
+       pub accept_underpaying_htlcs: bool,
 }
 
 impl ChannelConfig {
@@ -429,12 +461,14 @@ impl Default for ChannelConfig {
                        cltv_expiry_delta: 6 * 12, // 6 blocks/hour * 12 hours
                        max_dust_htlc_exposure_msat: 5_000_000,
                        force_close_avoidance_max_fee_satoshis: 1000,
+                       accept_underpaying_htlcs: false,
                }
        }
 }
 
 impl_writeable_tlv_based!(ChannelConfig, {
        (0, forwarding_fee_proportional_millionths, required),
+       (1, accept_underpaying_htlcs, (default_value, false)),
        (2, forwarding_fee_base_msat, required),
        (4, cltv_expiry_delta, required),
        (6, max_dust_htlc_exposure_msat, required),
@@ -543,6 +577,7 @@ impl crate::util::ser::Readable for LegacyChannelConfig {
                                cltv_expiry_delta,
                                force_close_avoidance_max_fee_satoshis,
                                forwarding_fee_base_msat,
+                               accept_underpaying_htlcs: false,
                        },
                        announced_channel,
                        commit_upfront_shutdown_pubkey,
index 3dbf4f89634b9b2c7330f774b8f0bd624414dd2c..dd9d2744e1c7927ba82bab9244a355020b5d9660 100644 (file)
@@ -50,11 +50,11 @@ pub(crate) mod crypto;
 pub mod logger;
 pub mod config;
 
-#[cfg(any(test, fuzzing, feature = "_test_utils"))]
+#[cfg(any(test, feature = "_test_utils"))]
 pub mod test_utils;
 
 /// impls of traits that add exra enforcement on the way they're called. Useful for detecting state
 /// machine errors and used in fuzz targets and tests.
-#[cfg(any(test, fuzzing, feature = "_test_utils"))]
+#[cfg(any(test, feature = "_test_utils"))]
 pub mod enforcing_trait_impls;
 
index fe61e9c7214e25a8ad901f5242aea701e03950fe..24979036ee3ceb7ecb2fa24af1586ba2adaa35e8 100644 (file)
@@ -45,6 +45,7 @@ use bitcoin::secp256k1::{SecretKey, PublicKey, Secp256k1, ecdsa::Signature, Scal
 use bitcoin::secp256k1::ecdh::SharedSecret;
 use bitcoin::secp256k1::ecdsa::RecoverableSignature;
 
+#[cfg(any(test, feature = "_test_utils"))]
 use regex;
 
 use crate::io;
@@ -738,6 +739,7 @@ impl TestLogger {
        /// 1. belong to the specified module and
        /// 2. match the given regex pattern.
        /// Assert that the number of occurrences equals the given `count`
+       #[cfg(any(test, feature = "_test_utils"))]
        pub fn assert_log_regex(&self, module: &str, pattern: regex::Regex, count: usize) {
                let log_entries = self.lines.lock().unwrap();
                let l: usize = log_entries.iter().filter(|&(&(ref m, ref l), _c)| {
diff --git a/pending_changelog/forward-underpaying-htlc.txt b/pending_changelog/forward-underpaying-htlc.txt
new file mode 100644 (file)
index 0000000..5b6c223
--- /dev/null
@@ -0,0 +1,6 @@
+## Backwards Compat
+
+* Forwarding less than the expected amount in `ChannelManager::forward_intercepted_htlc` may break
+       compatibility with versions of LDK prior to 0.0.116
+* Setting `ChannelConfig::accept_underpaying_htlcs` may break compatibility with versions of LDK
+       prior to 0.0.116, and unsetting the feature between restarts may lead to payment failures.