Add max path length knob to PaymentParameters.
[rust-lightning] / lightning / src / routing / router.rs
index f1901d7c50188e7e2b4e8b49d11c4bb590b4b829..4a4063f52f99e8ce89bf5e52e7e0fbf07cce2370 100644 (file)
@@ -13,7 +13,7 @@ use bitcoin::secp256k1::{PublicKey, Secp256k1, self};
 
 use crate::blinded_path::{BlindedHop, BlindedPath, Direction, IntroductionNode};
 use crate::blinded_path::payment::{ForwardNode, ForwardTlvs, PaymentConstraints, PaymentRelay, ReceiveTlvs};
-use crate::ln::PaymentHash;
+use crate::ln::types::PaymentHash;
 use crate::ln::channelmanager::{ChannelDetails, PaymentId, MIN_FINAL_CLTV_EXPIRY_DELTA};
 use crate::ln::features::{BlindedHopFeatures, Bolt11InvoiceFeatures, Bolt12InvoiceFeatures, ChannelFeatures, NodeFeatures};
 use crate::ln::msgs::{DecodeError, ErrorAction, LightningError, MAX_VALUE_MSAT};
@@ -654,6 +654,8 @@ 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;
 
+/// Estimated maximum number of hops that can be included in a payment path. May be inaccurate if
+/// payment metadata, custom TLVs, or blinded paths are included in the payment.
 // During routing, we only consider paths shorter than our maximum length estimate.
 // In the TLV onion format, there is no fixed maximum length, but the `hop_payloads`
 // field is always 1300 bytes. As the `tlv_payload` for each hop may vary in length, we have to
@@ -665,7 +667,7 @@ const MEDIAN_HOP_CLTV_EXPIRY_DELTA: u32 = 40;
 // (payment_secret and total_msat) = 93 bytes for the final hop.
 // Since the length of the potentially included `payment_metadata` is unknown to us, we round
 // down from (1300-93) / 61 = 19.78... to arrive at a conservative estimate of 19.
-const MAX_PATH_LENGTH_ESTIMATE: u8 = 19;
+pub const MAX_PATH_LENGTH_ESTIMATE: u8 = 19;
 
 /// Information used to route a payment.
 #[derive(Clone, Debug, Hash, PartialEq, Eq)]
@@ -684,6 +686,10 @@ pub struct PaymentParameters {
        /// Defaults to [`DEFAULT_MAX_PATH_COUNT`].
        pub max_path_count: u8,
 
+       /// The maximum number of [`Path::hops`] in any returned path.
+       /// Defaults to [`MAX_PATH_LENGTH_ESTIMATE`].
+       pub max_path_length: u8,
+
        /// Selects the maximum share of a channel's total capacity which will be sent over a channel,
        /// as a power of 1/2. A higher value prefers to send the payment using more MPP parts whereas
        /// a lower value prefers to send larger MPP parts, potentially saturating channels and
@@ -730,6 +736,7 @@ impl Writeable for PaymentParameters {
                        (8, *blinded_hints, optional_vec),
                        (9, self.payee.final_cltv_expiry_delta(), option),
                        (11, self.previously_failed_blinded_path_idxs, required_vec),
+                       (13, self.max_path_length, required),
                });
                Ok(())
        }
@@ -749,6 +756,7 @@ impl ReadableArgs<u32> for PaymentParameters {
                        (8, blinded_route_hints, optional_vec),
                        (9, final_cltv_expiry_delta, (default_value, default_final_cltv_expiry_delta)),
                        (11, previously_failed_blinded_path_idxs, optional_vec),
+                       (13, max_path_length, (default_value, MAX_PATH_LENGTH_ESTIMATE)),
                });
                let blinded_route_hints = blinded_route_hints.unwrap_or(vec![]);
                let payee = if blinded_route_hints.len() != 0 {
@@ -773,6 +781,7 @@ impl ReadableArgs<u32> for PaymentParameters {
                        expiry_time,
                        previously_failed_channels: previously_failed_channels.unwrap_or(Vec::new()),
                        previously_failed_blinded_path_idxs: previously_failed_blinded_path_idxs.unwrap_or(Vec::new()),
+                       max_path_length: _init_tlv_based_struct_field!(max_path_length, (default_value, unused)),
                })
        }
 }
@@ -789,6 +798,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_path_length: MAX_PATH_LENGTH_ESTIMATE,
                        max_channel_saturation_power_of_half: DEFAULT_MAX_CHANNEL_SATURATION_POW_HALF,
                        previously_failed_channels: Vec::new(),
                        previously_failed_blinded_path_idxs: Vec::new(),
@@ -828,6 +838,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_path_length: MAX_PATH_LENGTH_ESTIMATE,
                        max_channel_saturation_power_of_half: DEFAULT_MAX_CHANNEL_SATURATION_POW_HALF,
                        previously_failed_channels: Vec::new(),
                        previously_failed_blinded_path_idxs: Vec::new(),
@@ -1144,11 +1155,11 @@ pub struct FirstHopCandidate<'a> {
        ///
        /// [`find_route`] validates this prior to constructing a [`CandidateRouteHop`].
        ///
-       /// This is not exported to bindings users as lifetimes are not expressable in most languages.
+       /// This is not exported to bindings users as lifetimes are not expressible in most languages.
        pub details: &'a ChannelDetails,
        /// The node id of the payer, which is also the source side of this candidate route hop.
        ///
