@@ -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
@@ -7522,6 +7535,67 @@ mod tests {
75227535 assert_eq ! ( err, "Failed to find a path to the given destination" ) ;
75237536 } else { panic ! ( ) }
75247537 }
7538+
7539+ #[ test]
7540+ fn path_contribution_includes_min_htlc_overpay ( ) {
7541+ // Previously, the fuzzer hit a debug panic because we wouldn't include the amount overpaid to
7542+ // meet a last hop's min_htlc in the total collected paths value. We now include this value and
7543+ // also penalize hops along the overpaying path to ensure that it gets deprioritized in path
7544+ // selection, both tested here.
7545+ let secp_ctx = Secp256k1 :: new ( ) ;
7546+ let logger = Arc :: new ( ln_test_utils:: TestLogger :: new ( ) ) ;
7547+ let network_graph = Arc :: new ( NetworkGraph :: new ( Network :: Testnet , Arc :: clone ( & logger) ) ) ;
7548+ let scorer = ProbabilisticScorer :: new ( ProbabilisticScoringDecayParameters :: default ( ) , network_graph. clone ( ) , logger. clone ( ) ) ;
7549+ let keys_manager = ln_test_utils:: TestKeysInterface :: new ( & [ 0u8 ; 32 ] , Network :: Testnet ) ;
7550+ let random_seed_bytes = keys_manager. get_secure_random_bytes ( ) ;
7551+ let config = UserConfig :: default ( ) ;
7552+
7553+ // Values are taken from the fuzz input that uncovered this panic.
7554+ let amt_msat = 562_0000 ;
7555+ let ( _, our_id, _, nodes) = get_nodes ( & secp_ctx) ;
7556+ let first_hops = vec ! [
7557+ get_channel_details(
7558+ Some ( 83 ) , nodes[ 0 ] , channelmanager:: provided_init_features( & config) , 2199_0000 ,
7559+ ) ,
7560+ ] ;
7561+
7562+ let htlc_mins = [ 49_0000 , 1125_0000 ] ;
7563+ let payment_params = {
7564+ let blinded_path = BlindedPath {
7565+ introduction_node_id : nodes[ 0 ] ,
7566+ blinding_point : ln_test_utils:: pubkey ( 42 ) ,
7567+ blinded_hops : vec ! [
7568+ BlindedHop { blinded_node_id: ln_test_utils:: pubkey( 42 as u8 ) , encrypted_payload: Vec :: new( ) } ,
7569+ BlindedHop { blinded_node_id: ln_test_utils:: pubkey( 42 as u8 ) , encrypted_payload: Vec :: new( ) }
7570+ ] ,
7571+ } ;
7572+ let mut blinded_hints = Vec :: new ( ) ;
7573+ for htlc_min in htlc_mins {
7574+ blinded_hints. push ( ( BlindedPayInfo {
7575+ fee_base_msat : 0 ,
7576+ fee_proportional_millionths : 0 ,
7577+ htlc_minimum_msat : htlc_min,
7578+ htlc_maximum_msat : htlc_min * 100 ,
7579+ cltv_expiry_delta : 10 ,
7580+ features : BlindedHopFeatures :: empty ( ) ,
7581+ } , blinded_path. clone ( ) ) ) ;
7582+ }
7583+ let bolt12_features: Bolt12InvoiceFeatures = channelmanager:: provided_invoice_features ( & config) . to_context ( ) ;
7584+ PaymentParameters :: blinded ( blinded_hints. clone ( ) )
7585+ . with_bolt12_features ( bolt12_features. clone ( ) ) . unwrap ( )
7586+ } ;
7587+
7588+ let netgraph = network_graph. read_only ( ) ;
7589+ let route_params = RouteParameters :: from_payment_params_and_value (
7590+ payment_params, amt_msat) ;
7591+ let route = get_route (
7592+ & our_id, & route_params, & netgraph, Some ( & first_hops. iter ( ) . collect :: < Vec < _ > > ( ) ) ,
7593+ Arc :: clone ( & logger) , & scorer, & ProbabilisticScoringFeeParameters :: default ( ) ,
7594+ & random_seed_bytes
7595+ ) . unwrap ( ) ;
7596+ assert_eq ! ( route. paths. len( ) , 1 ) ;
7597+ assert_eq ! ( route. get_total_amount( ) , amt_msat) ;
7598+ }
75257599}
75267600
75277601#[ cfg( all( any( test, ldk_bench) , not( feature = "no-std" ) ) ) ]
0 commit comments