Skip to content

Commit 5023ff0

Browse files
authored
Merge pull request #1610 from TheBlueMatt/2022-07-no-rand-path-selection
2 parents 9d35026 + ff8d3f7 commit 5023ff0

File tree

2 files changed

+58
-120
lines changed

2 files changed

+58
-120
lines changed

lightning/src/ln/functional_tests.rs

-23
Original file line numberDiff line numberDiff line change
@@ -1059,26 +1059,6 @@ fn fake_network_test() {
10591059
fail_payment(&nodes[1], &vec!(&nodes[3], &nodes[2], &nodes[1])[..], payment_hash_2);
10601060
claim_payment(&nodes[1], &vec!(&nodes[2], &nodes[3], &nodes[1])[..], payment_preimage_1);
10611061

1062-
// Add a duplicate new channel from 2 to 4
1063-
let chan_5 = create_announced_chan_between_nodes(&nodes, 1, 3, InitFeatures::known(), InitFeatures::known());
1064-
1065-
// Send some payments across both channels
1066-
let payment_preimage_3 = route_payment(&nodes[0], &vec!(&nodes[1], &nodes[3])[..], 3000000).0;
1067-
let payment_preimage_4 = route_payment(&nodes[0], &vec!(&nodes[1], &nodes[3])[..], 3000000).0;
1068-
let payment_preimage_5 = route_payment(&nodes[0], &vec!(&nodes[1], &nodes[3])[..], 3000000).0;
1069-
1070-
1071-
route_over_limit(&nodes[0], &vec!(&nodes[1], &nodes[3])[..], 3000000);
1072-
let events = nodes[0].node.get_and_clear_pending_msg_events();
1073-
assert_eq!(events.len(), 0);
1074-
nodes[0].logger.assert_log_regex("lightning::ln::channelmanager".to_string(), regex::Regex::new(r"Cannot send value that would put us over the max HTLC value in flight our peer will accept \(\d+\)").unwrap(), 1);
1075-
1076-
//TODO: Test that routes work again here as we've been notified that the channel is full
1077-
1078-
claim_payment(&nodes[0], &vec!(&nodes[1], &nodes[3])[..], payment_preimage_3);
1079-
claim_payment(&nodes[0], &vec!(&nodes[1], &nodes[3])[..], payment_preimage_4);
1080-
claim_payment(&nodes[0], &vec!(&nodes[1], &nodes[3])[..], payment_preimage_5);
1081-
10821062
// Close down the channels...
10831063
close_channel(&nodes[0], &nodes[1], &chan_1.2, chan_1.3, true);
10841064
check_closed_event!(nodes[0], 1, ClosureReason::CooperativeClosure);
@@ -1092,9 +1072,6 @@ fn fake_network_test() {
10921072
close_channel(&nodes[1], &nodes[3], &chan_4.2, chan_4.3, false);
10931073
check_closed_event!(nodes[1], 1, ClosureReason::CooperativeClosure);
10941074
check_closed_event!(nodes[3], 1, ClosureReason::CooperativeClosure);
1095-
close_channel(&nodes[1], &nodes[3], &chan_5.2, chan_5.3, false);
1096-
check_closed_event!(nodes[1], 1, ClosureReason::CooperativeClosure);
1097-
check_closed_event!(nodes[3], 1, ClosureReason::CooperativeClosure);
10981075
}
10991076

11001077
#[test]

lightning/src/routing/router.rs

+58-97
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,7 @@ pub struct PaymentParameters {
238238
/// A value of 0 will allow payments up to and including a channel's total announced usable
239239
/// capacity, a value of one will only use up to half its capacity, two 1/4, etc.
240240
///
241-
/// Default value: 1
241+
/// Default value: 2
242242
pub max_channel_saturation_power_of_half: u8,
243243

244244
/// A list of SCIDs which this payment was previously attempted over and which caused the
@@ -253,7 +253,7 @@ impl_writeable_tlv_based!(PaymentParameters, {
253253
(2, features, option),
254254
(3, max_path_count, (default_value, DEFAULT_MAX_PATH_COUNT)),
255255
(4, route_hints, vec_type),
256-
(5, max_channel_saturation_power_of_half, (default_value, 1)),
256+
(5, max_channel_saturation_power_of_half, (default_value, 2)),
257257
(6, expiry_time, option),
258258
(7, previously_failed_channels, vec_type),
259259
});
@@ -268,7 +268,7 @@ impl PaymentParameters {
268268
expiry_time: None,
269269
max_total_cltv_expiry_delta: DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA,
270270
max_path_count: DEFAULT_MAX_PATH_COUNT,
271-
max_channel_saturation_power_of_half: 1,
271+
max_channel_saturation_power_of_half: 2,
272272
previously_failed_channels: Vec::new(),
273273
}
274274
}
@@ -754,7 +754,7 @@ where L::Target: Logger, GL::Target: Logger {
754754
pub(crate) fn get_route<L: Deref, S: Score>(
755755
our_node_pubkey: &PublicKey, payment_params: &PaymentParameters, network_graph: &ReadOnlyNetworkGraph,
756756
first_hops: Option<&[&ChannelDetails]>, final_value_msat: u64, final_cltv_expiry_delta: u32,
757-
logger: L, scorer: &S, random_seed_bytes: &[u8; 32]
757+
logger: L, scorer: &S, _random_seed_bytes: &[u8; 32]
758758
) -> Result<Route, LightningError>
759759
where L::Target: Logger {
760760
let payee_node_id = NodeId::from_pubkey(&payment_params.payee_pubkey);
@@ -796,11 +796,11 @@ where L::Target: Logger {
796796
// 4. See if we managed to collect paths which aggregately are able to transfer target value
797797
// (not recommended value).
798798
// 5. If yes, proceed. If not, fail routing.
799-
// 6. Randomly combine paths into routes having enough to fulfill the payment. (TODO: knapsack)
800-
// 7. Of all the found paths, select only those with the lowest total fee.
801-
// 8. The last path in every selected route is likely to be more than we need.
802-
// Reduce its value-to-transfer and recompute fees.
803-
// 9. Choose the best route by the lowest total fee.
799+
// 6. Select the paths which have the lowest cost (fee plus scorer penalty) per amount
800+
// transferred up to the transfer target value.
801+
// 7. Reduce the value of the last path until we are sending only the target value.
802+
// 8. If our maximum channel saturation limit caused us to pick two identical paths, combine
803+
// them so that we're not sending two HTLCs along the same path.
804804

805805
// As for the actual search algorithm,
806806
// we do a payee-to-payer pseudo-Dijkstra's sorting by each node's distance from the payee
@@ -1476,7 +1476,7 @@ where L::Target: Logger {
14761476
// Both these cases (and other cases except reaching recommended_value_msat) mean that
14771477
// paths_collection will be stopped because found_new_path==false.
14781478
// This is not necessarily a routing failure.
1479-
'path_construction: while let Some(RouteGraphNode { node_id, lowest_fee_to_node, total_cltv_delta, value_contribution_msat, path_htlc_minimum_msat, path_penalty_msat, path_length_to_node, .. }) = targets.pop() {
1479+
'path_construction: while let Some(RouteGraphNode { node_id, lowest_fee_to_node, total_cltv_delta, mut value_contribution_msat, path_htlc_minimum_msat, path_penalty_msat, path_length_to_node, .. }) = targets.pop() {
14801480

14811481
// Since we're going payee-to-payer, hitting our node as a target means we should stop
14821482
// traversing the graph and arrange the path out of what we found.
@@ -1542,7 +1542,9 @@ where L::Target: Logger {
15421542
// on some channels we already passed (assuming dest->source direction). Here, we
15431543
// recompute the fees again, so that if that's the case, we match the currently
15441544
// underpaid htlc_minimum_msat with fees.
1545-
payment_path.update_value_and_recompute_fees(cmp::min(value_contribution_msat, final_value_msat));
1545+
debug_assert_eq!(payment_path.get_value_msat(), value_contribution_msat);
1546+
value_contribution_msat = cmp::min(value_contribution_msat, final_value_msat);
1547+
payment_path.update_value_and_recompute_fees(value_contribution_msat);
15461548

15471549
// Since a path allows to transfer as much value as
15481550
// the smallest channel it has ("bottleneck"), we should recompute
@@ -1653,96 +1655,55 @@ where L::Target: Logger {
16531655
return Err(LightningError{err: "Failed to find a sufficient route to the given destination".to_owned(), action: ErrorAction::IgnoreError});
16541656
}
16551657

1656-
// Sort by total fees and take the best paths.
1657-
payment_paths.sort_unstable_by_key(|path| path.get_total_fee_paid_msat());
1658-
if payment_paths.len() > 50 {
1659-
payment_paths.truncate(50);
1660-
}
1658+
// Step (6).
1659+
let mut selected_route = payment_paths;
16611660

1662-
// Draw multiple sufficient routes by randomly combining the selected paths.
1663-
let mut drawn_routes = Vec::new();
1664-
let mut prng = ChaCha20::new(random_seed_bytes, &[0u8; 12]);
1665-
let mut random_index_bytes = [0u8; ::core::mem::size_of::<usize>()];
1661+
debug_assert_eq!(selected_route.iter().map(|p| p.get_value_msat()).sum::<u64>(), already_collected_value_msat);
1662+
let mut overpaid_value_msat = already_collected_value_msat - final_value_msat;
16661663

1667-
let num_permutations = payment_paths.len();
1668-
for _ in 0..num_permutations {
1669-
let mut cur_route = Vec::<PaymentPath>::new();
1670-
let mut aggregate_route_value_msat = 0;
1664+
// First, sort by the cost-per-value of the path, dropping the paths that cost the most for
1665+
// the value they contribute towards the payment amount.
1666+
// We sort in descending order as we will remove from the front in `retain`, next.
1667+
selected_route.sort_unstable_by(|a, b|
1668+
(((b.get_cost_msat() as u128) << 64) / (b.get_value_msat() as u128))
1669+
.cmp(&(((a.get_cost_msat() as u128) << 64) / (a.get_value_msat() as u128)))
1670+
);
16711671

1672-
// Step (6).
1673-
// Do a Fisher-Yates shuffle to create a random permutation of the payment paths
1674-
for cur_index in (1..payment_paths.len()).rev() {
1675-
prng.process_in_place(&mut random_index_bytes);
1676-
let random_index = usize::from_be_bytes(random_index_bytes).wrapping_rem(cur_index+1);
1677-
payment_paths.swap(cur_index, random_index);
1672+
// We should make sure that at least 1 path left.
1673+
let mut paths_left = selected_route.len();
1674+
selected_route.retain(|path| {
1675+
if paths_left == 1 {
1676+
return true
1677+
}
1678+
let path_value_msat = path.get_value_msat();
1679+
if path_value_msat <= overpaid_value_msat {
1680+
overpaid_value_msat -= path_value_msat;
1681+
paths_left -= 1;
1682+
return false;
16781683
}
1684+
true
1685+
});
1686+
debug_assert!(selected_route.len() > 0);
16791687

1688+
if overpaid_value_msat != 0 {
16801689
// Step (7).
1681-
for payment_path in &payment_paths {
1682-
cur_route.push(payment_path.clone());
1683-
aggregate_route_value_msat += payment_path.get_value_msat();
1684-
if aggregate_route_value_msat > final_value_msat {
1685-
// Last path likely overpaid. Substract it from the most expensive
1686-
// (in terms of proportional fee) path in this route and recompute fees.
1687-
// This might be not the most economically efficient way, but fewer paths
1688-
// also makes routing more reliable.
1689-
let mut overpaid_value_msat = aggregate_route_value_msat - final_value_msat;
1690-
1691-
// First, we drop some expensive low-value paths entirely if possible, since fewer
1692-
// paths is better: the payment is less likely to fail. In order to do so, we sort
1693-
// by value and fall back to total fees paid, i.e., in case of equal values we
1694-
// prefer lower cost paths.
1695-
cur_route.sort_unstable_by(|a, b| {
1696-
a.get_value_msat().cmp(&b.get_value_msat())
1697-
// Reverse ordering for cost, so we drop higher-cost paths first
1698-
.then_with(|| b.get_cost_msat().cmp(&a.get_cost_msat()))
1699-
});
1700-
1701-
// We should make sure that at least 1 path left.
1702-
let mut paths_left = cur_route.len();
1703-
cur_route.retain(|path| {
1704-
if paths_left == 1 {
1705-
return true
1706-
}
1707-
let mut keep = true;
1708-
let path_value_msat = path.get_value_msat();
1709-
if path_value_msat <= overpaid_value_msat {
1710-
keep = false;
1711-
overpaid_value_msat -= path_value_msat;
1712-
paths_left -= 1;
1713-
}
1714-
keep
1715-
});
1716-
1717-
if overpaid_value_msat == 0 {
1718-
break;
1719-
}
1690+
// Now, subtract the remaining overpaid value from the most-expensive path.
1691+
// TODO: this could also be optimized by also sorting by feerate_per_sat_routed,
1692+
// so that the sender pays less fees overall. And also htlc_minimum_msat.
1693+
selected_route.sort_unstable_by(|a, b| {
1694+
let a_f = a.hops.iter().map(|hop| hop.0.candidate.fees().proportional_millionths as u64).sum::<u64>();
1695+
let b_f = b.hops.iter().map(|hop| hop.0.candidate.fees().proportional_millionths as u64).sum::<u64>();
1696+
a_f.cmp(&b_f).then_with(|| b.get_cost_msat().cmp(&a.get_cost_msat()))
1697+
});
1698+
let expensive_payment_path = selected_route.first_mut().unwrap();
17201699

1721-
assert!(cur_route.len() > 0);
1722-
1723-
// Step (8).
1724-
// Now, subtract the overpaid value from the most-expensive path.
1725-
// TODO: this could also be optimized by also sorting by feerate_per_sat_routed,
1726-
// so that the sender pays less fees overall. And also htlc_minimum_msat.
1727-
cur_route.sort_unstable_by_key(|path| { path.hops.iter().map(|hop| hop.0.candidate.fees().proportional_millionths as u64).sum::<u64>() });
1728-
let expensive_payment_path = cur_route.first_mut().unwrap();
1729-
1730-
// We already dropped all the small value paths above, meaning all the
1731-
// remaining paths are larger than remaining overpaid_value_msat.
1732-
// Thus, this can't be negative.
1733-
let expensive_path_new_value_msat = expensive_payment_path.get_value_msat() - overpaid_value_msat;
1734-
expensive_payment_path.update_value_and_recompute_fees(expensive_path_new_value_msat);
1735-
break;
1736-
}
1737-
}
1738-
drawn_routes.push(cur_route);
1700+
// We already dropped all the paths with value below `overpaid_value_msat` above, thus this
1701+
// can't go negative.
1702+
let expensive_path_new_value_msat = expensive_payment_path.get_value_msat() - overpaid_value_msat;
1703+
expensive_payment_path.update_value_and_recompute_fees(expensive_path_new_value_msat);
17391704
}
17401705

1741-
// Step (9).
1742-
// Select the best route by lowest total cost.
1743-
drawn_routes.sort_unstable_by_key(|paths| paths.iter().map(|path| path.get_cost_msat()).sum::<u64>());
1744-
let selected_route = drawn_routes.first_mut().unwrap();
1745-
1706+
// Step (8).
17461707
// Sort by the path itself and combine redundant paths.
17471708
// Note that we sort by SCIDs alone as its simpler but when combining we have to ensure we
17481709
// compare both SCIDs and NodeIds as individual nodes may use random aliases causing collisions
@@ -4796,7 +4757,7 @@ mod tests {
47964757
cltv_expiry_delta: 42,
47974758
htlc_minimum_msat: None,
47984759
htlc_maximum_msat: None,
4799-
}])]);
4760+
}])]).with_max_channel_saturation_power_of_half(0);
48004761

