Skip to content

Commit cdfdafa

Browse files
committed
Fix MPP routefinding when we first collect 95% of payment value
See comment in new test for more details.
1 parent e3bdfa0 commit cdfdafa

File tree

1 file changed

+98
-4
lines changed

1 file changed

+98
-4
lines changed

lightning/src/routing/router.rs

Lines changed: 98 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1113,10 +1113,13 @@ 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+
let reqd_channel_cap = if let Some (val) = final_value_msat
1117+
.checked_mul(ROUTE_CAPACITY_PROVISION_FACTOR).unwrap_or(u64::max_value())
1118+
.checked_add(match idx {
1119+
0 => 999,
1120+
_ => aggregate_next_hops_fee_msat.checked_add(999).unwrap_or(u64::max_value())
1121+
})
1122+
{ Some( val / 1000 ) } else { break; }; // converting from msat or breaking if max ~ infinity
11201123

11211124
let src_node_id = NodeId::from_pubkey(&hop.src_node_id);
11221125
let dest_node_id = NodeId::from_pubkey(&prev_hop_id);
@@ -4055,7 +4058,98 @@ mod tests {
40554058
assert_eq!(total_amount_paid_msat, 200_000);
40564059
assert_eq!(route.get_total_fees(), 150_000);
40574060
}
4061+
}
40584062

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

40614155
#[test]

0 commit comments

Comments
 (0)