Skip to content

Commit 4356809

Browse files
authored
Merge pull request #1526 from tnull/2022-06-fix-minimal-value-contrib
Fix per-path minimal value contribution during route finding
2 parents d6feb1c + 1dfabcb commit 4356809

File tree

2 files changed

+76
-40
lines changed

2 files changed

+76
-40
lines changed

lightning/src/ln/channelmanager.rs

-6
Original file line numberDiff line numberDiff line change
@@ -2529,12 +2529,6 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
25292529
if route.paths.len() < 1 {
25302530
return Err(PaymentSendFailure::ParameterError(APIError::RouteError{err: "There must be at least one path to send over"}));
25312531
}
2532-
if route.paths.len() > 10 {
2533-
// This limit is completely arbitrary - there aren't any real fundamental path-count
2534-
// limits. After we support retrying individual paths we should likely bump this, but
2535-
// for now more than 10 paths likely carries too much one-path failure.
2536-
return Err(PaymentSendFailure::ParameterError(APIError::RouteError{err: "Sending over more than 10 paths is not currently supported"}));
2537-
}
25382532
if payment_secret.is_none() && route.paths.len() > 1 {
25392533
return Err(PaymentSendFailure::ParameterError(APIError::APIMisuseError{err: "Payment secret is required for multi-path payments".to_string()}));
25402534
}

lightning/src/routing/router.rs

