Skip to content

Commit 5b8af34

Browse files
Update route hint selection to prefer lowest channel above minimum
This commit updates the way that we choose our preferred channel per counterparty when selecting route hints. Previously, we would choose the largest usable channel above our requested minimum. This change updates selection to prefer the smallest channel above the minimum amount (if provided, plus a scaling factor of 10% to allow some margin for error). This is the off chain version of not "grinding our change" - we want to supply route hints for channels that have enough inbound to facilitate the receive, but not overload our high inbound channels with smaller payments (since we may need this large chunk of inbound for larger payment later on). If there is no minimum amount provided, we err on the side of caution and just select our highest inbound channels so that payments of any size have a chance of succeeding. Likewise, if we have no channels above the minimum, we select the largest channel to maximize our changes of receiving the payment in multiple parts.
1 parent 0e28bcb commit 5b8af34

File tree

1 file changed

+91
-18
lines changed

1 file changed

+91
-18
lines changed

lightning-invoice/src/utils.rs

Lines changed: 91 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -509,7 +509,11 @@ fn _create_invoice_from_channelmanager_and_duration_since_epoch_with_payment_has
509509
///
510510
/// The filtering is based on the following criteria:
511511
/// * Only one channel per counterparty node
512-
/// * Always select the channel with the highest inbound capacity per counterparty node
512+
/// * If the counterparty has a channel that is above the `min_inbound_capacity_msat` + 10% scaling
513+
/// factor (to allow some margin for change in inbound), select the channel with the lowest
514+
/// inbound capacity that is above this threshold.
515+
/// * If no `min_inbound_capacity_msat` is specified, or the counterparty has no channels above the
516+
/// minimum + 10% scaling factor, select the channel with the highest inbound capacity per counterparty.
513517
/// * Prefer channels with capacity at least `min_inbound_capacity_msat` and where the channel
514518
/// `is_usable` (i.e. the peer is connected).
515519
/// * If any public channel exists, only public [`RouteHint`]s will be returned.
@@ -570,12 +574,16 @@ fn filter_channels<L: Deref>(
570574
// If this channel is public and the previous channel is not, ensure we replace the
571575
// previous channel to avoid announcing non-public channels.
572576
let new_now_public = channel.is_public && !entry.get().is_public;
577+
// Decide whether we prefer the currently selected channel with the node to the new one,
578+
// based on their inbound capacity.
579+
let prefer_current = prefer_current_channel(min_inbound_capacity_msat, current_max_capacity,
580+
channel.inbound_capacity_msat);
573581
// If the public-ness of the channel has not changed (in which case simply defer to
574-
// `new_now_public), and this channel has a greater capacity, prefer to announce
575-
// this channel.
576-
let new_higher_capacity = channel.is_public == entry.get().is_public &&
577-
channel.inbound_capacity_msat > current_max_capacity;
578-
if new_now_public || new_higher_capacity {
582+
// `new_now_public), and this channel has more desirable inbound than the incumbent,
583+
// prefer to include this channel.
584+
let new_channel_preferable = channel.is_public == entry.get().is_public && !prefer_current;
585+
586+
if new_now_public || new_channel_preferable {
579587
log_trace!(logger,
580588
"Preferring counterparty {} channel {} (SCID {:?}, {} msats) over {} (SCID {:?}, {} msats) for invoice route hints",
581589
log_pubkey!(channel.counterparty.node_id),
@@ -658,6 +666,41 @@ fn filter_channels<L: Deref>(
658666
.collect::<Vec<RouteHint>>()
659667
}
660668

669+
/// prefer_current_channel chooses a channel to use for route hints between a currently selected and candidate
670+
/// channel based on the inbound capacity of each channel and the minimum inbound capacity requested for the hints,
671+
/// returning true if the current channel should be preferred over the candidate channel.
672+
/// * If no minimum amount is requested, the channel with the most inbound is chosen to maximize the chances that a
673+
/// payment of any size will succeed.
674+
/// * If we have channels with inbound above our minimum requested inbound (plus a 10% scaling factor, expressed as a
675+
/// percentage) then we choose the lowest inbound channel with above this amount. If we have sufficient inbound
676+
/// channels, we don't want to deplete our larger channels with small payments (the off-chain version of "grinding
677+
/// our change").
678+
/// * If no channel above our minimum amount exists, then we just prefer the channel with the most inbound to give
679+
/// payments the best chance of succeeding in multiple parts.
680+
fn prefer_current_channel(min_inbound_capacity_msat: Option<u64>, current_channel: u64,
681+
candidate_channel: u64) -> bool {
682+
683+
// If no min amount is given for the hints, err of the side of caution and choose the largest channel inbound to
684+
// maximize chances of any payment succeeding.
685+
if min_inbound_capacity_msat.is_none() {
686+
return current_channel > candidate_channel
687+
}
688+
689+
let scaled_min_inbound = min_inbound_capacity_msat.unwrap() * 110;
690+
let current_sufficient = current_channel * 100 >= scaled_min_inbound;
691+
let candidate_sufficient = candidate_channel * 100 >= scaled_min_inbound;
692+
693+
if current_sufficient && candidate_sufficient {
694+
return current_channel < candidate_channel
695+
} else if current_sufficient {
696+
return true
697+
} else if candidate_sufficient {
698+
return false
699+
}
700+
701+
current_channel > candidate_channel
702+
}
703+
661704
#[cfg(test)]
662705
mod test {
663706
use core::time::Duration;
@@ -676,6 +719,34 @@ mod test {
676719
use crate::utils::create_invoice_from_channelmanager_and_duration_since_epoch;
677720
use std::collections::HashSet;
678721

722+
#[test]
723+
fn test_prefer_current_channel() {
724+
// No minimum, prefer larger candidate channel.
725+
assert_eq!(crate::utils::prefer_current_channel(None, 100, 200), false);
726+
727+
// No minimum, prefer larger current channel.
728+
assert_eq!(crate::utils::prefer_current_channel(None, 200, 100), true);
729+
730+
// Minimum set, prefer current channel over minimum + buffer.
731+
assert_eq!(crate::utils::prefer_current_channel(Some(100), 115, 100), true);
732+
733+
// Minimum set, prefer candidate channel over minimum + buffer.
734+
assert_eq!(crate::utils::prefer_current_channel(Some(100), 105, 125), false);
735+
736+
// Minimum set, both channels sufficient, prefer smaller current channel.
737+
assert_eq!(crate::utils::prefer_current_channel(Some(100), 115, 125), true);
738+
739+
// Minimum set, both channels sufficient, prefer smaller candidate channel.
740+
assert_eq!(crate::utils::prefer_current_channel(Some(100), 200, 160), false);
741+
742+
// Minimum set, neither sufficient, prefer larger current channel.
743+
assert_eq!(crate::utils::prefer_current_channel(Some(200), 100, 50), true);
744+
745+
// Minimum set, neither sufficient, prefer larger candidate channel.
746+
assert_eq!(crate::utils::prefer_current_channel(Some(200), 100, 150), false);
747+
}
748+
749+
679750
#[test]
680751
fn test_from_channelmanager() {
681752
let chanmon_cfgs = create_chanmon_cfgs(2);
@@ -891,17 +962,19 @@ mod test {
891962
}
892963

893964
#[test]
894-
fn test_hints_has_only_highest_inbound_capacity_channel() {
965+
fn test_hints_has_only_lowest_inbound_capacity_channel_above_minimum() {
895966
let chanmon_cfgs = create_chanmon_cfgs(2);
896967
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
897968
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
898969
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
899-
let _chan_1_0_low_inbound_capacity = create_unannounced_chan_between_nodes_with_value(&nodes, 1, 0, 100_000, 0);
900-
let chan_1_0_high_inbound_capacity = create_unannounced_chan_between_nodes_with_value(&nodes, 1, 0, 10_000_000, 0);
901-
let _chan_1_0_medium_inbound_capacity = create_unannounced_chan_between_nodes_with_value(&nodes, 1, 0, 1_000_000, 0);
970+
971+
let _chan_1_0_inbound_below_amt = create_unannounced_chan_between_nodes_with_value(&nodes, 1, 0, 10_000, 0);
972+
let _chan_1_0_large_inbound_above_amt = create_unannounced_chan_between_nodes_with_value(&nodes, 1, 0, 500_000, 0);
973+
let chan_1_0_low_inbound_above_amt = create_unannounced_chan_between_nodes_with_value(&nodes, 1, 0, 200_000, 0);
974+
902975
let mut scid_aliases = HashSet::new();
903-
scid_aliases.insert(chan_1_0_high_inbound_capacity.0.short_channel_id_alias.unwrap());
904-
match_invoice_routes(Some(5000), &nodes[0], scid_aliases);
976+
scid_aliases.insert(chan_1_0_low_inbound_above_amt.0.short_channel_id_alias.unwrap());
977+
match_invoice_routes(Some(100_000_000), &nodes[0], scid_aliases);
905978
}
906979

907980
#[test]
@@ -1474,7 +1547,7 @@ mod test {
14741547

14751548
#[test]
14761549
#[cfg(feature = "std")]
1477-
fn test_multi_node_hints_has_only_highest_inbound_capacity_channel() {
1550+
fn test_multi_node_hints_has_only_lowest_inbound_channel_above_minimum() {
14781551
let mut chanmon_cfgs = create_chanmon_cfgs(3);
14791552
let seed_1 = [42u8; 32];
14801553
let seed_2 = [43u8; 32];
@@ -1485,17 +1558,17 @@ mod test {
14851558
let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, None]);
14861559
let nodes = create_network(3, &node_cfgs, &node_chanmgrs);
14871560

1488-
let _chan_0_1_low_inbound_capacity = create_unannounced_chan_between_nodes_with_value(&nodes, 0, 1, 100_000, 0);
1489-
let chan_0_1_high_inbound_capacity = create_unannounced_chan_between_nodes_with_value(&nodes, 0, 1, 10_000_000, 0);
1490-
let _chan_0_1_medium_inbound_capacity = create_unannounced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0);
1561+
let _chan_0_1_below_amt = create_unannounced_chan_between_nodes_with_value(&nodes, 0, 1, 100_000, 0);
1562+
let _chan_0_1_above_amt_high_inbound = create_unannounced_chan_between_nodes_with_value(&nodes, 0, 1, 500_000, 0);
1563+
let chan_0_1_above_amt_low_inbound = create_unannounced_chan_between_nodes_with_value(&nodes, 0, 1, 180_000, 0);
14911564
let chan_0_2 = create_unannounced_chan_between_nodes_with_value(&nodes, 0, 2, 100000, 10001);
14921565

14931566
let mut scid_aliases = HashSet::new();
1494-
scid_aliases.insert(chan_0_1_high_inbound_capacity.0.short_channel_id_alias.unwrap());
1567+
scid_aliases.insert(chan_0_1_above_amt_low_inbound.0.short_channel_id_alias.unwrap());
14951568
scid_aliases.insert(chan_0_2.0.short_channel_id_alias.unwrap());
14961569

14971570
match_multi_node_invoice_routes(
1498-
Some(10_000),
1571+
Some(100_000_000),
14991572
&nodes[1],
15001573
vec![&nodes[1], &nodes[2],],
15011574
scid_aliases,

0 commit comments

Comments
 (0)