@@ -151,7 +151,8 @@ struct RouteGraphNode {
151
151
152
152
impl cmp:: Ord for RouteGraphNode {
153
153
fn cmp ( & self , other : & RouteGraphNode ) -> cmp:: Ordering {
154
- other. lowest_fee_to_peer_through_node . cmp ( & self . lowest_fee_to_peer_through_node )
154
+ cmp:: max ( other. lowest_fee_to_peer_through_node , other. path_htlc_minimum_msat )
155
+ . cmp ( & cmp:: max ( self . lowest_fee_to_peer_through_node , self . path_htlc_minimum_msat ) )
155
156
. then_with ( || other. pubkey . serialize ( ) . cmp ( & self . pubkey . serialize ( ) ) )
156
157
}
157
158
}
@@ -196,6 +197,9 @@ struct PathBuildingHop {
196
197
/// we don't fall below the minimum. Should not be updated manually and
197
198
/// generally should not be accessed.
198
199
htlc_minimum_msat : u64 ,
200
+ /// A mirror of the same field in RouteGraphNode. Note that this is only used during the graph
201
+ /// walk and may be invalid thereafter.
202
+ path_htlc_minimum_msat : u64 ,
199
203
}
200
204
201
205
// Instantiated with a list of hops with correct data in them collected during path finding,
@@ -609,6 +613,7 @@ pub fn get_route<L: Deref>(our_node_id: &PublicKey, network: &NetworkGraph, paye
609
613
hop_use_fee_msat: u64 :: max_value( ) ,
610
614
total_fee_msat: u64 :: max_value( ) ,
611
615
htlc_minimum_msat: $directional_info. htlc_minimum_msat,
616
+ path_htlc_minimum_msat,
612
617
}
613
618
} ) ;
614
619
@@ -664,20 +669,12 @@ pub fn get_route<L: Deref>(our_node_id: &PublicKey, network: &NetworkGraph, paye
664
669
// 1 BTC. Now, since the latter is more expensive, we gonna try to cut it
665
670
// by 0.5 BTC, but then match htlc_minimum_msat by paying a fee of 0.5 BTC
666
671
// to this channel.
667
- // TODO: this scoring could be smarter (e.g. 0.5*htlc_minimum_msat here).
668
- let mut old_cost = old_entry. total_fee_msat;
669
- if let Some ( increased_old_cost) = old_cost. checked_add( old_entry. htlc_minimum_msat) {
670
- old_cost = increased_old_cost;
671
- } else {
672
- old_cost = u64 :: max_value( ) ;
673
- }
674
-
675
- let mut new_cost = total_fee_msat;
676
- if let Some ( increased_new_cost) = new_cost. checked_add( $directional_info. htlc_minimum_msat) {
677
- new_cost = increased_new_cost;
678
- } else {
679
- new_cost = u64 :: max_value( ) ;
680
- }
672
+ // Ideally the scoring could be smarter (e.g. 0.5*htlc_minimum_msat here),
673
+ // but it may require additional tracking - we don't want to double-count
674
+ // the fees included in $incl_fee_next_hops_htlc_minimum_msat, but also
675
+ // can't use something that may decrease on future hops.
676
+ let old_cost = cmp:: max( old_entry. total_fee_msat, old_entry. path_htlc_minimum_msat) ;
677
+ let new_cost = cmp:: max( total_fee_msat, path_htlc_minimum_msat) ;
681
678
682
679
if new_cost < old_cost {
683
680
targets. push( new_graph_node) ;
@@ -693,9 +690,8 @@ pub fn get_route<L: Deref>(our_node_id: &PublicKey, network: &NetworkGraph, paye
693
690
cltv_expiry_delta: $directional_info. cltv_expiry_delta as u32 ,
694
691
} ;
695
692
old_entry. channel_fees = $directional_info. fees;
696
- // It's probably fine to replace the old entry, because the new one
697
- // passed the htlc_minimum-related checks above.
698
693
old_entry. htlc_minimum_msat = $directional_info. htlc_minimum_msat;
694
+ old_entry. path_htlc_minimum_msat = path_htlc_minimum_msat;
699
695
}
700
696
}
701
697
}
@@ -3480,6 +3476,156 @@ mod tests {
3480
3476
}
3481
3477
}
3482
3478
3479
+ #[ test]
3480
+ fn min_criteria_consistency ( ) {
3481
+ // Test that we don't use an inconsistent metric between updating and walking nodes during
3482
+ // our Dijkstra's pass. In the initial version of MPP, the "best source" for a given node
3483
+ // was updated with a different critera from the heap sorting, resulting in loops in
3484
+ // calculated paths. We test for that specific case here.
3485
+
3486
+ // We construct a network that looks like this:
3487
+ //
3488
+ // node2 -1(3)2- node3
3489
+ // 2 2
3490
+ // (2) (4)
3491
+ // 1 1
3492
+ // node1 -1(5)2- node4 -1(1)2- node6
3493
+ // 2
3494
+ // (6)
3495
+ // 1
3496
+ // our_node
3497
+ //
3498
+ // We create a loop on the side of our real path - our destination is node 6, with a
3499
+ // previous hop of node 4. From 4, the cheapest previous path is channel 2 from node 2,
3500
+ // followed by node 3 over channel 3. Thereafter, the cheapest next-hop is back to node 4
3501
+ // (this time over channel 4). Channel 4 has 0 htlc_minimum_msat whereas channel 1 (the
3502
+ // other channel with a previous-hop of node 4) has a high (but irrelevant to the overall
3503
+ // payment) htlc_minimum_msat. In the original algorithm, this resulted in node4's
3504
+ // "previous hop" being set to node 3, creating a loop in the path.
3505
+ let secp_ctx = Secp256k1 :: new ( ) ;
3506
+ let logger = Arc :: new ( test_utils:: TestLogger :: new ( ) ) ;
3507
+ let net_graph_msg_handler = NetGraphMsgHandler :: new ( genesis_block ( Network :: Testnet ) . header . block_hash ( ) , None , Arc :: clone ( & logger) ) ;
3508
+ let ( our_privkey, our_id, privkeys, nodes) = get_nodes ( & secp_ctx) ;
3509
+
3510
+ add_channel ( & net_graph_msg_handler, & secp_ctx, & our_privkey, & privkeys[ 1 ] , ChannelFeatures :: from_le_bytes ( id_to_feature_flags ( 6 ) ) , 6 ) ;
3511
+ update_channel ( & net_graph_msg_handler, & secp_ctx, & our_privkey, UnsignedChannelUpdate {
3512
+ chain_hash : genesis_block ( Network :: Testnet ) . header . block_hash ( ) ,
3513
+ short_channel_id : 6 ,
3514
+ timestamp : 1 ,
3515
+ flags : 0 ,
3516
+ cltv_expiry_delta : ( 6 << 8 ) | 0 ,
3517
+ htlc_minimum_msat : 0 ,
3518
+ htlc_maximum_msat : OptionalField :: Absent ,
3519
+ fee_base_msat : 0 ,
3520
+ fee_proportional_millionths : 0 ,
3521
+ excess_data : Vec :: new ( )
3522
+ } ) ;
3523
+ add_or_update_node ( & net_graph_msg_handler, & secp_ctx, & privkeys[ 1 ] , NodeFeatures :: from_le_bytes ( id_to_feature_flags ( 1 ) ) , 0 ) ;
3524
+
3525
+ add_channel ( & net_graph_msg_handler, & secp_ctx, & privkeys[ 1 ] , & privkeys[ 4 ] , ChannelFeatures :: from_le_bytes ( id_to_feature_flags ( 5 ) ) , 5 ) ;
3526
+ update_channel ( & net_graph_msg_handler, & secp_ctx, & privkeys[ 1 ] , UnsignedChannelUpdate {
3527
+ chain_hash : genesis_block ( Network :: Testnet ) . header . block_hash ( ) ,
3528
+ short_channel_id : 5 ,
3529
+ timestamp : 1 ,
3530
+ flags : 0 ,
3531
+ cltv_expiry_delta : ( 5 << 8 ) | 0 ,
3532
+ htlc_minimum_msat : 0 ,
3533
+ htlc_maximum_msat : OptionalField :: Absent ,
3534
+ fee_base_msat : 100 ,
3535
+ fee_proportional_millionths : 0 ,
3536
+ excess_data : Vec :: new ( )
3537
+ } ) ;
3538
+ add_or_update_node ( & net_graph_msg_handler, & secp_ctx, & privkeys[ 4 ] , NodeFeatures :: from_le_bytes ( id_to_feature_flags ( 4 ) ) , 0 ) ;
3539
+
3540
+ add_channel ( & net_graph_msg_handler, & secp_ctx, & privkeys[ 4 ] , & privkeys[ 3 ] , ChannelFeatures :: from_le_bytes ( id_to_feature_flags ( 4 ) ) , 4 ) ;
3541
+ update_channel ( & net_graph_msg_handler, & secp_ctx, & privkeys[ 4 ] , UnsignedChannelUpdate {
3542
+ chain_hash : genesis_block ( Network :: Testnet ) . header . block_hash ( ) ,
3543
+ short_channel_id : 4 ,
3544
+ timestamp : 1 ,
3545
+ flags : 0 ,
3546
+ cltv_expiry_delta : ( 4 << 8 ) | 0 ,
3547
+ htlc_minimum_msat : 0 ,
3548
+ htlc_maximum_msat : OptionalField :: Absent ,
3549
+ fee_base_msat : 0 ,
3550
+ fee_proportional_millionths : 0 ,
3551
+ excess_data : Vec :: new ( )
3552
+ } ) ;
3553
+ add_or_update_node ( & net_graph_msg_handler, & secp_ctx, & privkeys[ 3 ] , NodeFeatures :: from_le_bytes ( id_to_feature_flags ( 3 ) ) , 0 ) ;
3554
+
3555
+ add_channel ( & net_graph_msg_handler, & secp_ctx, & privkeys[ 3 ] , & privkeys[ 2 ] , ChannelFeatures :: from_le_bytes ( id_to_feature_flags ( 3 ) ) , 3 ) ;
3556
+ update_channel ( & net_graph_msg_handler, & secp_ctx, & privkeys[ 3 ] , UnsignedChannelUpdate {
3557
+ chain_hash : genesis_block ( Network :: Testnet ) . header . block_hash ( ) ,
3558
+ short_channel_id : 3 ,
3559
+ timestamp : 1 ,
3560
+ flags : 0 ,
3561
+ cltv_expiry_delta : ( 3 << 8 ) | 0 ,
3562
+ htlc_minimum_msat : 0 ,
3563
+ htlc_maximum_msat : OptionalField :: Absent ,
3564
+ fee_base_msat : 0 ,
3565
+ fee_proportional_millionths : 0 ,
3566
+ excess_data : Vec :: new ( )
3567
+ } ) ;
3568
+ add_or_update_node ( & net_graph_msg_handler, & secp_ctx, & privkeys[ 2 ] , NodeFeatures :: from_le_bytes ( id_to_feature_flags ( 2 ) ) , 0 ) ;
3569
+
3570
+ add_channel ( & net_graph_msg_handler, & secp_ctx, & privkeys[ 2 ] , & privkeys[ 4 ] , ChannelFeatures :: from_le_bytes ( id_to_feature_flags ( 2 ) ) , 2 ) ;
3571
+ update_channel ( & net_graph_msg_handler, & secp_ctx, & privkeys[ 2 ] , UnsignedChannelUpdate {
3572
+ chain_hash : genesis_block ( Network :: Testnet ) . header . block_hash ( ) ,
3573
+ short_channel_id : 2 ,
3574
+ timestamp : 1 ,
3575
+ flags : 0 ,
3576
+ cltv_expiry_delta : ( 2 << 8 ) | 0 ,
3577
+ htlc_minimum_msat : 0 ,
3578
+ htlc_maximum_msat : OptionalField :: Absent ,
3579
+ fee_base_msat : 0 ,
3580
+ fee_proportional_millionths : 0 ,
3581
+ excess_data : Vec :: new ( )
3582
+ } ) ;
3583
+
3584
+ add_channel ( & net_graph_msg_handler, & secp_ctx, & privkeys[ 4 ] , & privkeys[ 6 ] , ChannelFeatures :: from_le_bytes ( id_to_feature_flags ( 1 ) ) , 1 ) ;
3585
+ update_channel ( & net_graph_msg_handler, & secp_ctx, & privkeys[ 4 ] , UnsignedChannelUpdate {
3586
+ chain_hash : genesis_block ( Network :: Testnet ) . header . block_hash ( ) ,
3587
+ short_channel_id : 1 ,
3588
+ timestamp : 1 ,
3589
+ flags : 0 ,
3590
+ cltv_expiry_delta : ( 1 << 8 ) | 0 ,
3591
+ htlc_minimum_msat : 100 ,
3592
+ htlc_maximum_msat : OptionalField :: Absent ,
3593
+ fee_base_msat : 0 ,
3594
+ fee_proportional_millionths : 0 ,
3595
+ excess_data : Vec :: new ( )
3596
+ } ) ;
3597
+ add_or_update_node ( & net_graph_msg_handler, & secp_ctx, & privkeys[ 6 ] , NodeFeatures :: from_le_bytes ( id_to_feature_flags ( 6 ) ) , 0 ) ;
3598
+
3599
+ {
3600
+ // Now ensure the route flows simply over nodes 1 and 4 to 6.
3601
+ let route = get_route ( & our_id, & net_graph_msg_handler. network_graph . read ( ) . unwrap ( ) , & nodes[ 6 ] , None , None , & Vec :: new ( ) , 10_000 , 42 , Arc :: clone ( & logger) ) . unwrap ( ) ;
3602
+ assert_eq ! ( route. paths. len( ) , 1 ) ;
3603
+ assert_eq ! ( route. paths[ 0 ] . len( ) , 3 ) ;
3604
+
3605
+ assert_eq ! ( route. paths[ 0 ] [ 0 ] . pubkey, nodes[ 1 ] ) ;
3606
+ assert_eq ! ( route. paths[ 0 ] [ 0 ] . short_channel_id, 6 ) ;
3607
+ assert_eq ! ( route. paths[ 0 ] [ 0 ] . fee_msat, 100 ) ;
3608
+ assert_eq ! ( route. paths[ 0 ] [ 0 ] . cltv_expiry_delta, ( 5 << 8 ) | 0 ) ;
3609
+ assert_eq ! ( route. paths[ 0 ] [ 0 ] . node_features. le_flags( ) , & id_to_feature_flags( 1 ) ) ;
3610
+ assert_eq ! ( route. paths[ 0 ] [ 0 ] . channel_features. le_flags( ) , & id_to_feature_flags( 6 ) ) ;
3611
+
3612
+ assert_eq ! ( route. paths[ 0 ] [ 1 ] . pubkey, nodes[ 4 ] ) ;
3613
+ assert_eq ! ( route. paths[ 0 ] [ 1 ] . short_channel_id, 5 ) ;
3614
+ assert_eq ! ( route. paths[ 0 ] [ 1 ] . fee_msat, 0 ) ;
3615
+ assert_eq ! ( route. paths[ 0 ] [ 1 ] . cltv_expiry_delta, ( 1 << 8 ) | 0 ) ;
3616
+ assert_eq ! ( route. paths[ 0 ] [ 1 ] . node_features. le_flags( ) , & id_to_feature_flags( 4 ) ) ;
3617
+ assert_eq ! ( route. paths[ 0 ] [ 1 ] . channel_features. le_flags( ) , & id_to_feature_flags( 5 ) ) ;
3618
+
3619
+ assert_eq ! ( route. paths[ 0 ] [ 2 ] . pubkey, nodes[ 6 ] ) ;
3620
+ assert_eq ! ( route. paths[ 0 ] [ 2 ] . short_channel_id, 1 ) ;
3621
+ assert_eq ! ( route. paths[ 0 ] [ 2 ] . fee_msat, 10_000 ) ;
3622
+ assert_eq ! ( route. paths[ 0 ] [ 2 ] . cltv_expiry_delta, 42 ) ;
3623
+ assert_eq ! ( route. paths[ 0 ] [ 2 ] . node_features. le_flags( ) , & id_to_feature_flags( 6 ) ) ;
3624
+ assert_eq ! ( route. paths[ 0 ] [ 2 ] . channel_features. le_flags( ) , & id_to_feature_flags( 1 ) ) ;
3625
+ }
3626
+ }
3627
+
3628
+
3483
3629
#[ test]
3484
3630
fn exact_fee_liquidity_limit ( ) {
3485
3631
// Test that if, while walking the graph, we find a hop that has exactly enough liquidity
0 commit comments