+76-34
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,11 @@ impl_writeable_tlv_based!(RouteParameters, {
176176
/// Maximum total CTLV difference we allow for a full payment path.
177177
pub const DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA: u32 = 1008;
178178

179+
/// Maximum number of paths we allow an MPP payment to have.
180+
// The default limit is currently set rather arbitrary - there aren't any real fundamental path-count
181+
// limits, but for now more than 10 paths likely carries too much one-path failure.
182+
pub const DEFAULT_MAX_MPP_PATH_COUNT: u8 = 10;
183+
179184
// The median hop CLTV expiry delta currently seen in the network.
180185
const MEDIAN_HOP_CLTV_EXPIRY_DELTA: u32 = 40;
181186

@@ -214,13 +219,19 @@ pub struct PaymentParameters {
214219
pub expiry_time: Option<u64>,
215220

216221
/// The maximum total CLTV delta we accept for the route.
222+
/// Defaults to [`DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA`].
217223
pub max_total_cltv_expiry_delta: u32,
224+
225+
/// The maximum number of paths that may be used by MPP payments.
226+
/// Defaults to [`DEFAULT_MAX_MPP_PATH_COUNT`].
227+
pub max_mpp_path_count: u8,
218228
}
219229

220230
impl_writeable_tlv_based!(PaymentParameters, {
221231
(0, payee_pubkey, required),
222232
(1, max_total_cltv_expiry_delta, (default_value, DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA)),
223233
(2, features, option),
234+
(3, max_mpp_path_count, (default_value, DEFAULT_MAX_MPP_PATH_COUNT)),
224235
(4, route_hints, vec_type),
225236
(6, expiry_time, option),
226237
});
@@ -234,6 +245,7 @@ impl PaymentParameters {
234245
route_hints: vec![],
235246
expiry_time: None,
236247
max_total_cltv_expiry_delta: DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA,
248+
max_mpp_path_count: DEFAULT_MAX_MPP_PATH_COUNT,
237249
}
238250
}
239251

@@ -269,6 +281,13 @@ impl PaymentParameters {
269281
pub fn with_max_total_cltv_expiry_delta(self, max_total_cltv_expiry_delta: u32) -> Self {
270282
Self { max_total_cltv_expiry_delta, ..self }
271283
}
284+
285+
/// Includes a limit for the maximum number of payment paths that may be used by MPP.
286+
///
287+
/// (C-not exported) since bindings don't support move semantics
288+
pub fn with_max_mpp_path_count(self, max_mpp_path_count: u8) -> Self {
289+
Self { max_mpp_path_count, ..self }
290+
}
272291
}
273292

274293
/// A list of hops along a payment path terminating with a channel to the recipient.
@@ -790,6 +809,11 @@ where L::Target: Logger {
790809
node_info.features.supports_basic_mpp()
791810
} else { false }
792811
} else { false };
812+
813+
if allow_mpp && payment_params.max_mpp_path_count == 0 {
814+
return Err(LightningError{err: "Can't find an MPP route with no paths allowed.".to_owned(), action: ErrorAction::IgnoreError});
815+
}
816+
793817
log_trace!(logger, "Searching for a route from payer {} to payee {} {} MPP and {} first hops {}overriding the network graph", our_node_pubkey,
794818
payment_params.payee_pubkey, if allow_mpp { "with" } else { "without" },
795819
first_hops.map(|hops| hops.len()).unwrap_or(0), if first_hops.is_some() { "" } else { "not " });
@@ -840,18 +864,30 @@ where L::Target: Logger {
840864
let recommended_value_msat = final_value_msat * ROUTE_CAPACITY_PROVISION_FACTOR as u64;
841865
let mut path_value_msat = final_value_msat;
842866

867+
// Routing Fragmentation Mitigation heuristic:
868+
//
869+
// Routing fragmentation across many payment paths increases the overall routing
870+
// fees as you have irreducible routing fees per-link used (`fee_base_msat`).
871+
// Taking too many smaller paths also increases the chance of payment failure.
872+
// Thus to avoid this effect, we require from our collected links to provide
873+
// at least a minimal contribution to the recommended value yet-to-be-fulfilled.
874+
// This requirement is currently set to be 1/max_mpp_path_count of the payment
875+
// value to ensure we only ever return routes that do not violate this limit.
876+
let minimal_value_contribution_msat: u64 = if allow_mpp {
877+
(final_value_msat + (payment_params.max_mpp_path_count as u64 - 1)) / payment_params.max_mpp_path_count as u64
878+
} else {
879+
final_value_msat
880+
};
881+
843882
// Keep track of how much liquidity has been used in selected channels. Used to determine
844883
// if the channel can be used by additional MPP paths or to inform path finding decisions. It is
845884
// aware of direction *only* to ensure that the correct htlc_maximum_msat value is used. Hence,
846885
// liquidity used in one direction will not offset any used in the opposite direction.
847886
let mut used_channel_liquidities: HashMap<(u64, bool), u64> =
848887
HashMap::with_capacity(network_nodes.len());
849888

850-
// Keeping track of how much value we already collected across other paths. Helps to decide:
851-
// - how much a new path should be transferring (upper bound);
852-
// - whether a channel should be disregarded because
853-
// it's available liquidity is too small comparing to how much more we need to collect;
854-
// - when we want to stop looking for new paths.
889+
// Keeping track of how much value we already collected across other paths. Helps to decide
890+
// when we want to stop looking for new paths.
855891
let mut already_collected_value_msat = 0;
856892

857893
for (_, channels) in first_hop_targets.iter_mut() {
@@ -913,26 +949,6 @@ where L::Target: Logger {
913949
*used_liquidity_msat
914950
});
915951

916-
// Routing Fragmentation Mitigation heuristic:
917-
//
918-
// Routing fragmentation across many payment paths increases the overall routing
919-
// fees as you have irreducible routing fees per-link used (`fee_base_msat`).
920-
// Taking too many smaller paths also increases the chance of payment failure.
921-
// Thus to avoid this effect, we require from our collected links to provide
922-
// at least a minimal contribution to the recommended value yet-to-be-fulfilled.
923-
//
924-
// This requirement is currently 5% of the remaining-to-be-collected value.
925-
// This means as we successfully advance in our collection,
926-
// the absolute liquidity contribution is lowered,
927-
// thus increasing the number of potential channels to be selected.
928-
929-
// Derive the minimal liquidity contribution with a ratio of 20 (5%, rounded up)
930-
// or 100% if we're not allowed to do multipath payments.
931-
let minimal_value_contribution_msat: u64 = if allow_mpp {
932-
(recommended_value_msat - already_collected_value_msat + 19) / 20
933-
} else {
934-
final_value_msat
935-
};
936952
// Verify the liquidity offered by this channel complies to the minimal contribution.
937953
let contributes_sufficient_value = available_value_contribution_msat >= minimal_value_contribution_msat;
938954
// Do not consider candidate hops that would exceed the maximum path length.
@@ -1504,10 +1520,8 @@ where L::Target: Logger {
15041520
*used_channel_liquidities.entry((victim_scid, true)).or_default() = exhausted;
15051521
}
15061522

1507-
// Track the total amount all our collected paths allow to send so that we:
1508-
// - know when to stop looking for more paths
1509-
// - know which of the hops are useless considering how much more sats we need
1510-
// (contributes_sufficient_value)
1523+
// Track the total amount all our collected paths allow to send so that we know
1524+
// when to stop looking for more paths
15111525
already_collected_value_msat += value_contribution_msat;
15121526

15131527
payment_paths.push(payment_path);
@@ -1678,6 +1692,8 @@ where L::Target: Logger {
16781692
});
16791693
selected_paths.push(path);
16801694
}
1695+
// Make sure we would never create a route with more paths than we allow.
1696+
debug_assert!(selected_paths.len() <= payment_params.max_mpp_path_count.into());
16811697

