Skip to content

Commit 51414fb

Browse files
committed
Use cost / path amt limit as the pathfinding score, not cost
While walking nodes in our Dijkstra's pathfinding, we may find a channel which is amount-limited to less than the amount we're currently trying to send. This is fine, and when we encounter such nodes we simply limit the amount we'd send in this path if we pick the channel. When we encounter such a path, we keep summing the cost across hops as we go, keeping whatever scores we assigned to channels between the amount-limited one and the recipient, but using the new limited amount for any channels we look at later as we walk towards the sender. This leads to somewhat inconsistent scores, especially as our scorer assigns a large portion of its penalties and a portion of network fees are proportional to the amount. Thus, we end up with a somewhat higher score than we "should" for this path as later hops use a high proportional cost. We accepted this as a simple way to bias against small-value paths and many MPP parts. Sadly, in practice it appears our bias is not strong enough, as several users have reported that we often attempt far too many MPP parts. In practice, if we encounter a channel with a small limit early in the Dijkstra's pass (towards the end of the path), we may prefer it over many other paths as we start assigning very low costs early on before we've accumulated much cost from larger channels. Here, we swap the `cost` Dijkstra's score for `cost / path amount`. This should bias much stronger against many MPP parts by preferring larger paths proportionally to their amount. This somewhat better aligns with our goal - if we have to pick multiple paths, we should be searching for paths the optimize fee-per-sat-sent, not strictly the fee paid. However, it might bias us against smaller paths somewhat stronger than we want - because we're still using the fees/scores calculated with the sought amount for hops processed already, but are now dividing by a smaller sent amount when walking further hops, we will bias "incorrectly" (and fairly strongly) against smaller parts. Still, because of the complaints on pathfinding performance due to too many MPP paths, it seems like a worthwhile tradeoff, as ultimately MPP splitting is always the domain of heuristics anyway.
1 parent a31da0a commit 51414fb

File tree

1 file changed

+27
-14
lines changed

1 file changed

+27
-14
lines changed

lightning/src/routing/router.rs

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1396,7 +1396,7 @@ impl_writeable_tlv_based!(RouteHintHop, {
13961396
#[repr(align(32))] // Force the size to 32 bytes
13971397
struct RouteGraphNode {
13981398
node_counter: u32,
1399-
score: u64,
1399+
score: u128,
14001400
// The maximum value a yet-to-be-constructed payment path might flow through this node.
14011401
// This value is upper-bounded by us by:
14021402
// - how much is needed for a path being constructed
@@ -2122,7 +2122,16 @@ impl<'a> PaymentPath<'a> {
21222122
return result;
21232123
}
21242124

2125-
fn get_cost_msat(&self) -> u64 {
2125+
fn get_cost(&self) -> u128 {
2126+
let fee_cost = self.get_fee_cost_msat();
2127+
if fee_cost == u64::MAX {
2128+
u64::MAX.into()
2129+
} else {
2130+
((fee_cost as u128) << 64) / self.get_value_msat() as u128
2131+
}
2132+
}
2133+
2134+
fn get_fee_cost_msat(&self) -> u64 {
21262135
self.get_total_fee_paid_msat().saturating_add(self.get_path_penalty_msat())
21272136
}
21282137

@@ -2991,15 +3000,22 @@ where L::Target: Logger {
29913000
// but it may require additional tracking - we don't want to double-count
29923001
// the fees included in $next_hops_path_htlc_minimum_msat, but also
29933002
// can't use something that may decrease on future hops.
2994-
let old_cost = cmp::max(old_entry.total_fee_msat, old_entry.path_htlc_minimum_msat)
3003+
let old_fee_cost = cmp::max(old_entry.total_fee_msat, old_entry.path_htlc_minimum_msat)
29953004
.saturating_add(old_entry.path_penalty_msat);
2996-
let new_cost = cmp::max(total_fee_msat, path_htlc_minimum_msat)
3005+
let new_fee_cost = cmp::max(total_fee_msat, path_htlc_minimum_msat)
29973006
.saturating_add(path_penalty_msat);
2998-
let should_replace =
2999-
new_cost < old_cost
3000-
|| (new_cost == old_cost && old_entry.value_contribution_msat < value_contribution_msat);
3007+
let old_cost = if old_fee_cost != u64::MAX && old_entry.value_contribution_msat != 0 {
3008+
((old_fee_cost as u128) << 64) / old_entry.value_contribution_msat as u128
3009+
} else {
3010+
u128::MAX
3011+
};
3012+
let new_cost = if new_fee_cost != u64::MAX {
3013+
((new_fee_cost as u128) << 64) / value_contribution_msat as u128
3014+
} else {
3015+
u128::MAX
3016+
};
30013017

3002-
if !old_entry.was_processed && should_replace {
3018+
if !old_entry.was_processed && new_cost < old_cost {
30033019
#[cfg(all(not(ldk_bench), any(test, fuzzing)))]
30043020
{
30053021
assert!(!old_entry.best_path_from_hop_selected);
@@ -3008,7 +3024,7 @@ where L::Target: Logger {
30083024

30093025
let new_graph_node = RouteGraphNode {
30103026
node_counter: src_node_counter,
3011-
score: cmp::max(total_fee_msat, path_htlc_minimum_msat).saturating_add(path_penalty_msat),
3027+
score: new_cost,
30123028
total_cltv_delta: hop_total_cltv_delta as u16,
30133029
value_contribution_msat,
30143030
path_length_to_node,
@@ -3558,10 +3574,7 @@ where L::Target: Logger {
35583574
// First, sort by the cost-per-value of the path, dropping the paths that cost the most for
35593575
// the value they contribute towards the payment amount.
35603576
// We sort in descending order as we will remove from the front in `retain`, next.
3561-
selected_route.sort_unstable_by(|a, b|
3562-
(((b.get_cost_msat() as u128) << 64) / (b.get_value_msat() as u128))
3563-
.cmp(&(((a.get_cost_msat() as u128) << 64) / (a.get_value_msat() as u128)))
3564-
);
3577+
selected_route.sort_unstable_by(|a, b| b.get_cost().cmp(&a.get_cost()));
35653578

35663579
// We should make sure that at least 1 path left.
35673580
let mut paths_left = selected_route.len();
@@ -3587,7 +3600,7 @@ where L::Target: Logger {
35873600
selected_route.sort_unstable_by(|a, b| {
35883601
let a_f = a.hops.iter().map(|hop| hop.0.candidate.fees().proportional_millionths as u64).sum::<u64>();
35893602
let b_f = b.hops.iter().map(|hop| hop.0.candidate.fees().proportional_millionths as u64).sum::<u64>();
3590-
a_f.cmp(&b_f).then_with(|| b.get_cost_msat().cmp(&a.get_cost_msat()))
3603+
a_f.cmp(&b_f).then_with(|| b.get_fee_cost_msat().cmp(&a.get_fee_cost_msat()))
35913604
});
35923605
let expensive_payment_path = selected_route.first_mut().unwrap();
35933606

0 commit comments

Comments
 (0)