@@ -194,6 +194,10 @@ struct PathBuildingHop {
194
194
/// we don't fall below the minimum. Should not be updated manually and
195
195
/// generally should not be accessed.
196
196
htlc_minimum_msat : u64 ,
197
+ /// Keeps track of sats we overpaid to hit htlc_minimum_msat on this hop. It is useful for
198
+ /// routing, so that we know how much a hop has to actually pay in fees: the total fee is
199
+ /// next_hop_use_fee_msat + overpaid_fee_msat.
200
+ overpaid_fee_msat : u64 ,
197
201
}
198
202
199
203
impl PathBuildingHop {
@@ -238,8 +242,7 @@ impl PaymentPath {
238
242
239
243
// If an amount transferred by the path is updated, the fees should be adjusted.
240
244
// Any other way to change fees may result in an inconsistency.
241
- // Also, it's only safe to reduce the value, to not violate limits like
242
- // htlc_minimum_msat.
245
+ // Also, it's only safe to reduce the value, to not violate channel upper bounds.
243
246
fn update_value_and_recompute_fees ( & mut self , value_msat : u64 ) {
244
247
if value_msat == self . hops . last ( ) . unwrap ( ) . route_hop . fee_msat {
245
248
// Nothing to change.
@@ -248,9 +251,10 @@ impl PaymentPath {
248
251
assert ! ( value_msat < self . hops. last( ) . unwrap( ) . route_hop. fee_msat) ;
249
252
250
253
let mut total_fee_paid_msat = 0 as u64 ;
251
- // TODO: while reducing the transferred value on hop N, we could cause some N+M hop to fall
252
- // below its htlc_minimum_msat in terms of amount being transferred that. We should handle
253
- // it here similarly to how we handle it in add_entry!.
254
+ // While reducing the transferred value on hop N, we could cause some N+M hop to fall
255
+ // below its htlc_minimum_msat in terms of amount being transferred that. We handle it here
256
+ // similarly to add_entry! by tracking incl_fee_next_hops_htlc_minimum_msat.
257
+ let mut incl_fee_next_hops_htlc_minimum_msat = 0 ;
254
258
for i in ( 0 ..self . hops . len ( ) ) . rev ( ) {
255
259
let last_hop = i == self . hops . len ( ) - 1 ;
256
260
@@ -269,7 +273,8 @@ impl PaymentPath {
269
273
// set it too high just to maliciously take more fees by exploiting this
270
274
// match htlc_minimum_msat logic.
271
275
let mut cur_hop_transferred_amount_msat = total_fee_paid_msat + value_msat;
272
- if let Some ( extra_fees_msat) = cur_hop. htlc_minimum_msat . checked_sub ( cur_hop_transferred_amount_msat) {
276
+ let effective_htlc_minimum_msat = cmp:: max ( incl_fee_next_hops_htlc_minimum_msat, cur_hop. htlc_minimum_msat ) ;
277
+ if let Some ( extra_fees_msat) = effective_htlc_minimum_msat. checked_sub ( cur_hop_transferred_amount_msat) {
273
278
cur_hop_transferred_amount_msat += extra_fees_msat;
274
279
total_fee_paid_msat += extra_fees_msat;
275
280
cur_hop_fees_msat += extra_fees_msat;
@@ -299,6 +304,12 @@ impl PaymentPath {
299
304
unreachable ! ( ) ;
300
305
}
301
306
}
307
+
308
+ // Recompute whole-path htlc_minimum_msat, so that previous hops won't underpay.
309
+ incl_fee_next_hops_htlc_minimum_msat = match compute_fees ( incl_fee_next_hops_htlc_minimum_msat, cur_hop. channel_fees ) {
310
+ Some ( fee_msat) => cmp:: max ( fee_msat, cur_hop. htlc_minimum_msat ) ,
311
+ None => unreachable ! ( ) , // Should have been handled in add_entry.
312
+ } ;
302
313
}
303
314
}
304
315
}
@@ -496,21 +507,21 @@ pub fn get_route<L: Deref>(our_node_id: &PublicKey, network: &NetworkGraph, paye
496
507
497
508
let value_contribution_msat = cmp:: min( available_value_contribution_msat, $next_hops_value_contribution) ;
498
509
// Includes paying fees for the use of the following channels.
499
- let amount_to_transfer_over_msat: u64 = match value_contribution_msat. checked_add( $next_hops_fee_msat) {
510
+ let mut amount_to_transfer_over_msat: u64 = match value_contribution_msat. checked_add( $next_hops_fee_msat) {
500
511
Some ( result) => result,
501
512
// Can't overflow due to how the values were computed right above.
502
513
None => unreachable!( ) ,
503
514
} ;
504
- let over_path_minimum_msat = amount_to_transfer_over_msat >= $directional_info . htlc_minimum_msat &&
505
- amount_to_transfer_over_msat >= $incl_fee_next_hops_htlc_minimum_msat ;
506
-
507
- // If HTLC minimum is larger than the amount we're going to transfer, we shouldn't
508
- // bother considering this channel.
509
- // Since we're choosing amount_to_transfer_over_msat as maximum possible, it can
510
- // be only reduced later (not increased), so this channel should just be skipped
511
- // as not sufficient.
512
- // TODO: Explore simply adding fee to hit htlc_minimum_msat
513
- if contributes_sufficient_value && over_path_minimum_msat {
515
+
516
+ // If HTLC minimum is larger than the amount we're going to transfer, we shouldn
517
+ // transfer that value instead, thus overpaying fees.
518
+ let effective_htlc_minimum_msat = cmp :: max ( $directional_info . htlc_minimum_msat , $incl_fee_next_hops_htlc_minimum_msat ) ;
519
+ let overpaid_fee_msat = match effective_htlc_minimum_msat . checked_sub ( amount_to_transfer_over_msat ) {
520
+ Some ( fee_msat ) => fee_msat ,
521
+ None => 0 ,
522
+ } ;
523
+ amount_to_transfer_over_msat += overpaid_fee_msat ;
524
+ if contributes_sufficient_value {
514
525
let hm_entry = dist. entry( & $src_node_id) ;
515
526
let old_entry = hm_entry. or_insert_with( || {
516
527
// If there was previously no known way to access
@@ -542,6 +553,7 @@ pub fn get_route<L: Deref>(our_node_id: &PublicKey, network: &NetworkGraph, paye
542
553
hop_use_fee_msat: u64 :: max_value( ) ,
543
554
total_fee_msat: u64 :: max_value( ) ,
544
555
htlc_minimum_msat: $directional_info. htlc_minimum_msat,
556
+ overpaid_fee_msat
545
557
}
546
558
} ) ;
547
559
@@ -588,28 +600,26 @@ pub fn get_route<L: Deref>(our_node_id: &PublicKey, network: &NetworkGraph, paye
588
600
} ,
589
601
} ;
590
602
591
- // Update the way of reaching $src_node_id with the given $chan_id (from $dest_node_id),
592
- // if this way is cheaper than the already known
603
+ // Update the way of reaching $src_node_id with the given $chan_id (from
604
+ // $dest_node_id), if this way is cheaper than the already known
593
605
// (considering the cost to "reach" this channel from the route destination,
594
- // the cost of using this channel,
606
+ // the cost of using this channel (with possible overpayment for htlc min)
595
607
// and the cost of routing to the source node of this channel).
596
- // Also, consider that htlc_minimum_msat_difference, because we might end up
597
- // paying it. Consider the following exploit:
598
- // we use 2 paths to transfer 1.5 BTC. One of them is 0-fee normal 1 BTC path,
599
- // and for the other one we picked a 1sat-fee path with htlc_minimum_msat of
600
- // 1 BTC. Now, since the latter is more expensive, we gonna try to cut it
601
- // by 0.5 BTC, but then match htlc_minimum_msat by paying a fee of 0.5 BTC
602
- // to this channel.
608
+ // Consider the following exploit: we use 2 paths to transfer 1.5 BTC.
609
+ // One of them is 0-fee normal 1 BTC path, and for the other one we picked a
610
+ // 1sat-fee path with htlc_minimum_msat of 1 BTC. Now, since the latter is
611
+ // more expensive, we gonna try to cut it by 0.5 BTC, but then match
612
+ // htlc_minimum_msat by paying a fee of 0.5 BTC to this channel.
603
613
// TODO: this scoring could be smarter (e.g. 0.5*htlc_minimum_msat here).
604
614
let mut old_cost = old_entry. total_fee_msat;
605
- if let Some ( increased_old_cost) = old_cost. checked_add( old_entry. htlc_minimum_msat ) {
615
+ if let Some ( increased_old_cost) = old_cost. checked_add( old_entry. overpaid_fee_msat ) {
606
616
old_cost = increased_old_cost;
607
617
} else {
608
618
old_cost = u64 :: max_value( ) ;
609
619
}
610
620
611
621
let mut new_cost = total_fee_msat;
612
- if let Some ( increased_new_cost) = new_cost. checked_add( $directional_info . htlc_minimum_msat ) {
622
+ if let Some ( increased_new_cost) = new_cost. checked_add( overpaid_fee_msat ) {
613
623
new_cost = increased_new_cost;
614
624
} else {
615
625
new_cost = u64 :: max_value( ) ;
@@ -629,9 +639,19 @@ pub fn get_route<L: Deref>(our_node_id: &PublicKey, network: &NetworkGraph, paye
629
639
cltv_expiry_delta: $directional_info. cltv_expiry_delta as u32 ,
630
640
} ;
631
641
old_entry. channel_fees = $directional_info. fees;
632
- // It's probably fine to replace the old entry, because the new one
633
- // passed the htlc_minimum-related checks above.
634
- old_entry. htlc_minimum_msat = $directional_info. htlc_minimum_msat;
642
+ old_entry. htlc_minimum_msat = old_entry. htlc_minimum_msat;
643
+ // These info about an entry is only implicitly associated with
644
+ // the path which will be taken further: for example, if there are
645
+ // two options to get here from the payee side, we will store the
646
+ // cheapest, although it might have higher overpayment fee on this
647
+ // hop to match htlc_minimum_msat. Then, if the other path is associated
648
+ // with this entry for some reason, it might not be able to pay htlc_min
649
+ // anymore. To avoid that, here we take the worst-case values.
650
+ // It can't really be exploited because it's all about getting to one
651
+ // node, which should not have too-high fees in the first place,
652
+ // because otherwise it can be "punished" by this selection even if
653
+ // only one channel there is too expensive.
654
+ old_entry. overpaid_fee_msat = cmp:: max( overpaid_fee_msat, old_entry. overpaid_fee_msat) ;
635
655
}
636
656
}
637
657
}
@@ -817,11 +837,11 @@ pub fn get_route<L: Deref>(our_node_id: &PublicKey, network: &NetworkGraph, paye
817
837
// We "propagate" the fees one hop backward (topologically) here,
818
838
// so that fees paid for a HTLC forwarding on the current channel are
819
839
// associated with the previous channel (where they will be subtracted).
820
- ordered_hops. last_mut ( ) . unwrap ( ) . route_hop . fee_msat = new_entry. hop_use_fee_msat ;
840
+ ordered_hops. last_mut ( ) . unwrap ( ) . route_hop . fee_msat = new_entry. hop_use_fee_msat + ordered_hops . last_mut ( ) . unwrap ( ) . overpaid_fee_msat ;
821
841
ordered_hops. last_mut ( ) . unwrap ( ) . route_hop . cltv_expiry_delta = new_entry. route_hop . cltv_expiry_delta ;
822
842
ordered_hops. push ( new_entry. clone ( ) ) ;
823
843
}
824
- ordered_hops. last_mut ( ) . unwrap ( ) . route_hop . fee_msat = value_contribution_msat;
844
+ ordered_hops. last_mut ( ) . unwrap ( ) . route_hop . fee_msat = value_contribution_msat + ordered_hops . last_mut ( ) . unwrap ( ) . overpaid_fee_msat ;
825
845
ordered_hops. last_mut ( ) . unwrap ( ) . hop_use_fee_msat = 0 ;
826
846
ordered_hops. last_mut ( ) . unwrap ( ) . route_hop . cltv_expiry_delta = final_cltv;
827
847
@@ -1535,7 +1555,6 @@ mod tests {
1535
1555
} ) ;
1536
1556
1537
1557
// Check against amount_to_transfer_over_msat.
1538
- // We will be transferring 300_000_000 msat.
1539
1558
// Set minimal HTLC of 200_000_000 msat.
1540
1559
update_channel ( & net_graph_msg_handler, & secp_ctx, & our_privkey, UnsignedChannelUpdate {
1541
1560
chain_hash : genesis_block ( Network :: Testnet ) . header . block_hash ( ) ,
@@ -1565,10 +1584,9 @@ mod tests {
1565
1584
excess_data : Vec :: new ( )
1566
1585
} ) ;
1567
1586
1568
- // Not possible to send 299_999_999: we first allocate 200_000_000 for the channel#12,
1569
- // and then the amount_to_transfer_over_msat over channel#2 is less than our limit.
1570
- if let Err ( LightningError { err, action : ErrorAction :: IgnoreError } ) = get_route ( & our_id, & net_graph_msg_handler. network_graph . read ( ) . unwrap ( ) , & nodes[ 2 ] , None , & Vec :: new ( ) , 199_999_999 , 42 , Arc :: clone ( & logger) ) {
1571
- assert_eq ! ( err, "Failed to find a path to the given destination" ) ;
1587
+ // Not possible to send 299_999_999.
1588
+ if let Err ( LightningError { err, action : ErrorAction :: IgnoreError } ) = get_route ( & our_id, & net_graph_msg_handler. network_graph . read ( ) . unwrap ( ) , & nodes[ 2 ] , None , & Vec :: new ( ) , 299_999_999 , 42 , Arc :: clone ( & logger) ) {
1589
+ assert_eq ! ( err, "Failed to find a sufficient route to the given destination" ) ;
1572
1590
} else { panic ! ( ) ; }
1573
1591
1574
1592
// Lift the restriction on the first hop.
0 commit comments