@@ -1787,10 +1787,11 @@ where L::Target: Logger {
17871787						// might violate htlc_minimum_msat on the hops which are next along the 
17881788						// payment path (upstream to the payee). To avoid that, we recompute 
17891789						// 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) ; 
17941795						let  hm_entry = dist. entry( $src_node_id) ; 
17951796						let  old_entry = hm_entry. or_insert_with( || { 
17961797							// If there was previously no known way to access the source node 
@@ -7354,6 +7355,78 @@ mod tests {
73547355		assert_eq ! ( route. paths. len( ) ,  1 ) ; 
73557356		assert_eq ! ( route. get_total_amount( ) ,  amt_msat) ; 
73567357	} 
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+ 	} 
73577430} 
73587431
73597432#[ cfg( all( any( test,  ldk_bench) ,  not( feature = "no-std" ) ) ) ]  
0 commit comments