Skip to content

Commit 4a68d70

Browse files
Abide by max path length param in router.
We now only limit the number of unblinded intermediate hops. Previously we limited the total number of hops, including the final hop and any blinded hops. Also adds some testing by augmenting existing tests.
1 parent 507b809 commit 4a68d70

File tree

1 file changed

+56
-23
lines changed

1 file changed

+56
-23
lines changed

lightning/src/routing/router.rs

Lines changed: 56 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1119,7 +1119,7 @@ struct RouteGraphNode {
11191119
// considering their capacity and fees
11201120
value_contribution_msat: u64,
11211121
total_cltv_delta: u32,
1122-
/// The number of hops walked up to this node.
1122+
/// The number of intermediate hops walked up to this node.
11231123
path_length_to_node: u8,
11241124
is_intro_node: bool,
11251125
}
@@ -1466,6 +1466,17 @@ impl<'a> CandidateRouteHop<'a> {
14661466
CandidateRouteHop::OneHopBlinded(_) => None,
14671467
}
14681468
}
1469+
/// Whether this candidate is an unblinded forwarding hop along the path, i.e. not the final
1470+
/// hop or part of a blinded path.
1471+
pub fn is_intermediate_hop(&self, payee_node_id_opt: &Option<NodeId>) -> bool {
1472+
match self {
1473+
CandidateRouteHop::FirstHop(h) => !h.targets_blinded_path && &self.target() != payee_node_id_opt,
1474+
CandidateRouteHop::PublicHop(h) => !h.targets_blinded_path && &self.target() != payee_node_id_opt,
1475+
CandidateRouteHop::PrivateHop(_) => &self.target() != payee_node_id_opt,
1476+
CandidateRouteHop::Blinded(_) => false,
1477+
CandidateRouteHop::OneHopBlinded(_) => false,
1478+
}
1479+
}
14691480
}
14701481