16821698
if let Some(features) = &payment_params.features {
16831699
for path in selected_paths.iter_mut() {
@@ -3999,7 +4015,8 @@ mod tests {
39994015
let scorer = test_utils::TestScorer::with_penalty(0);
40004016
let keys_manager = test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
40014017
let random_seed_bytes = keys_manager.get_secure_random_bytes();
4002-
let payment_params = PaymentParameters::from_node_id(nodes[2]).with_features(InvoiceFeatures::known());
4018+
let payment_params = PaymentParameters::from_node_id(nodes[2])
4019+
.with_features(InvoiceFeatures::known());
40034020

40044021
// We need a route consisting of 3 paths:
40054022
// From our node to node2 via node0, node7, node1 (three paths one hop each).
@@ -4092,15 +4109,39 @@ mod tests {
40924109
{
40934110
// Attempt to route more than available results in a failure.
40944111
if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(
4095-
&our_id, &payment_params, &network_graph.read_only(), None, 300_000, 42, Arc::clone(&logger), &scorer, &random_seed_bytes) {
4096-
assert_eq!(err, "Failed to find a sufficient route to the given destination");
4112+
&our_id, &payment_params, &network_graph.read_only(), None, 300_000, 42,
4113+
Arc::clone(&logger), &scorer, &random_seed_bytes) {
4114+
assert_eq!(err, "Failed to find a sufficient route to the given destination");
4115+
} else { panic!(); }
4116+
}
4117+
4118+
{
4119+
// Attempt to route while setting max_mpp_path_count to 0 results in a failure.
4120+
let zero_payment_params = payment_params.clone().with_max_mpp_path_count(0);
4121+
if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(
4122+
&our_id, &zero_payment_params, &network_graph.read_only(), None, 100, 42,
4123+
Arc::clone(&logger), &scorer, &random_seed_bytes) {
4124+
assert_eq!(err, "Can't find an MPP route with no paths allowed.");
4125+
} else { panic!(); }
4126+
}
4127+
4128+
{
4129+
// Attempt to route while setting max_mpp_path_count to 3 results in a failure.
4130+
// This is the case because the minimal_value_contribution_msat would require each path
4131+
// to account for 1/3 of the total value, which is violated by 2 out of 3 paths.
4132+
let fail_payment_params = payment_params.clone().with_max_mpp_path_count(3);
4133+
if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(
4134+
&our_id, &fail_payment_params, &network_graph.read_only(), None, 250_000, 42,
4135+
Arc::clone(&logger), &scorer, &random_seed_bytes) {
4136+
assert_eq!(err, "Failed to find a sufficient route to the given destination");
40974137
} else { panic!(); }
40984138
}
40994139

41004140
{
41014141
// Now, attempt to route 250 sats (just a bit below the capacity).
41024142
// Our algorithm should provide us with these 3 paths.
4103-
let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 250_000, 42, Arc::clone(&logger), &scorer, &random_seed_bytes).unwrap();
4143+
let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None,
4144+
250_000, 42, Arc::clone(&logger), &scorer, &random_seed_bytes).unwrap();
41044145
assert_eq!(route.paths.len(), 3);
41054146
let mut total_amount_paid_msat = 0;
41064147
for path in &route.paths {
@@ -4113,7 +4154,8 @@ mod tests {
41134154

41144155
{
41154156
// Attempt to route an exact amount is also fine
4116-
let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 290_000, 42, Arc::clone(&logger), &scorer, &random_seed_bytes).unwrap();
4157+
let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None,
4158+
290_000, 42, Arc::clone(&logger), &scorer, &random_seed_bytes).unwrap();
41174159
assert_eq!(route.paths.len(), 3);
41184160
let mut total_amount_paid_msat = 0;
41194161
for path in &route.paths {

0 commit comments

Comments
 (0)