Skip to content

Commit f75b6cb

Browse files
authored
Merge pull request #1600 from TheBlueMatt/2022-07-explicit-avoid-retries
2 parents 5cca9a0 + a3547e2 commit f75b6cb

File tree

4 files changed

+101
-27
lines changed

4 files changed

+101
-27
lines changed

lightning/src/ln/channelmanager.rs

+7-2
Original file line numberDiff line numberDiff line change
@@ -3894,7 +3894,7 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
38943894
return;
38953895
}
38963896
mem::drop(channel_state_lock);
3897-
let retry = if let Some(payment_params_data) = payment_params {
3897+
let mut retry = if let Some(payment_params_data) = payment_params {
38983898
let path_last_hop = path.last().expect("Outbound payments must have had a valid path");
38993899
Some(RouteParameters {
39003900
payment_params: payment_params_data.clone(),
@@ -3930,6 +3930,9 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
39303930
// TODO: If we decided to blame ourselves (or one of our channels) in
39313931
// process_onion_failure we should close that channel as it implies our
39323932
// next-hop is needlessly blaming us!
3933+
if let Some(scid) = short_channel_id {
3934+
retry.as_mut().map(|r| r.payment_params.previously_failed_channels.push(scid));
3935+
}
39333936
events::Event::PaymentPathFailed {
39343937
payment_id: Some(payment_id),
39353938
payment_hash: payment_hash.clone(),
@@ -3959,14 +3962,16 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
39593962
// ChannelDetails.
39603963
// TODO: For non-temporary failures, we really should be closing the
39613964
// channel here as we apparently can't relay through them anyway.
3965+
let scid = path.first().unwrap().short_channel_id;
3966+
retry.as_mut().map(|r| r.payment_params.previously_failed_channels.push(scid));
39623967
events::Event::PaymentPathFailed {
39633968
payment_id: Some(payment_id),
39643969
payment_hash: payment_hash.clone(),
39653970
rejected_by_dest: path.len() == 1,
39663971
network_update: None,
39673972
all_paths_failed,
39683973
path: path.clone(),
3969-
short_channel_id: Some(path.first().unwrap().short_channel_id),
3974+
short_channel_id: Some(scid),
39703975
retry,
39713976
#[cfg(test)]
39723977
error_code: Some(*failure_code),

lightning/src/ln/functional_test_utils.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -1492,7 +1492,7 @@ pub fn expect_payment_failed_conditions<'a, 'b, 'c, 'd, 'e>(
14921492
let mut events = node.node.get_and_clear_pending_events();
14931493
assert_eq!(events.len(), 1);
14941494
let expected_payment_id = match events.pop().unwrap() {
1495-
Event::PaymentPathFailed { payment_hash, rejected_by_dest, path, retry, payment_id, network_update,
1495+
Event::PaymentPathFailed { payment_hash, rejected_by_dest, path, retry, payment_id, network_update, short_channel_id,
14961496
#[cfg(test)]
14971497
error_code,
14981498
#[cfg(test)]
@@ -1502,6 +1502,9 @@ pub fn expect_payment_failed_conditions<'a, 'b, 'c, 'd, 'e>(
15021502
assert!(retry.is_some(), "expected retry.is_some()");
15031503
assert_eq!(retry.as_ref().unwrap().final_value_msat, path.last().unwrap().fee_msat, "Retry amount should match last hop in path");
15041504
assert_eq!(retry.as_ref().unwrap().payment_params.payee_pubkey, path.last().unwrap().pubkey, "Retry payee node_id should match last hop in path");
1505+
if let Some(scid) = short_channel_id {
1506+
assert!(retry.as_ref().unwrap().payment_params.previously_failed_channels.contains(&scid));
1507+
}
15051508

15061509
#[cfg(test)]
15071510
{

lightning/src/routing/router.rs

+48-6
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,11 @@ pub struct PaymentParameters {
240240
///
241241
/// Default value: 1
242242
pub max_channel_saturation_power_of_half: u8,
243+
244+
/// A list of SCIDs which this payment was previously attempted over and which caused the
245+
/// payment to fail. Future attempts for the same payment shouldn't be relayed through any of
246+
/// these SCIDs.
247+
pub previously_failed_channels: Vec<u64>,
243248
}
244249

245250
impl_writeable_tlv_based!(PaymentParameters, {
@@ -250,6 +255,7 @@ impl_writeable_tlv_based!(PaymentParameters, {
250255
(4, route_hints, vec_type),
251256
(5, max_channel_saturation_power_of_half, (default_value, 1)),
252257
(6, expiry_time, option),
258+
(7, previously_failed_channels, vec_type),
253259
});
254260

255261
impl PaymentParameters {
@@ -263,6 +269,7 @@ impl PaymentParameters {
263269
max_total_cltv_expiry_delta: DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA,
264270
max_path_count: DEFAULT_MAX_PATH_COUNT,
265271
max_channel_saturation_power_of_half: 1,
272+
previously_failed_channels: Vec::new(),
266273
}
267274
}
268275

@@ -1002,7 +1009,7 @@ where L::Target: Logger {
10021009
let contributes_sufficient_value = available_value_contribution_msat >= minimal_value_contribution_msat;
10031010
// Do not consider candidate hops that would exceed the maximum path length.
10041011
let path_length_to_node = $next_hops_path_length + 1;
1005-
let doesnt_exceed_max_path_length = path_length_to_node <= MAX_PATH_LENGTH_ESTIMATE;
1012+
let exceeds_max_path_length = path_length_to_node > MAX_PATH_LENGTH_ESTIMATE;
10061013

10071014
// Do not consider candidates that exceed the maximum total cltv expiry limit.
10081015
// In order to already account for some of the privacy enhancing random CLTV
@@ -1013,7 +1020,7 @@ where L::Target: Logger {
10131020
.unwrap_or(payment_params.max_total_cltv_expiry_delta - final_cltv_expiry_delta);
10141021
let hop_total_cltv_delta = ($next_hops_cltv_delta as u32)
10151022
.saturating_add($candidate.cltv_expiry_delta());
1016-
let doesnt_exceed_cltv_delta_limit = hop_total_cltv_delta <= max_total_cltv_expiry_delta;
1023+
let exceeds_cltv_delta_limit = hop_total_cltv_delta > max_total_cltv_expiry_delta;
10171024

10181025
let value_contribution_msat = cmp::min(available_value_contribution_msat, $next_hops_value_contribution);
10191026
// Includes paying fees for the use of the following channels.
@@ -1033,15 +1040,19 @@ where L::Target: Logger {
10331040
(amount_to_transfer_over_msat < $next_hops_path_htlc_minimum_msat &&
10341041
recommended_value_msat > $next_hops_path_htlc_minimum_msat));
10351042

1043+
let payment_failed_on_this_channel =
1044+
payment_params.previously_failed_channels.contains(&short_channel_id);
1045+
10361046
// If HTLC minimum is larger than the amount we're going to transfer, we shouldn't
10371047
// bother considering this channel. If retrying with recommended_value_msat may
10381048
// allow us to hit the HTLC minimum limit, set htlc_minimum_limit so that we go
10391049
// around again with a higher amount.
1040-
if contributes_sufficient_value && doesnt_exceed_max_path_length &&
1041-
doesnt_exceed_cltv_delta_limit && may_overpay_to_meet_path_minimum_msat {
1050+
if !contributes_sufficient_value || exceeds_max_path_length ||
1051+
exceeds_cltv_delta_limit || payment_failed_on_this_channel {
1052+
// Path isn't useful, ignore it and move on.
1053+
} else if may_overpay_to_meet_path_minimum_msat {
10421054
hit_minimum_limit = true;
1043-
} else if contributes_sufficient_value && doesnt_exceed_max_path_length &&
1044-
doesnt_exceed_cltv_delta_limit && over_path_minimum_msat {
1055+
} else if over_path_minimum_msat {
10451056
// Note that low contribution here (limited by available_liquidity_msat)
10461057
// might violate htlc_minimum_msat on the hops which are next along the
10471058
// payment path (upstream to the payee). To avoid that, we recompute
@@ -1993,6 +2004,8 @@ mod tests {
19932004
use prelude::*;
19942005
use sync::{self, Arc};
19952006

2007+
use core::convert::TryInto;
2008+
19962009
fn get_channel_details(short_channel_id: Option<u64>, node_id: PublicKey,
19972010
features: InitFeatures, outbound_capacity_msat: u64) -> channelmanager::ChannelDetails {
19982011
channelmanager::ChannelDetails {
@@ -5573,6 +5586,35 @@ mod tests {
55735586
}
55745587
}
55755588

5589+
#[test]
5590+
fn avoids_recently_failed_paths() {
5591+
// Ensure that the router always avoids all of the `previously_failed_channels` channels by
5592+
// randomly inserting channels into it until we can't find a route anymore.
5593+
let (secp_ctx, network, _, _, logger) = build_graph();
5594+
let (_, our_id, _, nodes) = get_nodes(&secp_ctx);
5595+
let network_graph = network.read_only();
5596+
5597+
let scorer = test_utils::TestScorer::with_penalty(0);
5598+
let mut payment_params = PaymentParameters::from_node_id(nodes[6]).with_route_hints(last_hops(&nodes))
5599+
.with_max_path_count(1);
5600+
let keys_manager = test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
5601+
let random_seed_bytes = keys_manager.get_secure_random_bytes();
5602+
5603+
// We should be able to find a route initially, and then after we fail a few random
5604+
// channels eventually we won't be able to any longer.
5605+
assert!(get_route(&our_id, &payment_params, &network_graph, None, 100, 0, Arc::clone(&logger), &scorer, &random_seed_bytes).is_ok());
5606+
loop {
5607+
if let Ok(route) = get_route(&our_id, &payment_params, &network_graph, None, 100, 0, Arc::clone(&logger), &scorer, &random_seed_bytes) {
5608+
for chan in route.paths[0].iter() {
5609+
assert!(!payment_params.previously_failed_channels.contains(&chan.short_channel_id));
5610+
}
5611+
let victim = (u64::from_ne_bytes(random_seed_bytes[0..8].try_into().unwrap()) as usize)
5612+
% route.paths[0].len();
5613+
payment_params.previously_failed_channels.push(route.paths[0][victim].short_channel_id);
5614+
} else { break; }
5615+
}
5616+
}
5617+
55765618
#[test]
55775619
fn limits_path_length() {
55785620
let (secp_ctx, network, _, _, logger) = build_line_graph();

lightning/src/routing/scoring.rs

+42-18
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,25 @@ pub struct ProbabilisticScoringParameters {
394394
///
395395
/// Default value: 250 msat
396396
pub anti_probing_penalty_msat: u64,
397+
398+
/// This penalty is applied when the amount we're attempting to send over a channel exceeds our
399+
/// current estimate of the channel's available liquidity.
400+
///
401+
/// Note that in this case all other penalties, including the
402+
/// [`liquidity_penalty_multiplier_msat`] and [`amount_penalty_multiplier_msat`]-based
403+
/// penalties, as well as the [`base_penalty_msat`] and the [`anti_probing_penalty_msat`], if
404+
/// applicable, are still included in the overall penalty.
405+
///
406+
/// If you wish to avoid creating paths with such channels entirely, setting this to a value of
407+
/// `u64::max_value()` will guarantee that.
408+
///
409+
/// Default value: 1_0000_0000_000 msat (1 Bitcoin)
410+
///
411+
/// [`liquidity_penalty_multiplier_msat`]: Self::liquidity_penalty_multiplier_msat
412+
/// [`amount_penalty_multiplier_msat`]: Self::amount_penalty_multiplier_msat
413+
/// [`base_penalty_msat`]: Self::base_penalty_msat
414+
/// [`anti_probing_penalty_msat`]: Self::anti_probing_penalty_msat
415+
pub considered_impossible_penalty_msat: u64,
397416
}
398417

399418
/// Accounting for channel liquidity balance uncertainty.
@@ -522,6 +541,7 @@ impl ProbabilisticScoringParameters {
522541
amount_penalty_multiplier_msat: 0,
523542
manual_node_penalties: HashMap::new(),
524543
anti_probing_penalty_msat: 0,
544+
considered_impossible_penalty_msat: 0,
525545
}
526546
}
527547

@@ -543,6 +563,7 @@ impl Default for ProbabilisticScoringParameters {
543563
amount_penalty_multiplier_msat: 256,
544564
manual_node_penalties: HashMap::new(),
545565
anti_probing_penalty_msat: 250,
566+
considered_impossible_penalty_msat: 1_0000_0000_000,
546567
}
547568
}
548569
}
@@ -620,17 +641,12 @@ impl<L: Deref<Target = u64>, T: Time, U: Deref<Target = T>> DirectedChannelLiqui
620641
if amount_msat <= min_liquidity_msat {
621642
0
622643
} else if amount_msat >= max_liquidity_msat {
623-
if amount_msat > max_liquidity_msat {
624-
u64::max_value()
625-
} else if max_liquidity_msat != self.capacity_msat {
626-
// Avoid using the failed channel on retry.
627-
u64::max_value()
628-
} else {
629-
// Equivalent to hitting the else clause below with the amount equal to the
630-
// effective capacity and without any certainty on the liquidity upper bound.
631-
let negative_log10_times_2048 = NEGATIVE_LOG10_UPPER_BOUND * 2048;
632-
self.combined_penalty_msat(amount_msat, negative_log10_times_2048, params)
633-
}
644+
// Equivalent to hitting the else clause below with the amount equal to the effective
645+
// capacity and without any certainty on the liquidity upper bound, plus the
646+
// impossibility penalty.
647+
let negative_log10_times_2048 = NEGATIVE_LOG10_UPPER_BOUND * 2048;
648+
self.combined_penalty_msat(amount_msat, negative_log10_times_2048, params)
649+
.saturating_add(params.considered_impossible_penalty_msat)
634650
} else {
635651
let numerator = (max_liquidity_msat - amount_msat).saturating_add(1);
636652
let denominator = (max_liquidity_msat - min_liquidity_msat).saturating_add(1);
@@ -1624,7 +1640,7 @@ mod tests {
16241640
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 0);
16251641
let usage = ChannelUsage { amount_msat: 102_400, ..usage };
16261642
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 47);
1627-
let usage = ChannelUsage { amount_msat: 1_024_000, ..usage };
1643+
let usage = ChannelUsage { amount_msat: 1_023_999, ..usage };
16281644
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 2_000);
16291645

16301646
let usage = ChannelUsage {
@@ -1654,6 +1670,7 @@ mod tests {
16541670
let network_graph = network_graph(&logger);
16551671
let params = ProbabilisticScoringParameters {
16561672
liquidity_penalty_multiplier_msat: 1_000,
1673+
considered_impossible_penalty_msat: u64::max_value(),
16571674
..ProbabilisticScoringParameters::zero_penalty()
16581675
};
16591676
let scorer = ProbabilisticScorer::new(params, &network_graph, &logger)
@@ -1745,6 +1762,7 @@ mod tests {
17451762
let network_graph = network_graph(&logger);
17461763
let params = ProbabilisticScoringParameters {
17471764
liquidity_penalty_multiplier_msat: 1_000,
1765+
considered_impossible_penalty_msat: u64::max_value(),
17481766
..ProbabilisticScoringParameters::zero_penalty()
17491767
};
17501768
let mut scorer = ProbabilisticScorer::new(params, &network_graph, &logger);
@@ -1811,6 +1829,7 @@ mod tests {
18111829
let params = ProbabilisticScoringParameters {
18121830
liquidity_penalty_multiplier_msat: 1_000,
18131831
liquidity_offset_half_life: Duration::from_secs(10),
1832+
considered_impossible_penalty_msat: u64::max_value(),
18141833
..ProbabilisticScoringParameters::zero_penalty()
18151834
};
18161835
let mut scorer = ProbabilisticScorer::new(params, &network_graph, &logger);
@@ -1820,10 +1839,10 @@ mod tests {
18201839
let usage = ChannelUsage {
18211840
amount_msat: 0,
18221841
inflight_htlc_msat: 0,
1823-
effective_capacity: EffectiveCapacity::Total { capacity_msat: 1_024, htlc_maximum_msat: Some(1_000) },
1842+
effective_capacity: EffectiveCapacity::Total { capacity_msat: 1_024, htlc_maximum_msat: Some(1_024) },
18241843
};
18251844
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 0);
1826-
let usage = ChannelUsage { amount_msat: 1_024, ..usage };
1845+
let usage = ChannelUsage { amount_msat: 1_023, ..usage };
18271846
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 2_000);
18281847

18291848
scorer.payment_path_failed(&payment_path_for_amount(768).iter().collect::<Vec<_>>(), 42);
@@ -1867,20 +1886,20 @@ mod tests {
18671886
let usage = ChannelUsage { amount_msat: 1_023, ..usage };
18681887
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 2_000);
18691888
let usage = ChannelUsage { amount_msat: 1_024, ..usage };
1870-
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 2_000);
1889+
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), u64::max_value());
18711890

18721891
// Fully decay liquidity upper bound.
18731892
SinceEpoch::advance(Duration::from_secs(10));
18741893
let usage = ChannelUsage { amount_msat: 0, ..usage };
18751894
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 0);
18761895
let usage = ChannelUsage { amount_msat: 1_024, ..usage };
1877-
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 2_000);
1896+
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), u64::max_value());
18781897

18791898
SinceEpoch::advance(Duration::from_secs(10));
18801899
let usage = ChannelUsage { amount_msat: 0, ..usage };
18811900
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 0);
18821901
let usage = ChannelUsage { amount_msat: 1_024, ..usage };
1883-
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 2_000);
1902+
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), u64::max_value());
18841903
}
18851904

18861905
#[test]
@@ -1965,6 +1984,7 @@ mod tests {
19651984
let params = ProbabilisticScoringParameters {
19661985
liquidity_penalty_multiplier_msat: 1_000,
19671986
liquidity_offset_half_life: Duration::from_secs(10),
1987+
considered_impossible_penalty_msat: u64::max_value(),
19681988
..ProbabilisticScoringParameters::zero_penalty()
19691989
};
19701990
let mut scorer = ProbabilisticScorer::new(params.clone(), &network_graph, &logger);
@@ -2001,6 +2021,7 @@ mod tests {
20012021
let params = ProbabilisticScoringParameters {
20022022
liquidity_penalty_multiplier_msat: 1_000,
20032023
liquidity_offset_half_life: Duration::from_secs(10),
2024+
considered_impossible_penalty_msat: u64::max_value(),
20042025
..ProbabilisticScoringParameters::zero_penalty()
20052026
};
20062027
let mut scorer = ProbabilisticScorer::new(params.clone(), &network_graph, &logger);
@@ -2171,7 +2192,10 @@ mod tests {
21712192
fn accounts_for_inflight_htlc_usage() {
21722193
let logger = TestLogger::new();
21732194
let network_graph = network_graph(&logger);
2174-
let params = ProbabilisticScoringParameters::default();
2195+
let params = ProbabilisticScoringParameters {
2196+
considered_impossible_penalty_msat: u64::max_value(),
2197+
..ProbabilisticScoringParameters::zero_penalty()
2198+
};
21752199
let scorer = ProbabilisticScorer::new(params, &network_graph, &logger);
21762200
let source = source_node_id();
21772201
let target = target_node_id();

0 commit comments

Comments
 (0)