@@ -1787,10 +1787,11 @@ where L::Target: Logger {
1787
1787
// might violate htlc_minimum_msat on the hops which are next along the
1788
1788
// payment path (upstream to the payee). To avoid that, we recompute
1789
1789
// path fees knowing the final path contribution after constructing it.
1790
- let path_htlc_minimum_msat = cmp:: max(
1791
- compute_fees_saturating( $next_hops_path_htlc_minimum_msat, $candidate. fees( ) )
1792
- . saturating_add( $next_hops_path_htlc_minimum_msat) ,
1793
- $candidate. htlc_minimum_msat( ) ) ;
1790
+ let curr_min = cmp:: max(
1791
+ $next_hops_path_htlc_minimum_msat, $candidate. htlc_minimum_msat( )
1792
+ ) ;
1793
+ let path_htlc_minimum_msat = compute_fees_saturating( curr_min, $candidate. fees( ) )
1794
+ . saturating_add( curr_min) ;
1794
1795
let hm_entry = dist. entry( $src_node_id) ;
1795
1796
let old_entry = hm_entry. or_insert_with( || {
1796
1797
// If there was previously no known way to access the source node
@@ -7354,6 +7355,78 @@ mod tests {
7354
7355
assert_eq ! ( route. paths. len( ) , 1 ) ;
7355
7356
assert_eq ! ( route. get_total_amount( ) , amt_msat) ;
7356
7357
}
7358
+
7359
+ #[ test]
7360
+ fn candidate_path_min ( ) {
7361
+ // Test that if a candidate first_hop<>network_node channel does not have enough contribution
7362
+ // amount to cover the next channel's min htlc plus fees, we will not consider that candidate.
7363
+ // Previously, we were storing RouteGraphNodes with a path_min that did not include fees, and
7364
+ // would add a connecting first_hop node that did not have enough contribution amount, leading
7365
+ // to a debug panic upon invalid path construction.
7366
+ let secp_ctx = Secp256k1 :: new ( ) ;
7367
+ let logger = Arc :: new ( ln_test_utils:: TestLogger :: new ( ) ) ;
7368
+ let network_graph = Arc :: new ( NetworkGraph :: new ( Network :: Testnet , Arc :: clone ( & logger) ) ) ;
7369
+ let gossip_sync = P2PGossipSync :: new ( network_graph. clone ( ) , None , logger. clone ( ) ) ;
7370
+ let scorer = ProbabilisticScorer :: new ( ProbabilisticScoringDecayParameters :: default ( ) , network_graph. clone ( ) , logger. clone ( ) ) ;
7371
+ let keys_manager = ln_test_utils:: TestKeysInterface :: new ( & [ 0u8 ; 32 ] , Network :: Testnet ) ;
7372
+ let random_seed_bytes = keys_manager. get_secure_random_bytes ( ) ;
7373
+ let config = UserConfig :: default ( ) ;
7374
+
7375
+ // Values are taken from the fuzz input that uncovered this panic.
7376
+ let amt_msat = 7_4009_8048 ;
7377
+ let ( _, our_id, privkeys, nodes) = get_nodes ( & secp_ctx) ;
7378
+ let first_hops = vec ! [ get_channel_details(
7379
+ Some ( 200 ) , nodes[ 0 ] , channelmanager:: provided_init_features( & config) , 2_7345_2000
7380
+ ) ] ;
7381
+
7382
+ add_channel ( & gossip_sync, & secp_ctx, & privkeys[ 0 ] , & privkeys[ 6 ] , ChannelFeatures :: from_le_bytes ( id_to_feature_flags ( 6 ) ) , 6 ) ;
7383
+ update_channel ( & gossip_sync, & secp_ctx, & privkeys[ 0 ] , UnsignedChannelUpdate {
7384
+ chain_hash : genesis_block ( Network :: Testnet ) . header . block_hash ( ) ,
7385
+ short_channel_id : 6 ,
7386
+ timestamp : 1 ,
7387
+ flags : 0 ,
7388
+ cltv_expiry_delta : ( 6 << 4 ) | 0 ,
7389
+ htlc_minimum_msat : 0 ,
7390
+ htlc_maximum_msat : MAX_VALUE_MSAT ,
7391
+ fee_base_msat : 0 ,
7392
+ fee_proportional_millionths : 0 ,
7393
+ excess_data : Vec :: new ( )
7394
+ } ) ;
7395
+ add_or_update_node ( & gossip_sync, & secp_ctx, & privkeys[ 0 ] , NodeFeatures :: from_le_bytes ( id_to_feature_flags ( 1 ) ) , 0 ) ;
7396
+
7397
+ let htlc_min = 2_5165_8240 ;
7398
+ let blinded_hints = vec ! [
7399
+ ( BlindedPayInfo {
7400
+ fee_base_msat: 1_6778_3453 ,
7401
+ fee_proportional_millionths: 0 ,
7402
+ htlc_minimum_msat: htlc_min,
7403
+ htlc_maximum_msat: htlc_min * 100 ,
7404
+ cltv_expiry_delta: 10 ,
7405
+ features: BlindedHopFeatures :: empty( ) ,
7406
+ } , BlindedPath {
7407
+ introduction_node_id: nodes[ 0 ] ,
7408
+ blinding_point: ln_test_utils:: pubkey( 42 ) ,
7409
+ blinded_hops: vec![
7410
+ BlindedHop { blinded_node_id: ln_test_utils:: pubkey( 42 as u8 ) , encrypted_payload: Vec :: new( ) } ,
7411
+ BlindedHop { blinded_node_id: ln_test_utils:: pubkey( 42 as u8 ) , encrypted_payload: Vec :: new( ) }
7412
+ ] ,
7413
+ } )
7414
+ ] ;
7415
+ let bolt12_features: Bolt12InvoiceFeatures = channelmanager:: provided_invoice_features ( & config) . to_context ( ) ;
7416
+ let payment_params = PaymentParameters :: blinded ( blinded_hints. clone ( ) )
7417
+ . with_bolt12_features ( bolt12_features. clone ( ) ) . unwrap ( ) ;
7418
+ let route_params = RouteParameters :: from_payment_params_and_value (
7419
+ payment_params, amt_msat) ;
7420
+ let netgraph = network_graph. read_only ( ) ;
7421
+
7422
+ if let Err ( LightningError { err, .. } ) = get_route (
7423
+ & our_id, & route_params, & netgraph, Some ( & first_hops. iter ( ) . collect :: < Vec < _ > > ( ) ) ,
7424
+ Arc :: clone ( & logger) , & scorer, & ProbabilisticScoringFeeParameters :: default ( ) ,
7425
+ & random_seed_bytes
7426
+ ) {
7427
+ assert_eq ! ( err, "Failed to find a path to the given destination" ) ;
7428
+ } else { panic ! ( ) }
7429
+ }
7357
7430
}
7358
7431
7359
7432
#[ cfg( all( any( test, ldk_bench) , not( feature = "no-std" ) ) ) ]
0 commit comments