Merge pull request #1542 from ViktorTigerstrom/2022-06-prepare-maps-for-channels...
[rust-lightning] / lightning / src / routing / router.rs
index 90e658604ad1058040643e5b6be48fd93cfe29a8..289221601c5c82e92cd2db457dbfff69a5eef460 100644 (file)
@@ -17,7 +17,7 @@ use bitcoin::secp256k1::PublicKey;
 use ln::channelmanager::ChannelDetails;
 use ln::features::{ChannelFeatures, InvoiceFeatures, NodeFeatures};
 use ln::msgs::{DecodeError, ErrorAction, LightningError, MAX_VALUE_MSAT};
-use routing::gossip::{DirectedChannelInfoWithUpdate, EffectiveCapacity, ReadOnlyNetworkGraph, NodeId, RoutingFees};
+use routing::gossip::{DirectedChannelInfoWithUpdate, EffectiveCapacity, ReadOnlyNetworkGraph, NetworkGraph, NodeId, RoutingFees};
 use routing::scoring::{ChannelUsage, Score};
 use util::ser::{Writeable, Readable, Writer};
 use util::logger::{Level, Logger};
@@ -176,6 +176,11 @@ impl_writeable_tlv_based!(RouteParameters, {
 /// Maximum total CTLV difference we allow for a full payment path.
 pub const DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA: u32 = 1008;
 
+/// Maximum number of paths we allow an (MPP) payment to have.
+// The default limit is currently set rather arbitrary - there aren't any real fundamental path-count
+// limits, but for now more than 10 paths likely carries too much one-path failure.
+pub const DEFAULT_MAX_PATH_COUNT: u8 = 10;
+
 // The median hop CLTV expiry delta currently seen in the network.
 const MEDIAN_HOP_CLTV_EXPIRY_DELTA: u32 = 40;
 
@@ -214,13 +219,19 @@ pub struct PaymentParameters {
        pub expiry_time: Option<u64>,
 
        /// The maximum total CLTV delta we accept for the route.
+       /// Defaults to [`DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA`].
        pub max_total_cltv_expiry_delta: u32,
+
+       /// The maximum number of paths that may be used by (MPP) payments.
+       /// Defaults to [`DEFAULT_MAX_PATH_COUNT`].
+       pub max_path_count: u8,
 }
 
 impl_writeable_tlv_based!(PaymentParameters, {
        (0, payee_pubkey, required),
        (1, max_total_cltv_expiry_delta, (default_value, DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA)),
        (2, features, option),
+       (3, max_path_count, (default_value, DEFAULT_MAX_PATH_COUNT)),
        (4, route_hints, vec_type),
        (6, expiry_time, option),
 });
@@ -234,6 +245,7 @@ impl PaymentParameters {
                        route_hints: vec![],
                        expiry_time: None,
                        max_total_cltv_expiry_delta: DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA,
+                       max_path_count: DEFAULT_MAX_PATH_COUNT,
                }
        }
 
@@ -269,6 +281,13 @@ impl PaymentParameters {
        pub fn with_max_total_cltv_expiry_delta(self, max_total_cltv_expiry_delta: u32) -> Self {
                Self { max_total_cltv_expiry_delta, ..self }
        }
+
+       /// Includes a limit for the maximum number of payment paths that may be used.
+       ///
+       /// (C-not exported) since bindings don't support move semantics
+       pub fn with_max_path_count(self, max_path_count: u8) -> Self {
+               Self { max_path_count, ..self }
+       }
 }
 
 /// A list of hops along a payment path terminating with a channel to the recipient.
@@ -671,16 +690,17 @@ fn default_node_features() -> NodeFeatures {
 /// [`ChannelManager::list_usable_channels`]: crate::ln::channelmanager::ChannelManager::list_usable_channels
 /// [`Event::PaymentPathFailed`]: crate::util::events::Event::PaymentPathFailed
 /// [`NetworkGraph`]: crate::routing::gossip::NetworkGraph
-pub fn find_route<L: Deref, S: Score>(
+pub fn find_route<L: Deref, GL: Deref, S: Score>(
        our_node_pubkey: &PublicKey, route_params: &RouteParameters,
-       network_graph: &ReadOnlyNetworkGraph, first_hops: Option<&[&ChannelDetails]>, logger: L,
+       network_graph: &NetworkGraph<GL>, first_hops: Option<&[&ChannelDetails]>, logger: L,
        scorer: &S, random_seed_bytes: &[u8; 32]
 ) -> Result<Route, LightningError>
-where L::Target: Logger {
-       let mut route = get_route(our_node_pubkey, &route_params.payment_params, network_graph, first_hops,
+where L::Target: Logger, GL::Target: Logger {
+       let graph_lock = network_graph.read_only();
+       let mut route = get_route(our_node_pubkey, &route_params.payment_params, &graph_lock, first_hops,
                route_params.final_value_msat, route_params.final_cltv_expiry_delta, logger, scorer,
                random_seed_bytes)?;
-       add_random_cltv_offset(&mut route, &route_params.payment_params, network_graph, random_seed_bytes);
+       add_random_cltv_offset(&mut route, &route_params.payment_params, &graph_lock, random_seed_bytes);
        Ok(route)
 }
 
@@ -780,16 +800,23 @@ where L::Target: Logger {
        let network_channels = network_graph.channels();
        let network_nodes = network_graph.nodes();
 
+       if payment_params.max_path_count == 0 {
+               return Err(LightningError{err: "Can't find a route with no paths allowed.".to_owned(), action: ErrorAction::IgnoreError});
+       }
+
        // Allow MPP only if we have a features set from somewhere that indicates the payee supports
        // it. If the payee supports it they're supposed to include it in the invoice, so that should
        // work reliably.
-       let allow_mpp = if let Some(features) = &payment_params.features {
+       let allow_mpp = if payment_params.max_path_count == 1 {
+               false
+       } else if let Some(features) = &payment_params.features {
                features.supports_basic_mpp()
        } else if let Some(node) = network_nodes.get(&payee_node_id) {
                if let Some(node_info) = node.announcement_info.as_ref() {
                        node_info.features.supports_basic_mpp()
                } else { false }
        } else { false };
+
        log_trace!(logger, "Searching for a route from payer {} to payee {} {} MPP and {} first hops {}overriding the network graph", our_node_pubkey,
                payment_params.payee_pubkey, if allow_mpp { "with" } else { "without" },
                first_hops.map(|hops| hops.len()).unwrap_or(0), if first_hops.is_some() { "" } else { "not " });
@@ -840,6 +867,21 @@ where L::Target: Logger {
        let recommended_value_msat = final_value_msat * ROUTE_CAPACITY_PROVISION_FACTOR as u64;
        let mut path_value_msat = final_value_msat;
 
+       // Routing Fragmentation Mitigation heuristic:
+       //
+       // Routing fragmentation across many payment paths increases the overall routing
+       // fees as you have irreducible routing fees per-link used (`fee_base_msat`).
+       // Taking too many smaller paths also increases the chance of payment failure.
+       // Thus to avoid this effect, we require from our collected links to provide
+       // at least a minimal contribution to the recommended value yet-to-be-fulfilled.
+       // This requirement is currently set to be 1/max_path_count of the payment
+       // value to ensure we only ever return routes that do not violate this limit.
+       let minimal_value_contribution_msat: u64 = if allow_mpp {
+               (final_value_msat + (payment_params.max_path_count as u64 - 1)) / payment_params.max_path_count as u64
+       } else {
+               final_value_msat
+       };
+
        // 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,
@@ -847,11 +889,8 @@ where L::Target: Logger {
        let mut used_channel_liquidities: HashMap<(u64, bool), u64> =
                HashMap::with_capacity(network_nodes.len());
 
-       // Keeping track of how much value we already collected across other paths. Helps to decide:
-       // - how much a new path should be transferring (upper bound);
-       // - whether a channel should be disregarded because
-       //   it's available liquidity is too small comparing to how much more we need to collect;
-       // - when we want to stop looking for new paths.
+       // Keeping track of how much value we already collected across other paths. Helps to decide
+       // when we want to stop looking for new paths.
        let mut already_collected_value_msat = 0;
 
        for (_, channels) in first_hop_targets.iter_mut() {
@@ -913,26 +952,6 @@ where L::Target: Logger {
                                                        *used_liquidity_msat
                                                });
 
-                                       // Routing Fragmentation Mitigation heuristic:
-                                       //
-                                       // Routing fragmentation across many payment paths increases the overall routing
-                                       // fees as you have irreducible routing fees per-link used (`fee_base_msat`).
-                                       // Taking too many smaller paths also increases the chance of payment failure.
-                                       // Thus to avoid this effect, we require from our collected links to provide
-                                       // at least a minimal contribution to the recommended value yet-to-be-fulfilled.
-                                       //
-                                       // This requirement is currently 5% of the remaining-to-be-collected value.
-                                       // This means as we successfully advance in our collection,
-                                       // the absolute liquidity contribution is lowered,
-                                       // thus increasing the number of potential channels to be selected.
-
-                                       // Derive the minimal liquidity contribution with a ratio of 20 (5%, rounded up)
-                                       // or 100% if we're not allowed to do multipath payments.
-                                       let minimal_value_contribution_msat: u64 = if allow_mpp {
-                                               (recommended_value_msat - already_collected_value_msat + 19) / 20
-                                       } else {
-                                               final_value_msat
-                                       };
                                        // Verify the liquidity offered by this channel complies to the minimal contribution.
                                        let contributes_sufficient_value = available_value_contribution_msat >= minimal_value_contribution_msat;
                                        // Do not consider candidate hops that would exceed the maximum path length.
@@ -1504,10 +1523,8 @@ where L::Target: Logger {
                                        *used_channel_liquidities.entry((victim_scid, true)).or_default() = exhausted;
                                }
 
-                               // Track the total amount all our collected paths allow to send so that we:
-                               // - know when to stop looking for more paths
-                               // - know which of the hops are useless considering how much more sats we need
-                               //   (contributes_sufficient_value)
+                               // Track the total amount all our collected paths allow to send so that we know
+                               // when to stop looking for more paths
                                already_collected_value_msat += value_contribution_msat;
 
                                payment_paths.push(payment_path);
@@ -1678,6 +1695,8 @@ where L::Target: Logger {
                });
                selected_paths.push(path);
        }
+       // 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());
 
        if let Some(features) = &payment_params.features {
                for path in selected_paths.iter_mut() {
@@ -1787,15 +1806,16 @@ fn add_random_cltv_offset(route: &mut Route, payment_params: &PaymentParameters,
 /// exclude the payer, but include the payee). This may be useful, e.g., for probing the chosen path.
 ///
 /// Re-uses logic from `find_route`, so the restrictions described there also apply here.
-pub fn build_route_from_hops<L: Deref>(
+pub fn build_route_from_hops<L: Deref, GL: Deref>(
        our_node_pubkey: &PublicKey, hops: &[PublicKey], route_params: &RouteParameters,
-       network_graph: &ReadOnlyNetworkGraph, logger: L, random_seed_bytes: &[u8; 32]
+       network_graph: &NetworkGraph<GL>, logger: L, random_seed_bytes: &[u8; 32]
 ) -> Result<Route, LightningError>
-where L::Target: Logger {
+where L::Target: Logger, GL::Target: Logger {
+       let graph_lock = network_graph.read_only();
        let mut route = build_route_from_hops_internal(
-               our_node_pubkey, hops, &route_params.payment_params, &network_graph,
+               our_node_pubkey, hops, &route_params.payment_params, &graph_lock,
                route_params.final_value_msat, route_params.final_cltv_expiry_delta, logger, random_seed_bytes)?;
-       add_random_cltv_offset(&mut route, &route_params.payment_params, &network_graph, random_seed_bytes);
+       add_random_cltv_offset(&mut route, &route_params.payment_params, &graph_lock, random_seed_bytes);
        Ok(route)
 }
 
@@ -1831,6 +1851,10 @@ fn build_route_from_hops_internal<L: Deref>(
                fn payment_path_failed(&mut self, _path: &[&RouteHop], _short_channel_id: u64) {}
 
                fn payment_path_successful(&mut self, _path: &[&RouteHop]) {}
+
+               fn probe_failed(&mut self, _path: &[&RouteHop], _short_channel_id: u64) {}
+
+               fn probe_successful(&mut self, _path: &[&RouteHop]) {}
        }
 
        impl<'a> Writeable for HopScorer {
@@ -1858,11 +1882,11 @@ fn build_route_from_hops_internal<L: Deref>(
 
 #[cfg(test)]
 mod tests {
-       use routing::gossip::{NetworkGraph, P2PGossipSync, NodeId};
+       use routing::gossip::{NetworkGraph, P2PGossipSync, NodeId, EffectiveCapacity};
        use routing::router::{get_route, build_route_from_hops_internal, add_random_cltv_offset, default_node_features,
                PaymentParameters, Route, RouteHint, RouteHintHop, RouteHop, RoutingFees,
                DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA, MAX_PATH_LENGTH_ESTIMATE};
-       use routing::scoring::{ChannelUsage, Score};
+       use routing::scoring::{ChannelUsage, Score, ProbabilisticScorer, ProbabilisticScoringParameters};
        use chain::transaction::OutPoint;
        use chain::keysinterface::KeysInterface;
        use ln::features::{ChannelFeatures, InitFeatures, InvoiceFeatures, NodeFeatures};
@@ -1921,6 +1945,7 @@ mod tests {
                        is_usable: true, is_public: true,
                        inbound_htlc_minimum_msat: None,
                        inbound_htlc_maximum_msat: None,
+                       config: None,
                }
        }
 
@@ -3999,7 +4024,8 @@ mod tests {
                let scorer = test_utils::TestScorer::with_penalty(0);
                let keys_manager = test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
                let random_seed_bytes = keys_manager.get_secure_random_bytes();
-               let payment_params = PaymentParameters::from_node_id(nodes[2]).with_features(InvoiceFeatures::known());
+               let payment_params = PaymentParameters::from_node_id(nodes[2])
+                       .with_features(InvoiceFeatures::known());
 
                // We need a route consisting of 3 paths:
                // From our node to node2 via node0, node7, node1 (three paths one hop each).
@@ -4092,15 +4118,39 @@ mod tests {
                {
                        // Attempt to route more than available results in a failure.
                        if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(
-                                       &our_id, &payment_params, &network_graph.read_only(), None, 300_000, 42, Arc::clone(&logger), &scorer, &random_seed_bytes) {
-                               assert_eq!(err, "Failed to find a sufficient route to the given destination");
+                               &our_id, &payment_params, &network_graph.read_only(), None, 300_000, 42,
+                               Arc::clone(&logger), &scorer, &random_seed_bytes) {
+                                       assert_eq!(err, "Failed to find a sufficient route to the given destination");
+                       } else { panic!(); }
+               }
+
+               {
+                       // Attempt to route while setting max_path_count to 0 results in a failure.
+                       let zero_payment_params = payment_params.clone().with_max_path_count(0);
+                       if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(
+                               &our_id, &zero_payment_params, &network_graph.read_only(), None, 100, 42,
+                               Arc::clone(&logger), &scorer, &random_seed_bytes) {
+                                       assert_eq!(err, "Can't find a route with no paths allowed.");
+                       } else { panic!(); }
+               }
+
+               {
+                       // Attempt to route while setting max_path_count to 3 results in a failure.
+                       // This is the case because the minimal_value_contribution_msat would require each path
+                       // to account for 1/3 of the total value, which is violated by 2 out of 3 paths.
+                       let fail_payment_params = payment_params.clone().with_max_path_count(3);
+                       if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(
+                               &our_id, &fail_payment_params, &network_graph.read_only(), None, 250_000, 42,
+                               Arc::clone(&logger), &scorer, &random_seed_bytes) {
+                                       assert_eq!(err, "Failed to find a sufficient route to the given destination");
                        } else { panic!(); }
                }
 
                {
                        // Now, attempt to route 250 sats (just a bit below the capacity).
                        // Our algorithm should provide us with these 3 paths.
-                       let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 250_000, 42, Arc::clone(&logger), &scorer, &random_seed_bytes).unwrap();
+                       let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None,
+                               250_000, 42, Arc::clone(&logger), &scorer, &random_seed_bytes).unwrap();
                        assert_eq!(route.paths.len(), 3);
                        let mut total_amount_paid_msat = 0;
                        for path in &route.paths {
@@ -4113,7 +4163,8 @@ mod tests {
 
                {
                        // Attempt to route an exact amount is also fine
-                       let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 290_000, 42, Arc::clone(&logger), &scorer, &random_seed_bytes).unwrap();
+                       let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None,
+                               290_000, 42, Arc::clone(&logger), &scorer, &random_seed_bytes).unwrap();
                        assert_eq!(route.paths.len(), 3);
                        let mut total_amount_paid_msat = 0;
                        for path in &route.paths {
@@ -5269,6 +5320,8 @@ mod tests {
 
                fn payment_path_failed(&mut self, _path: &[&RouteHop], _short_channel_id: u64) {}
                fn payment_path_successful(&mut self, _path: &[&RouteHop]) {}
+               fn probe_failed(&mut self, _path: &[&RouteHop], _short_channel_id: u64) {}
+               fn probe_successful(&mut self, _path: &[&RouteHop]) {}
        }
 
        struct BadNodeScorer {
@@ -5287,6 +5340,8 @@ mod tests {
 
                fn payment_path_failed(&mut self, _path: &[&RouteHop], _short_channel_id: u64) {}
                fn payment_path_successful(&mut self, _path: &[&RouteHop]) {}
+               fn probe_failed(&mut self, _path: &[&RouteHop], _short_channel_id: u64) {}
+               fn probe_successful(&mut self, _path: &[&RouteHop]) {}
        }
 
        #[test]
@@ -5670,6 +5725,43 @@ mod tests {
                        }
                }
        }
+
+       #[test]
+       fn honors_manual_penalties() {
+               let (secp_ctx, network_graph, _, _, logger) = build_line_graph();
+               let (_, our_id, _, nodes) = get_nodes(&secp_ctx);
+
+               let keys_manager = test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
+               let random_seed_bytes = keys_manager.get_secure_random_bytes();
+
+               let scorer_params = ProbabilisticScoringParameters::default();
+               let mut scorer = ProbabilisticScorer::new(scorer_params, Arc::clone(&network_graph), Arc::clone(&logger));
+
+               // First check set manual penalties are returned by the scorer.
+               let usage = ChannelUsage {
+                       amount_msat: 0,
+                       inflight_htlc_msat: 0,
+                       effective_capacity: EffectiveCapacity::Total { capacity_msat: 1_024_000, htlc_maximum_msat: Some(1_000) },
+               };
+               scorer.set_manual_penalty(&NodeId::from_pubkey(&nodes[3]), 123);
+               scorer.set_manual_penalty(&NodeId::from_pubkey(&nodes[4]), 456);
+               assert_eq!(scorer.channel_penalty_msat(42, &NodeId::from_pubkey(&nodes[3]), &NodeId::from_pubkey(&nodes[4]), usage), 456);
+
+               // Then check we can get a normal route
+               let payment_params = PaymentParameters::from_node_id(nodes[10]);
+               let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 100, 42, Arc::clone(&logger), &scorer, &random_seed_bytes);
+               assert!(route.is_ok());
+
+               // Then check that we can't get a route if we ban an intermediate node.
+               scorer.add_banned(&NodeId::from_pubkey(&nodes[3]));
+               let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 100, 42, Arc::clone(&logger), &scorer, &random_seed_bytes);
+               assert!(route.is_err());
+
+               // Finally make sure we can route again, when we remove the ban.
+               scorer.remove_banned(&NodeId::from_pubkey(&nodes[3]));
+               let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 100, 42, Arc::clone(&logger), &scorer, &random_seed_bytes);
+               assert!(route.is_ok());
+       }
 }
 
 #[cfg(all(test, not(feature = "no-std")))]
@@ -5764,6 +5856,7 @@ mod benches {
                        is_public: true,
                        inbound_htlc_minimum_msat: None,
                        inbound_htlc_maximum_msat: None,
+                       config: None,
                }
        }