Skip to content

Commit 2239885

Browse files
authored
Merge pull request #1168 from TheBlueMatt/2021-11-mpp-routing-fixes
Fix MPP routefinding when we first collect 95% of payment value
2 parents 3cb3d18 + 1180b63 commit 2239885

File tree

1 file changed

+109
-5
lines changed

1 file changed

+109
-5
lines changed

lightning/src/routing/router.rs

Lines changed: 109 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1113,10 +1113,16 @@ where L::Target: Logger {
11131113
fees: hop.fees,
11141114
};
11151115

1116-
let reqd_channel_cap = if let Some (val) = final_value_msat.checked_add(match idx {
1117-
0 => 999,
1118-
_ => aggregate_next_hops_fee_msat.checked_add(999).unwrap_or(u64::max_value())
1119-
}) { Some( val / 1000 ) } else { break; }; // converting from msat or breaking if max ~ infinity
1116+
// We want a value of final_value_msat * ROUTE_CAPACITY_PROVISION_FACTOR but we
1117+
// need it to increment at each hop by the fee charged at later hops. Further,
1118+
// we need to ensure we round up when we divide to get satoshis.
1119+
let channel_cap_msat = final_value_msat
1120+
.checked_mul(ROUTE_CAPACITY_PROVISION_FACTOR).and_then(|v| v.checked_add(aggregate_next_hops_fee_msat))
1121+
.unwrap_or(u64::max_value());
1122+
let channel_cap_sat = match channel_cap_msat.checked_add(999) {
1123+
None => break, // We overflowed above, just ignore this route hint
1124+
Some(val) => Some(val / 1000),
1125+
};
11201126

