Skip to content

Commit 56c75f0

Browse files
committed
Consider many first-hop paths to the same counterparty in routing
Previously we'd simply overwritten "the" first hop path to each counterparty when routing, however this results in us ignoring all channels except the last one in the `ChannelDetails` list per counterparty.
1 parent 6edb4fe commit 56c75f0

File tree

1 file changed

+75
-13
lines changed

1 file changed

+75
-13
lines changed

lightning/src/routing/router.rs

Lines changed: 75 additions & 13 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

@@ -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
}
@@ -1013,8 +1021,17 @@ 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();
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 {
10181035
} else if let Some(node) = network_nodes.get(&ordered_hops.last().unwrap().0.pubkey) {
10191036
if let Some(node_info) = node.announcement_info.as_ref() {
10201037
ordered_hops.last_mut().unwrap().1 = node_info.features.clone();
@@ -4220,6 +4237,51 @@ mod tests {
42204237
}
42214238
}
42224239

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

0 commit comments

Comments
 (0)