@@ -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,
@@ -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
@@ -645,20 +650,12 @@ pub fn get_route<L: Deref>(our_node_id: &PublicKey, network: &NetworkGraph, paye
645
650
// 1 BTC. Now, since the latter is more expensive, we gonna try to cut it
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
- // 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;
652
- } else {
653
- old_cost = u64 :: max_value( ) ;
654
- }
655
-
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;
659
- } else {
660
- new_cost = u64 :: max_value( ) ;
661
- }
653
+ // Ideally the scoring could be smarter (e.g. 0.5*htlc_minimum_msat here),
654
+ // but it may require additional tracking - we don't want to double-count
655
+ // the fees included in $incl_fee_next_hops_htlc_minimum_msat, but also
656
+ // can't use something that may decrease on future hops.
657
+ let old_cost = cmp:: max( old_entry. total_fee_msat, old_entry. path_htlc_minimum_msat) ;
658
+ let new_cost = cmp:: max( total_fee_msat, $incl_fee_next_hops_htlc_minimum_msat) ;
662
659
663
660
if new_cost < old_cost {
664
661
targets. push( new_graph_node) ;
@@ -674,9 +671,8 @@ pub fn get_route<L: Deref>(our_node_id: &PublicKey, network: &NetworkGraph, paye
674
671
cltv_expiry_delta: $directional_info. cltv_expiry_delta as u32 ,
675
672
} ;
676
673
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
674
old_entry. htlc_minimum_msat = $directional_info. htlc_minimum_msat;
675
+ old_entry. path_htlc_minimum_msat = $incl_fee_next_hops_htlc_minimum_msat;
680
676
}
681
677
}
682
678
}
@@ -3400,6 +3396,156 @@ mod tests {
3400
3396
}
3401
3397
}
3402
3398
3399
+ #[ test]
3400
+ fn min_criteria_consistency ( ) {
3401
+ // Test that we don't use an inconsistent metric between updating and walking nodes during
3402
+ // our Dijkstra's pass. In the initial version of MPP, the "best source" for a given node
3403
+ // was updated with a different critera from the heap sorting, resulting in loops in
3404
+ // calculated paths. We test for that specific case here.
3405
+
3406
+ // We construct a network that looks like this:
3407
+ //
3408
+ // node2 -1(3)2- node3
3409
+ // 2 2
3410
+ // (2) (4)
3411
+ // 1 1
3412
+ // node1 -1(5)2- node4 -1(1)2- node6
3413
+ // 2
3414
+ // (6)
3415
+ // 1
3416
+ // our_node
3417
+ //
3418
+ // We create a loop on the side of our real path - our destination is node 6, with a
3419
+ // previous hop of node 4. From 4, the cheapest previous path is channel 2 from node 2,
3420
+ // followed by node 3 over channel 3. Thereafter, the cheapest next-hop is back to node 4
3421
+ // (this time over channel 4). Channel 4 has 0 htlc_minimum_msat whereas channel 1 (the
3422
+ // other channel with a previous-hop of node 4) has a high (but irrelevant to the overall
3423
+ // payment) htlc_minimum_msat. In the original algorithm, this resulted in node4's
3424
+ // "previous hop" being set to node 3, creating a loop in the path.
3425
+ let secp_ctx = Secp256k1 :: new ( ) ;
3426
+ let logger = Arc :: new ( test_utils:: TestLogger :: new ( ) ) ;
3427
+ let net_graph_msg_handler = NetGraphMsgHandler :: new ( genesis_block ( Network :: Testnet ) . header . block_hash ( ) , None , Arc :: clone ( & logger) ) ;
3428
+ let ( our_privkey, our_id, privkeys, nodes) = get_nodes ( & secp_ctx) ;
3429
+
3430
+ add_channel ( & net_graph_msg_handler, & secp_ctx, & our_privkey, & privkeys[ 1 ] , ChannelFeatures :: from_le_bytes ( id_to_feature_flags ( 6 ) ) , 6 ) ;
3431
+ update_channel ( & net_graph_msg_handler, & secp_ctx, & our_privkey, UnsignedChannelUpdate {
3432
+ chain_hash : genesis_block ( Network :: Testnet ) . header . block_hash ( ) ,
3433
+ short_channel_id : 6 ,
3434
+ timestamp : 1 ,
3435
+ flags : 0 ,
3436
+ cltv_expiry_delta : ( 6 << 8 ) | 0 ,
3437
+ htlc_minimum_msat : 0 ,
3438
+ htlc_maximum_msat : OptionalField :: Absent ,
3439
+ fee_base_msat : 0 ,
3440
+ fee_proportional_millionths : 0 ,
3441
+ excess_data : Vec :: new ( )
3442
+ } ) ;
3443
+ add_or_update_node ( & net_graph_msg_handler, & secp_ctx, & privkeys[ 1 ] , NodeFeatures :: from_le_bytes ( id_to_feature_flags ( 1 ) ) , 0 ) ;
3444
+
3445
+ add_channel ( & net_graph_msg_handler, & secp_ctx, & privkeys[ 1 ] , & privkeys[ 4 ] , ChannelFeatures :: from_le_bytes ( id_to_feature_flags ( 5 ) ) , 5 ) ;
3446
+ update_channel ( & net_graph_msg_handler, & secp_ctx, & privkeys[ 1 ] , UnsignedChannelUpdate {
3447
+ chain_hash : genesis_block ( Network :: Testnet ) . header . block_hash ( ) ,
3448
+ short_channel_id : 5 ,
3449
+ timestamp : 1 ,
3450
+ flags : 0 ,
3451
+ cltv_expiry_delta : ( 5 << 8 ) | 0 ,
3452
+ htlc_minimum_msat : 0 ,
3453
+ htlc_maximum_msat : OptionalField :: Absent ,
3454
+ fee_base_msat : 100 ,
3455
+ fee_proportional_millionths : 0 ,
3456
+ excess_data : Vec :: new ( )
3457
+ } ) ;
3458
+ add_or_update_node ( & net_graph_msg_handler, & secp_ctx, & privkeys[ 4 ] , NodeFeatures :: from_le_bytes ( id_to_feature_flags ( 4 ) ) , 0 ) ;
3459
+
3460
+ add_channel ( & net_graph_msg_handler, & secp_ctx, & privkeys[ 4 ] , & privkeys[ 3 ] , ChannelFeatures :: from_le_bytes ( id_to_feature_flags ( 4 ) ) , 4 ) ;
3461
+ update_channel ( & net_graph_msg_handler, & secp_ctx, & privkeys[ 4 ] , UnsignedChannelUpdate {
3462
+ chain_hash : genesis_block ( Network :: Testnet ) . header . block_hash ( ) ,
3463
+ short_channel_id : 4 ,
3464
+ timestamp : 1 ,
3465
+ flags : 0 ,
3466
+ cltv_expiry_delta : ( 4 << 8 ) | 0 ,
3467
+ htlc_minimum_msat : 0 ,
3468
+ htlc_maximum_msat : OptionalField :: Absent ,
3469
+ fee_base_msat : 0 ,
3470
+ fee_proportional_millionths : 0 ,
3471
+ excess_data : Vec :: new ( )
3472
+ } ) ;
3473
+ add_or_update_node ( & net_graph_msg_handler, & secp_ctx, & privkeys[ 3 ] , NodeFeatures :: from_le_bytes ( id_to_feature_flags ( 3 ) ) , 0 ) ;
3474
+
3475
+ add_channel ( & net_graph_msg_handler, & secp_ctx, & privkeys[ 3 ] , & privkeys[ 2 ] , ChannelFeatures :: from_le_bytes ( id_to_feature_flags ( 3 ) ) , 3 ) ;
3476
+ update_channel ( & net_graph_msg_handler, & secp_ctx, & privkeys[ 3 ] , UnsignedChannelUpdate {
3477
+ chain_hash : genesis_block ( Network :: Testnet ) . header . block_hash ( ) ,
3478
+ short_channel_id : 3 ,
3479
+ timestamp : 1 ,
3480
+ flags : 0 ,
3481
+ cltv_expiry_delta : ( 3 << 8 ) | 0 ,
3482
+ htlc_minimum_msat : 0 ,
3483
+ htlc_maximum_msat : OptionalField :: Absent ,
3484
+ fee_base_msat : 0 ,
3485
+ fee_proportional_millionths : 0 ,
3486
+ excess_data : Vec :: new ( )
3487
+ } ) ;
3488
+ add_or_update_node ( & net_graph_msg_handler, & secp_ctx, & privkeys[ 2 ] , NodeFeatures :: from_le_bytes ( id_to_feature_flags ( 2 ) ) , 0 ) ;
3489
+
3490
+ add_channel ( & net_graph_msg_handler, & secp_ctx, & privkeys[ 2 ] , & privkeys[ 4 ] , ChannelFeatures :: from_le_bytes ( id_to_feature_flags ( 2 ) ) , 2 ) ;
3491
+ update_channel ( & net_graph_msg_handler, & secp_ctx, & privkeys[ 2 ] , UnsignedChannelUpdate {
3492
+ chain_hash : genesis_block ( Network :: Testnet ) . header . block_hash ( ) ,
3493
+ short_channel_id : 2 ,
3494
+ timestamp : 1 ,
3495
+ flags : 0 ,
3496
+ cltv_expiry_delta : ( 2 << 8 ) | 0 ,
3497
+ htlc_minimum_msat : 0 ,
3498
+ htlc_maximum_msat : OptionalField :: Absent ,
3499
+ fee_base_msat : 0 ,
3500
+ fee_proportional_millionths : 0 ,
3501
+ excess_data : Vec :: new ( )
3502
+ } ) ;
3503
+
3504
+ add_channel ( & net_graph_msg_handler, & secp_ctx, & privkeys[ 4 ] , & privkeys[ 6 ] , ChannelFeatures :: from_le_bytes ( id_to_feature_flags ( 1 ) ) , 1 ) ;
3505
+ update_channel ( & net_graph_msg_handler, & secp_ctx, & privkeys[ 4 ] , UnsignedChannelUpdate {
3506
+ chain_hash : genesis_block ( Network :: Testnet ) . header . block_hash ( ) ,
3507
+ short_channel_id : 1 ,
3508
+ timestamp : 1 ,
3509
+ flags : 0 ,
3510
+ cltv_expiry_delta : ( 1 << 8 ) | 0 ,
3511
+ htlc_minimum_msat : 100 ,
3512
+ htlc_maximum_msat : OptionalField :: Absent ,
3513
+ fee_base_msat : 0 ,
3514
+ fee_proportional_millionths : 0 ,
3515
+ excess_data : Vec :: new ( )
3516
+ } ) ;
3517
+ add_or_update_node ( & net_graph_msg_handler, & secp_ctx, & privkeys[ 6 ] , NodeFeatures :: from_le_bytes ( id_to_feature_flags ( 6 ) ) , 0 ) ;
3518
+
3519
+ {
3520
+ // Now ensure the route flows simply over nodes 1 and 4 to 6.
3521
+ 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 ( ) ;
3522
+ assert_eq ! ( route. paths. len( ) , 1 ) ;
3523
+ assert_eq ! ( route. paths[ 0 ] . len( ) , 3 ) ;
3524
+
3525
+ assert_eq ! ( route. paths[ 0 ] [ 0 ] . pubkey, nodes[ 1 ] ) ;
3526
+ assert_eq ! ( route. paths[ 0 ] [ 0 ] . short_channel_id, 6 ) ;
3527
+ assert_eq ! ( route. paths[ 0 ] [ 0 ] . fee_msat, 100 ) ;
3528
+ assert_eq ! ( route. paths[ 0 ] [ 0 ] . cltv_expiry_delta, ( 5 << 8 ) | 0 ) ;
3529
+ assert_eq ! ( route. paths[ 0 ] [ 0 ] . node_features. le_flags( ) , & id_to_feature_flags( 1 ) ) ;
3530
+ assert_eq ! ( route. paths[ 0 ] [ 0 ] . channel_features. le_flags( ) , & id_to_feature_flags( 6 ) ) ;
3531
+
3532
+ assert_eq ! ( route. paths[ 0 ] [ 1 ] . pubkey, nodes[ 4 ] ) ;
3533
+ assert_eq ! ( route. paths[ 0 ] [ 1 ] . short_channel_id, 5 ) ;
3534
+ assert_eq ! ( route. paths[ 0 ] [ 1 ] . fee_msat, 0 ) ;
3535
+ assert_eq ! ( route. paths[ 0 ] [ 1 ] . cltv_expiry_delta, ( 1 << 8 ) | 0 ) ;
3536
+ assert_eq ! ( route. paths[ 0 ] [ 1 ] . node_features. le_flags( ) , & id_to_feature_flags( 4 ) ) ;
3537
+ assert_eq ! ( route. paths[ 0 ] [ 1 ] . channel_features. le_flags( ) , & id_to_feature_flags( 5 ) ) ;
3538
+
3539
+ assert_eq ! ( route. paths[ 0 ] [ 2 ] . pubkey, nodes[ 6 ] ) ;
3540
+ assert_eq ! ( route. paths[ 0 ] [ 2 ] . short_channel_id, 1 ) ;
3541
+ assert_eq ! ( route. paths[ 0 ] [ 2 ] . fee_msat, 10_000 ) ;
3542
+ assert_eq ! ( route. paths[ 0 ] [ 2 ] . cltv_expiry_delta, 42 ) ;
3543
+ assert_eq ! ( route. paths[ 0 ] [ 2 ] . node_features. le_flags( ) , & id_to_feature_flags( 6 ) ) ;
3544
+ assert_eq ! ( route. paths[ 0 ] [ 2 ] . channel_features. le_flags( ) , & id_to_feature_flags( 1 ) ) ;
3545
+ }
3546
+ }
3547
+
3548
+
3403
3549
#[ test]
3404
3550
fn exact_fee_liquidity_limit ( ) {
3405
3551
// Test that if, while walking the graph, we find a hop that has exactly enough liquidity
0 commit comments