48014762
// Keep only two paths from us to nodes[2], both with a 99sat HTLC maximum, with one with
48024763
// no fee and one with a 1msat fee. Previously, trying to route 100 sats to nodes[2] here
@@ -5780,7 +5741,7 @@ mod tests {
57805741
flags: 0,
57815742
cltv_expiry_delta: (4 << 4) | 1,
57825743
htlc_minimum_msat: 0,
5783-
htlc_maximum_msat: OptionalField::Present(200_000_000),
5744+
htlc_maximum_msat: OptionalField::Present(250_000_000),
57845745
fee_base_msat: 0,
57855746
fee_proportional_millionths: 0,
57865747
excess_data: Vec::new()
@@ -5792,7 +5753,7 @@ mod tests {
57925753
flags: 0,
57935754
cltv_expiry_delta: (13 << 4) | 1,
57945755
htlc_minimum_msat: 0,
5795-
htlc_maximum_msat: OptionalField::Present(200_000_000),
5756+
htlc_maximum_msat: OptionalField::Present(250_000_000),
57965757
fee_base_msat: 0,
57975758
fee_proportional_millionths: 0,
57985759
excess_data: Vec::new()
@@ -5801,8 +5762,8 @@ mod tests {
58015762
let payment_params = PaymentParameters::from_node_id(nodes[2]).with_features(InvoiceFeatures::known());
58025763
let keys_manager = test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
58035764
let random_seed_bytes = keys_manager.get_secure_random_bytes();
5804-
// 150,000 sat is less than the available liquidity on each channel, set above.
5805-
let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 150_000_000, 42, Arc::clone(&logger), &scorer, &random_seed_bytes).unwrap();
5765+
// 100,000 sats is less than the available liquidity on each channel, set above.
5766+
let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 100_000_000, 42, Arc::clone(&logger), &scorer, &random_seed_bytes).unwrap();
58065767
assert_eq!(route.paths.len(), 2);
58075768
assert!((route.paths[0][1].short_channel_id == 4 && route.paths[1][1].short_channel_id == 13) ||
58085769
(route.paths[1][1].short_channel_id == 4 && route.paths[0][1].short_channel_id == 13));

0 commit comments

Comments
 (0)