Merge pull request #3026 from valentinewallace/2024-04-limit-path-len
[rust-lightning] / lightning / src / ln / onion_utils.rs
index 0abcb7de26df55468001afff373a0d5281e81e59..9a207c9e52d1dd1805592bf039dd9ff84387a6b4 100644 (file)
@@ -7,14 +7,17 @@
 // You may not use this file except in accordance with one or both of these
 // licenses.
 
+use crate::blinded_path::BlindedHop;
 use crate::crypto::chacha20::ChaCha20;
 use crate::crypto::streams::ChaChaReader;
+use crate::ln::channel::TOTAL_BITCOIN_SUPPLY_SATOSHIS;
 use crate::ln::channelmanager::{HTLCSource, RecipientOnionFields};
+use crate::ln::features::{ChannelFeatures, NodeFeatures};
 use crate::ln::msgs;
 use crate::ln::types::{PaymentHash, PaymentPreimage};
 use crate::ln::wire::Encode;
 use crate::routing::gossip::NetworkUpdate;
-use crate::routing::router::{BlindedTail, Path, RouteHop};
+use crate::routing::router::{Path, RouteHop, RouteParameters};
 use crate::sign::NodeSigner;
 use crate::util::errors::{self, APIError};
 use crate::util::logger::Logger;
@@ -174,18 +177,60 @@ pub(super) fn construct_onion_keys<T: secp256k1::Signing>(
 }
 
 /// returns the hop data, as well as the first-hop value_msat and CLTV value we should send.
-pub(super) fn build_onion_payloads(
-       path: &Path, total_msat: u64, mut recipient_onion: RecipientOnionFields,
+pub(super) fn build_onion_payloads<'a>(
+       path: &'a Path, total_msat: u64, recipient_onion: &'a RecipientOnionFields,
        starting_htlc_offset: u32, keysend_preimage: &Option<PaymentPreimage>,
-) -> Result<(Vec<msgs::OutboundOnionPayload>, u64, u32), APIError> {
-       let mut cur_value_msat = 0u64;
-       let mut cur_cltv = starting_htlc_offset;
-       let mut last_short_channel_id = 0;
+) -> Result<(Vec<msgs::OutboundOnionPayload<'a>>, u64, u32), APIError> {
        let mut res: Vec<msgs::OutboundOnionPayload> = Vec::with_capacity(
                path.hops.len() + path.blinded_tail.as_ref().map_or(0, |t| t.hops.len()),
        );
+       let blinded_tail_with_hop_iter = path.blinded_tail.as_ref().map(|bt| BlindedTailHopIter {
+               hops: bt.hops.iter(),
+               blinding_point: bt.blinding_point,
+               final_value_msat: bt.final_value_msat,
+               excess_final_cltv_expiry_delta: bt.excess_final_cltv_expiry_delta,
+       });
+
+       let (value_msat, cltv) = build_onion_payloads_callback(
+               path.hops.iter(),
+               blinded_tail_with_hop_iter,
+               total_msat,
+               recipient_onion,
+               starting_htlc_offset,
+               keysend_preimage,
+               |action, payload| match action {
+                       PayloadCallbackAction::PushBack => res.push(payload),
+                       PayloadCallbackAction::PushFront => res.insert(0, payload),
+               },
+       )?;
+       Ok((res, value_msat, cltv))
+}
 
