@@ -425,13 +425,18 @@ pub fn get_route<L: Deref>(our_node_id: &PublicKey, network: &NetworkGraph, paye
425
425
let mut targets = BinaryHeap :: new ( ) ; //TODO: Do we care about switching to eg Fibbonaci heap?
426
426
let mut dist = HashMap :: with_capacity ( network. get_nodes ( ) . len ( ) ) ;
427
427
428
+ // During routing, if we ignore a path due to an htlc_minimum_msat limit, we set this,
429
+ // indicating that we may wish to try again with a higher value, potentially paying to meet an
430
+ // htlc_minimum with extra fees while still finding a cheaper path.
431
+ let mut hit_minimum_limit;
432
+
428
433
// When arranging a route, we select multiple paths so that we can make a multi-path payment.
429
- // Don't stop searching for paths when we think they're
430
- // sufficient to transfer a given value aggregately.
431
- // Search for higher value, so that we collect many more paths,
432
- // and then select the best combination among them.
434
+ // We start with a path_value of the exact amount we want, and if that generates a route we may
435
+ // return it immediately. Otherwise, we don't stop searching for paths until we have 3x the
436
+ // amount we want in total across paths, selecting the best subset at the end.
433
437
const ROUTE_CAPACITY_PROVISION_FACTOR : u64 = 3 ;
434
438
let recommended_value_msat = final_value_msat * ROUTE_CAPACITY_PROVISION_FACTOR as u64 ;
439
+ let mut path_value_msat = final_value_msat;
435
440
436
441
// Allow MPP only if we have a features set from somewhere that indicates the payee supports
437
442
// it. If the payee supports it they're supposed to include it in the invoice, so that should
@@ -555,8 +560,9 @@ pub fn get_route<L: Deref>(our_node_id: &PublicKey, network: &NetworkGraph, paye
555
560
// Since we're choosing amount_to_transfer_over_msat as maximum possible, it can
556
561
// be only reduced later (not increased), so this channel should just be skipped
557
562
// as not sufficient.
558
- // TODO: Explore simply adding fee to hit htlc_minimum_msat
559
- if contributes_sufficient_value && amount_to_transfer_over_msat >= $directional_info. htlc_minimum_msat {
563
+ if amount_to_transfer_over_msat < $directional_info. htlc_minimum_msat {
564
+ hit_minimum_limit = true ;
565
+ } else if contributes_sufficient_value {
560
566
// Note that low contribution here (limited by available_liquidity_msat)
561
567
// might violate htlc_minimum_msat on the hops which are next along the
562
568
// payment path (upstream to the payee). To avoid that, we recompute path
@@ -743,12 +749,13 @@ pub fn get_route<L: Deref>(our_node_id: &PublicKey, network: &NetworkGraph, paye
743
749
// the further iterations of path finding. Also don't erase first_hop_targets.
744
750
targets. clear ( ) ;
745
751
dist. clear ( ) ;
752
+ hit_minimum_limit = false ;
746
753
747
754
// If first hop is a private channel and the only way to reach the payee, this is the only
748
755
// place where it could be added.
749
756
if first_hops. is_some ( ) {
750
757
if let Some ( & ( ref first_hop, ref features, ref outbound_capacity_msat) ) = first_hop_targets. get ( & payee) {
751
- add_entry ! ( first_hop, * our_node_id, payee, dummy_directional_info, Some ( outbound_capacity_msat / 1000 ) , features. to_context( ) , 0 , recommended_value_msat ) ;
758
+ add_entry ! ( first_hop, * our_node_id, payee, dummy_directional_info, Some ( outbound_capacity_msat / 1000 ) , features. to_context( ) , 0 , path_value_msat ) ;
752
759
}
753
760
}
754
761
@@ -761,7 +768,7 @@ pub fn get_route<L: Deref>(our_node_id: &PublicKey, network: &NetworkGraph, paye
761
768
// If not, targets.pop() will not even let us enter the loop in step 2.
762
769
None => { } ,
763
770
Some ( node) => {
764
- add_entries_to_cheapest_to_target_node ! ( node, payee, 0 , recommended_value_msat ) ;
771
+ add_entries_to_cheapest_to_target_node ! ( node, payee, 0 , path_value_msat ) ;
765
772
} ,
766
773
}
767
774
@@ -780,7 +787,7 @@ pub fn get_route<L: Deref>(our_node_id: &PublicKey, network: &NetworkGraph, paye
780
787
// bit lazy here. In the future, we should pull them out via our
781
788
// ChannelManager, but there's no reason to waste the space until we
782
789
// need them.
783
- add_entry ! ( first_hop, * our_node_id , hop. src_node_id, dummy_directional_info, Some ( outbound_capacity_msat / 1000 ) , features. to_context( ) , 0 , recommended_value_msat ) ;
790
+ add_entry ! ( first_hop, * our_node_id , hop. src_node_id, dummy_directional_info, Some ( outbound_capacity_msat / 1000 ) , features. to_context( ) , 0 , path_value_msat ) ;
784
791
true
785
792
} else {
786
793
// In any other case, only add the hop if the source is in the regular network
@@ -800,7 +807,7 @@ pub fn get_route<L: Deref>(our_node_id: &PublicKey, network: &NetworkGraph, paye
800
807
htlc_maximum_msat : hop. htlc_maximum_msat ,
801
808
fees : hop. fees ,
802
809
} ;
803
- add_entry ! ( hop. short_channel_id, hop. src_node_id, payee, directional_info, None :: <u64 >, ChannelFeatures :: empty( ) , 0 , recommended_value_msat ) ;
810
+ add_entry ! ( hop. short_channel_id, hop. src_node_id, payee, directional_info, None :: <u64 >, ChannelFeatures :: empty( ) , 0 , path_value_msat ) ;
804
811
}
805
812
}
806
813
@@ -925,13 +932,23 @@ pub fn get_route<L: Deref>(our_node_id: &PublicKey, network: &NetworkGraph, paye
925
932
}
926
933
927
934
// Step (3).
928
- // Stop either when recommended value is reached,
929
- // or if during last iteration no new path was found .
930
- // In the latter case, making another path finding attempt could not help,
931
- // because we deterministically terminate the search due to low liquidity.
935
+ // Stop either when the recommended value is reached or if no new path was found in this
936
+ // iteration.
937
+ // In the latter case, making another path finding attempt won't help,
938
+ // because we deterministically terminated the search due to low liquidity.
932
939
if already_collected_value_msat >= recommended_value_msat || !found_new_path {
933
940
break ' paths_collection;
934
941
}
942
+ // Further, if this was our first walk of the graph, and we weren't limited by an
943
+ // htlc_minim_msat, return immediately because this path should suffice. If we were limited
944
+ // by an htlc_minimum_msat value, find another path with a higher value, potentially
945
+ // allowing us to pay fees to meet the htlc_minimum while still keeping a lower total fee.
946
+ if already_collected_value_msat == final_value_msat && payment_paths. len ( ) == 1 {
947
+ if !hit_minimum_limit {
948
+ break ' paths_collection;
949
+ }
950
+ path_value_msat = recommended_value_msat;
951
+ }
935
952
}
936
953
937
954
// Step (4).
@@ -3434,6 +3451,78 @@ mod tests {
3434
3451
assert_eq ! ( total_amount_paid_msat, 90_000 ) ;
3435
3452
}
3436
3453
}
3454
+
3455
+ #[ test]
3456
+ fn exact_fee_liquidity_limit ( ) {
3457
+ // Test that if, while walking the graph, we find a hop that has exactly enough liquidity
3458
+ // for us, including later hop fees, we take it. In the first version of our MPP algorithm
3459
+ // we calculated fees on a higher value, resulting in us ignoring such paths.
3460
+ let ( secp_ctx, net_graph_msg_handler, _, logger) = build_graph ( ) ;
3461
+ let ( our_privkey, our_id, privkeys, nodes) = get_nodes ( & secp_ctx) ;
3462
+
3463
+ // We modify the graph to set the htlc_maximum of channel 2 to below the value we wish to
3464
+ // send.
3465
+ update_channel ( & net_graph_msg_handler, & secp_ctx, & our_privkey, UnsignedChannelUpdate {
3466
+ chain_hash : genesis_block ( Network :: Testnet ) . header . block_hash ( ) ,
3467
+ short_channel_id : 2 ,
3468
+ timestamp : 2 ,
3469
+ flags : 0 ,
3470
+ cltv_expiry_delta : 0 ,
3471
+ htlc_minimum_msat : 0 ,
3472
+ htlc_maximum_msat : OptionalField :: Present ( 85_000 ) ,
3473
+ fee_base_msat : 0 ,
3474
+ fee_proportional_millionths : 0 ,
3475
+ excess_data : Vec :: new ( )
3476
+ } ) ;
3477
+
3478
+ update_channel ( & net_graph_msg_handler, & secp_ctx, & our_privkey, UnsignedChannelUpdate {
3479
+ chain_hash : genesis_block ( Network :: Testnet ) . header . block_hash ( ) ,
3480
+ short_channel_id : 12 ,
3481
+ timestamp : 2 ,
3482
+ flags : 0 ,
3483
+ cltv_expiry_delta : ( 4 << 8 ) | 1 ,
3484
+ htlc_minimum_msat : 0 ,
3485
+ htlc_maximum_msat : OptionalField :: Present ( 270_000 ) ,
3486
+ fee_base_msat : 0 ,
3487
+ fee_proportional_millionths : 1000000 ,
3488
+ excess_data : Vec :: new ( )
3489
+ } ) ;
3490
+
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 : 13 ,
3494
+ timestamp : 2 ,
3495
+ flags : 1 | 2 ,
3496
+ cltv_expiry_delta : ( 13 << 8 ) | 2 ,
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
+ {
3505
+ // Now, attempt to route 125 sats (just a bit below the capacity of 3 channels).
3506
+ // Our algorithm should provide us with these 3 paths.
3507
+ let route = get_route ( & our_id, & net_graph_msg_handler. network_graph . read ( ) . unwrap ( ) , & nodes[ 2 ] , None , None , & Vec :: new ( ) , 90_000 , 42 , Arc :: clone ( & logger) ) . unwrap ( ) ;
3508
+ assert_eq ! ( route. paths. len( ) , 1 ) ;
3509
+ assert_eq ! ( route. paths[ 0 ] . len( ) , 2 ) ;
3510
+
3511
+ assert_eq ! ( route. paths[ 0 ] [ 0 ] . pubkey, nodes[ 7 ] ) ;
3512
+ assert_eq ! ( route. paths[ 0 ] [ 0 ] . short_channel_id, 12 ) ;
3513
+ assert_eq ! ( route. paths[ 0 ] [ 0 ] . fee_msat, 90_000 * 2 ) ;
3514
+ assert_eq ! ( route. paths[ 0 ] [ 0 ] . cltv_expiry_delta, ( 13 << 8 ) | 1 ) ;
3515
+ assert_eq ! ( route. paths[ 0 ] [ 0 ] . node_features. le_flags( ) , & id_to_feature_flags( 8 ) ) ;
3516
+ assert_eq ! ( route. paths[ 0 ] [ 0 ] . channel_features. le_flags( ) , & id_to_feature_flags( 12 ) ) ;
3517
+
3518
+ assert_eq ! ( route. paths[ 0 ] [ 1 ] . pubkey, nodes[ 2 ] ) ;
3519
+ assert_eq ! ( route. paths[ 0 ] [ 1 ] . short_channel_id, 13 ) ;
3520
+ assert_eq ! ( route. paths[ 0 ] [ 1 ] . fee_msat, 90_000 ) ;
3521
+ assert_eq ! ( route. paths[ 0 ] [ 1 ] . cltv_expiry_delta, 42 ) ;
3522
+ assert_eq ! ( route. paths[ 0 ] [ 1 ] . node_features. le_flags( ) , & id_to_feature_flags( 3 ) ) ;
3523
+ assert_eq ! ( route. paths[ 0 ] [ 1 ] . channel_features. le_flags( ) , & id_to_feature_flags( 13 ) ) ;
3524
+ }
3525
+ }
3437
3526
}
3438
3527
3439
3528
#[ cfg( all( test, feature = "unstable" ) ) ]
0 commit comments