Skip to content

Commit af68d4f

Browse files
Sort route hints by inbound capacity and limit to 3 hints
1 parent 096f285 commit af68d4f

File tree

1 file changed

+62
-10
lines changed

1 file changed

+62
-10
lines changed

lightning-invoice/src/utils.rs

Lines changed: 62 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -203,11 +203,11 @@ where
203203
for PhantomRouteHints { channels, phantom_scid, real_node_pubkey } in phantom_route_hints {
204204
log_trace!(logger, "Generating phantom route hints for node {}",
205205
log_pubkey!(real_node_pubkey));
206-
let mut route_hints = filter_channels(channels, amt_msat, &logger);
206+
let mut route_hints = sort_and_filter_channels(channels, amt_msat, &logger);
207207

208-
// If we have any public channel, the route hints from `filter_channels` will be empty.
209-
// In that case we create a RouteHint on which we will push a single hop with the phantom
210-
// route into the invoice, and let the sender find the path to the `real_node_pubkey`
208+
// If we have any public channel, the route hints from `sort_and_filter_channels` will be
209+
// empty. In that case we create a RouteHint on which we will push a single hop with the
210+
// phantom route into the invoice, and let the sender find the path to the `real_node_pubkey`
211211
// node by looking at our public channels.
212212
if route_hints.is_empty() {
213213
route_hints.push(RouteHint(vec![]))
@@ -485,7 +485,7 @@ fn _create_invoice_from_channelmanager_and_duration_since_epoch_with_payment_has
485485
invoice = invoice.amount_milli_satoshis(amt);
486486
}
487487

488-
let route_hints = filter_channels(channels, amt_msat, &logger);
488+
let route_hints = sort_and_filter_channels(channels, amt_msat, &logger);
489489
for hint in route_hints {
490490
invoice = invoice.private_route(hint);
491491
}
@@ -504,7 +504,7 @@ fn _create_invoice_from_channelmanager_and_duration_since_epoch_with_payment_has
504504
}
505505
}
506506