14711482
/// A unique(ish) identifier for a specific [`CandidateRouteHop`].
@@ -1873,6 +1884,7 @@ pub(crate) fn get_route<L: Deref, S: ScoreLookUp>(
18731884
where L::Target: Logger {
18741885

18751886
let payment_params = &route_params.payment_params;
1887+
let max_intermediate_hops = core::cmp::min(payment_params.max_intermediate_hops, MAX_PATH_LENGTH_ESTIMATE);
18761888
let final_value_msat = route_params.final_value_msat;
18771889
// If we're routing to a blinded recipient, we won't have their node id. Therefore, keep the
18781890
// unblinded payee id as an option. We also need a non-optional "payee id" for path construction,
@@ -2168,8 +2180,9 @@ where L::Target: Logger {
21682180
// Verify the liquidity offered by this channel complies to the minimal contribution.
21692181
let contributes_sufficient_value = available_value_contribution_msat >= minimal_value_contribution_msat;
21702182
// Do not consider candidate hops that would exceed the maximum path length.
2171-
let path_length_to_node = $next_hops_path_length + 1;
2172-
let exceeds_max_path_length = path_length_to_node > MAX_PATH_LENGTH_ESTIMATE;
2183+
let intermediate_path_length_to_node = $next_hops_path_length
2184+
+ if $candidate.is_intermediate_hop(&payee_node_id_opt) { 1 } else { 0 };
2185+
let exceeds_max_path_length = intermediate_path_length_to_node > max_intermediate_hops;
21732186

21742187
// Do not consider candidates that exceed the maximum total cltv expiry limit.
21752188
// In order to already account for some of the privacy enhancing random CLTV
@@ -2380,7 +2393,7 @@ where L::Target: Logger {
23802393
score: cmp::max(total_fee_msat, path_htlc_minimum_msat).saturating_add(path_penalty_msat),
23812394
total_cltv_delta: hop_total_cltv_delta,
23822395
value_contribution_msat,
2383-
path_length_to_node,
2396+
path_length_to_node: intermediate_path_length_to_node,
23842397
is_intro_node: $candidate.blinded_hint_idx().is_some(),
23852398
};
23862399
targets.push(new_graph_node);
@@ -2625,9 +2638,8 @@ where L::Target: Logger {
26252638
};
26262639
let path_min = candidate.htlc_minimum_msat().saturating_add(
26272640
compute_fees_saturating(candidate.htlc_minimum_msat(), candidate.fees()));
2628-
add_entry!(&first_hop_candidate, blinded_path_fee,
2629-
path_contribution_msat, path_min, 0_u64, candidate.cltv_expiry_delta(),
2630-
candidate.blinded_path().map_or(1, |bp| bp.blinded_hops.len() as u8));
2641+
add_entry!(&first_hop_candidate, blinded_path_fee, path_contribution_msat, path_min,
2642+
0_u64, candidate.cltv_expiry_delta(), 0);
26312643
}
26322644
}
26332645
}
@@ -2653,7 +2665,7 @@ where L::Target: Logger {
26532665
let mut aggregate_next_hops_path_htlc_minimum_msat: u64 = 0;
26542666
let mut aggregate_next_hops_path_penalty_msat: u64 = 0;
26552667
let mut aggregate_next_hops_cltv_delta: u32 = 0;
2656-
let mut aggregate_next_hops_path_length: u8 = 0;
2668+
let mut aggregate_next_hops_intermediate_path_len: u8 = 0;
26572669
let mut aggregate_path_contribution_msat = path_value_msat;
26582670

26592671
for (idx, (hop, prev_hop_id)) in hop_iter.zip(prev_hop_iter).enumerate() {
@@ -2680,7 +2692,7 @@ where L::Target: Logger {
26802692
if let Some(hop_used_msat) = add_entry!(&candidate,
26812693
aggregate_next_hops_fee_msat, aggregate_path_contribution_msat,
26822694
aggregate_next_hops_path_htlc_minimum_msat, aggregate_next_hops_path_penalty_msat,
2683-
aggregate_next_hops_cltv_delta, aggregate_next_hops_path_length)
2695+
aggregate_next_hops_cltv_delta, aggregate_next_hops_intermediate_path_len)
26842696
{
26852697
aggregate_path_contribution_msat = hop_used_msat;
26862698
} else {
@@ -2707,8 +2719,12 @@ where L::Target: Logger {
27072719
aggregate_next_hops_cltv_delta = aggregate_next_hops_cltv_delta
27082720
.saturating_add(hop.cltv_expiry_delta as u32);
27092721

2710-
aggregate_next_hops_path_length = aggregate_next_hops_path_length
2711-
.saturating_add(1);
2722+
if idx > 0 {
2723+
// The final hop doesn't count towards the number of intermediate hops, so only start
2724+
// incrementing here after it's been processed.
2725+
aggregate_next_hops_intermediate_path_len = aggregate_next_hops_intermediate_path_len
2726+
.saturating_add(1);
2727+
}
27122728

27132729
// Searching for a direct channel between last checked hop and first_hop_targets
27142730
if let Some(first_channels) = first_hop_targets.get(target) {
@@ -2723,7 +2739,7 @@ where L::Target: Logger {
27232739
add_entry!(&first_hop_candidate,
27242740
aggregate_next_hops_fee_msat, aggregate_path_contribution_msat,
27252741
aggregate_next_hops_path_htlc_minimum_msat, aggregate_next_hops_path_penalty_msat,
2726-
aggregate_next_hops_cltv_delta, aggregate_next_hops_path_length);
2742+
aggregate_next_hops_cltv_delta, aggregate_next_hops_intermediate_path_len);
27272743
}
27282744
}
27292745

@@ -2775,7 +2791,7 @@ where L::Target: Logger {
27752791
aggregate_next_hops_path_htlc_minimum_msat,
27762792
aggregate_next_hops_path_penalty_msat,
27772793
aggregate_next_hops_cltv_delta,
2778-
aggregate_next_hops_path_length);
2794+
aggregate_next_hops_intermediate_path_len);
27792795
}
27802796
}
27812797
}
@@ -3390,7 +3406,8 @@ mod tests {
33903406
fn simple_route_test() {
33913407
let (secp_ctx, network_graph, _, _, logger) = build_graph();
33923408
let (_, our_id, _, nodes) = get_nodes(&secp_ctx);
3393-
let payment_params = PaymentParameters::from_node_id(nodes[2], 42);
3409+
let mut payment_params = PaymentParameters::from_node_id(nodes[2], 42);
3410+
payment_params.max_intermediate_hops = 1;
33943411
let scorer = ln_test_utils::TestScorer::new();
33953412
let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
33963413
let random_seed_bytes = keys_manager.get_secure_random_bytes();
@@ -3405,7 +3422,7 @@ mod tests {
34053422
assert_eq!(err, "Cannot send a payment of 0 msat");
34063423
} else { panic!(); }
34073424

3408-
let route_params = RouteParameters::from_payment_params_and_value(payment_params, 100);
3425+
let mut route_params = RouteParameters::from_payment_params_and_value(payment_params, 100);
34093426
let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
34103427
Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap();
34113428
assert_eq!(route.paths[0].hops.len(), 2);
@@ -3423,6 +3440,10 @@ mod tests {
34233440
assert_eq!(route.paths[0].hops[1].cltv_expiry_delta, 42);
34243441
assert_eq!(route.paths[0].hops[1].node_features.le_flags(), &id_to_feature_flags(3));
34253442
assert_eq!(route.paths[0].hops[1].channel_features.le_flags(), &id_to_feature_flags(4));
3443+
3444+
route_params.payment_params.max_intermediate_hops = 0;
3445+
get_route(&our_id, &route_params, &network_graph.read_only(), None,
3446+
Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap_err();
34263447
}
34273448

34283449
#[test]
@@ -3827,7 +3848,8 @@ mod tests {
38273848
});
38283849

38293850
// If all the channels require some features we don't understand, route should fail
3830-
let route_params = RouteParameters::from_payment_params_and_value(payment_params, 100);
3851+
let mut route_params = RouteParameters::from_payment_params_and_value(payment_params, 100);
3852+
route_params.payment_params.max_intermediate_hops = 1;
38313853
if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(&our_id,
38323854
&route_params, &network_graph.read_only(), None, Arc::clone(&logger), &scorer,
38333855
&Default::default(), &random_seed_bytes) {
@@ -4083,8 +4105,9 @@ mod tests {
40834105
} else { panic!(); }
40844106
}
40854107

4086-
let payment_params = PaymentParameters::from_node_id(nodes[6], 42)
4108+
let mut payment_params = PaymentParameters::from_node_id(nodes[6], 42)
40874109
.with_route_hints(last_hops_multi_private_channels(&nodes)).unwrap();
4110+
payment_params.max_intermediate_hops = 4;
40884111
let route_params = RouteParameters::from_payment_params_and_value(payment_params, 100);
40894112
let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
40904113
Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap();
@@ -4242,7 +4265,8 @@ mod tests {
42424265
let keys_manager = ln_test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
42434266
let random_seed_bytes = keys_manager.get_secure_random_bytes();
42444267
// Test through channels 2, 3, 0xff00, 0xff01.
4245-
// Test shows that multiple hop hints are considered.
4268+
// Test shows that multi-hop route hints are considered and factored correctly into the
4269+
// max path length.
42464270

42474271
// Disabling channels 6 & 7 by flags=2
42484272
update_channel(&gossip_sync, &secp_ctx, &privkeys[2], UnsignedChannelUpdate {
@@ -4270,7 +4294,8 @@ mod tests {
42704294
excess_data: Vec::new()
42714295
});
42724296

4273-
let route_params = RouteParameters::from_payment_params_and_value(payment_params, 100);
4297+
let mut route_params = RouteParameters::from_payment_params_and_value(payment_params, 100);
4298+
route_params.payment_params.max_intermediate_hops = 3;
42744299
let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
42754300
Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap();
42764301
assert_eq!(route.paths[0].hops.len(), 4);
@@ -4302,6 +4327,10 @@ mod tests {
43024327
assert_eq!(route.paths[0].hops[3].cltv_expiry_delta, 42);
43034328
assert_eq!(route.paths[0].hops[3].node_features.le_flags(), default_node_features().le_flags()); // We dont pass flags in from invoices yet
43044329
assert_eq!(route.paths[0].hops[3].channel_features.le_flags(), &Vec::<u8>::new()); // We can't learn any flags from invoices, sadly
4330+
4331+
route_params.payment_params.max_intermediate_hops = 2;
4332+
get_route(&our_id, &route_params, &network_graph.read_only(), None,
4333+
Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap_err();
43054334
}
43064335

43074336
#[test]
@@ -6843,16 +6872,20 @@ mod tests {
68436872
let random_seed_bytes = keys_manager.get_secure_random_bytes();
68446873

68456874
// First check we can actually create a long route on this graph.
6846-
let feasible_payment_params = PaymentParameters::from_node_id(nodes[18], 0);
6875+
let mut feasible_payment_params = PaymentParameters::from_node_id(nodes[18], 0);
6876+
// Check that we default to MAX_PATH_LENGTH_ESTIMATE for the path length limit.
6877+
assert_eq!(feasible_payment_params.max_intermediate_hops, MAX_PATH_LENGTH_ESTIMATE);
6878+
feasible_payment_params.max_intermediate_hops = 18;
68476879
let route_params = RouteParameters::from_payment_params_and_value(
68486880
feasible_payment_params, 100);
68496881
let route = get_route(&our_id, &route_params, &network_graph, None, Arc::clone(&logger),
68506882
&scorer, &Default::default(), &random_seed_bytes).unwrap();
68516883
let path = route.paths[0].hops.iter().map(|hop| hop.short_channel_id).collect::<Vec<_>>();
6852-
assert!(path.len() == MAX_PATH_LENGTH_ESTIMATE.into());
6884+
assert!(path.len() == route_params.payment_params.max_intermediate_hops as usize + 1);
68536885

6854-
// But we can't create a path surpassing the MAX_PATH_LENGTH_ESTIMATE limit.
6855-
let fail_payment_params = PaymentParameters::from_node_id(nodes[19], 0);
6886+
// But we can't create a path surpassing the parameterized path length limit.
6887+
let mut fail_payment_params = PaymentParameters::from_node_id(nodes[19], 0);
6888+
fail_payment_params.max_intermediate_hops = 18;
68566889
let route_params = RouteParameters::from_payment_params_and_value(
68576890
fail_payment_params, 100);
68586891
match get_route(&our_id, &route_params, &network_graph, None, Arc::clone(&logger), &scorer,

0 commit comments

Comments
 (0)