@@ -2163,11 +2163,13 @@ where L::Target: Logger {
2163
2163
. map_or ( None , |inc| inc. checked_add ( aggregate_next_hops_fee_msat) ) ;
2164
2164
aggregate_next_hops_fee_msat = if let Some ( val) = hops_fee { val } else { break ; } ;
2165
2165
2166
- let hop_htlc_minimum_msat = candidate. htlc_minimum_msat ( ) ;
2167
- let hop_htlc_minimum_msat_inc = if let Some ( val) = compute_fees ( aggregate_next_hops_path_htlc_minimum_msat, hop. fees ) { val } else { break ; } ;
2168
- let hops_path_htlc_minimum = aggregate_next_hops_path_htlc_minimum_msat
2169
- . checked_add ( hop_htlc_minimum_msat_inc) ;
2170
- aggregate_next_hops_path_htlc_minimum_msat = if let Some ( val) = hops_path_htlc_minimum { cmp:: max ( hop_htlc_minimum_msat, val) } else { break ; } ;
2166
+ aggregate_next_hops_path_htlc_minimum_msat = {
2167
+ let curr_htlc_min = cmp:: max (
2168
+ candidate. htlc_minimum_msat ( ) , aggregate_next_hops_path_htlc_minimum_msat
2169
+ ) ;
2170
+ let curr_htlc_min_fee = if let Some ( val) = compute_fees ( curr_htlc_min, hop. fees ) { val } else { break } ;
2171
+ if let Some ( min) = curr_htlc_min. checked_add ( curr_htlc_min_fee) { min } else { break }
2172
+ } ;
2171
2173
2172
2174
if idx == route. 0 . len ( ) - 1 {
2173
2175
// The last hop in this iterator is the first hop in
@@ -7185,6 +7187,134 @@ mod tests {
7185
7187
assert_eq ! ( err, "Failed to find a path to the given destination" ) ;
7186
7188
} else { panic ! ( ) }
7187
7189
}
7190
+
7191
+ #[ test]
7192
+ fn min_htlc_overpay_violates_max_htlc ( ) {
7193
+ // Test that if overpaying to meet a later hop's min_htlc and causes us to violate an earlier
7194
+ // hop's max_htlc, we discard that invalid path. Previously we would consider the path to be
7195
+ // valid and hit a debug panic asserting that the used liquidity for a hop was less than its
7196
+ // available liquidity limit.
7197
+ let secp_ctx = Secp256k1 :: new ( ) ;
7198
+ let logger = Arc :: new ( ln_test_utils:: TestLogger :: new ( ) ) ;
7199
+ let network_graph = Arc :: new ( NetworkGraph :: new ( Network :: Testnet , Arc :: clone ( & logger) ) ) ;
7200
+ let scorer = ln_test_utils:: TestScorer :: new ( ) ;
7201
+ let keys_manager = ln_test_utils:: TestKeysInterface :: new ( & [ 0u8 ; 32 ] , Network :: Testnet ) ;
7202
+ let random_seed_bytes = keys_manager. get_secure_random_bytes ( ) ;
7203
+ let config = UserConfig :: default ( ) ;
7204
+
7205
+ // Values are taken from the fuzz input that uncovered this panic.
7206
+ let amt_msat = 7_4009_8048 ;
7207
+ let ( _, our_id, _, nodes) = get_nodes ( & secp_ctx) ;
7208
+ let first_hop_outbound_capacity = 2_7345_2000 ;
7209
+ let first_hops = vec ! [ get_channel_details(
7210
+ Some ( 200 ) , nodes[ 0 ] , channelmanager:: provided_init_features( & config) ,
7211
+ first_hop_outbound_capacity
7212
+ ) ] ;
7213
+
7214
+ let route_hint = RouteHint ( vec ! [ RouteHintHop {
7215
+ src_node_id: nodes[ 0 ] ,
7216
+ short_channel_id: 44 ,
7217
+ fees: RoutingFees {
7218
+ base_msat: 1_6778_3453 ,
7219
+ proportional_millionths: 0 ,
7220
+ } ,
7221
+ cltv_expiry_delta: 10 ,
7222
+ htlc_minimum_msat: Some ( 2_5165_8240 ) ,
7223
+ htlc_maximum_msat: None ,
7224
+ } ] ) ;
7225
+
7226
+ let payment_params = PaymentParameters :: from_node_id ( nodes[ 1 ] , 42 )
7227
+ . with_route_hints ( vec ! [ route_hint] ) . unwrap ( )
7228
+ . with_bolt11_features ( channelmanager:: provided_invoice_features ( & config) ) . unwrap ( ) ;
7229
+
7230
+ let netgraph = network_graph. read_only ( ) ;
7231
+ let route_params = RouteParameters :: from_payment_params_and_value (
7232
+ payment_params, amt_msat) ;
7233
+ if let Err ( LightningError { err, .. } ) = get_route (
7234
+ & our_id, & route_params, & netgraph, Some ( & first_hops. iter ( ) . collect :: < Vec < _ > > ( ) ) ,
7235
+ Arc :: clone ( & logger) , & scorer, & ( ) , & random_seed_bytes
7236
+ ) {
7237
+ assert_eq ! ( err, "Failed to find a path to the given destination" ) ;
7238
+ } else { panic ! ( ) }
7239
+ }
7240
+
7241
+ #[ test]
7242
+ fn previously_used_liquidity_violates_max_htlc ( ) {
7243
+ // Test that if a candidate hop would cause us to violate an upstream hop's available
7244
+ // contribution amount due to a previously found path, we will not consider that candidate in
7245
+ // path construction. Previously we would construct an invalid path and hit a debug panic
7246
+ // asserting that the used liquidity for a hop was less than its available liquidity limit.
7247
+ let secp_ctx = Secp256k1 :: new ( ) ;
7248
+ let logger = Arc :: new ( ln_test_utils:: TestLogger :: new ( ) ) ;
7249
+ let network_graph = Arc :: new ( NetworkGraph :: new ( Network :: Testnet , Arc :: clone ( & logger) ) ) ;
7250
+ let scorer = ln_test_utils:: TestScorer :: new ( ) ;
7251
+ let keys_manager = ln_test_utils:: TestKeysInterface :: new ( & [ 0u8 ; 32 ] , Network :: Testnet ) ;
7252
+ let random_seed_bytes = keys_manager. get_secure_random_bytes ( ) ;
7253
+ let config = UserConfig :: default ( ) ;
7254
+
7255
+ // Values are taken from the fuzz input that uncovered this panic.
7256
+ let amt_msat = 52_4288 ;
7257
+ let ( _, our_id, _, nodes) = get_nodes ( & secp_ctx) ;
7258
+ let first_hops = vec ! [ get_channel_details(
7259
+ Some ( 161 ) , nodes[ 0 ] , channelmanager:: provided_init_features( & config) , 486_4000
7260
+ ) , get_channel_details(
7261
+ Some ( 122 ) , nodes[ 0 ] , channelmanager:: provided_init_features( & config) , 179_5000
7262
+ ) ] ;
7263
+
7264
+ let route_hints = vec ! [ RouteHint ( vec![ RouteHintHop {
7265
+ src_node_id: nodes[ 0 ] ,
7266
+ short_channel_id: 42 ,
7267
+ fees: RoutingFees {
7268
+ base_msat: 0 ,
7269
+ proportional_millionths: 0 ,
7270
+ } ,
7271
+ cltv_expiry_delta: 10 ,
7272
+ htlc_minimum_msat: Some ( 1_4392 ) ,
7273
+ htlc_maximum_msat: Some ( 143_9200 ) ,
7274
+ } ] ) , RouteHint ( vec![ RouteHintHop {
7275
+ src_node_id: nodes[ 0 ] ,
7276
+ short_channel_id: 43 ,
7277
+ fees: RoutingFees {
7278
+ base_msat: 425_9840 ,
7279
+ proportional_millionths: 0 ,
7280
+ } ,
7281
+ cltv_expiry_delta: 10 ,
7282
+ htlc_minimum_msat: Some ( 19_7401 ) ,
7283
+ htlc_maximum_msat: Some ( 1974_0100 ) ,
7284
+ } ] ) , RouteHint ( vec![ RouteHintHop {
7285
+ src_node_id: nodes[ 0 ] ,
7286
+ short_channel_id: 44 ,
7287
+ fees: RoutingFees {
7288
+ base_msat: 0 ,
7289
+ proportional_millionths: 0 ,
7290
+ } ,
7291
+ cltv_expiry_delta: 10 ,
7292
+ htlc_minimum_msat: Some ( 1027 ) ,
7293
+ htlc_maximum_msat: Some ( 10_2700 ) ,
7294
+ } ] ) , RouteHint ( vec![ RouteHintHop {
7295
+ src_node_id: nodes[ 0 ] ,
7296
+ short_channel_id: 45 ,
7297
+ fees: RoutingFees {
7298
+ base_msat: 0 ,
7299
+ proportional_millionths: 0 ,
7300
+ } ,
7301
+ cltv_expiry_delta: 10 ,
7302
+ htlc_minimum_msat: Some ( 6_5535 ) ,
7303
+ htlc_maximum_msat: Some ( 655_3500 ) ,
7304
+ } ] ) ] ;
7305
+
7306
+ let payment_params = PaymentParameters :: from_node_id ( nodes[ 1 ] , 42 )
7307
+ . with_route_hints ( route_hints) . unwrap ( )
7308
+ . with_bolt11_features ( channelmanager:: provided_invoice_features ( & config) ) . unwrap ( ) ;
7309
+
7310
+ let netgraph = network_graph. read_only ( ) ;
7311
+ let route_params = RouteParameters :: from_payment_params_and_value (
7312
+ payment_params, amt_msat) ;
7313
+ assert ! ( get_route(
7314
+ & our_id, & route_params, & netgraph, Some ( & first_hops. iter( ) . collect:: <Vec <_>>( ) ) ,
7315
+ Arc :: clone( & logger) , & scorer, & ( ) , & random_seed_bytes
7316
+ ) . is_ok( ) ) ;
7317
+ }
7188
7318
}
7189
7319
7190
7320
#[ cfg( all( any( test, ldk_bench) , not( feature = "no-std" ) ) ) ]
0 commit comments