507-
/// Filters the `channels` for an invoice, and returns the corresponding `RouteHint`s to include
507+
/// Sorts and filters the `channels` for an invoice, and returns the corresponding `RouteHint`s to include
508508
/// in the invoice.
509509
///
510510
/// The filtering is based on the following criteria:
@@ -520,7 +520,10 @@ fn _create_invoice_from_channelmanager_and_duration_since_epoch_with_payment_has
520520
/// * If any public, announced, channel exists (i.e. a channel with 7+ confs, to ensure the
521521
/// announcement has had a chance to propagate), no [`RouteHint`]s will be returned, as the
522522
/// sender is expected to find the path by looking at the public channels instead.
523-
fn filter_channels<L: Deref>(
523+
/// * Limited to a total of 3 channels.
524+
/// * Sorted by lowest inbound capacity if an online channel with the minimum amount requested exists,
525+
/// otherwise sort by highest inbound capacity to give the payment the best chance of succeeding.
526+
fn sort_and_filter_channels<L: Deref>(
524527
channels: Vec<ChannelDetails>, min_inbound_capacity_msat: Option<u64>, logger: &L
525528
) -> Vec<RouteHint> where L::Target: Logger {
526529
let mut filtered_channels: HashMap<PublicKey, ChannelDetails> = HashMap::new();
@@ -626,7 +629,7 @@ fn filter_channels<L: Deref>(
626629
// the payment value and where we're currently connected to the channel counterparty.
627630
// Even if we cannot satisfy both goals, always ensure we include *some* hints, preferring
628631
// those which meet at least one criteria.
629-
filtered_channels
632+
let mut eligible_channels = filtered_channels
630633
.into_iter()
631634
.map(|(_, channel)| channel)
632635
.filter(|channel| {
@@ -663,8 +666,15 @@ fn filter_channels<L: Deref>(
663666

664667
include_channel
665668
})
666-
.map(route_hint_from_channel)
667-
.collect::<Vec<RouteHint>>()
669+
.collect::<Vec<ChannelDetails>>();
670+
671+
eligible_channels.sort_unstable_by(|a, b| {
672+
if online_min_capacity_channel_exists {
673+
a.inbound_capacity_msat.cmp(&b.inbound_capacity_msat)
674+
} else {
675+
b.inbound_capacity_msat.cmp(&a.inbound_capacity_msat)
676+
}});
677+
eligible_channels.into_iter().take(3).map(route_hint_from_channel).collect::<Vec<RouteHint>>()
668678
}
669679

670680
/// prefer_current_channel chooses a channel to use for route hints between a currently selected and candidate
@@ -1006,6 +1016,48 @@ mod test {
10061016
match_invoice_routes(Some(1_000_000_000), &nodes[0], scid_aliases);
10071017
}
10081018

1019+
#[test]
1020+
fn test_insufficient_inbound_sort_by_highest_capacity() {
1021+
let chanmon_cfgs = create_chanmon_cfgs(5);
1022+
let node_cfgs = create_node_cfgs(5, &chanmon_cfgs);
1023+
let node_chanmgrs = create_node_chanmgrs(5, &node_cfgs, &[None, None, None, None, None]);
1024+
let nodes = create_network(5, &node_cfgs, &node_chanmgrs);
1025+
let _chan_1_0 = create_unannounced_chan_between_nodes_with_value(&nodes, 1, 0, 100_000, 0);
1026+
let chan_2_0 = create_unannounced_chan_between_nodes_with_value(&nodes, 2, 0, 200_000, 0);
1027+
let chan_3_0 = create_unannounced_chan_between_nodes_with_value(&nodes, 3, 0, 300_000, 0);
1028+
let chan_4_0 = create_unannounced_chan_between_nodes_with_value(&nodes, 4, 0, 400_000, 0);
1029+
1030+
// When no single channel has enough inbound capacity for the payment, we expect the three
1031+
// highest inbound channels to be chosen.
1032+
let mut scid_aliases = HashSet::new();
1033+
scid_aliases.insert(chan_2_0.0.short_channel_id_alias.unwrap());
1034+
scid_aliases.insert(chan_3_0.0.short_channel_id_alias.unwrap());
1035+
scid_aliases.insert(chan_4_0.0.short_channel_id_alias.unwrap());
1036+
1037+
match_invoice_routes(Some(1_000_000_000), &nodes[0], scid_aliases.clone());
1038+
}
1039+
1040+
#[test]
1041+
fn test_sufficient_inbound_sort_by_lowest_capacity() {
1042+
let chanmon_cfgs = create_chanmon_cfgs(5);
1043+
let node_cfgs = create_node_cfgs(5, &chanmon_cfgs);
1044+
let node_chanmgrs = create_node_chanmgrs(5, &node_cfgs, &[None, None, None, None, None]);
1045+
let nodes = create_network(5, &node_cfgs, &node_chanmgrs);
1046+
let chan_1_0 = create_unannounced_chan_between_nodes_with_value(&nodes, 1, 0, 100_000, 0);
1047+
let chan_2_0 = create_unannounced_chan_between_nodes_with_value(&nodes, 2, 0, 200_000, 0);
1048+
let chan_3_0 = create_unannounced_chan_between_nodes_with_value(&nodes, 3, 0, 300_000, 0);
1049+
let _chan_4_0 = create_unannounced_chan_between_nodes_with_value(&nodes, 4, 0, 400_000, 0);
1050+
1051+
// When we have channels that have sufficient inbound for the payment, test that we sort
1052+
// by lowest inbound capacity.
1053+
let mut scid_aliases = HashSet::new();
1054+
scid_aliases.insert(chan_1_0.0.short_channel_id_alias.unwrap());
1055+
scid_aliases.insert(chan_2_0.0.short_channel_id_alias.unwrap());
1056+
scid_aliases.insert(chan_3_0.0.short_channel_id_alias.unwrap());
1057+
1058+
match_invoice_routes(Some(50_000_000), &nodes[0], scid_aliases.clone());
1059+
}
1060+
10091061
#[test]
10101062
fn test_forwarding_info_not_assigned_channel_excluded_from_hints() {
10111063
let chanmon_cfgs = create_chanmon_cfgs(3);

0 commit comments

Comments
 (0)