Skip to content

Commit 178827f

Browse files
committed
Consider maximum path length during path finding.
1 parent 342698f commit 178827f

File tree

1 file changed

+158
-24
lines changed

1 file changed

+158
-24
lines changed

lightning/src/routing/router.rs

Lines changed: 158 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,8 @@ struct RouteGraphNode {
327327
/// All penalties incurred from this hop on the way to the destination, as calculated using
328328
/// channel scoring.
329329
path_penalty_msat: u64,
330+
/// The number of hops walked up to this node.
331+
path_length_to_node: u8,
330332
}
331333

332334
impl cmp::Ord for RouteGraphNode {
@@ -796,11 +798,11 @@ where L::Target: Logger {
796798
// The main heap containing all candidate next-hops sorted by their score (max(A* fee,
797799
// htlc_minimum)). Ideally this would be a heap which allowed cheap score reduction instead of
798800
// adding duplicate entries when we find a better path to a given node.
799-
let mut targets = BinaryHeap::new();
801+
let mut targets: BinaryHeap<RouteGraphNode> = BinaryHeap::new();
800802

801803
// Map from node_id to information about the best current path to that node, including feerate
802804
// information.
803-
let mut dist = HashMap::with_capacity(network_nodes.len());
805+
let mut dist: HashMap<NodeId, PathBuildingHop> = HashMap::with_capacity(network_nodes.len());
804806

805807
// During routing, if we ignore a path due to an htlc_minimum_msat limit, we set this,
806808
// indicating that we may wish to try again with a higher value, potentially paying to meet an
@@ -829,6 +831,20 @@ where L::Target: Logger {
829831
// - when we want to stop looking for new paths.
830832
let mut already_collected_value_msat = 0;
831833

834+
// We only consider paths shorter than our maximum length estimate.
835+
// In the legacy onion format, the maximum number of hops used to be a fixed value of 20.
836+
// However, in the TLV onion format, there is no fixed maximum length, but the `hop_payloads`
837+
// field is always 1300 bytes. As the `tlv_payload` for each hop may vary in length, we have to
838+
// estimate how many hops the route may have so that it actually fits the `hop_payloads` field.
839+
//
840+
// We estimate 3+32 (payload length and HMAC) + 2+8 (amt_to_forward) + 2+4 (outgoing_cltv_value) +
841+
// 2+8 (short_channel_id) = 61 bytes for each intermediate hop and 3+32
842+
// (payload length and HMAC) + 2+8 (amt_to_forward) + 2+4 (outgoing_cltv_value) + 2+32+8
843+
// (payment_secret and total_msat) = 93 bytes for the final hop.
844+
// Since the length of the potentially included `payment_metadata` is unkown to us, we round
845+
// from (1300-93) / 61 = 19.78... just to arrive again at a conservative estimate of 20.
846+
const MAX_PATH_LENGTH_ESTIMATE: u8 = 20;
847+
832848
for (_, channels) in first_hop_targets.iter_mut() {
833849
// Sort the first_hops channels to the same node(s) in priority order of which channel we'd
834850
// most like to use.
@@ -860,8 +876,8 @@ where L::Target: Logger {
860876
// since that value has to be transferred over this channel.
861877
// Returns whether this channel caused an update to `targets`.
862878
( $candidate: expr, $src_node_id: expr, $dest_node_id: expr, $next_hops_fee_msat: expr,
863-
$next_hops_value_contribution: expr, $next_hops_path_htlc_minimum_msat: expr,
864-
$next_hops_path_penalty_msat: expr, $next_hops_cltv_delta: expr ) => { {
879+
$next_hops_value_contribution: expr, $next_hops_path_htlc_minimum_msat: expr,
880+
$next_hops_path_penalty_msat: expr, $next_hops_cltv_delta: expr, $next_hops_path_length: expr ) => { {
865881
// We "return" whether we updated the path at the end, via this:
866882
let mut did_add_update_path_to_src_node = false;
867883
// Channels to self should not be used. This is more of belt-and-suspenders, because in
@@ -905,6 +921,9 @@ where L::Target: Logger {
905921
};
906922
// Verify the liquidity offered by this channel complies to the minimal contribution.
907923
let contributes_sufficient_value = available_value_contribution_msat >= minimal_value_contribution_msat;
924+
// Do not consider candidate hops that would exceed the maximum path length.
925+
let path_length_to_node = $next_hops_path_length + 1;
926+
let doesnt_exceed_max_path_length = path_length_to_node <= MAX_PATH_LENGTH_ESTIMATE;
908927

909928
// Do not consider candidates that exceed the maximum total cltv expiry limit.
910929
// In order to already account for some of the privacy enhancing random CLTV
@@ -939,9 +958,11 @@ where L::Target: Logger {
939958
// bother considering this channel. If retrying with recommended_value_msat may
940959
// allow us to hit the HTLC minimum limit, set htlc_minimum_limit so that we go
941960
// around again with a higher amount.
942-
if contributes_sufficient_value && doesnt_exceed_cltv_delta_limit && may_overpay_to_meet_path_minimum_msat {
961+
if contributes_sufficient_value && doesnt_exceed_max_path_length &&
962+
doesnt_exceed_cltv_delta_limit && may_overpay_to_meet_path_minimum_msat {
943963
hit_minimum_limit = true;
944-
} else if contributes_sufficient_value && doesnt_exceed_cltv_delta_limit && over_path_minimum_msat {
964+
} else if contributes_sufficient_value && doesnt_exceed_max_path_length &&
965+
doesnt_exceed_cltv_delta_limit && over_path_minimum_msat {
945966
// Note that low contribution here (limited by available_liquidity_msat)
946967
// might violate htlc_minimum_msat on the hops which are next along the
947968
// payment path (upstream to the payee). To avoid that, we recompute
@@ -1038,6 +1059,7 @@ where L::Target: Logger {
10381059
value_contribution_msat: value_contribution_msat,
10391060
path_htlc_minimum_msat,
10401061
path_penalty_msat,
1062+
path_length_to_node,
10411063
};
10421064

10431065
// Update the way of reaching $src_node_id with the given short_channel_id (from $dest_node_id),
@@ -1121,7 +1143,9 @@ where L::Target: Logger {
11211143
// meaning how much will be paid in fees after this node (to the best of our knowledge).
11221144
// This data can later be helpful to optimize routing (pay lower fees).
11231145
macro_rules! add_entries_to_cheapest_to_target_node {
1124-
( $node: expr, $node_id: expr, $fee_to_target_msat: expr, $next_hops_value_contribution: expr, $next_hops_path_htlc_minimum_msat: expr, $next_hops_path_penalty_msat: expr, $next_hops_cltv_delta: expr ) => {
1146+
( $node: expr, $node_id: expr, $fee_to_target_msat: expr, $next_hops_value_contribution: expr,
1147+
$next_hops_path_htlc_minimum_msat: expr, $next_hops_path_penalty_msat: expr,
1148+
$next_hops_cltv_delta: expr, $next_hops_path_length: expr ) => {
11251149
let skip_node = if let Some(elem) = dist.get_mut(&$node_id) {
11261150
let was_processed = elem.was_processed;
11271151
elem.was_processed = true;
@@ -1138,7 +1162,10 @@ where L::Target: Logger {
11381162
if let Some(first_channels) = first_hop_targets.get(&$node_id) {
11391163
for details in first_channels {
11401164
let candidate = CandidateRouteHop::FirstHop { details };
1141-
add_entry!(candidate, our_node_id, $node_id, $fee_to_target_msat, $next_hops_value_contribution, $next_hops_path_htlc_minimum_msat, $next_hops_path_penalty_msat, $next_hops_cltv_delta);
1165+
add_entry!(candidate, our_node_id, $node_id, $fee_to_target_msat,
1166+
$next_hops_value_contribution,
1167+
$next_hops_path_htlc_minimum_msat, $next_hops_path_penalty_msat,
1168+
$next_hops_cltv_delta, $next_hops_path_length);
11421169
}
11431170
}
11441171

@@ -1161,7 +1188,12 @@ where L::Target: Logger {
11611188
info: directed_channel.with_update().unwrap(),
11621189
short_channel_id: *chan_id,
11631190
};
1164-
add_entry!(candidate, *source, $node_id, $fee_to_target_msat, $next_hops_value_contribution, $next_hops_path_htlc_minimum_msat, $next_hops_path_penalty_msat, $next_hops_cltv_delta);
1191+
add_entry!(candidate, *source, $node_id,
1192+
$fee_to_target_msat,
1193+
$next_hops_value_contribution,
1194+
$next_hops_path_htlc_minimum_msat,
1195+
$next_hops_path_penalty_msat,
1196+
$next_hops_cltv_delta, $next_hops_path_length);
11651197
}
11661198
}
11671199
}
@@ -1188,8 +1220,10 @@ where L::Target: Logger {
11881220
if let Some(first_channels) = first_hop_targets.get(&payee_node_id) {
11891221
for details in first_channels {
11901222
let candidate = CandidateRouteHop::FirstHop { details };
1191-
let added = add_entry!(candidate, our_node_id, payee_node_id, 0, path_value_msat, 0, 0u64, 0);
1192-
log_trace!(logger, "{} direct route to payee via SCID {}", if added { "Added" } else { "Skipped" }, candidate.short_channel_id());
1223+
let added = add_entry!(candidate, our_node_id, payee_node_id, 0, path_value_msat,
1224+
0, 0u64, 0, 0);
1225+
log_trace!(logger, "{} direct route to payee via SCID {}",
1226+
if added { "Added" } else { "Skipped" }, candidate.short_channel_id());
11931227
}
11941228
}
11951229

@@ -1202,7 +1236,7 @@ where L::Target: Logger {
12021236
// If not, targets.pop() will not even let us enter the loop in step 2.
12031237
None => {},
12041238
Some(node) => {
1205-
add_entries_to_cheapest_to_target_node!(node, payee_node_id, 0, path_value_msat, 0, 0u64, 0);
1239+
add_entries_to_cheapest_to_target_node!(node, payee_node_id, 0, path_value_msat, 0, 0u64, 0, 0);
12061240
},
12071241
}
12081242

@@ -1229,6 +1263,7 @@ where L::Target: Logger {
12291263
let mut aggregate_next_hops_path_htlc_minimum_msat: u64 = 0;
12301264
let mut aggregate_next_hops_path_penalty_msat: u64 = 0;
12311265
let mut aggregate_next_hops_cltv_delta: u32 = 0;
1266+
let mut aggregate_next_hops_path_length: u8 = 0;
12321267

12331268
for (idx, (hop, prev_hop_id)) in hop_iter.zip(prev_hop_iter).enumerate() {
12341269
let source = NodeId::from_pubkey(&hop.src_node_id);
@@ -1244,23 +1279,34 @@ where L::Target: Logger {
12441279
.unwrap_or_else(|| CandidateRouteHop::PrivateHop { hint: hop });
12451280
let capacity_msat = candidate.effective_capacity().as_msat();
12461281
aggregate_next_hops_path_penalty_msat = aggregate_next_hops_path_penalty_msat
1247-
.saturating_add(scorer.channel_penalty_msat(hop.short_channel_id, final_value_msat, capacity_msat, &source, &target));
1282+
.saturating_add(scorer.channel_penalty_msat(hop.short_channel_id,
1283+
final_value_msat, capacity_msat, &source, &target));
12481284

12491285
aggregate_next_hops_cltv_delta = aggregate_next_hops_cltv_delta
12501286
.saturating_add(hop.cltv_expiry_delta as u32);
12511287

1252-
if !add_entry!(candidate, source, target, aggregate_next_hops_fee_msat, path_value_msat, aggregate_next_hops_path_htlc_minimum_msat, aggregate_next_hops_path_penalty_msat, aggregate_next_hops_cltv_delta) {
1253-
// If this hop was not used then there is no use checking the preceding hops
1254-
// in the RouteHint. We can break by just searching for a direct channel between
1255-
// last checked hop and first_hop_targets
1288+
aggregate_next_hops_path_length = aggregate_next_hops_path_length
1289+
.saturating_add(1);
1290+
1291+
if !add_entry!(candidate, source, target, aggregate_next_hops_fee_msat,
1292+
path_value_msat, aggregate_next_hops_path_htlc_minimum_msat,
1293+
aggregate_next_hops_path_penalty_msat,
1294+
aggregate_next_hops_cltv_delta, aggregate_next_hops_path_length) {
1295+
// If this hop was not used then there is no use checking the preceding
1296+
// hops in the RouteHint. We can break by just searching for a direct
1297+
// channel between last checked hop and first_hop_targets.
12561298
hop_used = false;
12571299
}
12581300

12591301
// Searching for a direct channel between last checked hop and first_hop_targets
12601302
if let Some(first_channels) = first_hop_targets.get(&NodeId::from_pubkey(&prev_hop_id)) {
12611303
for details in first_channels {
12621304
let candidate = CandidateRouteHop::FirstHop { details };
1263-
add_entry!(candidate, our_node_id, NodeId::from_pubkey(&prev_hop_id), aggregate_next_hops_fee_msat, path_value_msat, aggregate_next_hops_path_htlc_minimum_msat, aggregate_next_hops_path_penalty_msat, aggregate_next_hops_cltv_delta);
1305+
add_entry!(candidate, our_node_id, NodeId::from_pubkey(&prev_hop_id),
1306+
aggregate_next_hops_fee_msat, path_value_msat,
1307+
aggregate_next_hops_path_htlc_minimum_msat,
1308+
aggregate_next_hops_path_penalty_msat, aggregate_next_hops_cltv_delta,
1309+
aggregate_next_hops_path_length);
12641310
}
12651311
}
12661312

@@ -1296,7 +1342,13 @@ where L::Target: Logger {
12961342
if let Some(first_channels) = first_hop_targets.get(&NodeId::from_pubkey(&hop.src_node_id)) {
12971343
for details in first_channels {
12981344
let candidate = CandidateRouteHop::FirstHop { details };
1299-
add_entry!(candidate, our_node_id, NodeId::from_pubkey(&hop.src_node_id), aggregate_next_hops_fee_msat, path_value_msat, aggregate_next_hops_path_htlc_minimum_msat, aggregate_next_hops_path_penalty_msat, aggregate_next_hops_cltv_delta);
1345+
add_entry!(candidate, our_node_id,
1346+
NodeId::from_pubkey(&hop.src_node_id),
1347+
aggregate_next_hops_fee_msat, path_value_msat,
1348+
aggregate_next_hops_path_htlc_minimum_msat,
1349+
aggregate_next_hops_path_penalty_msat,
1350+
aggregate_next_hops_cltv_delta,
1351+
aggregate_next_hops_path_length);
13001352
}
13011353
}
13021354
}
@@ -1319,13 +1371,13 @@ where L::Target: Logger {
13191371
// Both these cases (and other cases except reaching recommended_value_msat) mean that
13201372
// paths_collection will be stopped because found_new_path==false.
13211373
// This is not necessarily a routing failure.
1322-
'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, .. }) = targets.pop() {
1374+
'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() {
13231375

13241376
// Since we're going payee-to-payer, hitting our node as a target means we should stop
13251377
// traversing the graph and arrange the path out of what we found.
13261378
if node_id == our_node_id {
13271379
let mut new_entry = dist.remove(&our_node_id).unwrap();
1328-
let mut ordered_hops = vec!((new_entry.clone(), default_node_features.clone()));
1380+
let mut ordered_hops: Vec<(PathBuildingHop, NodeFeatures)> = vec!((new_entry.clone(), default_node_features.clone()));
13291381

13301382
'path_walk: loop {
13311383
let mut features_set = false;
@@ -1441,7 +1493,9 @@ where L::Target: Logger {
14411493
match network_nodes.get(&node_id) {
14421494
None => {},
14431495
Some(node) => {
1444-
add_entries_to_cheapest_to_target_node!(node, node_id, lowest_fee_to_node, value_contribution_msat, path_htlc_minimum_msat, path_penalty_msat, total_cltv_delta);
1496+
add_entries_to_cheapest_to_target_node!(node, node_id, lowest_fee_to_node,
1497+
value_contribution_msat, path_htlc_minimum_msat, path_penalty_msat,
1498+
total_cltv_delta, path_length_to_node);
14451499
},
14461500
}
14471501
}
@@ -1703,7 +1757,7 @@ mod tests {
17031757
use chain::keysinterface::KeysInterface;
17041758
use ln::features::{ChannelFeatures, InitFeatures, InvoiceFeatures, NodeFeatures};
17051759
use ln::msgs::{ErrorAction, LightningError, OptionalField, UnsignedChannelAnnouncement, ChannelAnnouncement, RoutingMessageHandler,
1706-
NodeAnnouncement, UnsignedNodeAnnouncement, ChannelUpdate, UnsignedChannelUpdate};
1760+
NodeAnnouncement, UnsignedNodeAnnouncement, ChannelUpdate, UnsignedChannelUpdate};
17071761
use ln::channelmanager;
17081762
use util::test_utils;
17091763
use util::chacha20::ChaCha20;
@@ -1836,7 +1890,7 @@ mod tests {
18361890
}
18371891

18381892
fn get_nodes(secp_ctx: &Secp256k1<All>) -> (SecretKey, PublicKey, Vec<SecretKey>, Vec<PublicKey>) {
1839-
let privkeys: Vec<SecretKey> = (2..10).map(|i| {
1893+
let privkeys: Vec<SecretKey> = (2..23).map(|i| {
18401894
SecretKey::from_slice(&hex::decode(format!("{:02x}", i).repeat(32)).unwrap()[..]).unwrap()
18411895
}).collect();
18421896

@@ -1863,6 +1917,57 @@ mod tests {
18631917
}
18641918
}
18651919

1920+
fn build_line_graph() -> (
1921+
Secp256k1<All>, sync::Arc<NetworkGraph>, NetGraphMsgHandler<sync::Arc<NetworkGraph>,
1922+
sync::Arc<test_utils::TestChainSource>, sync::Arc<crate::util::test_utils::TestLogger>>,
1923+
sync::Arc<test_utils::TestChainSource>, sync::Arc<test_utils::TestLogger>,
1924+
) {
1925+
let secp_ctx = Secp256k1::new();
1926+
let logger = Arc::new(test_utils::TestLogger::new());
1927+
let chain_monitor = Arc::new(test_utils::TestChainSource::new(Network::Testnet));
1928+
let network_graph = Arc::new(NetworkGraph::new(genesis_block(Network::Testnet).header.block_hash()));
1929+
let net_graph_msg_handler = NetGraphMsgHandler::new(Arc::clone(&network_graph), None, Arc::clone(&logger));
1930+
1931+
// Build network from our_id to node 20:
1932+
// our_id -1(1)2- node0 -1(2)2- node1 - ... - node20
1933+
let (our_privkey, _, privkeys, _) = get_nodes(&secp_ctx);
1934+
1935+
for (idx, (cur_privkey, next_privkey)) in core::iter::once(&our_privkey)
1936+
.chain(privkeys.iter()).zip(privkeys.iter()).enumerate() {
1937+
let cur_short_channel_id = (idx as u64) + 1;
1938+
add_channel(&net_graph_msg_handler, &secp_ctx, &cur_privkey, &next_privkey,
1939+
ChannelFeatures::from_le_bytes(id_to_feature_flags(1)), cur_short_channel_id);
1940+
update_channel(&net_graph_msg_handler, &secp_ctx, &cur_privkey, UnsignedChannelUpdate {
1941+
chain_hash: genesis_block(Network::Testnet).header.block_hash(),
1942+
short_channel_id: cur_short_channel_id,
1943+
timestamp: idx as u32,
1944+
flags: 0,
1945+
cltv_expiry_delta: 0,
1946+
htlc_minimum_msat: 0,
1947+
htlc_maximum_msat: OptionalField::Absent,
1948+
fee_base_msat: 0,
1949+
fee_proportional_millionths: 0,
1950+
excess_data: Vec::new()
1951+
});
1952+
update_channel(&net_graph_msg_handler, &secp_ctx, &next_privkey, UnsignedChannelUpdate {
1953+
chain_hash: genesis_block(Network::Testnet).header.block_hash(),
1954+
short_channel_id: cur_short_channel_id,
1955+
timestamp: (idx as u32)+1,
1956+
flags: 1,
1957+
cltv_expiry_delta: 0,
1958+
htlc_minimum_msat: 0,
1959+
htlc_maximum_msat: OptionalField::Absent,
1960+
fee_base_msat: 0,
1961+
fee_proportional_millionths: 0,
1962+
excess_data: Vec::new()
1963+
});
1964+
add_or_update_node(&net_graph_msg_handler, &secp_ctx, next_privkey,
1965+
NodeFeatures::from_le_bytes(id_to_feature_flags(1)), 0);
1966+
}
1967+
1968+
(secp_ctx, network_graph, net_graph_msg_handler, chain_monitor, logger)
1969+
}
1970+
18661971
fn build_graph() -> (
18671972
Secp256k1<All>,
18681973
sync::Arc<NetworkGraph>,
@@ -5213,6 +5318,35 @@ mod tests {
52135318
}
52145319
}
52155320

5321+
#[test]
5322+
fn limits_path_length() {
5323+
let (secp_ctx, network, _, _, logger) = build_line_graph();
5324+
let (_, our_id, _, nodes) = get_nodes(&secp_ctx);
5325+
let network_graph = network.read_only();
5326+
5327+
let scorer = test_utils::TestScorer::with_penalty(0);
5328+
let keys_manager = test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
5329+
let random_seed_bytes = keys_manager.get_secure_random_bytes();
5330+
5331+
// First check we can actually create a long route on this graph.
5332+
let feasible_payment_params = PaymentParameters::from_node_id(nodes[19]);
5333+
let route = get_route(&our_id, &feasible_payment_params, &network_graph, None, 100, 0,
5334+
Arc::clone(&logger), &scorer, &random_seed_bytes).unwrap();
5335+
let path = route.paths[0].iter().map(|hop| hop.short_channel_id).collect::<Vec<_>>();
5336+
assert!(path.len() == 20);
5337+
5338+
// But we can't create a path surpassing the 20-hop MAX_PATH_LENGTH_ESTIMATE limit.
5339+
let fail_payment_params = PaymentParameters::from_node_id(nodes[20]);
5340+
match get_route(&our_id, &fail_payment_params, &network_graph, None, 100, 0,
5341+
Arc::clone(&logger), &scorer, &random_seed_bytes)
5342+
{
5343+
Err(LightningError { err, .. } ) => {
5344+
assert_eq!(err, "Failed to find a path to the given destination");
5345+
},
5346+
Ok(_) => panic!("Expected error"),
5347+
}
5348+
}
5349+
52165350
#[test]
52175351
fn adds_and_limits_cltv_offset() {
52185352
let (secp_ctx, network_graph, _, _, logger) = build_graph();

0 commit comments

Comments
 (0)