Skip to content

Commit 17d75a8

Browse files
authored
Merge pull request #1100 from TheBlueMatt/2021-09-multihop-route-hint-fix
Consider many first-hop paths to the same counterparty in routing
2 parents 7aa2cac + cc70446 commit 17d75a8

File tree

1 file changed

+93
-31
lines changed

1 file changed

+93
-31
lines changed

lightning/src/routing/router.rs

Lines changed: 93 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -487,20 +487,23 @@ pub fn get_route<L: Deref>(our_node_id: &PublicKey, network: &NetworkGraph, paye
487487
node_info.features.supports_basic_mpp()
488488
} else { false }
489489
} else { false };
490+
log_trace!(logger, "Searching for a route from payer {} to payee {} {} MPP", our_node_id, payee,
491+
if allow_mpp { "with" } else { "without" });
490492

491493
// Step (1).
492494
// Prepare the data we'll use for payee-to-payer search by
493495
// inserting first hops suggested by the caller as targets.
494496
// Our search will then attempt to reach them while traversing from the payee node.
495-
let mut first_hop_targets: HashMap<_, (_, ChannelFeatures, _, NodeFeatures)> =
497+
let mut first_hop_targets: HashMap<_, Vec<(_, ChannelFeatures, _, NodeFeatures)>> =
496498
HashMap::with_capacity(if first_hops.is_some() { first_hops.as_ref().unwrap().len() } else { 0 });
497499
if let Some(hops) = first_hops {
498500
for chan in hops {
499501
let short_channel_id = chan.short_channel_id.expect("first_hops should be filled in with usable channels, not pending ones");
500502
if chan.counterparty.node_id == *our_node_id {
501503
return Err(LightningError{err: "First hop cannot have our_node_id as a destination.".to_owned(), action: ErrorAction::IgnoreError});
502504
}
503-
first_hop_targets.insert(chan.counterparty.node_id, (short_channel_id, chan.counterparty.features.to_context(), chan.outbound_capacity_msat, chan.counterparty.features.to_context()));
505+
first_hop_targets.entry(chan.counterparty.node_id).or_insert(Vec::new())
506+
.push((short_channel_id, chan.counterparty.features.to_context(), chan.outbound_capacity_msat, chan.counterparty.features.to_context()));
504507
}
505508
if first_hop_targets.is_empty() {
506509
return Err(LightningError{err: "Cannot route when there are no outbound routes away from us".to_owned(), action: ErrorAction::IgnoreError});
@@ -824,8 +827,8 @@ pub fn get_route<L: Deref>(our_node_id: &PublicKey, network: &NetworkGraph, paye
824827
};
825828

826829
if !skip_node {
827-
if first_hops.is_some() {
828-
if let Some(&(ref first_hop, ref features, ref outbound_capacity_msat, _)) = first_hop_targets.get(&$node_id) {
830+
if let Some(first_channels) = first_hop_targets.get(&$node_id) {
831+
for (ref first_hop, ref features, ref outbound_capacity_msat, _) in first_channels {
829832
add_entry!(first_hop, *our_node_id, $node_id, dummy_directional_info, Some(outbound_capacity_msat / 1000), features, $fee_to_target_msat, $next_hops_value_contribution, $next_hops_path_htlc_minimum_msat);
830833
}
831834
}
@@ -878,9 +881,10 @@ pub fn get_route<L: Deref>(our_node_id: &PublicKey, network: &NetworkGraph, paye
878881

879882
// If first hop is a private channel and the only way to reach the payee, this is the only
880883
// place where it could be added.
881-
if first_hops.is_some() {
882-
if let Some(&(ref first_hop, ref features, ref outbound_capacity_msat, _)) = first_hop_targets.get(&payee) {
883-
add_entry!(first_hop, *our_node_id, payee, dummy_directional_info, Some(outbound_capacity_msat / 1000), features, 0, path_value_msat, 0);
884+
if let Some(first_channels) = first_hop_targets.get(&payee) {
885+
for (ref first_hop, ref features, ref outbound_capacity_msat, _) in first_channels {
886+
let added = add_entry!(first_hop, *our_node_id, payee, dummy_directional_info, Some(outbound_capacity_msat / 1000), features, 0, path_value_msat, 0);
887+
log_trace!(logger, "{} direct route to payee via SCID {}", if added { "Added" } else { "Skipped" }, first_hop);
884888
}
885889
}
886890

@@ -897,7 +901,7 @@ pub fn get_route<L: Deref>(our_node_id: &PublicKey, network: &NetworkGraph, paye
897901
},
898902
}
899903

900-
// Step (1).
904+
// Step (2).
901905
// If a caller provided us with last hops, add them to routing targets. Since this happens
902906
// earlier than general path finding, they will be somewhat prioritized, although currently
903907
// it matters only if the fees are exactly the same.
@@ -949,8 +953,10 @@ pub fn get_route<L: Deref>(our_node_id: &PublicKey, network: &NetworkGraph, paye
949953
}
950954

951955
// Searching for a direct channel between last checked hop and first_hop_targets
952-
if let Some(&(ref first_hop, ref features, ref outbound_capacity_msat, _)) = first_hop_targets.get(&prev_hop_id) {
953-
add_entry!(first_hop, *our_node_id , prev_hop_id, dummy_directional_info, Some(outbound_capacity_msat / 1000), features, aggregate_next_hops_fee_msat, path_value_msat, aggregate_next_hops_path_htlc_minimum_msat);
956+
if let Some(first_channels) = first_hop_targets.get(&prev_hop_id) {
957+
for (ref first_hop, ref features, ref outbound_capacity_msat, _) in first_channels {
958+
add_entry!(first_hop, *our_node_id , prev_hop_id, dummy_directional_info, Some(outbound_capacity_msat / 1000), features, aggregate_next_hops_fee_msat, path_value_msat, aggregate_next_hops_path_htlc_minimum_msat);
959+
}
954960
}
955961

956962
if !hop_used {
@@ -981,8 +987,10 @@ pub fn get_route<L: Deref>(our_node_id: &PublicKey, network: &NetworkGraph, paye
981987
// Note that we *must* check if the last hop was added as `add_entry`
982988
// always assumes that the third argument is a node to which we have a
983989
// path.
984-
if let Some(&(ref first_hop, ref features, ref outbound_capacity_msat, _)) = first_hop_targets.get(&hop.src_node_id) {
985-
add_entry!(first_hop, *our_node_id , hop.src_node_id, dummy_directional_info, Some(outbound_capacity_msat / 1000), features, aggregate_next_hops_fee_msat, path_value_msat, aggregate_next_hops_path_htlc_minimum_msat);
990+
if let Some(first_channels) = first_hop_targets.get(&hop.src_node_id) {
991+
for (ref first_hop, ref features, ref outbound_capacity_msat, _) in first_channels {
992+
add_entry!(first_hop, *our_node_id , hop.src_node_id, dummy_directional_info, Some(outbound_capacity_msat / 1000), features, aggregate_next_hops_fee_msat, path_value_msat, aggregate_next_hops_path_htlc_minimum_msat);
993+
}
986994
}
987995
}
988996
}
@@ -995,7 +1003,7 @@ pub fn get_route<L: Deref>(our_node_id: &PublicKey, network: &NetworkGraph, paye
9951003
// last hops communicated by the caller, and the payment receiver.
9961004
let mut found_new_path = false;
9971005

998-
// Step (2).
1006+
// Step (3).
9991007
// If this loop terminates due the exhaustion of targets, two situations are possible:
10001008
// - not enough outgoing liquidity:
10011009
// 0 < already_collected_value_msat < final_value_msat
@@ -1013,20 +1021,30 @@ pub fn get_route<L: Deref>(our_node_id: &PublicKey, network: &NetworkGraph, paye
10131021
let mut ordered_hops = vec!((new_entry.clone(), NodeFeatures::empty()));
10141022

10151023
'path_walk: loop {
1016-
if let Some(&(_, _, _, ref features)) = first_hop_targets.get(&ordered_hops.last().unwrap().0.pubkey) {
1017-
ordered_hops.last_mut().unwrap().1 = features.clone();
1018-
} else if let Some(node) = network_nodes.get(&ordered_hops.last().unwrap().0.pubkey) {
1019-
if let Some(node_info) = node.announcement_info.as_ref() {
1020-
ordered_hops.last_mut().unwrap().1 = node_info.features.clone();
1024+
let mut features_set = false;
1025+
if let Some(first_channels) = first_hop_targets.get(&ordered_hops.last().unwrap().0.pubkey) {
1026+
for (scid, _, _, ref features) in first_channels {
1027+
if *scid == ordered_hops.last().unwrap().0.short_channel_id {
1028+
ordered_hops.last_mut().unwrap().1 = features.clone();
1029+
features_set = true;
1030+
break;
1031+
}
1032+
}
1033+
}
1034+
if !features_set {
1035+
if let Some(node) = network_nodes.get(&ordered_hops.last().unwrap().0.pubkey) {
1036+
if let Some(node_info) = node.announcement_info.as_ref() {
1037+
ordered_hops.last_mut().unwrap().1 = node_info.features.clone();
1038+
} else {
1039+
ordered_hops.last_mut().unwrap().1 = NodeFeatures::empty();
1040+
}
10211041
} else {
1022-
ordered_hops.last_mut().unwrap().1 = NodeFeatures::empty();
1042+
// We should be able to fill in features for everything except the last
1043+
// hop, if the last hop was provided via a BOLT 11 invoice (though we
1044+
// should be able to extend it further as BOLT 11 does have feature
1045+
// flags for the last hop node itself).
1046+
assert!(ordered_hops.last().unwrap().0.pubkey == *payee);
10231047
}
1024-
} else {
1025-
// We should be able to fill in features for everything except the last
1026-
// hop, if the last hop was provided via a BOLT 11 invoice (though we
1027-
// should be able to extend it further as BOLT 11 does have feature
1028-
// flags for the last hop node itself).
1029-
assert!(ordered_hops.last().unwrap().0.pubkey == *payee);
10301048
}
10311049

10321050
// Means we succesfully traversed from the payer to the payee, now
@@ -1130,7 +1148,7 @@ pub fn get_route<L: Deref>(our_node_id: &PublicKey, network: &NetworkGraph, paye
11301148
break 'paths_collection;
11311149
}
11321150

1133-
// Step (3).
1151+
// Step (4).
11341152
// Stop either when the recommended value is reached or if no new path was found in this
11351153
// iteration.
11361154
// In the latter case, making another path finding attempt won't help,
@@ -1154,7 +1172,7 @@ pub fn get_route<L: Deref>(our_node_id: &PublicKey, network: &NetworkGraph, paye
11541172
}
11551173
}
11561174

1157-
// Step (4).
1175+
// Step (5).
11581176
if payment_paths.len() == 0 {
11591177
return Err(LightningError{err: "Failed to find a path to the given destination".to_owned(), action: ErrorAction::IgnoreError});
11601178
}
@@ -1175,12 +1193,12 @@ pub fn get_route<L: Deref>(our_node_id: &PublicKey, network: &NetworkGraph, paye
11751193
let mut cur_route = Vec::<PaymentPath>::new();
11761194
let mut aggregate_route_value_msat = 0;
11771195

1178-
// Step (5).
1196+
// Step (6).
11791197
// TODO: real random shuffle
11801198
// Currently just starts with i_th and goes up to i-1_th in a looped way.
11811199
let cur_payment_paths = [&payment_paths[i..], &payment_paths[..i]].concat();
11821200

1183-
// Step (6).
1201+
// Step (7).
11841202
for payment_path in cur_payment_paths {
11851203
cur_route.push(payment_path.clone());
11861204
aggregate_route_value_msat += payment_path.get_value_msat();
@@ -1219,7 +1237,7 @@ pub fn get_route<L: Deref>(our_node_id: &PublicKey, network: &NetworkGraph, paye
12191237

12201238
assert!(cur_route.len() > 0);
12211239

1222-
// Step (7).
1240+
// Step (8).
12231241
// Now, substract the overpaid value from the most-expensive path.
12241242
// TODO: this could also be optimized by also sorting by feerate_per_sat_routed,
12251243
// so that the sender pays less fees overall. And also htlc_minimum_msat.
@@ -1236,7 +1254,7 @@ pub fn get_route<L: Deref>(our_node_id: &PublicKey, network: &NetworkGraph, paye
12361254
drawn_routes.push(cur_route);
12371255
}
12381256

1239-
// Step (8).
1257+
// Step (9).
12401258
// Select the best route by lowest total fee.
12411259
drawn_routes.sort_by_key(|paths| paths.iter().map(|path| path.get_total_fee_paid_msat()).sum::<u64>());
12421260
let mut selected_paths = Vec::<Vec<RouteHop>>::new();
@@ -4220,6 +4238,50 @@ mod tests {
42204238
}
42214239
}
42224240

4241+
#[test]
4242+
fn multiple_direct_first_hops() {
4243+
// Previously we'd only ever considered one first hop path per counterparty.
4244+
// However, as we don't restrict users to one channel per peer, we really need to support
4245+
// looking at all first hop paths.
4246+
// Here we test that we do not ignore all-but-the-last first hop paths per counterparty (as
4247+
// we used to do by overwriting the `first_hop_targets` hashmap entry) and that we can MPP
4248+
// route over multiple channels with the same first hop.
4249+
let secp_ctx = Secp256k1::new();
4250+
let (_, our_id, _, nodes) = get_nodes(&secp_ctx);
4251+
let logger = Arc::new(test_utils::TestLogger::new());
4252+
let network_graph = NetworkGraph::new(genesis_block(Network::Testnet).header.block_hash());
4253+
4254+
{
4255+
let route = get_route(&our_id, &network_graph, &nodes[0], Some(InvoiceFeatures::known()), Some(&[
4256+
&get_channel_details(Some(3), nodes[0], InitFeatures::known(), 200_000),
4257+
&get_channel_details(Some(2), nodes[0], InitFeatures::known(), 10_000),
4258+
]), &[], 100_000, 42, Arc::clone(&logger)).unwrap();
4259+
assert_eq!(route.paths.len(), 1);
4260+
assert_eq!(route.paths[0].len(), 1);
4261+
4262+
assert_eq!(route.paths[0][0].pubkey, nodes[0]);
4263+
assert_eq!(route.paths[0][0].short_channel_id, 3);
4264+
assert_eq!(route.paths[0][0].fee_msat, 100_000);
4265+
}
4266+
{
4267+
let route = get_route(&our_id, &network_graph, &nodes[0], Some(InvoiceFeatures::known()), Some(&[
4268+
&get_channel_details(Some(3), nodes[0], InitFeatures::known(), 50_000),
4269+
&get_channel_details(Some(2), nodes[0], InitFeatures::known(), 50_000),
4270+
]), &[], 100_000, 42, Arc::clone(&logger)).unwrap();
4271+
assert_eq!(route.paths.len(), 2);
4272+
assert_eq!(route.paths[0].len(), 1);
4273+
assert_eq!(route.paths[1].len(), 1);
4274+
4275+
assert_eq!(route.paths[0][0].pubkey, nodes[0]);
4276+
assert_eq!(route.paths[0][0].short_channel_id, 3);
4277+
assert_eq!(route.paths[0][0].fee_msat, 50_000);
4278+
4279+
assert_eq!(route.paths[1][0].pubkey, nodes[0]);
4280+
assert_eq!(route.paths[1][0].short_channel_id, 2);
4281+
assert_eq!(route.paths[1][0].fee_msat, 50_000);
4282+
}
4283+
}
4284+
42234285
#[test]
42244286
fn total_fees_single_path() {
42254287
let route = Route {

0 commit comments

Comments
 (0)