11211127
let src_node_id = NodeId::from_pubkey(&hop.src_node_id);
11221128
let dest_node_id = NodeId::from_pubkey(&prev_hop_id);
@@ -1128,7 +1134,7 @@ where L::Target: Logger {
11281134
// sufficient value to route `final_value_msat`. Note that in the case of "0-value"
11291135
// invoices where the invoice does not specify value this may not be the case, but
11301136
// better to include the hints than not.
1131-
if !add_entry!(hop.short_channel_id, src_node_id, dest_node_id, directional_info, reqd_channel_cap, &empty_channel_features, aggregate_next_hops_fee_msat, path_value_msat, aggregate_next_hops_path_htlc_minimum_msat, aggregate_next_hops_path_penalty_msat) {
1137+
if !add_entry!(hop.short_channel_id, src_node_id, dest_node_id, directional_info, channel_cap_sat, &empty_channel_features, aggregate_next_hops_fee_msat, path_value_msat, aggregate_next_hops_path_htlc_minimum_msat, aggregate_next_hops_path_penalty_msat) {
11321138
// If this hop was not used then there is no use checking the preceding hops
11331139
// in the RouteHint. We can break by just searching for a direct channel between
11341140
// last checked hop and first_hop_targets
@@ -4055,7 +4061,105 @@ mod tests {
40554061
assert_eq!(total_amount_paid_msat, 200_000);
40564062
assert_eq!(route.get_total_fees(), 150_000);
40574063
}
4064+
}
40584065

4066+
#[test]
4067+
fn mpp_with_last_hops() {
4068+
// Previously, if we tried to send an MPP payment to a destination which was only reachable
4069+
// via a single last-hop route hint, we'd fail to route if we first collected routes
4070+
// totaling close but not quite enough to fund the full payment.
4071+
//
4072+
// This was because we considered last-hop hints to have exactly the sought payment amount
4073+
// instead of the amount we were trying to collect, needlessly limiting our path searching
4074+
// at the very first hop.
4075+
//
4076+
// Specifically, this interacted with our "all paths must fund at least 5% of total target"
4077+
// criterion to cause us to refuse all routes at the last hop hint which would be considered
4078+
// to only have the remaining to-collect amount in available liquidity.
4079+
//
4080+
// This bug appeared in production in some specific channel configurations.
4081+
let (secp_ctx, network_graph, net_graph_msg_handler, _, logger) = build_graph();
4082+
let (our_privkey, our_id, privkeys, nodes) = get_nodes(&secp_ctx);
4083+
let scorer = test_utils::TestScorer::with_fixed_penalty(0);
4084+
let payee = Payee::from_node_id(PublicKey::from_slice(&[02; 33]).unwrap()).with_features(InvoiceFeatures::known())
4085+
.with_route_hints(vec![RouteHint(vec![RouteHintHop {
4086+
src_node_id: nodes[2],
4087+
short_channel_id: 42,
4088+
fees: RoutingFees { base_msat: 0, proportional_millionths: 0 },
4089+
cltv_expiry_delta: 42,
4090+
htlc_minimum_msat: None,
4091+
htlc_maximum_msat: None,
4092+
}])]);
4093+
4094+
// Keep only two paths from us to nodes[2], both with a 99sat HTLC maximum, with one with
4095+
// no fee and one with a 1msat fee. Previously, trying to route 100 sats to nodes[2] here
4096+
// would first use the no-fee route and then fail to find a path along the second route as
4097+
// we think we can only send up to 1 additional sat over the last-hop but refuse to as its
4098+
// under 5% of our payment amount.
4099+
update_channel(&net_graph_msg_handler, &secp_ctx, &our_privkey, UnsignedChannelUpdate {
4100+
chain_hash: genesis_block(Network::Testnet).header.block_hash(),
4101+
short_channel_id: 1,
4102+
timestamp: 2,
4103+
flags: 0,
4104+
cltv_expiry_delta: u16::max_value(),
4105+
htlc_minimum_msat: 0,
4106+
htlc_maximum_msat: OptionalField::Present(99_000),
4107+
fee_base_msat: u32::max_value(),
4108+
fee_proportional_millionths: u32::max_value(),
4109+
excess_data: Vec::new()
4110+
});
4111+
update_channel(&net_graph_msg_handler, &secp_ctx, &our_privkey, UnsignedChannelUpdate {
4112+
chain_hash: genesis_block(Network::Testnet).header.block_hash(),
4113+
short_channel_id: 2,
4114+
timestamp: 2,
4115+
flags: 0,
4116+
cltv_expiry_delta: u16::max_value(),
4117+
htlc_minimum_msat: 0,
4118+
htlc_maximum_msat: OptionalField::Present(99_000),
4119+
fee_base_msat: u32::max_value(),
4120+
fee_proportional_millionths: u32::max_value(),
4121+
excess_data: Vec::new()
4122+
});
4123+
update_channel(&net_graph_msg_handler, &secp_ctx, &privkeys[1], UnsignedChannelUpdate {
4124+
chain_hash: genesis_block(Network::Testnet).header.block_hash(),
4125+
short_channel_id: 4,
4126+
timestamp: 2,
4127+
flags: 0,
4128+
cltv_expiry_delta: (4 << 8) | 1,
4129+
htlc_minimum_msat: 0,
4130+
htlc_maximum_msat: OptionalField::Absent,
4131+
fee_base_msat: 1,
4132+
fee_proportional_millionths: 0,
4133+
excess_data: Vec::new()
4134+
});
4135+
update_channel(&net_graph_msg_handler, &secp_ctx, &privkeys[7], UnsignedChannelUpdate {
4136+
chain_hash: genesis_block(Network::Testnet).header.block_hash(),
4137+
short_channel_id: 13,
4138+
timestamp: 2,
4139+
flags: 0|2, // Channel disabled
4140+
cltv_expiry_delta: (13 << 8) | 1,
4141+
htlc_minimum_msat: 0,
4142+
htlc_maximum_msat: OptionalField::Absent,
4143+
fee_base_msat: 0,
4144+
fee_proportional_millionths: 2000000,
4145+
excess_data: Vec::new()
4146+
});
4147+
4148+
// Get a route for 100 sats and check that we found the MPP route no problem and didn't
4149+
// overpay at all.
4150+
let route = get_route(&our_id, &payee, &network_graph, None, 100_000, 42, Arc::clone(&logger), &scorer).unwrap();
4151+
assert_eq!(route.paths.len(), 2);
4152+
// Paths are somewhat randomly ordered, but:
4153+
// * the first is channel 2 (1 msat fee) -> channel 4 -> channel 42
4154+
// * the second is channel 1 (0 fee, but 99 sat maximum) -> channel 3 -> channel 42
4155+
assert_eq!(route.paths[0][0].short_channel_id, 2);
4156+
assert_eq!(route.paths[0][0].fee_msat, 1);
4157+
assert_eq!(route.paths[0][2].fee_msat, 1_000);
4158+
assert_eq!(route.paths[1][0].short_channel_id, 1);
4159+
assert_eq!(route.paths[1][0].fee_msat, 0);
4160+
assert_eq!(route.paths[1][2].fee_msat, 99_000);
4161+
assert_eq!(route.get_total_fees(), 1);
4162+
assert_eq!(route.get_total_amount(), 100_000);
40594163
}
40604164

40614165
#[test]

0 commit comments

Comments
 (0)