Skip to content

Commit a68e90c

Browse files
committed
Use floor when computing max value of a path, not ceil
When calculating the maximum contribution of a path to a larger route, we want to ensure we don't overshoot as that might cause us to violate a maximum value limit. In 209cb2a, we started by calculating with `ceil`, which can trigger exactly that, so here we drop the extra addition, switching us to `floor`.
1 parent 559a784 commit a68e90c

File tree

1 file changed

+94
-2
lines changed

1 file changed

+94
-2
lines changed

lightning/src/routing/router.rs

+94-2
Original file line numberDiff line numberDiff line change
@@ -2145,11 +2145,10 @@ impl<'a> PaymentPath<'a> {
21452145
let (next_hops_aggregated_base, next_hops_aggregated_prop) =
21462146
crate::blinded_path::payment::compute_aggregated_base_prop_fee(next_hops_feerates_iter).unwrap();
21472147

2148-
// ceil(((hop_max_msat - agg_base) * 1_000_000) / (1_000_000 + agg_prop))
2148+
// floor(((hop_max_msat - agg_base) * 1_000_000) / (1_000_000 + agg_prop))
21492149
let hop_max_final_value_contribution = (hop_max_msat as u128)
21502150
.checked_sub(next_hops_aggregated_base as u128)
21512151
.and_then(|f| f.checked_mul(1_000_000))
2152-
.and_then(|f| f.checked_add(1_000_000 - 1))
21532152
.and_then(|f| f.checked_add(next_hops_aggregated_prop as u128))
21542153
.map(|f| f / ((next_hops_aggregated_prop as u128).saturating_add(1_000_000)));
21552154

@@ -8662,6 +8661,99 @@ mod tests {
86628661
assert_eq!(route.get_total_fees(), 123);
86638662
}
86648663

8664+
#[test]
8665+
fn test_max_final_contribution() {
8666+
// When `compute_max_final_value_contribution` was added, it had a bug where it would
8667+
// over-estimate the maximum value contribution of a hop by using `ceil` rather than
8668+
// `floor`. This tests that case by attempting to send 1 million sats over a channel where
8669+
// the remaining hops have a base fee of zero and a proportional fee of 1 millionth.
8670+
8671+
let (secp_ctx, network_graph, gossip_sync, _, logger) = build_graph();
8672+
let (our_privkey, our_id, privkeys, nodes) = get_nodes(&secp_ctx);
8673+
let scorer = ln_test_utils::TestScorer::new();
8674+
let random_seed_bytes = [42; 32];
8675+
8676+
// Enable channel 1, setting max HTLC to 1M sats
8677+
update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate {
8678+
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
8679+
short_channel_id: 1,
8680+
timestamp: 2,
8681+
message_flags: 1, // Only must_be_one
8682+
channel_flags: 0,
8683+
cltv_expiry_delta: (1 << 4) | 1,
8684+
htlc_minimum_msat: 0,
8685+
htlc_maximum_msat: 1_000_000,
8686+
fee_base_msat: 0,
8687+
fee_proportional_millionths: 0,
8688+
excess_data: Vec::new()
8689+
});
8690+
8691+
// Set the fee on channel 3 to zero
8692+
update_channel(&gossip_sync, &secp_ctx, &privkeys[0], UnsignedChannelUpdate {
8693+
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
8694+
short_channel_id: 3,
8695+
timestamp: 2,
8696+
message_flags: 1, // Only must_be_one
8697+
channel_flags: 0,
8698+
cltv_expiry_delta: (3 << 4) | 1,
8699+
htlc_minimum_msat: 0,
8700+
htlc_maximum_msat: 1_000_000_000,
8701+
fee_base_msat: 0,
8702+
fee_proportional_millionths: 0,
8703+
excess_data: Vec::new()
8704+
});
8705+
8706+
// Set the fee on channel 6 to 1 millionth
8707+
update_channel(&gossip_sync, &secp_ctx, &privkeys[2], UnsignedChannelUpdate {
8708+
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
8709+
short_channel_id: 6,
8710+
timestamp: 2,
8711+
message_flags: 1, // Only must_be_one
8712+
channel_flags: 0,
8713+
cltv_expiry_delta: (6 << 4) | 1,
8714+
htlc_minimum_msat: 0,
8715+
htlc_maximum_msat: 1_000_000_000,
8716+
fee_base_msat: 0,
8717+
fee_proportional_millionths: 1,
8718+
excess_data: Vec::new()
8719+
});
8720+
8721+
// Now attempt to pay over the channel 1 -> channel 3 -> channel 6 path
8722+
// This should fail as we need to send 1M + 1 sats to cover the fee but channel 1 only
8723+
// allows for 1M sats to flow over it.
8724+
let config = UserConfig::default();
8725+
let payment_params = PaymentParameters::from_node_id(nodes[4], 42)
8726+
.with_bolt11_features(channelmanager::provided_bolt11_invoice_features(&config))
8727+
.unwrap();
8728+
let route_params = RouteParameters::from_payment_params_and_value(payment_params, 1_000_000);
8729+
get_route(&our_id, &route_params, &network_graph.read_only(), None,
8730+
Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap_err();
8731+
8732+
// Now set channel 1 max HTLC to 1M + 1 sats
8733+
update_channel(&gossip_sync, &secp_ctx, &our_privkey, UnsignedChannelUpdate {
8734+
chain_hash: ChainHash::using_genesis_block(Network::Testnet),
8735+
short_channel_id: 1,
8736+
timestamp: 3,
8737+
message_flags: 1, // Only must_be_one
8738+
channel_flags: 0,
8739+
cltv_expiry_delta: (1 << 4) | 1,
8740+
htlc_minimum_msat: 0,
8741+
htlc_maximum_msat: 1_000_001,
8742+
fee_base_msat: 0,
8743+
fee_proportional_millionths: 0,
8744+
excess_data: Vec::new()
8745+
});
8746+
8747+
// And attempt the same payment again, but this time it should work.
8748+
let route = get_route(&our_id, &route_params, &network_graph.read_only(), None,
8749+
Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes).unwrap();
8750+
assert_eq!(route.paths.len(), 1);
8751+
assert_eq!(route.paths[0].hops.len(), 3);
8752+
assert_eq!(route.paths[0].hops[0].short_channel_id, 1);
8753+
assert_eq!(route.paths[0].hops[1].short_channel_id, 3);
8754+
assert_eq!(route.paths[0].hops[2].short_channel_id, 6);
8755+
}
8756+
86658757
#[test]
86668758
fn allow_us_being_first_hint() {
86678759
// Check that we consider a route hint even if we are the src of the first hop.

0 commit comments

Comments
 (0)