@@ -1265,7 +1265,11 @@ impl<'a> PaymentPath<'a> {
12651265	// Note that this function is not aware of the available_liquidity limit, and thus does not 
12661266	// support increasing the value being transferred beyond what was selected during the initial 
12671267	// routing passes. 
1268- 	fn  update_value_and_recompute_fees ( & mut  self ,  value_msat :  u64 )  { 
1268+ 	// 
1269+ 	// Returns the amount that this path contributes to the total payment value, which may be greater 
1270+ 	// than `value_msat` if we had to overpay to meet the final node's `htlc_minimum_msat`. 
1271+ 	fn  update_value_and_recompute_fees ( & mut  self ,  value_msat :  u64 )  -> u64  { 
1272+ 		let  mut  extra_contribution_msat = 0 ; 
12691273		let  mut  total_fee_paid_msat = 0  as  u64 ; 
12701274		for  i in  ( 0 ..self . hops . len ( ) ) . rev ( )  { 
12711275			let  last_hop = i == self . hops . len ( )  - 1 ; 
@@ -1280,6 +1284,7 @@ impl<'a> PaymentPath<'a> {
12801284
12811285			let  cur_hop = & mut  self . hops . get_mut ( i) . unwrap ( ) . 0 ; 
12821286			cur_hop. next_hops_fee_msat  = total_fee_paid_msat; 
1287+ 			cur_hop. path_penalty_msat  += extra_contribution_msat; 
12831288			// Overpay in fees if we can't save these funds due to htlc_minimum_msat. 
12841289			// We try to account for htlc_minimum_msat in scoring (add_entry!), so that nodes don't 
12851290			// set it too high just to maliciously take more fees by exploiting this 
@@ -1295,8 +1300,15 @@ impl<'a> PaymentPath<'a> {
12951300				// Also, this can't be exploited more heavily than *announce a free path and fail 
12961301				// all payments*. 
12971302				cur_hop_transferred_amount_msat += extra_fees_msat; 
1298- 				total_fee_paid_msat += extra_fees_msat; 
1299- 				cur_hop_fees_msat += extra_fees_msat; 
1303+ 
1304+ 				// We remember and return the extra fees on the final hop to allow accounting for 
1305+ 				// them in the path's value contribution. 
1306+ 				if  last_hop { 
1307+ 					extra_contribution_msat = extra_fees_msat; 
1308+ 				}  else  { 
1309+ 					total_fee_paid_msat += extra_fees_msat; 
1310+ 					cur_hop_fees_msat += extra_fees_msat; 
1311+ 				} 
13001312			} 
13011313
13021314			if  last_hop { 
@@ -1324,6 +1336,7 @@ impl<'a> PaymentPath<'a> {
13241336				} 
13251337			} 
13261338		} 
1339+ 		value_msat + extra_contribution_msat
13271340	} 
13281341} 
13291342
@@ -2330,8 +2343,8 @@ where L::Target: Logger {
23302343				// recompute the fees again, so that if that's the case, we match the currently 
23312344				// underpaid htlc_minimum_msat with fees. 
23322345				debug_assert_eq ! ( payment_path. get_value_msat( ) ,  value_contribution_msat) ; 
2333- 				value_contribution_msat  = cmp:: min ( value_contribution_msat,  final_value_msat) ; 
2334- 				payment_path. update_value_and_recompute_fees ( value_contribution_msat ) ; 
2346+ 				let  desired_value_contribution  = cmp:: min ( value_contribution_msat,  final_value_msat) ; 
2347+ 				value_contribution_msat =  payment_path. update_value_and_recompute_fees ( desired_value_contribution ) ; 
23352348
23362349				// Since a path allows to transfer as much value as 
23372350				// the smallest channel it has ("bottleneck"), we should recompute 
@@ -7521,6 +7534,67 @@ mod tests {
75217534			assert_eq ! ( err,  "Failed to find a path to the given destination" ) ; 
75227535		}  else  {  panic ! ( )  } 
75237536	} 
7537+ 
7538+ 	#[ test]  
7539+ 	fn  path_contribution_includes_min_htlc_overpay ( )  { 
7540+ 		// Previously, the fuzzer hit a debug panic because we wouldn't include the amount overpaid to 
7541+ 		// meet a last hop's min_htlc in the total collected paths value. We now include this value and 
7542+ 		// also penalize hops along the overpaying path to ensure that it gets deprioritized in path 
7543+ 		// selection, both tested here. 
7544+ 		let  secp_ctx = Secp256k1 :: new ( ) ; 
7545+ 		let  logger = Arc :: new ( ln_test_utils:: TestLogger :: new ( ) ) ; 
7546+ 		let  network_graph = Arc :: new ( NetworkGraph :: new ( Network :: Testnet ,  Arc :: clone ( & logger) ) ) ; 
7547+ 		let  scorer = ProbabilisticScorer :: new ( ProbabilisticScoringDecayParameters :: default ( ) ,  network_graph. clone ( ) ,  logger. clone ( ) ) ; 
7548+ 		let  keys_manager = ln_test_utils:: TestKeysInterface :: new ( & [ 0u8 ;  32 ] ,  Network :: Testnet ) ; 
7549+ 		let  random_seed_bytes = keys_manager. get_secure_random_bytes ( ) ; 
7550+ 		let  config = UserConfig :: default ( ) ; 
7551+ 
7552+ 		// Values are taken from the fuzz input that uncovered this panic. 
7553+ 		let  amt_msat = 562_0000 ; 
7554+ 		let  ( _,  our_id,  _,  nodes)  = get_nodes ( & secp_ctx) ; 
7555+ 		let  first_hops = vec ! [ 
7556+ 			get_channel_details( 
7557+ 				Some ( 83 ) ,  nodes[ 0 ] ,  channelmanager:: provided_init_features( & config) ,  2199_0000 , 
7558+ 			) , 
7559+ 		] ; 
7560+ 
7561+ 		let  htlc_mins = [ 49_0000 ,  1125_0000 ] ; 
7562+ 		let  payment_params = { 
7563+ 			let  blinded_path = BlindedPath  { 
7564+ 				introduction_node_id :  nodes[ 0 ] , 
7565+ 				blinding_point :  ln_test_utils:: pubkey ( 42 ) , 
7566+ 				blinded_hops :  vec ! [ 
7567+ 					BlindedHop  {  blinded_node_id:  ln_test_utils:: pubkey( 42  as  u8 ) ,  encrypted_payload:  Vec :: new( )  } , 
7568+ 					BlindedHop  {  blinded_node_id:  ln_test_utils:: pubkey( 42  as  u8 ) ,  encrypted_payload:  Vec :: new( )  } 
7569+ 				] , 
7570+ 			} ; 
7571+ 			let  mut  blinded_hints = Vec :: new ( ) ; 
7572+ 			for  htlc_min in  htlc_mins. iter ( )  { 
7573+ 				blinded_hints. push ( ( BlindedPayInfo  { 
7574+ 					fee_base_msat :  0 , 
7575+ 					fee_proportional_millionths :  0 , 
7576+ 					htlc_minimum_msat :  * htlc_min, 
7577+ 					htlc_maximum_msat :  * htlc_min *  100 , 
7578+ 					cltv_expiry_delta :  10 , 
7579+ 					features :  BlindedHopFeatures :: empty ( ) , 
7580+ 				} ,  blinded_path. clone ( ) ) ) ; 
7581+ 			} 
7582+ 			let  bolt12_features:  Bolt12InvoiceFeatures  = channelmanager:: provided_invoice_features ( & config) . to_context ( ) ; 
7583+ 			PaymentParameters :: blinded ( blinded_hints. clone ( ) ) 
7584+ 				. with_bolt12_features ( bolt12_features. clone ( ) ) . unwrap ( ) 
7585+ 		} ; 
7586+ 
7587+ 		let  netgraph = network_graph. read_only ( ) ; 
7588+ 		let  route_params = RouteParameters :: from_payment_params_and_value ( 
7589+ 			payment_params,  amt_msat) ; 
7590+ 		let  route = get_route ( 
7591+ 			& our_id,  & route_params,  & netgraph,  Some ( & first_hops. iter ( ) . collect :: < Vec < _ > > ( ) ) , 
7592+ 			Arc :: clone ( & logger) ,  & scorer,  & ProbabilisticScoringFeeParameters :: default ( ) , 
7593+ 			& random_seed_bytes
7594+ 		) . unwrap ( ) ; 
7595+ 		assert_eq ! ( route. paths. len( ) ,  1 ) ; 
7596+ 		assert_eq ! ( route. get_total_amount( ) ,  amt_msat) ; 
7597+ 	} 
75247598} 
75257599
75267600#[ cfg( all( any( test,  ldk_bench) ,  not( feature = "no-std" ) ) ) ]  
0 commit comments