Skip to content

Commit 5cca9a0

Browse files
authored
Merge pull request #1605 from TheBlueMatt/2022-07-smaller-mpp-parts
Avoid saturating channels before we split payments
2 parents b760407 + e6d40a7 commit 5cca9a0

File tree

4 files changed

+168
-39
lines changed

4 files changed

+168
-39
lines changed

lightning/src/ln/functional_tests.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1824,9 +1824,12 @@ fn test_channel_reserve_holding_cell_htlcs() {
18241824

18251825
// attempt to send amt_msat > their_max_htlc_value_in_flight_msat
18261826
{
1827-
let (mut route, our_payment_hash, _, our_payment_secret) = get_route_and_payment_hash!(nodes[0], nodes[2], recv_value_0);
1827+
let payment_params = PaymentParameters::from_node_id(nodes[2].node.get_our_node_id())
1828+
.with_features(InvoiceFeatures::known()).with_max_channel_saturation_power_of_half(0);
1829+
let (mut route, our_payment_hash, _, our_payment_secret) = get_route_and_payment_hash!(nodes[0], nodes[2], payment_params, recv_value_0, TEST_FINAL_CLTV);
18281830
route.paths[0].last_mut().unwrap().fee_msat += 1;
18291831
assert!(route.paths[0].iter().rev().skip(1).all(|h| h.fee_msat == feemsat));
1832+
18301833
unwrap_send_err!(nodes[0].node.send_payment(&route, our_payment_hash, &Some(our_payment_secret)), true, APIError::ChannelUnavailable { ref err },
18311834
assert!(regex::Regex::new(r"Cannot send value that would put us over the max HTLC value in flight our peer will accept \(\d+\)").unwrap().is_match(err)));
18321835
assert!(nodes[0].node.get_and_clear_pending_msg_events().is_empty());
@@ -1845,7 +1848,12 @@ fn test_channel_reserve_holding_cell_htlcs() {
18451848
if stat01.value_to_self_msat < stat01.channel_reserve_msat + commit_tx_fee_all_htlcs + ensure_htlc_amounts_above_dust_buffer + amt_msat {
18461849
break;
18471850
}
1848-
send_payment(&nodes[0], &vec![&nodes[1], &nodes[2]][..], recv_value_0);
1851+
1852+
let payment_params = PaymentParameters::from_node_id(nodes[2].node.get_our_node_id())
1853+
.with_features(InvoiceFeatures::known()).with_max_channel_saturation_power_of_half(0);
1854+
let route = get_route!(nodes[0], payment_params, recv_value_0, TEST_FINAL_CLTV).unwrap();
1855+
let (payment_preimage, ..) = send_along_route(&nodes[0], route, &[&nodes[1], &nodes[2]], recv_value_0);
1856+
claim_payment(&nodes[0], &[&nodes[1], &nodes[2]], payment_preimage);
18491857

18501858
let (stat01_, stat11_, stat12_, stat22_) = (
18511859
get_channel_value_stat!(nodes[0], chan_1.2),

lightning/src/ln/payment_tests.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -942,7 +942,7 @@ fn failed_probe_yields_event() {
942942

943943
let payment_params = PaymentParameters::from_node_id(nodes[2].node.get_our_node_id());
944944

945-
let (route, _, _, _) = get_route_and_payment_hash!(&nodes[0], nodes[2], &payment_params, 9_999_000, 42);
945+
let (route, _, _, _) = get_route_and_payment_hash!(&nodes[0], nodes[2], &payment_params, 9_998_000, 42);
946946

947947
let (payment_hash, payment_id) = nodes[0].node.send_probe(route.paths[0].clone()).unwrap();
948948

lightning/src/routing/gossip.rs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -816,10 +816,6 @@ impl<'a> DirectedChannelInfoWithUpdate<'a> {
816816
/// Returns the [`EffectiveCapacity`] of the channel in the direction.
817817
#[inline]
818818
pub(super) fn effective_capacity(&self) -> EffectiveCapacity { self.inner.effective_capacity() }
819-
820-
/// Returns the maximum HTLC amount allowed over the channel in the direction.
821-
#[inline]
822-
pub(super) fn htlc_maximum_msat(&self) -> u64 { self.inner.htlc_maximum_msat() }
823819
}
824820

825821
impl<'a> fmt::Debug for DirectedChannelInfoWithUpdate<'a> {

lightning/src/routing/router.rs

Lines changed: 157 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,21 @@ pub struct PaymentParameters {
225225
/// The maximum number of paths that may be used by (MPP) payments.
226226
/// Defaults to [`DEFAULT_MAX_PATH_COUNT`].
227227
pub max_path_count: u8,
228+
229+
/// Selects the maximum share of a channel's total capacity which will be sent over a channel,
230+
/// as a power of 1/2. A higher value prefers to send the payment using more MPP parts whereas
231+
/// a lower value prefers to send larger MPP parts, potentially saturating channels and
232+
/// increasing failure probability for those paths.
233+
///
234+
/// Note that this restriction will be relaxed during pathfinding after paths which meet this
235+
/// restriction have been found. While paths which meet this criteria will be searched for, it
236+
/// is ultimately up to the scorer to select them over other paths.
237+
///
238+
/// A value of 0 will allow payments up to and including a channel's total announced usable
239+
/// capacity, a value of one will only use up to half its capacity, two 1/4, etc.
240+
///
241+
/// Default value: 1
242+
pub max_channel_saturation_power_of_half: u8,
228243
}
229244

230245
impl_writeable_tlv_based!(PaymentParameters, {
@@ -233,6 +248,7 @@ impl_writeable_tlv_based!(PaymentParameters, {
233248
(2, features, option),
234249
(3, max_path_count, (default_value, DEFAULT_MAX_PATH_COUNT)),
235250
(4, route_hints, vec_type),
251+
(5, max_channel_saturation_power_of_half, (default_value, 1)),
236252
(6, expiry_time, option),
237253
});
238254

@@ -246,6 +262,7 @@ impl PaymentParameters {
246262
expiry_time: None,
247263
max_total_cltv_expiry_delta: DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA,
248264
max_path_count: DEFAULT_MAX_PATH_COUNT,
265+
max_channel_saturation_power_of_half: 1,
249266
}
250267
}
251268

@@ -288,6 +305,13 @@ impl PaymentParameters {
288305
pub fn with_max_path_count(self, max_path_count: u8) -> Self {
289306
Self { max_path_count, ..self }
290307
}
308+
309+
/// Includes a limit for the maximum number of payment paths that may be used.
310+
///
311+
/// (C-not exported) since bindings don't support move semantics
312+
pub fn with_max_channel_saturation_power_of_half(self, max_channel_saturation_power_of_half: u8) -> Self {
313+
Self { max_channel_saturation_power_of_half, ..self }
314+
}
291315
}
292316

293317
/// A list of hops along a payment path terminating with a channel to the recipient.
@@ -433,16 +457,6 @@ impl<'a> CandidateRouteHop<'a> {
433457
}
434458
}
435459

436-
fn htlc_maximum_msat(&self) -> u64 {
437-
match self {
438-
CandidateRouteHop::FirstHop { details } => details.next_outbound_htlc_limit_msat,
439-
CandidateRouteHop::PublicHop { info, .. } => info.htlc_maximum_msat(),
440-
CandidateRouteHop::PrivateHop { hint } => {
441-
hint.htlc_maximum_msat.unwrap_or(u64::max_value())
442-
},
443-
}
444-
}
445-
446460
fn fees(&self) -> RoutingFees {
447461
match self {
448462
CandidateRouteHop::FirstHop { .. } => RoutingFees {
@@ -464,6 +478,33 @@ impl<'a> CandidateRouteHop<'a> {
464478
}
465479
}
466480

481+
#[inline]
482+
fn max_htlc_from_capacity(capacity: EffectiveCapacity, max_channel_saturation_power_of_half: u8) -> u64 {
483+
let saturation_shift: u32 = max_channel_saturation_power_of_half as u32;
484+
match capacity {
485+
EffectiveCapacity::ExactLiquidity { liquidity_msat } => liquidity_msat,
486+
EffectiveCapacity::Infinite => u64::max_value(),
487+
EffectiveCapacity::Unknown => EffectiveCapacity::Unknown.as_msat(),
488+
EffectiveCapacity::MaximumHTLC { amount_msat } =>
489+
amount_msat.checked_shr(saturation_shift).unwrap_or(0),
490+
EffectiveCapacity::Total { capacity_msat, htlc_maximum_msat: None } =>
491+
capacity_msat.checked_shr(saturation_shift).unwrap_or(0),
492+
EffectiveCapacity::Total { capacity_msat, htlc_maximum_msat: Some(htlc_max) } =>
493+
cmp::min(capacity_msat.checked_shr(saturation_shift).unwrap_or(0), htlc_max),
494+
}
495+
}
496+
497+
fn iter_equal<I1: Iterator, I2: Iterator>(mut iter_a: I1, mut iter_b: I2)
498+
-> bool where I1::Item: PartialEq<I2::Item> {
499+
loop {
500+
let a = iter_a.next();
501+
let b = iter_b.next();
502+
if a.is_none() && b.is_none() { return true; }
503+
if a.is_none() || b.is_none() { return false; }
504+
if a.unwrap().ne(&b.unwrap()) { return false; }
505+
}
506+
}
507+
467508
/// It's useful to keep track of the hops associated with the fees required to use them,
468509
/// so that we can choose cheaper paths (as per Dijkstra's algorithm).
469510
/// Fee values should be updated only in the context of the whole path, see update_value_and_recompute_fees.
@@ -571,10 +612,9 @@ impl<'a> PaymentPath<'a> {
571612
// to the fees being paid not lining up with the actual limits.
572613
//
573614
// Note that this function is not aware of the available_liquidity limit, and thus does not
574-
// support increasing the value being transferred.
615+
// support increasing the value being transferred beyond what was selected during the initial
616+
// routing passes.
575617
fn update_value_and_recompute_fees(&mut self, value_msat: u64) {
576-
assert!(value_msat <= self.hops.last().unwrap().0.fee_msat);
577-
578618
let mut total_fee_paid_msat = 0 as u64;
579619
for i in (0..self.hops.len()).rev() {
580620
let last_hop = i == self.hops.len() - 1;
@@ -882,6 +922,11 @@ where L::Target: Logger {
882922
final_value_msat
883923
};
884924

925+
// When we start collecting routes we enforce the max_channel_saturation_power_of_half
926+
// requirement strictly. After we've collected enough (or if we fail to find new routes) we
927+
// drop the requirement by setting this to 0.
928+
let mut channel_saturation_pow_half = payment_params.max_channel_saturation_power_of_half;
929+
885930
// Keep track of how much liquidity has been used in selected channels. Used to determine
886931
// if the channel can be used by additional MPP paths or to inform path finding decisions. It is
887932
// aware of direction *only* to ensure that the correct htlc_maximum_msat value is used. Hence,
@@ -934,7 +979,8 @@ where L::Target: Logger {
934979
// - for first and last hops early in get_route
935980
if $src_node_id != $dest_node_id {
936981
let short_channel_id = $candidate.short_channel_id();
937-
let htlc_maximum_msat = $candidate.htlc_maximum_msat();
982+
let effective_capacity = $candidate.effective_capacity();
983+
let htlc_maximum_msat = max_htlc_from_capacity(effective_capacity, channel_saturation_pow_half);
938984

939985
// It is tricky to subtract $next_hops_fee_msat from available liquidity here.
940986
// It may be misleading because we might later choose to reduce the value transferred
@@ -1084,7 +1130,7 @@ where L::Target: Logger {
10841130
let channel_usage = ChannelUsage {
10851131
amount_msat: amount_to_transfer_over_msat,
10861132
inflight_htlc_msat: used_liquidity_msat,
1087-
effective_capacity: $candidate.effective_capacity(),
1133+
effective_capacity,
10881134
};
10891135
let channel_penalty_msat = scorer.channel_penalty_msat(
10901136
short_channel_id, &$src_node_id, &$dest_node_id, channel_usage
@@ -1505,12 +1551,14 @@ where L::Target: Logger {
15051551
.entry((hop.candidate.short_channel_id(), *prev_hop < hop.node_id))
15061552
.and_modify(|used_liquidity_msat| *used_liquidity_msat += spent_on_hop_msat)
15071553
.or_insert(spent_on_hop_msat);
1508-
if *used_liquidity_msat == hop.candidate.htlc_maximum_msat() {
1554+
let hop_capacity = hop.candidate.effective_capacity();
1555+
let hop_max_msat = max_htlc_from_capacity(hop_capacity, channel_saturation_pow_half);
1556+
if *used_liquidity_msat == hop_max_msat {
15091557
// If this path used all of this channel's available liquidity, we know
15101558
// this path will not be selected again in the next loop iteration.
15111559
prevented_redundant_path_selection = true;
15121560
}
1513-
debug_assert!(*used_liquidity_msat <= hop.candidate.htlc_maximum_msat());
1561+
debug_assert!(*used_liquidity_msat <= hop_max_msat);
15141562
}
15151563
if !prevented_redundant_path_selection {
15161564
// If we weren't capped by hitting a liquidity limit on a channel in the path,
@@ -1551,6 +1599,10 @@ where L::Target: Logger {
15511599
}
15521600

15531601
if !allow_mpp {
1602+
if !found_new_path && channel_saturation_pow_half != 0 {
1603+
channel_saturation_pow_half = 0;
1604+
continue 'paths_collection;
1605+
}
15541606
// If we don't support MPP, no use trying to gather more value ever.
15551607
break 'paths_collection;
15561608
}
@@ -1560,7 +1612,9 @@ where L::Target: Logger {
15601612
// iteration.
15611613
// In the latter case, making another path finding attempt won't help,
15621614
// because we deterministically terminated the search due to low liquidity.
1563-
if already_collected_value_msat >= recommended_value_msat || !found_new_path {
1615+
if !found_new_path && channel_saturation_pow_half != 0 {
1616+
channel_saturation_pow_half = 0;
1617+
} else if already_collected_value_msat >= recommended_value_msat || !found_new_path {
15641618
log_trace!(logger, "Have now collected {} msat (seeking {} msat) in paths. Last path loop {} a new path.",
15651619
already_collected_value_msat, recommended_value_msat, if found_new_path { "found" } else { "did not find" });
15661620
break 'paths_collection;
@@ -1676,8 +1730,32 @@ where L::Target: Logger {
16761730
// Step (9).
16771731
// Select the best route by lowest total cost.
16781732
drawn_routes.sort_unstable_by_key(|paths| paths.iter().map(|path| path.get_cost_msat()).sum::<u64>());
1733+
let selected_route = drawn_routes.first_mut().unwrap();
1734+
1735+
// Sort by the path itself and combine redundant paths.
1736+
// Note that we sort by SCIDs alone as its simpler but when combining we have to ensure we
1737+
// compare both SCIDs and NodeIds as individual nodes may use random aliases causing collisions
1738+
// across nodes.
1739+
selected_route.sort_unstable_by_key(|path| {
1740+
let mut key = [0u64; MAX_PATH_LENGTH_ESTIMATE as usize];
1741+
debug_assert!(path.hops.len() <= key.len());
1742+
for (scid, key) in path.hops.iter().map(|h| h.0.candidate.short_channel_id()).zip(key.iter_mut()) {
1743+
*key = scid;
1744+
}
1745+
key
1746+
});
1747+
for idx in 0..(selected_route.len() - 1) {
1748+
if idx + 1 >= selected_route.len() { break; }
1749+
if iter_equal(selected_route[idx ].hops.iter().map(|h| (h.0.candidate.short_channel_id(), h.0.node_id)),
1750+
selected_route[idx + 1].hops.iter().map(|h| (h.0.candidate.short_channel_id(), h.0.node_id))) {
1751+
let new_value = selected_route[idx].get_value_msat() + selected_route[idx + 1].get_value_msat();
1752+
selected_route[idx].update_value_and_recompute_fees(new_value);
1753+
selected_route.remove(idx + 1);
1754+
}
1755+
}
1756+
16791757
let mut selected_paths = Vec::<Vec<Result<RouteHop, LightningError>>>::new();
1680-
for payment_path in drawn_routes.first().unwrap() {
1758+
for payment_path in selected_route {
16811759
let mut path = payment_path.hops.iter().map(|(payment_hop, node_features)| {
16821760
Ok(RouteHop {
16831761
pubkey: PublicKey::from_slice(payment_hop.node_id.as_slice()).map_err(|_| LightningError{err: format!("Public key {:?} is invalid", &payment_hop.node_id), action: ErrorAction::IgnoreAndLog(Level::Trace)})?,
@@ -4763,17 +4841,18 @@ mod tests {
47634841

47644842
// Get a route for 100 sats and check that we found the MPP route no problem and didn't
47654843
// overpay at all.
4766-
let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 100_000, 42, Arc::clone(&logger), &scorer, &random_seed_bytes).unwrap();
4844+
let mut route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 100_000, 42, Arc::clone(&logger), &scorer, &random_seed_bytes).unwrap();
47674845
assert_eq!(route.paths.len(), 2);
4768-
// Paths are somewhat randomly ordered, but:
4769-
// * the first is channel 2 (1 msat fee) -> channel 4 -> channel 42
4770-
// * the second is channel 1 (0 fee, but 99 sat maximum) -> channel 3 -> channel 42
4771-
assert_eq!(route.paths[0][0].short_channel_id, 2);
4772-
assert_eq!(route.paths[0][0].fee_msat, 1);
4773-
assert_eq!(route.paths[0][2].fee_msat, 1_000);
4774-
assert_eq!(route.paths[1][0].short_channel_id, 1);
4775-
assert_eq!(route.paths[1][0].fee_msat, 0);
4776-
assert_eq!(route.paths[1][2].fee_msat, 99_000);
4846+
route.paths.sort_by_key(|path| path[0].short_channel_id);
4847+
// Paths are manually ordered ordered by SCID, so:
4848+
// * the first is channel 1 (0 fee, but 99 sat maximum) -> channel 3 -> channel 42
4849+
// * the second is channel 2 (1 msat fee) -> channel 4 -> channel 42
4850+
assert_eq!(route.paths[0][0].short_channel_id, 1);
4851+
assert_eq!(route.paths[0][0].fee_msat, 0);
4852+
assert_eq!(route.paths[0][2].fee_msat, 99_000);
4853+
assert_eq!(route.paths[1][0].short_channel_id, 2);
4854+
assert_eq!(route.paths[1][0].fee_msat, 1);
4855+
assert_eq!(route.paths[1][2].fee_msat, 1_000);
47774856
assert_eq!(route.get_total_fees(), 1);
47784857
assert_eq!(route.get_total_amount(), 100_000);
47794858
}
@@ -4787,7 +4866,8 @@ mod tests {
47874866
let scorer = test_utils::TestScorer::with_penalty(0);
47884867
let keys_manager = test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
47894868
let random_seed_bytes = keys_manager.get_secure_random_bytes();
4790-
let payment_params = PaymentParameters::from_node_id(nodes[2]).with_features(InvoiceFeatures::known());
4869+
let payment_params = PaymentParameters::from_node_id(nodes[2]).with_features(InvoiceFeatures::known())
4870+
.with_max_channel_saturation_power_of_half(0);
47914871

47924872
// We need a route consisting of 3 paths:
47934873
// From our node to node2 via node0, node7, node1 (three paths one hop each).
@@ -5235,12 +5315,13 @@ mod tests {
52355315
assert_eq!(route.paths[0].len(), 1);
52365316
assert_eq!(route.paths[1].len(), 1);
52375317

5318+
assert!((route.paths[0][0].short_channel_id == 3 && route.paths[1][0].short_channel_id == 2) ||
5319+
(route.paths[0][0].short_channel_id == 2 && route.paths[1][0].short_channel_id == 3));
5320+
52385321
assert_eq!(route.paths[0][0].pubkey, nodes[0]);
5239-
assert_eq!(route.paths[0][0].short_channel_id, 3);
52405322
assert_eq!(route.paths[0][0].fee_msat, 50_000);
52415323

52425324
assert_eq!(route.paths[1][0].pubkey, nodes[0]);
5243-
assert_eq!(route.paths[1][0].short_channel_id, 2);
52445325
assert_eq!(route.paths[1][0].fee_msat, 50_000);
52455326
}
52465327

@@ -5641,6 +5722,50 @@ mod tests {
56415722
}
56425723
}
56435724

5725+
#[test]
5726+
fn avoids_saturating_channels() {
5727+
let (secp_ctx, network_graph, gossip_sync, _, logger) = build_graph();
5728+
let (_, our_id, privkeys, nodes) = get_nodes(&secp_ctx);
5729+
5730+
let scorer = ProbabilisticScorer::new(Default::default(), &*network_graph, Arc::clone(&logger));
5731+
5732+
// Set the fee on channel 13 to 100% to match channel 4 giving us two equivalent paths (us
5733+
// -> node 7 -> node2 and us -> node 1 -> node 2) which we should balance over.
5734+
update_channel(&gossip_sync, &secp_ctx, &privkeys[1], UnsignedChannelUpdate {
5735+
chain_hash: genesis_block(Network::Testnet).header.block_hash(),
5736+
short_channel_id: 4,
5737+
timestamp: 2,
5738+
flags: 0,
5739+
cltv_expiry_delta: (4 << 4) | 1,
5740+
htlc_minimum_msat: 0,
5741+
htlc_maximum_msat: OptionalField::Present(200_000_000),
5742+
fee_base_msat: 0,
5743+
fee_proportional_millionths: 0,
5744+
excess_data: Vec::new()
5745+
});
5746+
update_channel(&gossip_sync, &secp_ctx, &privkeys[7], UnsignedChannelUpdate {
5747+
chain_hash: genesis_block(Network::Testnet).header.block_hash(),
5748+
short_channel_id: 13,
5749+
timestamp: 2,
5750+
flags: 0,
5751+
cltv_expiry_delta: (13 << 4) | 1,
5752+
htlc_minimum_msat: 0,
5753+
htlc_maximum_msat: OptionalField::Present(200_000_000),
5754+
fee_base_msat: 0,
5755+
fee_proportional_millionths: 0,
5756+
excess_data: Vec::new()
5757+
});
5758+
5759+
let payment_params = PaymentParameters::from_node_id(nodes[2]).with_features(InvoiceFeatures::known());
5760+
let keys_manager = test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
5761+
let random_seed_bytes = keys_manager.get_secure_random_bytes();
5762+
// 150,000 sat is less than the available liquidity on each channel, set above.
5763+
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();
5764+
assert_eq!(route.paths.len(), 2);
5765+
assert!((route.paths[0][1].short_channel_id == 4 && route.paths[1][1].short_channel_id == 13) ||
5766+
(route.paths[1][1].short_channel_id == 4 && route.paths[0][1].short_channel_id == 13));
5767+
}
5768+
56445769
#[cfg(not(feature = "no-std"))]
56455770
pub(super) fn random_init_seed() -> u64 {
56465771
// Because the default HashMap in std pulls OS randomness, we can use it as a (bad) RNG.

0 commit comments

Comments
 (0)