@@ -2204,11 +2204,15 @@ where L::Target: Logger {
22042204						. map_or ( None ,  |inc| inc. checked_add ( aggregate_next_hops_fee_msat) ) ; 
22052205					aggregate_next_hops_fee_msat = if  let  Some ( val)  = hops_fee {  val }  else  {  break ;  } ; 
22062206
2207- 					let  hop_htlc_minimum_msat = candidate. htlc_minimum_msat ( ) ; 
2208- 					let  hop_htlc_minimum_msat_inc = if  let  Some ( val)  = compute_fees ( aggregate_next_hops_path_htlc_minimum_msat,  hop. fees )  {  val }  else  {  break ;  } ; 
2209- 					let  hops_path_htlc_minimum = aggregate_next_hops_path_htlc_minimum_msat
2210- 						. checked_add ( hop_htlc_minimum_msat_inc) ; 
2211- 					aggregate_next_hops_path_htlc_minimum_msat = if  let  Some ( val)  = hops_path_htlc_minimum {  cmp:: max ( hop_htlc_minimum_msat,  val)  }  else  {  break ;  } ; 
2207+ 					// The next channel will need to relay this channel's min_htlc *plus* the fees taken by 
2208+ 					// this route hint's source node to forward said min over this channel. 
2209+ 					aggregate_next_hops_path_htlc_minimum_msat = { 
2210+ 						let  curr_htlc_min = cmp:: max ( 
2211+ 							candidate. htlc_minimum_msat ( ) ,  aggregate_next_hops_path_htlc_minimum_msat
2212+ 						) ; 
2213+ 						let  curr_htlc_min_fee = if  let  Some ( val)  = compute_fees ( curr_htlc_min,  hop. fees )  {  val }  else  {  break  } ; 
2214+ 						if  let  Some ( min)  = curr_htlc_min. checked_add ( curr_htlc_min_fee)  {  min }  else  {  break  } 
2215+ 					} ; 
22122216
22132217					if  idx == route. 0 . len ( )  - 1  { 
22142218						// The last hop in this iterator is the first hop in 
@@ -7280,6 +7284,118 @@ mod tests {
72807284			assert_eq ! ( err,  "Failed to find a path to the given destination" ) ; 
72817285		}  else  {  panic ! ( )  } 
72827286	} 
7287+ 
7288+ 	#[ test]  
7289+ 	fn  min_htlc_overpay_violates_max_htlc ( )  { 
7290+ 		// Test that if overpaying to meet a later hop's min_htlc and causes us to violate an earlier 
7291+ 		// hop's max_htlc, we don't consider that candidate hop valid. Previously we would add this hop 
7292+ 		// to `targets` and build an invalid path with it, and subsquently hit a debug panic asserting 
7293+ 		// that the used liquidity for a hop was less than its available liquidity limit. 
7294+ 		let  secp_ctx = Secp256k1 :: new ( ) ; 
7295+ 		let  logger = Arc :: new ( ln_test_utils:: TestLogger :: new ( ) ) ; 
7296+ 		let  network_graph = Arc :: new ( NetworkGraph :: new ( Network :: Testnet ,  Arc :: clone ( & logger) ) ) ; 
7297+ 		let  scorer = ln_test_utils:: TestScorer :: new ( ) ; 
7298+ 		let  keys_manager = ln_test_utils:: TestKeysInterface :: new ( & [ 0u8 ;  32 ] ,  Network :: Testnet ) ; 
7299+ 		let  random_seed_bytes = keys_manager. get_secure_random_bytes ( ) ; 
7300+ 		let  config = UserConfig :: default ( ) ; 
7301+ 
7302+ 		// Values are taken from the fuzz input that uncovered this panic. 
7303+ 		let  amt_msat = 7_4009_8048 ; 
7304+ 		let  ( _,  our_id,  _,  nodes)  = get_nodes ( & secp_ctx) ; 
7305+ 		let  first_hop_outbound_capacity = 2_7345_2000 ; 
7306+ 		let  first_hops = vec ! [ get_channel_details( 
7307+ 			Some ( 200 ) ,  nodes[ 0 ] ,  channelmanager:: provided_init_features( & config) , 
7308+ 			first_hop_outbound_capacity
7309+ 		) ] ; 
7310+ 
7311+ 		let  base_fee = 1_6778_3453 ; 
7312+ 		let  htlc_min = 2_5165_8240 ; 
7313+ 		let  payment_params =  { 
7314+ 			let  route_hint = RouteHint ( vec ! [ RouteHintHop  { 
7315+ 				src_node_id:  nodes[ 0 ] , 
7316+ 				short_channel_id:  42 , 
7317+ 				fees:  RoutingFees  { 
7318+ 					base_msat:  base_fee, 
7319+ 					proportional_millionths:  0 , 
7320+ 				} , 
7321+ 				cltv_expiry_delta:  10 , 
7322+ 				htlc_minimum_msat:  Some ( htlc_min) , 
7323+ 				htlc_maximum_msat:  None , 
7324+ 			} ] ) ; 
7325+ 
7326+ 			PaymentParameters :: from_node_id ( nodes[ 1 ] ,  42 ) 
7327+ 				. with_route_hints ( vec ! [ route_hint] ) . unwrap ( ) 
7328+ 				. with_bolt11_features ( channelmanager:: provided_invoice_features ( & config) ) . unwrap ( ) 
7329+ 		} ; 
7330+ 
7331+ 		let  netgraph = network_graph. read_only ( ) ; 
7332+ 		let  route_params = RouteParameters :: from_payment_params_and_value ( 
7333+ 			payment_params,  amt_msat) ; 
7334+ 		if  let  Err ( LightningError  {  err,  .. } )  = get_route ( 
7335+ 			& our_id,  & route_params,  & netgraph,  Some ( & first_hops. iter ( ) . collect :: < Vec < _ > > ( ) ) , 
7336+ 			Arc :: clone ( & logger) ,  & scorer,  & ( ) ,  & random_seed_bytes
7337+ 		)  { 
7338+ 			assert_eq ! ( err,  "Failed to find a path to the given destination" ) ; 
7339+ 		}  else  {  panic ! ( )  } 
7340+ 	} 
7341+ 
7342+ 	#[ test]  
7343+ 	fn  previously_used_liquidity_violates_max_htlc ( )  { 
7344+ 		// Test that if a candidate first_hop<>route_hint_src_node channel does not have enough 
7345+ 		// contribution amount to cover the next hop's min_htlc plus fees, we will not consider that 
7346+ 		// candidate. In this case, the candidate does not have enough due to a previous path taking up 
7347+ 		// some of its liquidity. Previously we would construct an invalid path and hit a debug panic 
7348+ 		// asserting that the used liquidity for a hop was less than its available liquidity limit. 
7349+ 		let  secp_ctx = Secp256k1 :: new ( ) ; 
7350+ 		let  logger = Arc :: new ( ln_test_utils:: TestLogger :: new ( ) ) ; 
7351+ 		let  network_graph = Arc :: new ( NetworkGraph :: new ( Network :: Testnet ,  Arc :: clone ( & logger) ) ) ; 
7352+ 		let  scorer = ln_test_utils:: TestScorer :: new ( ) ; 
7353+ 		let  keys_manager = ln_test_utils:: TestKeysInterface :: new ( & [ 0u8 ;  32 ] ,  Network :: Testnet ) ; 
7354+ 		let  random_seed_bytes = keys_manager. get_secure_random_bytes ( ) ; 
7355+ 		let  config = UserConfig :: default ( ) ; 
7356+ 
7357+ 		// Values are taken from the fuzz input that uncovered this panic. 
7358+ 		let  amt_msat = 52_4288 ; 
7359+ 		let  ( _,  our_id,  _,  nodes)  = get_nodes ( & secp_ctx) ; 
7360+ 		let  first_hops = vec ! [ get_channel_details( 
7361+ 			Some ( 161 ) ,  nodes[ 0 ] ,  channelmanager:: provided_init_features( & config) ,  486_4000 
7362+ 		) ,  get_channel_details( 
7363+ 			Some ( 122 ) ,  nodes[ 0 ] ,  channelmanager:: provided_init_features( & config) ,  179_5000 
7364+ 		) ] ; 
7365+ 
7366+ 		let  base_fees = [ 0 ,  425_9840 ,  0 ,  0 ] ; 
7367+ 		let  htlc_mins = [ 1_4392 ,  19_7401 ,  1027 ,  6_5535 ] ; 
7368+ 		let  payment_params = { 
7369+ 			let  mut  route_hints = Vec :: new ( ) ; 
7370+ 			for  ( idx,  ( base_fee,  htlc_min) )  in  base_fees. iter ( ) . zip ( htlc_mins. iter ( ) ) . enumerate ( )  { 
7371+ 				route_hints. push ( RouteHint ( vec ! [ RouteHintHop  { 
7372+ 					src_node_id:  nodes[ 0 ] , 
7373+ 					short_channel_id:  42  + idx as  u64 , 
7374+ 					fees:  RoutingFees  { 
7375+ 						base_msat:  * base_fee, 
7376+ 						proportional_millionths:  0 , 
7377+ 					} , 
7378+ 					cltv_expiry_delta:  10 , 
7379+ 					htlc_minimum_msat:  Some ( * htlc_min) , 
7380+ 					htlc_maximum_msat:  Some ( htlc_min *  100 ) , 
7381+ 				} ] ) ) ; 
7382+ 			} 
7383+ 			PaymentParameters :: from_node_id ( nodes[ 1 ] ,  42 ) 
7384+ 				. with_route_hints ( route_hints) . unwrap ( ) 
7385+ 				. with_bolt11_features ( channelmanager:: provided_invoice_features ( & config) ) . unwrap ( ) 
7386+ 		} ; 
7387+ 
7388+ 		let  netgraph = network_graph. read_only ( ) ; 
7389+ 		let  route_params = RouteParameters :: from_payment_params_and_value ( 
7390+ 			payment_params,  amt_msat) ; 
7391+ 
7392+ 		let  route = get_route ( 
7393+ 			& our_id,  & route_params,  & netgraph,  Some ( & first_hops. iter ( ) . collect :: < Vec < _ > > ( ) ) , 
7394+ 			Arc :: clone ( & logger) ,  & scorer,  & ( ) ,  & random_seed_bytes
7395+ 		) . unwrap ( ) ; 
7396+ 		assert_eq ! ( route. paths. len( ) ,  1 ) ; 
7397+ 		assert_eq ! ( route. get_total_amount( ) ,  amt_msat) ; 
7398+ 	} 
72837399} 
72847400
72857401#[ cfg( all( any( test,  ldk_bench) ,  not( feature = "no-std" ) ) ) ]  
0 commit comments