@@ -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
+ ( other. lowest_fee_to_peer_through_node + other. path_htlc_minimum_msat )
155
+ . cmp ( & ( 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,
@@ -590,6 +594,7 @@ pub fn get_route<L: Deref>(our_node_id: &PublicKey, network: &NetworkGraph, paye
590
594
hop_use_fee_msat: u64 :: max_value( ) ,
591
595
total_fee_msat: u64 :: max_value( ) ,
592
596
htlc_minimum_msat: $directional_info. htlc_minimum_msat,
597
+ path_htlc_minimum_msat,
593
598
}
594
599
} ) ;
595
600
@@ -646,19 +651,17 @@ pub fn get_route<L: Deref>(our_node_id: &PublicKey, network: &NetworkGraph, paye
646
651
// by 0.5 BTC, but then match htlc_minimum_msat by paying a fee of 0.5 BTC
647
652
// to this channel.
648
653
// TODO: this scoring could be smarter (e.g. 0.5*htlc_minimum_msat here).
649
- let mut old_cost = old_entry. total_fee_msat;
650
- if let Some ( increased_old_cost) = old_cost. checked_add( old_entry. htlc_minimum_msat) {
651
- old_cost = increased_old_cost;
654
+ let old_cost = if let Some ( increased_old_cost) = old_entry. total_fee_msat. checked_add( old_entry. path_htlc_minimum_msat) {
655
+ increased_old_cost
652
656
} else {
653
- old_cost = u64 :: max_value( ) ;
654
- }
657
+ u64 :: max_value( )
658
+ } ;
655
659
656
- let mut new_cost = total_fee_msat;
657
- if let Some ( increased_new_cost) = new_cost. checked_add( $directional_info. htlc_minimum_msat) {
658
- new_cost = increased_new_cost;
660
+ let new_cost = if let Some ( increased_new_cost) = total_fee_msat. checked_add( path_htlc_minimum_msat) {
661
+ increased_new_cost
659
662
} else {
660
- new_cost = u64 :: max_value( ) ;
661
- }
663
+ u64 :: max_value( )
664
+ } ;
662
665
663
666
if new_cost < old_cost {
664
667
targets. push( new_graph_node) ;
@@ -674,9 +677,8 @@ pub fn get_route<L: Deref>(our_node_id: &PublicKey, network: &NetworkGraph, paye
674
677
cltv_expiry_delta: $directional_info. cltv_expiry_delta as u32 ,
675
678
} ;
676
679
old_entry. channel_fees = $directional_info. fees;
677
- // It's probably fine to replace the old entry, because the new one
678
- // passed the htlc_minimum-related checks above.
679
680
old_entry. htlc_minimum_msat = $directional_info. htlc_minimum_msat;
681
+ old_entry. path_htlc_minimum_msat = path_htlc_minimum_msat;
680
682
}
681
683
}
682
684
}
@@ -3400,6 +3402,161 @@ mod tests {
3400
3402
}
3401
3403
}
3402
3404
3405
+ #[ test]
3406
+ fn min_criteria_consistency ( ) {
3407
+ // Test that we don't use an inconsistent metric between updating and walking nodes during
3408
+ // our Dijkstra's pass. In the initial version of MPP, the "best source" for a given node
3409
+ // was updated with a different critera from the heap sorting, resulting in loops in
3410
+ // calculated paths. We test for that specific case here.
3411
+ let ( secp_ctx, net_graph_msg_handler, _, logger) = build_graph ( ) ;
3412
+ let ( our_privkey, our_id, privkeys, nodes) = get_nodes ( & secp_ctx) ;
3413
+
3414
+
3415
+ // We construct a network that looks like this:
3416
+ //
3417
+ // node2 -1(3)2- node3
3418
+ // 2 2
3419
+ // (2) (4)
3420
+ // 1 1
3421
+ // node1 -1(5)2- node4 -1(1)2- node6
3422
+ // 2
3423
+ // (6)
3424
+ // 1
3425
+ // our_node
3426
+ //
3427
+ // We create a loop on the side of our real path - our destination is node 6, with a
3428
+ // previous hop of node 4. From 4, the cheapest previous path is channel 2 from node 2,
3429
+ // followed by node 3 over channel 3. Thereafter, the cheapest next-hop is back to node 4
3430
+ // (this time over channel 4). Channel 4 has 0 htlc_minimum_msat whereas channel 1 (the
3431
+ // other channel with a previous-hop of node 4) has a high (but irrelevant to the overall
3432
+ // payment) htlc_minimum_msat. In the original algorithm, this resulted in node4's
3433
+ // "previous hop" being set to node 3, creating a loop in the path.
3434
+ let secp_ctx = Secp256k1 :: new ( ) ;
3435
+ let logger = Arc :: new ( test_utils:: TestLogger :: new ( ) ) ;
3436
+ let chain_monitor = Arc :: new ( test_utils:: TestChainSource :: new ( Network :: Testnet ) ) ;
3437
+ let net_graph_msg_handler = NetGraphMsgHandler :: new ( genesis_block ( Network :: Testnet ) . header . block_hash ( ) , None , Arc :: clone ( & logger) ) ;
3438
+
3439
+ let ( our_privkey, _, privkeys, _) = get_nodes ( & secp_ctx) ;
3440
+
3441
+ add_channel ( & net_graph_msg_handler, & secp_ctx, & our_privkey, & privkeys[ 1 ] , ChannelFeatures :: from_le_bytes ( id_to_feature_flags ( 6 ) ) , 6 ) ;
3442
+ update_channel ( & net_graph_msg_handler, & secp_ctx, & our_privkey, UnsignedChannelUpdate {
3443
+ chain_hash : genesis_block ( Network :: Testnet ) . header . block_hash ( ) ,
3444
+ short_channel_id : 6 ,
3445
+ timestamp : 1 ,
3446
+ flags : 0 ,
3447
+ cltv_expiry_delta : ( 6 << 8 ) | 0 ,
3448
+ htlc_minimum_msat : 0 ,
3449
+ htlc_maximum_msat : OptionalField :: Absent ,
3450
+ fee_base_msat : 0 ,
3451
+ fee_proportional_millionths : 0 ,
3452
+ excess_data : Vec :: new ( )
3453
+ } ) ;
3454
+ add_or_update_node ( & net_graph_msg_handler, & secp_ctx, & privkeys[ 1 ] , NodeFeatures :: from_le_bytes ( id_to_feature_flags ( 1 ) ) , 0 ) ;
3455
+
3456
+ add_channel ( & net_graph_msg_handler, & secp_ctx, & privkeys[ 1 ] , & privkeys[ 4 ] , ChannelFeatures :: from_le_bytes ( id_to_feature_flags ( 5 ) ) , 5 ) ;
3457
+ update_channel ( & net_graph_msg_handler, & secp_ctx, & privkeys[ 1 ] , UnsignedChannelUpdate {
3458
+ chain_hash : genesis_block ( Network :: Testnet ) . header . block_hash ( ) ,
3459
+ short_channel_id : 5 ,
3460
+ timestamp : 1 ,
3461
+ flags : 0 ,
3462
+ cltv_expiry_delta : ( 5 << 8 ) | 0 ,
3463
+ htlc_minimum_msat : 0 ,
3464
+ htlc_maximum_msat : OptionalField :: Absent ,
3465
+ fee_base_msat : 100 ,
3466
+ fee_proportional_millionths : 0 ,
3467
+ excess_data : Vec :: new ( )
3468
+ } ) ;
3469
+ add_or_update_node ( & net_graph_msg_handler, & secp_ctx, & privkeys[ 4 ] , NodeFeatures :: from_le_bytes ( id_to_feature_flags ( 4 ) ) , 0 ) ;
3470
+
3471
+ add_channel ( & net_graph_msg_handler, & secp_ctx, & privkeys[ 4 ] , & privkeys[ 3 ] , ChannelFeatures :: from_le_bytes ( id_to_feature_flags ( 4 ) ) , 4 ) ;
3472
+ update_channel ( & net_graph_msg_handler, & secp_ctx, & privkeys[ 4 ] , UnsignedChannelUpdate {
3473
+ chain_hash : genesis_block ( Network :: Testnet ) . header . block_hash ( ) ,
3474
+ short_channel_id : 4 ,
3475
+ timestamp : 1 ,
3476
+ flags : 0 ,
3477
+ cltv_expiry_delta : ( 4 << 8 ) | 0 ,
3478
+ htlc_minimum_msat : 0 ,
3479
+ htlc_maximum_msat : OptionalField :: Absent ,
3480
+ fee_base_msat : 0 ,
3481
+ fee_proportional_millionths : 0 ,
3482
+ excess_data : Vec :: new ( )
3483
+ } ) ;
3484
+ add_or_update_node ( & net_graph_msg_handler, & secp_ctx, & privkeys[ 3 ] , NodeFeatures :: from_le_bytes ( id_to_feature_flags ( 3 ) ) , 0 ) ;
3485
+
3486
+ add_channel ( & net_graph_msg_handler, & secp_ctx, & privkeys[ 3 ] , & privkeys[ 2 ] , ChannelFeatures :: from_le_bytes ( id_to_feature_flags ( 3 ) ) , 3 ) ;
3487
+ update_channel ( & net_graph_msg_handler, & secp_ctx, & privkeys[ 3 ] , UnsignedChannelUpdate {
3488
+ chain_hash : genesis_block ( Network :: Testnet ) . header . block_hash ( ) ,
3489
+ short_channel_id : 3 ,
3490
+ timestamp : 1 ,
3491
+ flags : 0 ,
3492
+ cltv_expiry_delta : ( 3 << 8 ) | 0 ,
3493
+ htlc_minimum_msat : 0 ,
3494
+ htlc_maximum_msat : OptionalField :: Absent ,
3495
+ fee_base_msat : 0 ,
3496
+ fee_proportional_millionths : 0 ,
3497
+ excess_data : Vec :: new ( )
3498
+ } ) ;
3499
+ add_or_update_node ( & net_graph_msg_handler, & secp_ctx, & privkeys[ 2 ] , NodeFeatures :: from_le_bytes ( id_to_feature_flags ( 2 ) ) , 0 ) ;
3500
+
3501
+ add_channel ( & net_graph_msg_handler, & secp_ctx, & privkeys[ 2 ] , & privkeys[ 4 ] , ChannelFeatures :: from_le_bytes ( id_to_feature_flags ( 2 ) ) , 2 ) ;
3502
+ update_channel ( & net_graph_msg_handler, & secp_ctx, & privkeys[ 2 ] , UnsignedChannelUpdate {
3503
+ chain_hash : genesis_block ( Network :: Testnet ) . header . block_hash ( ) ,
3504
+ short_channel_id : 2 ,
3505
+ timestamp : 1 ,
3506
+ flags : 0 ,
3507
+ cltv_expiry_delta : ( 2 << 8 ) | 0 ,
3508
+ htlc_minimum_msat : 0 ,
3509
+ htlc_maximum_msat : OptionalField :: Absent ,
3510
+ fee_base_msat : 0 ,
3511
+ fee_proportional_millionths : 0 ,
3512
+ excess_data : Vec :: new ( )
3513
+ } ) ;
3514
+
3515
+ add_channel ( & net_graph_msg_handler, & secp_ctx, & privkeys[ 4 ] , & privkeys[ 6 ] , ChannelFeatures :: from_le_bytes ( id_to_feature_flags ( 1 ) ) , 1 ) ;
3516
+ update_channel ( & net_graph_msg_handler, & secp_ctx, & privkeys[ 4 ] , UnsignedChannelUpdate {
3517
+ chain_hash : genesis_block ( Network :: Testnet ) . header . block_hash ( ) ,
3518
+ short_channel_id : 1 ,
3519
+ timestamp : 1 ,
3520
+ flags : 0 ,
3521
+ cltv_expiry_delta : ( 1 << 8 ) | 0 ,
3522
+ htlc_minimum_msat : 100 ,
3523
+ htlc_maximum_msat : OptionalField :: Absent ,
3524
+ fee_base_msat : 0 ,
3525
+ fee_proportional_millionths : 0 ,
3526
+ excess_data : Vec :: new ( )
3527
+ } ) ;
3528
+ add_or_update_node ( & net_graph_msg_handler, & secp_ctx, & privkeys[ 6 ] , NodeFeatures :: from_le_bytes ( id_to_feature_flags ( 6 ) ) , 0 ) ;
3529
+
3530
+ {
3531
+ // Now ensure the route flows simply over nodes 1 and 4 to 6.
3532
+ let route = get_route ( & our_id, & net_graph_msg_handler. network_graph . read ( ) . unwrap ( ) , & nodes[ 6 ] , None , & Vec :: new ( ) , 10_000 , 42 , Arc :: clone ( & logger) ) . unwrap ( ) ;
3533
+ assert_eq ! ( route. paths. len( ) , 1 ) ;
3534
+ assert_eq ! ( route. paths[ 0 ] . len( ) , 3 ) ;
3535
+
3536
+ assert_eq ! ( route. paths[ 0 ] [ 0 ] . pubkey, nodes[ 1 ] ) ;
3537
+ assert_eq ! ( route. paths[ 0 ] [ 0 ] . short_channel_id, 6 ) ;
3538
+ assert_eq ! ( route. paths[ 0 ] [ 0 ] . fee_msat, 100 ) ;
3539
+ assert_eq ! ( route. paths[ 0 ] [ 0 ] . cltv_expiry_delta, ( 5 << 8 ) | 0 ) ;
3540
+ assert_eq ! ( route. paths[ 0 ] [ 0 ] . node_features. le_flags( ) , & id_to_feature_flags( 1 ) ) ;
3541
+ assert_eq ! ( route. paths[ 0 ] [ 0 ] . channel_features. le_flags( ) , & id_to_feature_flags( 6 ) ) ;
3542
+
3543
+ assert_eq ! ( route. paths[ 0 ] [ 1 ] . pubkey, nodes[ 4 ] ) ;
3544
+ assert_eq ! ( route. paths[ 0 ] [ 1 ] . short_channel_id, 5 ) ;
3545
+ assert_eq ! ( route. paths[ 0 ] [ 1 ] . fee_msat, 0 ) ;
3546
+ assert_eq ! ( route. paths[ 0 ] [ 1 ] . cltv_expiry_delta, ( 1 << 8 ) | 0 ) ;
3547
+ assert_eq ! ( route. paths[ 0 ] [ 1 ] . node_features. le_flags( ) , & id_to_feature_flags( 4 ) ) ;
3548
+ assert_eq ! ( route. paths[ 0 ] [ 1 ] . channel_features. le_flags( ) , & id_to_feature_flags( 5 ) ) ;
3549
+
3550
+ assert_eq ! ( route. paths[ 0 ] [ 2 ] . pubkey, nodes[ 6 ] ) ;
3551
+ assert_eq ! ( route. paths[ 0 ] [ 2 ] . short_channel_id, 1 ) ;
3552
+ assert_eq ! ( route. paths[ 0 ] [ 2 ] . fee_msat, 10_000 ) ;
3553
+ assert_eq ! ( route. paths[ 0 ] [ 2 ] . cltv_expiry_delta, 42 ) ;
3554
+ assert_eq ! ( route. paths[ 0 ] [ 2 ] . node_features. le_flags( ) , & id_to_feature_flags( 6 ) ) ;
3555
+ assert_eq ! ( route. paths[ 0 ] [ 2 ] . channel_features. le_flags( ) , & id_to_feature_flags( 1 ) ) ;
3556
+ }
3557
+ }
3558
+
3559
+
3403
3560
#[ test]
3404
3561
fn exact_fee_liquidity_limit ( ) {
3405
3562
// Test that if, while walking the graph, we find a hop that has exactly enough liquidity
0 commit comments