Skip to content

Commit 633dcfe

Browse files
committed
[router] Do not fail if we are exactly (or 3x) limited by a maximum
The new MPP routing algorithm attempts to build paths with a higher value than our payment to find paths which may allow us to pay a fee to meet an htlc_minimum limit. This is great if we're min-bounded, however it results in us rejecting paths where we are bounded by a maximum near the end of the path, with fees, and then bounded by a minimum earlier in the path. Further, it means we will not find the cheapest path where paths have a lower relative fee but a higher absolute fee. Instead, we calculate routes using the actual amount we wish to send. To maintain the previous behavior of searching for cheaper paths where we can "pay the difference" to meet an htlc_minimum, we detect if we were minimum-bounded during graph walking and, if we are, we walk the graph again with a higher value.
1 parent 562de9b commit 633dcfe

File tree

1 file changed

+103
-14
lines changed

1 file changed

+103
-14
lines changed

lightning/src/routing/router.rs

Lines changed: 103 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -425,13 +425,18 @@ pub fn get_route<L: Deref>(our_node_id: &PublicKey, network: &NetworkGraph, paye
425425
let mut targets = BinaryHeap::new(); //TODO: Do we care about switching to eg Fibbonaci heap?
426426
let mut dist = HashMap::with_capacity(network.get_nodes().len());
427427

428+
// During routing, if we ignore a path due to an htlc_minimum_msat limit, we set this,
429+
// indicating that we may wish to try again with a higher value, potentially paying to meet an
430+
// htlc_minimum with extra fees while still finding a cheaper path.
431+
let mut hit_minimum_limit;
432+
428433
// When arranging a route, we select multiple paths so that we can make a multi-path payment.
429-
// Don't stop searching for paths when we think they're
430-
// sufficient to transfer a given value aggregately.
431-
// Search for higher value, so that we collect many more paths,
432-
// and then select the best combination among them.
434+
// We start with a path_value of the exact amount we want, and if that generates a route we may
435+
// return it immediately. Otherwise, we don't stop searching for paths until we have 3x the
436+
// amount we want in total across paths, selecting the best subset at the end.
433437
const ROUTE_CAPACITY_PROVISION_FACTOR: u64 = 3;
434438
let recommended_value_msat = final_value_msat * ROUTE_CAPACITY_PROVISION_FACTOR as u64;
439+
let mut path_value_msat = final_value_msat;
435440

436441
// Allow MPP only if we have a features set from somewhere that indicates the payee supports
437442
// it. If the payee supports it they're supposed to include it in the invoice, so that should
@@ -555,8 +560,9 @@ pub fn get_route<L: Deref>(our_node_id: &PublicKey, network: &NetworkGraph, paye
555560
// Since we're choosing amount_to_transfer_over_msat as maximum possible, it can
556561
// be only reduced later (not increased), so this channel should just be skipped
557562
// as not sufficient.
558-
// TODO: Explore simply adding fee to hit htlc_minimum_msat
559-
if contributes_sufficient_value && amount_to_transfer_over_msat >= $directional_info.htlc_minimum_msat {
563+
if amount_to_transfer_over_msat < $directional_info.htlc_minimum_msat {
564+
hit_minimum_limit = true;
565+
} else if contributes_sufficient_value {
560566
// Note that low contribution here (limited by available_liquidity_msat)
561567
// might violate htlc_minimum_msat on the hops which are next along the
562568
// payment path (upstream to the payee). To avoid that, we recompute path
@@ -743,12 +749,13 @@ pub fn get_route<L: Deref>(our_node_id: &PublicKey, network: &NetworkGraph, paye
743749
// the further iterations of path finding. Also don't erase first_hop_targets.
744750
targets.clear();
745751
dist.clear();
752+
hit_minimum_limit = false;
746753

747754
// If first hop is a private channel and the only way to reach the payee, this is the only
748755
// place where it could be added.
749756
if first_hops.is_some() {
750757
if let Some(&(ref first_hop, ref features, ref outbound_capacity_msat)) = first_hop_targets.get(&payee) {
751-
add_entry!(first_hop, *our_node_id, payee, dummy_directional_info, Some(outbound_capacity_msat / 1000), features.to_context(), 0, recommended_value_msat);
758+
add_entry!(first_hop, *our_node_id, payee, dummy_directional_info, Some(outbound_capacity_msat / 1000), features.to_context(), 0, path_value_msat);
752759
}
753760
}
754761

@@ -761,7 +768,7 @@ pub fn get_route<L: Deref>(our_node_id: &PublicKey, network: &NetworkGraph, paye
761768
// If not, targets.pop() will not even let us enter the loop in step 2.
762769
None => {},
763770
Some(node) => {
764-
add_entries_to_cheapest_to_target_node!(node, payee, 0, recommended_value_msat);
771+
add_entries_to_cheapest_to_target_node!(node, payee, 0, path_value_msat);
765772
},
766773
}
767774

@@ -780,7 +787,7 @@ pub fn get_route<L: Deref>(our_node_id: &PublicKey, network: &NetworkGraph, paye
780787
// bit lazy here. In the future, we should pull them out via our
781788
// ChannelManager, but there's no reason to waste the space until we
782789
// need them.
783-
add_entry!(first_hop, *our_node_id , hop.src_node_id, dummy_directional_info, Some(outbound_capacity_msat / 1000), features.to_context(), 0, recommended_value_msat);
790+
add_entry!(first_hop, *our_node_id , hop.src_node_id, dummy_directional_info, Some(outbound_capacity_msat / 1000), features.to_context(), 0, path_value_msat);
784791
true
785792
} else {
786793
// In any other case, only add the hop if the source is in the regular network
@@ -800,7 +807,7 @@ pub fn get_route<L: Deref>(our_node_id: &PublicKey, network: &NetworkGraph, paye
800807
htlc_maximum_msat: hop.htlc_maximum_msat,
801808
fees: hop.fees,
802809
};
803-
add_entry!(hop.short_channel_id, hop.src_node_id, payee, directional_info, None::<u64>, ChannelFeatures::empty(), 0, recommended_value_msat);
810+
add_entry!(hop.short_channel_id, hop.src_node_id, payee, directional_info, None::<u64>, ChannelFeatures::empty(), 0, path_value_msat);
804811
}
805812
}
806813

@@ -925,13 +932,23 @@ pub fn get_route<L: Deref>(our_node_id: &PublicKey, network: &NetworkGraph, paye
925932
}
926933

927934
// Step (3).
928-
// Stop either when recommended value is reached,
929-
// or if during last iteration no new path was found.
930-
// In the latter case, making another path finding attempt could not help,
931-
// because we deterministically terminate the search due to low liquidity.
935+
// Stop either when the recommended value is reached or if no new path was found in this
936+
// iteration.
937+
// In the latter case, making another path finding attempt won't help,
938+
// because we deterministically terminated the search due to low liquidity.
932939
if already_collected_value_msat >= recommended_value_msat || !found_new_path {
933940
break 'paths_collection;
934941
}
942+
// Further, if this was our first walk of the graph, and we weren't limited by an
943+
// htlc_minim_msat, return immediately because this path should suffice. If we were limited
944+
// by an htlc_minimum_msat value, find another path with a higher value, potentially
945+
// allowing us to pay fees to meet the htlc_minimum while still keeping a lower total fee.
946+
if already_collected_value_msat == final_value_msat && payment_paths.len() == 1 {
947+
if !hit_minimum_limit {
948+
break 'paths_collection;
949+
}
950+
path_value_msat = recommended_value_msat;
951+
}
935952
}
936953

937954
// Step (4).
@@ -3434,6 +3451,78 @@ mod tests {
34343451
assert_eq!(total_amount_paid_msat, 90_000);
34353452
}
34363453
}
3454+
3455+
#[test]
3456+
fn exact_fee_liquidity_limit() {
3457+
// Test that if, while walking the graph, we find a hop that has exactly enough liquidity
3458+
// for us, including later hop fees, we take it. In the first version of our MPP algorithm
3459+
// we calculated fees on a higher value, resulting in us ignoring such paths.
3460+
let (secp_ctx, net_graph_msg_handler, _, logger) = build_graph();
3461+
let (our_privkey, our_id, privkeys, nodes) = get_nodes(&secp_ctx);
3462+
3463+
// We modify the graph to set the htlc_maximum of channel 2 to below the value we wish to
3464+
// send.
3465+
update_channel(&net_graph_msg_handler, &secp_ctx, &our_privkey, UnsignedChannelUpdate {
3466+
chain_hash: genesis_block(Network::Testnet).header.block_hash(),
3467+
short_channel_id: 2,
3468+
timestamp: 2,
3469+
flags: 0,
3470+
cltv_expiry_delta: 0,
3471+
htlc_minimum_msat: 0,
3472+
htlc_maximum_msat: OptionalField::Present(85_000),
3473+
fee_base_msat: 0,
3474+
fee_proportional_millionths: 0,
3475+
excess_data: Vec::new()
3476+
});
3477+
3478+
update_channel(&net_graph_msg_handler, &secp_ctx, &our_privkey, UnsignedChannelUpdate {
3479+
chain_hash: genesis_block(Network::Testnet).header.block_hash(),
3480+
short_channel_id: 12,
3481+
timestamp: 2,
3482+
flags: 0,
3483+
cltv_expiry_delta: (4 << 8) | 1,
3484+
htlc_minimum_msat: 0,
3485+
htlc_maximum_msat: OptionalField::Present(270_000),
3486+
fee_base_msat: 0,
3487+
fee_proportional_millionths: 1000000,
3488+
excess_data: Vec::new()
3489+
});
3490+
3491+
update_channel(&net_graph_msg_handler, &secp_ctx, &privkeys[2], UnsignedChannelUpdate {
3492+
chain_hash: genesis_block(Network::Testnet).header.block_hash(),
3493+
short_channel_id: 13,
3494+
timestamp: 2,
3495+
flags: 1 | 2,
3496+
cltv_expiry_delta: (13 << 8) | 2,
3497+
htlc_minimum_msat: 0,
3498+
htlc_maximum_msat: OptionalField::Absent,
3499+
fee_base_msat: 0,
3500+
fee_proportional_millionths: 0,
3501+
excess_data: Vec::new()
3502+
});
3503+
3504+
{
3505+
// Now, attempt to route 125 sats (just a bit below the capacity of 3 channels).
3506+
// Our algorithm should provide us with these 3 paths.
3507+
let route = get_route(&our_id, &net_graph_msg_handler.network_graph.read().unwrap(), &nodes[2], None, None, &Vec::new(), 90_000, 42, Arc::clone(&logger)).unwrap();
3508+
assert_eq!(route.paths.len(), 1);
3509+
assert_eq!(route.paths[0].len(), 2);
3510+
3511+
assert_eq!(route.paths[0][0].pubkey, nodes[7]);
3512+
assert_eq!(route.paths[0][0].short_channel_id, 12);
3513+
assert_eq!(route.paths[0][0].fee_msat, 90_000*2);
3514+
assert_eq!(route.paths[0][0].cltv_expiry_delta, (13 << 8) | 1);
3515+
assert_eq!(route.paths[0][0].node_features.le_flags(), &id_to_feature_flags(8));
3516+
assert_eq!(route.paths[0][0].channel_features.le_flags(), &id_to_feature_flags(12));
3517+
3518+
assert_eq!(route.paths[0][1].pubkey, nodes[2]);
3519+
assert_eq!(route.paths[0][1].short_channel_id, 13);
3520+
assert_eq!(route.paths[0][1].fee_msat, 90_000);
3521+
assert_eq!(route.paths[0][1].cltv_expiry_delta, 42);
3522+
assert_eq!(route.paths[0][1].node_features.le_flags(), &id_to_feature_flags(3));
3523+
assert_eq!(route.paths[0][1].channel_features.le_flags(), &id_to_feature_flags(13));
3524+
}
3525+
}
34373526
}
34383527

34393528
#[cfg(all(test, feature = "unstable"))]

0 commit comments

Comments
 (0)