-       /// This is not exported to bindings users as lifetimes are not expressable in most languages.
+       /// This is not exported to bindings users as lifetimes are not expressible in most languages.
        pub payer_node_id: &'a NodeId,
 }
 
@@ -1158,7 +1169,7 @@ pub struct PublicHopCandidate<'a> {
        /// Information about the channel, including potentially its capacity and
        /// direction-specific information.
        ///
-       /// This is not exported to bindings users as lifetimes are not expressable in most languages.
+       /// This is not exported to bindings users as lifetimes are not expressible in most languages.
        pub info: DirectedChannelInfo<'a>,
        /// The short channel ID of the channel, i.e. the identifier by which we refer to this
        /// channel.
@@ -1170,11 +1181,11 @@ pub struct PublicHopCandidate<'a> {
 pub struct PrivateHopCandidate<'a> {
        /// Information about the private hop communicated via BOLT 11.
        ///
-       /// This is not exported to bindings users as lifetimes are not expressable in most languages.
+       /// This is not exported to bindings users as lifetimes are not expressible in most languages.
        pub hint: &'a RouteHintHop,
        /// Node id of the next hop in BOLT 11 route hint.
        ///
-       /// This is not exported to bindings users as lifetimes are not expressable in most languages.
+       /// This is not exported to bindings users as lifetimes are not expressible in most languages.
        pub target_node_id: &'a NodeId
 }
 
@@ -1189,7 +1200,7 @@ pub struct BlindedPathCandidate<'a> {
        /// Information about the blinded path including the fee, HTLC amount limits, and
        /// cryptographic material required to build an HTLC through the given path.
        ///
-       /// This is not exported to bindings users as lifetimes are not expressable in most languages.
+       /// This is not exported to bindings users as lifetimes are not expressible in most languages.
        pub hint: &'a (BlindedPayInfo, BlindedPath),
        /// Index of the hint in the original list of blinded hints.
        ///
@@ -1211,7 +1222,7 @@ pub struct OneHopBlindedPathCandidate<'a> {
        ///
        /// Note that the [`BlindedPayInfo`] is ignored here.
        ///
-       /// This is not exported to bindings users as lifetimes are not expressable in most languages.
+       /// This is not exported to bindings users as lifetimes are not expressible in most languages.
        pub hint: &'a (BlindedPayInfo, BlindedPath),
        /// Index of the hint in the original list of blinded hints.
        ///
@@ -1874,6 +1885,9 @@ where L::Target: Logger {
                return Err(LightningError{err: "Cannot send a payment of 0 msat".to_owned(), action: ErrorAction::IgnoreError});
        }
 
+       let introduction_node_id_cache = payment_params.payee.blinded_route_hints().iter()
+               .map(|(_, path)| path.public_introduction_node_id(network_graph))
+               .collect::<Vec<_>>();
        match &payment_params.payee {
                Payee::Clear { route_hints, node_id, .. } => {
                        for route in route_hints.iter() {
@@ -1885,17 +1899,19 @@ where L::Target: Logger {
                        }
                },
                Payee::Blinded { route_hints, .. } => {
-                       if route_hints.iter().all(|(_, path)| path.public_introduction_node_id(network_graph) == Some(&our_node_id)) {
+                       if introduction_node_id_cache.iter().all(|introduction_node_id| *introduction_node_id == Some(&our_node_id)) {
                                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() {
+                       for ((_, blinded_path), introduction_node_id) in route_hints.iter().zip(introduction_node_id_cache.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.public_introduction_node_id(network_graph) == Some(&our_node_id) {
+                               } else if *introduction_node_id == Some(&our_node_id) {
                                        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.public_introduction_node_id(network_graph) != blinded_path.public_introduction_node_id(network_graph))
+                                       route_hints
+                                               .iter().zip(introduction_node_id_cache.iter())
+                                               .filter(|((_, p), _)| p.blinded_hops.len() == 1)
+                                               .any(|(_, p_introduction_node_id)| p_introduction_node_id != introduction_node_id)
                                {
                                        return Err(LightningError{err: format!("1-hop blinded paths must all have matching introduction node ids"), action: ErrorAction::IgnoreError});
                                }
@@ -2540,7 +2556,7 @@ where L::Target: Logger {
                        // 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.
-                       let source_node_id = match hint.1.public_introduction_node_id(network_graph) {
+                       let source_node_id = match introduction_node_id_cache[hint_idx] {
                                Some(node_id) => node_id,
                                None => match &hint.1.introduction_node {
                                        IntroductionNode::NodeId(pubkey) => {
@@ -3286,7 +3302,7 @@ 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::ChannelId;
+       use crate::ln::types::ChannelId;
        use crate::ln::features::{BlindedHopFeatures, ChannelFeatures, InitFeatures, NodeFeatures};
        use crate::ln::msgs::{ErrorAction, LightningError, UnsignedChannelUpdate, MAX_VALUE_MSAT};
        use crate::ln::channelmanager;
@@ -8408,7 +8424,7 @@ pub(crate) mod bench_utils {
        use crate::chain::transaction::OutPoint;
        use crate::routing::scoring::ScoreUpdate;
        use crate::sign::KeysManager;
-       use crate::ln::ChannelId;
+       use crate::ln::types::ChannelId;
        use crate::ln::channelmanager::{self, ChannelCounterparty};
        use crate::util::config::UserConfig;
        use crate::util::test_utils::TestLogger;