@@ -1819,10 +1819,11 @@ where L::Target: Logger {
18191819						// might violate htlc_minimum_msat on the hops which are next along the 
18201820						// payment path (upstream to the payee). To avoid that, we recompute 
18211821						// path fees knowing the final path contribution after constructing it. 
1822- 						let  path_htlc_minimum_msat = cmp:: max( 
1823- 							compute_fees_saturating( $next_hops_path_htlc_minimum_msat,  $candidate. fees( ) ) 
1824- 								. saturating_add( $next_hops_path_htlc_minimum_msat) , 
1825- 							$candidate. htlc_minimum_msat( ) ) ; 
1822+ 						let  curr_min = cmp:: max( 
1823+ 							$next_hops_path_htlc_minimum_msat,  $candidate. htlc_minimum_msat( ) 
1824+ 						) ; 
1825+ 						let  path_htlc_minimum_msat = compute_fees_saturating( curr_min,  $candidate. fees( ) ) 
1826+ 							. saturating_add( curr_min) ; 
18261827						let  hm_entry = dist. entry( $src_node_id) ; 
18271828						let  old_entry = hm_entry. or_insert_with( || { 
18281829							// If there was previously no known way to access the source node 
@@ -7449,6 +7450,78 @@ mod tests {
74497450		assert_eq ! ( route. paths. len( ) ,  1 ) ; 
74507451		assert_eq ! ( route. get_total_amount( ) ,  amt_msat) ; 
74517452	} 
7453+ 
7454+ 	#[ test]  
7455+ 	fn  candidate_path_min ( )  { 
7456+ 		// Test that if a candidate first_hop<>network_node channel does not have enough contribution 
7457+ 		// amount to cover the next channel's min htlc plus fees, we will not consider that candidate. 
7458+ 		// Previously, we were storing RouteGraphNodes with a path_min that did not include fees, and 
7459+ 		// would add a connecting first_hop node that did not have enough contribution amount, leading 
7460+ 		// to a debug panic upon invalid path construction. 
7461+ 		let  secp_ctx = Secp256k1 :: new ( ) ; 
7462+ 		let  logger = Arc :: new ( ln_test_utils:: TestLogger :: new ( ) ) ; 
7463+ 		let  network_graph = Arc :: new ( NetworkGraph :: new ( Network :: Testnet ,  Arc :: clone ( & logger) ) ) ; 
7464+ 		let  gossip_sync = P2PGossipSync :: new ( network_graph. clone ( ) ,  None ,  logger. clone ( ) ) ; 
7465+ 		let  scorer = ProbabilisticScorer :: new ( ProbabilisticScoringDecayParameters :: default ( ) ,  network_graph. clone ( ) ,  logger. clone ( ) ) ; 
7466+ 		let  keys_manager = ln_test_utils:: TestKeysInterface :: new ( & [ 0u8 ;  32 ] ,  Network :: Testnet ) ; 
7467+ 		let  random_seed_bytes = keys_manager. get_secure_random_bytes ( ) ; 
7468+ 		let  config = UserConfig :: default ( ) ; 
7469+ 
7470+ 		// Values are taken from the fuzz input that uncovered this panic. 
7471+ 		let  amt_msat = 7_4009_8048 ; 
7472+ 		let  ( _,  our_id,  privkeys,  nodes)  = get_nodes ( & secp_ctx) ; 
7473+ 		let  first_hops = vec ! [ get_channel_details( 
7474+ 			Some ( 200 ) ,  nodes[ 0 ] ,  channelmanager:: provided_init_features( & config) ,  2_7345_2000 
7475+ 		) ] ; 
7476+ 
7477+ 		add_channel ( & gossip_sync,  & secp_ctx,  & privkeys[ 0 ] ,  & privkeys[ 6 ] ,  ChannelFeatures :: from_le_bytes ( id_to_feature_flags ( 6 ) ) ,  6 ) ; 
7478+ 		update_channel ( & gossip_sync,  & secp_ctx,  & privkeys[ 0 ] ,  UnsignedChannelUpdate  { 
7479+ 			chain_hash :  genesis_block ( Network :: Testnet ) . header . block_hash ( ) , 
7480+ 			short_channel_id :  6 , 
7481+ 			timestamp :  1 , 
7482+ 			flags :  0 , 
7483+ 			cltv_expiry_delta :  ( 6  << 4 )  | 0 , 
7484+ 			htlc_minimum_msat :  0 , 
7485+ 			htlc_maximum_msat :  MAX_VALUE_MSAT , 
7486+ 			fee_base_msat :  0 , 
7487+ 			fee_proportional_millionths :  0 , 
7488+ 			excess_data :  Vec :: new ( ) 
7489+ 		} ) ; 
7490+ 		add_or_update_node ( & gossip_sync,  & secp_ctx,  & privkeys[ 0 ] ,  NodeFeatures :: from_le_bytes ( id_to_feature_flags ( 1 ) ) ,  0 ) ; 
7491+ 
7492+ 		let  htlc_min = 2_5165_8240 ; 
7493+ 		let  blinded_hints = vec ! [ 
7494+ 			( BlindedPayInfo  { 
7495+ 				fee_base_msat:  1_6778_3453 , 
7496+ 				fee_proportional_millionths:  0 , 
7497+ 				htlc_minimum_msat:  htlc_min, 
7498+ 				htlc_maximum_msat:  htlc_min *  100 , 
7499+ 				cltv_expiry_delta:  10 , 
7500+ 				features:  BlindedHopFeatures :: empty( ) , 
7501+ 			} ,  BlindedPath  { 
7502+ 				introduction_node_id:  nodes[ 0 ] , 
7503+ 				blinding_point:  ln_test_utils:: pubkey( 42 ) , 
7504+ 				blinded_hops:  vec![ 
7505+ 					BlindedHop  {  blinded_node_id:  ln_test_utils:: pubkey( 42  as  u8 ) ,  encrypted_payload:  Vec :: new( )  } , 
7506+ 					BlindedHop  {  blinded_node_id:  ln_test_utils:: pubkey( 42  as  u8 ) ,  encrypted_payload:  Vec :: new( )  } 
7507+ 				] , 
7508+ 			} ) 
7509+ 		] ; 
7510+ 		let  bolt12_features:  Bolt12InvoiceFeatures  = channelmanager:: provided_invoice_features ( & config) . to_context ( ) ; 
7511+ 		let  payment_params = PaymentParameters :: blinded ( blinded_hints. clone ( ) ) 
7512+ 			. with_bolt12_features ( bolt12_features. clone ( ) ) . unwrap ( ) ; 
7513+ 		let  route_params = RouteParameters :: from_payment_params_and_value ( 
7514+ 			payment_params,  amt_msat) ; 
7515+ 		let  netgraph = network_graph. read_only ( ) ; 
7516+ 
7517+ 		if  let  Err ( LightningError  {  err,  .. } )  = get_route ( 
7518+ 			& our_id,  & route_params,  & netgraph,  Some ( & first_hops. iter ( ) . collect :: < Vec < _ > > ( ) ) , 
7519+ 			Arc :: clone ( & logger) ,  & scorer,  & ProbabilisticScoringFeeParameters :: default ( ) , 
7520+ 			& random_seed_bytes
7521+ 		)  { 
7522+ 			assert_eq ! ( err,  "Failed to find a path to the given destination" ) ; 
7523+ 		}  else  {  panic ! ( )  } 
7524+ 	} 
74527525} 
74537526
74547527#[ cfg( all( any( test,  ldk_bench) ,  not( feature = "no-std" ) ) ) ]  
0 commit comments