-       for (idx, hop) in path.hops.iter().rev().enumerate() {
+struct BlindedTailHopIter<'a, I: Iterator<Item = &'a BlindedHop>> {
+       hops: I,
+       blinding_point: PublicKey,
+       final_value_msat: u64,
+       excess_final_cltv_expiry_delta: u32,
+}
+enum PayloadCallbackAction {
+       PushBack,
+       PushFront,
+}
+fn build_onion_payloads_callback<'a, H, B, F>(
+       hops: H, mut blinded_tail: Option<BlindedTailHopIter<'a, B>>, total_msat: u64,
+       recipient_onion: &'a RecipientOnionFields, starting_htlc_offset: u32,
+       keysend_preimage: &Option<PaymentPreimage>, mut callback: F,
+) -> Result<(u64, u32), APIError>
+where
+       H: DoubleEndedIterator<Item = &'a RouteHop>,
+       B: ExactSizeIterator<Item = &'a BlindedHop>,
+       F: FnMut(PayloadCallbackAction, msgs::OutboundOnionPayload<'a>),
+{
+       let mut cur_value_msat = 0u64;
+       let mut cur_cltv = starting_htlc_offset;
+       let mut last_short_channel_id = 0;
+
+       for (idx, hop) in hops.rev().enumerate() {
                // First hop gets special values so that it can check, on receipt, that everything is
                // exactly as it should be (and the next hop isn't trying to probe to find out if we're
                // the intended recipient).
@@ -196,47 +241,55 @@ pub(super) fn build_onion_payloads(
                        cur_cltv
                };
                if idx == 0 {
-                       if let Some(BlindedTail {
+                       if let Some(BlindedTailHopIter {
                                blinding_point,
                                hops,
                                final_value_msat,
                                excess_final_cltv_expiry_delta,
                                ..
-                       }) = &path.blinded_tail
+                       }) = blinded_tail.take()
                        {
-                               let mut blinding_point = Some(*blinding_point);
-                               for (i, blinded_hop) in hops.iter().enumerate() {
-                                       if i == hops.len() - 1 {
+                               let mut blinding_point = Some(blinding_point);
+                               let hops_len = hops.len();
+                               for (i, blinded_hop) in hops.enumerate() {
+                                       if i == hops_len - 1 {
                                                cur_value_msat += final_value_msat;
-                                               res.push(msgs::OutboundOnionPayload::BlindedReceive {
-                                                       sender_intended_htlc_amt_msat: *final_value_msat,
-                                                       total_msat,
-                                                       cltv_expiry_height: cur_cltv + excess_final_cltv_expiry_delta,
-                                                       encrypted_tlvs: blinded_hop.encrypted_payload.clone(),
-                                                       intro_node_blinding_point: blinding_point.take(),
-                                                       keysend_preimage: *keysend_preimage,
-                                                       custom_tlvs: recipient_onion.custom_tlvs.clone(),
-                                               });
+                                               callback(
+                                                       PayloadCallbackAction::PushBack,
+                                                       msgs::OutboundOnionPayload::BlindedReceive {
+                                                               sender_intended_htlc_amt_msat: final_value_msat,
+                                                               total_msat,
+                                                               cltv_expiry_height: cur_cltv + excess_final_cltv_expiry_delta,
+                                                               encrypted_tlvs: &blinded_hop.encrypted_payload,
+                                                               intro_node_blinding_point: blinding_point.take(),
+                                                               keysend_preimage: *keysend_preimage,
+                                                               custom_tlvs: &recipient_onion.custom_tlvs,
+                                                       },
+                                               );
                                        } else {
-                                               res.push(msgs::OutboundOnionPayload::BlindedForward {
-                                                       encrypted_tlvs: blinded_hop.encrypted_payload.clone(),
-                                                       intro_node_blinding_point: blinding_point.take(),
-                                               });
+                                               callback(
+                                                       PayloadCallbackAction::PushBack,
+                                                       msgs::OutboundOnionPayload::BlindedForward {
+                                                               encrypted_tlvs: &blinded_hop.encrypted_payload,
+                                                               intro_node_blinding_point: blinding_point.take(),
+                                                       },
+                                               );
                                        }
                                }
                        } else {
-                               res.push(msgs::OutboundOnionPayload::Receive {
-                                       payment_data: if let Some(secret) = recipient_onion.payment_secret.take() {
-                                               Some(msgs::FinalOnionHopData { payment_secret: secret, total_msat })
-                                       } else {
-                                               None
+                               callback(
+                                       PayloadCallbackAction::PushBack,
+                                       msgs::OutboundOnionPayload::Receive {
+                                               payment_data: recipient_onion.payment_secret.map(|payment_secret| {
+                                                       msgs::FinalOnionHopData { payment_secret, total_msat }
+                                               }),
+                                               payment_metadata: recipient_onion.payment_metadata.as_ref(),
+                                               keysend_preimage: *keysend_preimage,
+                                               custom_tlvs: &recipient_onion.custom_tlvs,
+                                               sender_intended_htlc_amt_msat: value_msat,
+                                               cltv_expiry_height: cltv,
                                        },
-                                       payment_metadata: recipient_onion.payment_metadata.take(),
-                                       keysend_preimage: *keysend_preimage,
-                                       custom_tlvs: recipient_onion.custom_tlvs.clone(),
-                                       sender_intended_htlc_amt_msat: value_msat,
-                                       cltv_expiry_height: cltv,
-                               });
+                               );
                        }
                } else {
                        let payload = msgs::OutboundOnionPayload::Forward {
@@ -244,7 +297,7 @@ pub(super) fn build_onion_payloads(
                                amt_to_forward: value_msat,
                                outgoing_cltv_value: cltv,
                        };
-                       res.insert(0, payload);
+                       callback(PayloadCallbackAction::PushFront, payload);
                }
                cur_value_msat += hop.fee_msat;
                if cur_value_msat >= 21000000 * 100000000 * 1000 {
@@ -256,7 +309,78 @@ pub(super) fn build_onion_payloads(
                }
                last_short_channel_id = hop.short_channel_id;
        }
-       Ok((res, cur_value_msat, cur_cltv))
+       Ok((cur_value_msat, cur_cltv))
+}
+
+pub(crate) const MIN_FINAL_VALUE_ESTIMATE_WITH_OVERPAY: u64 = 100_000_000;
+
+pub(crate) fn set_max_path_length(
+       route_params: &mut RouteParameters, recipient_onion: &RecipientOnionFields,
+       keysend_preimage: Option<PaymentPreimage>, best_block_height: u32,
+) -> Result<(), ()> {
+       const PAYLOAD_HMAC_LEN: usize = 32;
+       let unblinded_intermed_payload_len = msgs::OutboundOnionPayload::Forward {
+               short_channel_id: 42,
+               amt_to_forward: TOTAL_BITCOIN_SUPPLY_SATOSHIS,
+               outgoing_cltv_value: route_params.payment_params.max_total_cltv_expiry_delta,
+       }
+       .serialized_length()
+       .saturating_add(PAYLOAD_HMAC_LEN);
+
+       const OVERPAY_ESTIMATE_MULTIPLER: u64 = 3;
+       let final_value_msat_with_overpay_buffer = core::cmp::max(
+               route_params.final_value_msat.saturating_mul(OVERPAY_ESTIMATE_MULTIPLER),
+               MIN_FINAL_VALUE_ESTIMATE_WITH_OVERPAY,
+       );
+
+       let blinded_tail_opt = route_params
+               .payment_params
+               .payee
+               .blinded_route_hints()
+               .iter()
+               .map(|(_, path)| path)
+               .max_by_key(|path| path.serialized_length())
+               .map(|largest_path| BlindedTailHopIter {
+                       hops: largest_path.blinded_hops.iter(),
+                       blinding_point: largest_path.blinding_point,
+                       final_value_msat: final_value_msat_with_overpay_buffer,
+                       excess_final_cltv_expiry_delta: 0,
+               });
+
+       let unblinded_route_hop = RouteHop {
+               pubkey: PublicKey::from_slice(&[2; 33]).unwrap(),
+               node_features: NodeFeatures::empty(),
+               short_channel_id: 42,
+               channel_features: ChannelFeatures::empty(),
+               fee_msat: final_value_msat_with_overpay_buffer,
+               cltv_expiry_delta: route_params.payment_params.max_total_cltv_expiry_delta,
+               maybe_announced_channel: false,
+       };
+       let mut num_reserved_bytes: usize = 0;
+       let build_payloads_res = build_onion_payloads_callback(
+               core::iter::once(&unblinded_route_hop),
+               blinded_tail_opt,
+               final_value_msat_with_overpay_buffer,
+               &recipient_onion,
+               best_block_height,
+               &keysend_preimage,
+               |_, payload| {
+                       num_reserved_bytes = num_reserved_bytes
+                               .saturating_add(payload.serialized_length())
+                               .saturating_add(PAYLOAD_HMAC_LEN);
+               },
+       );
+       debug_assert!(build_payloads_res.is_ok());
+
+       let max_path_length = 1300usize
+               .checked_sub(num_reserved_bytes)
+               .map(|p| p / unblinded_intermed_payload_len)
+               .and_then(|l| u8::try_from(l.saturating_add(1)).ok())
+               .ok_or(())?;
+
+       route_params.payment_params.max_path_length =
+               core::cmp::min(max_path_length, route_params.payment_params.max_path_length);
+       Ok(())
 }
 
 /// Length of the onion data packet. Before TLV-based onions this was 20 65-byte hops, though now
@@ -1124,7 +1248,7 @@ where
 /// `cur_block_height` should be set to the best known block height + 1.
 pub fn create_payment_onion<T: secp256k1::Signing>(
        secp_ctx: &Secp256k1<T>, path: &Path, session_priv: &SecretKey, total_msat: u64,
-       recipient_onion: RecipientOnionFields, cur_block_height: u32, payment_hash: &PaymentHash,
+       recipient_onion: &RecipientOnionFields, cur_block_height: u32, payment_hash: &PaymentHash,
        keysend_preimage: &Option<PaymentPreimage>, prng_seed: [u8; 32],
 ) -> Result<(msgs::OnionPacket, u64, u32), APIError> {
        let onion_keys = construct_onion_keys(&secp_ctx, &path, &session_priv).map_err(|_| {