@@ -186,6 +186,10 @@ struct PathBuildingHop {
186
186
/// an estimated cost of reaching this hop.
187
187
/// Might get stale when fees are recomputed. Primarily for internal use.
188
188
total_fee_msat : u64 ,
189
+ /// This is useful for update_value_and_recompute_fees to make sure
190
+ /// we don't fall below the minimum. Should not be updated manually and
191
+ /// generally should not be accessed.
192
+ htlc_minimum_msat : u64 ,
189
193
}
190
194
191
195
impl PathBuildingHop {
@@ -230,14 +234,52 @@ impl PaymentPath {
230
234
231
235
// If an amount transferred by the path is updated, the fees should be adjusted.
232
236
// Any other way to change fees may result in an inconsistency.
233
- // The caller should make sure values don't fall below htlc_minimum_msat of the used channels.
237
+ // Also, it's only safe to reduce the value, to not violate limits like
238
+ // htlc_minimum_msat.
234
239
fn update_value_and_recompute_fees ( & mut self , value_msat : u64 ) {
240
+ if value_msat == self . hops . last ( ) . unwrap ( ) . route_hop . fee_msat {
241
+ // Nothing to change.
242
+ return ;
243
+ }
244
+ assert ! ( value_msat < self . hops. last( ) . unwrap( ) . route_hop. fee_msat) ;
245
+
235
246
let mut total_fee_paid_msat = 0 as u64 ;
236
- for i in ( 1 ..self . hops . len ( ) ) . rev ( ) {
237
- let cur_hop_amount_msat = total_fee_paid_msat + value_msat;
247
+ for i in ( 0 ..self . hops . len ( ) ) . rev ( ) {
248
+ let last_hop = i == self . hops . len ( ) - 1 ;
249
+
250
+ // For non-last-hop, this value will represent the fees paid on the current hop. It
251
+ // will consist of the fees for the use of the next hop, and extra fees to match
252
+ // htlc_minimum_msat of the current channel. Last hop is handled separately.
253
+ let mut cur_hop_fees_msat = 0 ;
254
+ if !last_hop {
255
+ cur_hop_fees_msat = self . hops . get ( i + 1 ) . unwrap ( ) . hop_use_fee_msat ;
256
+ }
257
+
238
258
let mut cur_hop = self . hops . get_mut ( i) . unwrap ( ) ;
239
259
cur_hop. next_hops_fee_msat = total_fee_paid_msat;
240
- if let Some ( new_fee) = compute_fees ( cur_hop_amount_msat, cur_hop. channel_fees ) {
260
+ // Overpay in fees if we can't save these funds due to htlc_minimum_msat.
261
+ // We try to account for htlc_minimum_msat in scoring (add_entry!), so that nodes don't
262
+ // set it too high just to maliciously take more fees by exploiting this
263
+ // match htlc_minimum_msat logic.
264
+ let mut cur_hop_transferred_amount_msat = total_fee_paid_msat + value_msat;
265
+ if let Some ( extra_fees_msat) = cur_hop. htlc_minimum_msat . checked_sub ( cur_hop_transferred_amount_msat) {
266
+ cur_hop_transferred_amount_msat += extra_fees_msat;
267
+ total_fee_paid_msat += extra_fees_msat;
268
+ cur_hop_fees_msat += extra_fees_msat;
269
+ }
270
+
271
+ if last_hop {
272
+ // Final hop is a special case: it usually has just value_msat (by design), but also
273
+ // it still could overpay for the htlc_minimum_msat.
274
+ cur_hop. route_hop . fee_msat = cur_hop_transferred_amount_msat;
275
+ } else {
276
+ // Propagate updated fees for the use of the channels to one hop back, where they
277
+ // will be actually paid (fee_msat). The last hop is handled above separately.
278
+ cur_hop. route_hop . fee_msat = cur_hop_fees_msat;
279
+ }
280
+
281
+ // Fee for the use of the current hop which will be deducted on the previous hop.
282
+ if let Some ( new_fee) = compute_fees ( cur_hop_transferred_amount_msat, cur_hop. channel_fees ) {
241
283
cur_hop. hop_use_fee_msat = new_fee;
242
284
total_fee_paid_msat += new_fee;
243
285
} else {
@@ -247,16 +289,6 @@ impl PaymentPath {
247
289
unreachable ! ( ) ;
248
290
}
249
291
}
250
-
251
- // Propagate updated fees for the use of the channels to one hop back,
252
- // where they will be actually paid (fee_msat).
253
- // For the last hop it will represent the value being transferred over this path.
254
- for i in 0 ..self . hops . len ( ) - 1 {
255
- let next_hop_use_fee_msat = self . hops . get ( i + 1 ) . unwrap ( ) . hop_use_fee_msat ;
256
- self . hops . get_mut ( i) . unwrap ( ) . route_hop . fee_msat = next_hop_use_fee_msat;
257
- }
258
- self . hops . last_mut ( ) . unwrap ( ) . route_hop . fee_msat = value_msat;
259
- self . hops . last_mut ( ) . unwrap ( ) . hop_use_fee_msat = 0 ;
260
292
}
261
293
}
262
294
@@ -466,17 +498,13 @@ pub fn get_route<L: Deref>(our_node_id: &PublicKey, network: &NetworkGraph, paye
466
498
None => unreachable!( ) ,
467
499
} ;
468
500
469
- // If HTLC minimum is larger than the whole value we're going to transfer, we shouldn't bother
470
- // even considering this channel, because it won't be useful.
501
+ // If HTLC minimum is larger than the amount we're going to transfer, we shouldn't
502
+ // bother considering this channel.
503
+ // Since we're choosing amount_to_transfer_over_msat as maximum possible, it can
504
+ // be only reduced later (not increased), so this channel should just be skipped
505
+ // as not sufficient.
471
506
// TODO: Explore simply adding fee to hit htlc_minimum_msat
472
- // This is separate from checking amount_to_transfer_over_msat, which also should be
473
- // lower-bounded by htlc_minimum_msat. Since we're choosing it as maximum possible,
474
- // this channel should just be skipped if it's not sufficient.
475
- // amount_to_transfer_over_msat can be both larger and smaller than final_value_msat,
476
- // so this can't be derived.
477
- let final_value_satisfies_htlc_minimum_msat = final_value_msat >= $directional_info. htlc_minimum_msat;
478
- if final_value_satisfies_htlc_minimum_msat &&
479
- contributes_sufficient_value && amount_to_transfer_over_msat >= $directional_info. htlc_minimum_msat {
507
+ if contributes_sufficient_value && amount_to_transfer_over_msat >= $directional_info. htlc_minimum_msat {
480
508
let hm_entry = dist. entry( & $src_node_id) ;
481
509
let old_entry = hm_entry. or_insert_with( || {
482
510
// If there was previously no known way to access
@@ -507,6 +535,7 @@ pub fn get_route<L: Deref>(our_node_id: &PublicKey, network: &NetworkGraph, paye
507
535
next_hops_fee_msat: u64 :: max_value( ) ,
508
536
hop_use_fee_msat: u64 :: max_value( ) ,
509
537
total_fee_msat: u64 :: max_value( ) ,
538
+ htlc_minimum_msat: $directional_info. htlc_minimum_msat,
510
539
}
511
540
} ) ;
512
541
@@ -554,7 +583,29 @@ pub fn get_route<L: Deref>(our_node_id: &PublicKey, network: &NetworkGraph, paye
554
583
// (considering the cost to "reach" this channel from the route destination,
555
584
// the cost of using this channel,
556
585
// and the cost of routing to the source node of this channel).
557
- if old_entry. total_fee_msat > total_fee_msat {
586
+ // Also, consider that htlc_minimum_msat_difference, because we might end up
587
+ // paying it. Consider the following exploit:
588
+ // we use 2 paths to transfer 1.5 BTC. One of them is 0-fee normal 1 BTC path,
589
+ // and for the other one we picked a 1sat-fee path with htlc_minimum_msat of
590
+ // 1 BTC. Now, since the latter is more expensive, we gonna try to cut it
591
+ // by 0.5 BTC, but then match htlc_minimum_msat by paying a fee of 0.5 BTC
592
+ // to this channel.
593
+ // TODO: this scoring could be smarter (e.g. 0.5*htlc_minimum_msat here).
594
+ let mut old_cost = old_entry. total_fee_msat;
595
+ if let Some ( increased_old_cost) = old_cost. checked_add( old_entry. htlc_minimum_msat) {
596
+ old_cost = increased_old_cost;
597
+ } else {
598
+ old_cost = u64 :: max_value( ) ;
599
+ }
600
+
601
+ let mut new_cost = total_fee_msat;
602
+ if let Some ( increased_new_cost) = new_cost. checked_add( $directional_info. htlc_minimum_msat) {
603
+ new_cost = increased_new_cost;
604
+ } else {
605
+ new_cost = u64 :: max_value( ) ;
606
+ }
607
+
608
+ if new_cost < old_cost {
558
609
targets. push( new_graph_node) ;
559
610
old_entry. next_hops_fee_msat = $next_hops_fee_msat;
560
611
old_entry. hop_use_fee_msat = hop_use_fee_msat;
@@ -568,6 +619,9 @@ pub fn get_route<L: Deref>(our_node_id: &PublicKey, network: &NetworkGraph, paye
568
619
cltv_expiry_delta: $directional_info. cltv_expiry_delta as u32 ,
569
620
} ;
570
621
old_entry. channel_fees = $directional_info. fees;
622
+ // It's probably fine to replace the old entry, because the new one
623
+ // passed the htlc_minimum-related checks above.
624
+ old_entry. htlc_minimum_msat = $directional_info. htlc_minimum_msat;
571
625
}
572
626
}
573
627
}
@@ -852,7 +906,7 @@ pub fn get_route<L: Deref>(our_node_id: &PublicKey, network: &NetworkGraph, paye
852
906
// Sort by value so that we drop many really-low values first, since
853
907
// fewer paths is better: the payment is less likely to fail.
854
908
// TODO: this could also be optimized by also sorting by feerate_per_sat_routed,
855
- // so that the sender pays less fees overall.
909
+ // so that the sender pays less fees overall. And also htlc_minimum_msat.
856
910
cur_route. sort_by_key ( |path| path. get_value_msat ( ) ) ;
857
911
// We should make sure that at least 1 path left.
858
912
let mut paths_left = cur_route. len ( ) ;
@@ -878,11 +932,20 @@ pub fn get_route<L: Deref>(our_node_id: &PublicKey, network: &NetworkGraph, paye
878
932
879
933
// Step (7).
880
934
// Now, substract the overpaid value from the most-expensive path.
935
+ // TODO: this could also be optimized by also sorting by feerate_per_sat_routed,
936
+ // so that the sender pays less fees overall. And also htlc_minimum_msat.
881
937
cur_route. sort_by_key ( |path| { path. hops . iter ( ) . map ( |hop| hop. channel_fees . proportional_millionths as u64 ) . sum :: < u64 > ( ) } ) ;
882
- let expensive_payment_path = cur_route. last_mut ( ) . unwrap ( ) ;
883
- let expensive_path_new_value_msat = expensive_payment_path. get_value_msat ( ) - overpaid_value_msat;
884
- // TODO: make sure expensive_path_new_value_msat doesn't cause to fall behind htlc_minimum_msat.
885
- expensive_payment_path. update_value_and_recompute_fees ( expensive_path_new_value_msat) ;
938
+ for expensive_payment_path in & mut cur_route {
939
+ if let Some ( expensive_path_new_value_msat) = expensive_payment_path. get_value_msat ( ) . checked_sub ( overpaid_value_msat) {
940
+ expensive_payment_path. update_value_and_recompute_fees ( expensive_path_new_value_msat) ;
941
+ break ;
942
+ } else {
943
+ // We already dropped all the small channels above, meaning all the
944
+ // remaining channels are larger than remaining overpaid_value_msat.
945
+ // Thus, this can't be negative.
946
+ unreachable ! ( ) ;
947
+ }
948
+ }
886
949
break ;
887
950
}
888
951
}
@@ -1385,33 +1448,6 @@ mod tests {
1385
1448
let ( our_privkey, our_id, privkeys, nodes) = get_nodes ( & secp_ctx) ;
1386
1449
1387
1450
// Simple route to 2 via 1
1388
- // Should fail if htlc_minimum can't be reached
1389
-
1390
- update_channel ( & net_graph_msg_handler, & secp_ctx, & our_privkey, UnsignedChannelUpdate {
1391
- chain_hash : genesis_block ( Network :: Testnet ) . header . block_hash ( ) ,
1392
- short_channel_id : 2 ,
1393
- timestamp : 2 ,
1394
- flags : 0 ,
1395
- cltv_expiry_delta : 0 ,
1396
- htlc_minimum_msat : 50_000 ,
1397
- htlc_maximum_msat : OptionalField :: Absent ,
1398
- fee_base_msat : 0 ,
1399
- fee_proportional_millionths : 0 ,
1400
- excess_data : Vec :: new ( )
1401
- } ) ;
1402
- // Make 0 fee.
1403
- update_channel ( & net_graph_msg_handler, & secp_ctx, & privkeys[ 1 ] , UnsignedChannelUpdate {
1404
- chain_hash : genesis_block ( Network :: Testnet ) . header . block_hash ( ) ,
1405
- short_channel_id : 4 ,
1406
- timestamp : 2 ,
1407
- flags : 0 ,
1408
- cltv_expiry_delta : 0 ,
1409
- htlc_minimum_msat : 0 ,
1410
- htlc_maximum_msat : OptionalField :: Absent ,
1411
- fee_base_msat : 0 ,
1412
- fee_proportional_millionths : 0 ,
1413
- excess_data : Vec :: new ( )
1414
- } ) ;
1415
1451
1416
1452
// Disable other paths
1417
1453
update_channel ( & net_graph_msg_handler, & secp_ctx, & our_privkey, UnsignedChannelUpdate {
@@ -1475,16 +1511,7 @@ mod tests {
1475
1511
excess_data : Vec :: new ( )
1476
1512
} ) ;
1477
1513
1478
- 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 ( ) , 49_999 , 42 , Arc :: clone ( & logger) ) {
1479
- assert_eq ! ( err, "Failed to find a path to the given destination" ) ;
1480
- } else { panic ! ( ) ; }
1481
-
1482
- // A payment above the minimum should pass
1483
- let route = get_route ( & our_id, & net_graph_msg_handler. network_graph . read ( ) . unwrap ( ) , & nodes[ 2 ] , None , & Vec :: new ( ) , 50_000 , 42 , Arc :: clone ( & logger) ) . unwrap ( ) ;
1484
- assert_eq ! ( route. paths[ 0 ] . len( ) , 2 ) ;
1485
-
1486
- // This was a check against final_value_msat.
1487
- // Now let's check against amount_to_transfer_over_msat.
1514
+ // Check against amount_to_transfer_over_msat.
1488
1515
// We will be transferring 300_000_000 msat.
1489
1516
// Set minimal HTLC of 200_000_000 msat.
1490
1517
update_channel ( & net_graph_msg_handler, & secp_ctx, & our_privkey, UnsignedChannelUpdate {
@@ -1540,6 +1567,140 @@ mod tests {
1540
1567
assert_eq ! ( route. paths[ 0 ] . len( ) , 2 ) ;
1541
1568
}
1542
1569
1570
+ #[ test]
1571
+ fn htlc_minimum_overpay_test ( ) {
1572
+ let ( secp_ctx, net_graph_msg_handler, _, logger) = build_graph ( ) ;
1573
+ let ( our_privkey, our_id, privkeys, nodes) = get_nodes ( & secp_ctx) ;
1574
+
1575
+ // A route to node#2 via two paths.
1576
+ // One path allows transferring 35-40 sats, another one also allows 35-40 sats.
1577
+ // Thus, they can't send 60 without overpaying.
1578
+ update_channel ( & net_graph_msg_handler, & secp_ctx, & our_privkey, UnsignedChannelUpdate {
1579
+ chain_hash : genesis_block ( Network :: Testnet ) . header . block_hash ( ) ,
1580
+ short_channel_id : 2 ,
1581
+ timestamp : 2 ,
1582
+ flags : 0 ,
1583
+ cltv_expiry_delta : 0 ,
1584
+ htlc_minimum_msat : 35_000 ,
1585
+ htlc_maximum_msat : OptionalField :: Present ( 40_000 ) ,
1586
+ fee_base_msat : 0 ,
1587
+ fee_proportional_millionths : 0 ,
1588
+ excess_data : Vec :: new ( )
1589
+ } ) ;
1590
+ update_channel ( & net_graph_msg_handler, & secp_ctx, & our_privkey, UnsignedChannelUpdate {
1591
+ chain_hash : genesis_block ( Network :: Testnet ) . header . block_hash ( ) ,
1592
+ short_channel_id : 12 ,
1593
+ timestamp : 3 ,
1594
+ flags : 0 ,
1595
+ cltv_expiry_delta : 0 ,
1596
+ htlc_minimum_msat : 35_000 ,
1597
+ htlc_maximum_msat : OptionalField :: Present ( 40_000 ) ,
1598
+ fee_base_msat : 0 ,
1599
+ fee_proportional_millionths : 0 ,
1600
+ excess_data : Vec :: new ( )
1601
+ } ) ;
1602
+
1603
+ // Make 0 fee.
1604
+ update_channel ( & net_graph_msg_handler, & secp_ctx, & privkeys[ 7 ] , UnsignedChannelUpdate {
1605
+ chain_hash : genesis_block ( Network :: Testnet ) . header . block_hash ( ) ,
1606
+ short_channel_id : 13 ,
1607
+ timestamp : 2 ,
1608
+ flags : 0 ,
1609
+ cltv_expiry_delta : 0 ,
1610
+ htlc_minimum_msat : 0 ,
1611
+ htlc_maximum_msat : OptionalField :: Absent ,
1612
+ fee_base_msat : 0 ,
1613
+ fee_proportional_millionths : 0 ,
1614
+ excess_data : Vec :: new ( )
1615
+ } ) ;
1616
+ update_channel ( & net_graph_msg_handler, & secp_ctx, & privkeys[ 1 ] , UnsignedChannelUpdate {
1617
+ chain_hash : genesis_block ( Network :: Testnet ) . header . block_hash ( ) ,
1618
+ short_channel_id : 4 ,
1619
+ timestamp : 2 ,
1620
+ flags : 0 ,
1621
+ cltv_expiry_delta : 0 ,
1622
+ htlc_minimum_msat : 0 ,
1623
+ htlc_maximum_msat : OptionalField :: Absent ,
1624
+ fee_base_msat : 0 ,
1625
+ fee_proportional_millionths : 0 ,
1626
+ excess_data : Vec :: new ( )
1627
+ } ) ;
1628
+
1629
+ // Disable other paths
1630
+ update_channel ( & net_graph_msg_handler, & secp_ctx, & our_privkey, UnsignedChannelUpdate {
1631
+ chain_hash : genesis_block ( Network :: Testnet ) . header . block_hash ( ) ,
1632
+ short_channel_id : 1 ,
1633
+ timestamp : 3 ,
1634
+ flags : 2 , // to disable
1635
+ cltv_expiry_delta : 0 ,
1636
+ htlc_minimum_msat : 0 ,
1637
+ htlc_maximum_msat : OptionalField :: Absent ,
1638
+ fee_base_msat : 0 ,
1639
+ fee_proportional_millionths : 0 ,
1640
+ excess_data : Vec :: new ( )
1641
+ } ) ;
1642
+
1643
+ let route = get_route ( & our_id, & net_graph_msg_handler. network_graph . read ( ) . unwrap ( ) , & nodes[ 2 ] , None , & Vec :: new ( ) , 60_000 , 42 , Arc :: clone ( & logger) ) . unwrap ( ) ;
1644
+ // Overpay fees to hit htlc_minimum_msat.
1645
+ let overpaid_fees = route. paths [ 0 ] [ 0 ] . fee_msat + route. paths [ 1 ] [ 0 ] . fee_msat ;
1646
+ // TODO: this could be better balanced to overpay 10k and not 15k.
1647
+ assert_eq ! ( overpaid_fees, 15_000 ) ;
1648
+
1649
+ // Now, test that if there are 2 paths, a "cheaper" by fee path wouldn't be prioritized
1650
+ // while taking even more fee to match htlc_minimum_msat.
1651
+ update_channel ( & net_graph_msg_handler, & secp_ctx, & our_privkey, UnsignedChannelUpdate {
1652
+ chain_hash : genesis_block ( Network :: Testnet ) . header . block_hash ( ) ,
1653
+ short_channel_id : 12 ,
1654
+ timestamp : 4 ,
1655
+ flags : 0 ,
1656
+ cltv_expiry_delta : 0 ,
1657
+ htlc_minimum_msat : 65_000 ,
1658
+ htlc_maximum_msat : OptionalField :: Present ( 80_000 ) ,
1659
+ fee_base_msat : 0 ,
1660
+ fee_proportional_millionths : 0 ,
1661
+ excess_data : Vec :: new ( )
1662
+ } ) ;
1663
+ update_channel ( & net_graph_msg_handler, & secp_ctx, & our_privkey, UnsignedChannelUpdate {
1664
+ chain_hash : genesis_block ( Network :: Testnet ) . header . block_hash ( ) ,
1665
+ short_channel_id : 2 ,
1666
+ timestamp : 3 ,
1667
+ flags : 0 ,
1668
+ cltv_expiry_delta : 0 ,
1669
+ htlc_minimum_msat : 0 ,
1670
+ htlc_maximum_msat : OptionalField :: Absent ,
1671
+ fee_base_msat : 0 ,
1672
+ fee_proportional_millionths : 0 ,
1673
+ excess_data : Vec :: new ( )
1674
+ } ) ;
1675
+ update_channel ( & net_graph_msg_handler, & secp_ctx, & privkeys[ 1 ] , UnsignedChannelUpdate {
1676
+ chain_hash : genesis_block ( Network :: Testnet ) . header . block_hash ( ) ,
1677
+ short_channel_id : 4 ,
1678
+ timestamp : 4 ,
1679
+ flags : 0 ,
1680
+ cltv_expiry_delta : 0 ,
1681
+ htlc_minimum_msat : 0 ,
1682
+ htlc_maximum_msat : OptionalField :: Absent ,
1683
+ fee_base_msat : 0 ,
1684
+ fee_proportional_millionths : 100_000 ,
1685
+ excess_data : Vec :: new ( )
1686
+ } ) ;
1687
+
1688
+ let route = get_route ( & our_id, & net_graph_msg_handler. network_graph . read ( ) . unwrap ( ) , & nodes[ 2 ] , None , & Vec :: new ( ) , 60_000 , 42 , Arc :: clone ( & logger) ) . unwrap ( ) ;
1689
+ // Fine to overpay for htlc_minimum_msat if it allows us to save fee.
1690
+ assert_eq ! ( route. paths. len( ) , 1 ) ;
1691
+ assert_eq ! ( route. paths[ 0 ] [ 0 ] . short_channel_id, 12 ) ;
1692
+ let fees = route. paths [ 0 ] [ 0 ] . fee_msat ;
1693
+ assert_eq ! ( fees, 5_000 ) ;
1694
+
1695
+ let route = get_route ( & our_id, & net_graph_msg_handler. network_graph . read ( ) . unwrap ( ) , & nodes[ 2 ] , None , & Vec :: new ( ) , 50_000 , 42 , Arc :: clone ( & logger) ) . unwrap ( ) ;
1696
+ // Not fine to overpay for htlc_minimum_msat if it requires paying more than fee on
1697
+ // the other channel.
1698
+ assert_eq ! ( route. paths. len( ) , 1 ) ;
1699
+ assert_eq ! ( route. paths[ 0 ] [ 0 ] . short_channel_id, 2 ) ;
1700
+ let fees = route. paths [ 0 ] [ 0 ] . fee_msat ;
1701
+ assert_eq ! ( fees, 5_000 ) ;
1702
+ }
1703
+
1543
1704
#[ test]
1544
1705
fn disable_channels_test ( ) {
1545
1706
let ( secp_ctx, net_graph_msg_handler, _, logger) = build_graph ( ) ;
0 commit comments