@@ -22,7 +22,7 @@ use util::ser::{Writeable, Readable};
22
22
use util:: logger:: Logger ;
23
23
24
24
use std:: cmp;
25
- use std:: collections:: { HashMap , BinaryHeap } ;
25
+ use std:: collections:: { HashMap , BinaryHeap , HashSet } ;
26
26
use std:: ops:: Deref ;
27
27
use std:: hash:: { Hash , Hasher } ;
28
28
@@ -138,7 +138,7 @@ pub struct RouteHint {
138
138
pub htlc_maximum_msat : Option < u64 > ,
139
139
}
140
140
141
- #[ derive( Eq , PartialEq ) ]
141
+ #[ derive( Eq , PartialEq , Clone ) ]
142
142
struct RouteGraphNode {
143
143
pubkey : PublicKey ,
144
144
lowest_fee_to_peer_through_node : u64 ,
@@ -212,6 +212,7 @@ pub fn get_route<L: Deref>(our_node_id: &PublicKey, network: &NetworkGraph, targ
212
212
213
213
let mut targets = BinaryHeap :: new ( ) ; //TODO: Do we care about switching to eg Fibbonaci heap?
214
214
let mut dist = HashMap :: with_capacity ( network. get_nodes ( ) . len ( ) ) ;
215
+ let recommended_available_msat = final_value_msat * 4 ;
215
216
216
217
let mut first_hop_targets = HashMap :: with_capacity ( if first_hops. is_some ( ) { first_hops. as_ref ( ) . unwrap ( ) . len ( ) } else { 0 } ) ;
217
218
if let Some ( hops) = first_hops {
@@ -236,6 +237,9 @@ pub fn get_route<L: Deref>(our_node_id: &PublicKey, network: &NetworkGraph, targ
236
237
}
237
238
}
238
239
240
+ // Track the use of channels to reduce their available capacity in the next MPP rounds.
241
+ let mut used_channels_with_amounts_msat = HashMap :: new ( ) ;
242
+
239
243
macro_rules! add_entry {
240
244
// Adds entry which goes from $src_node_id to $dest_node_id
241
245
// over the channel with id $chan_id with fees described in
@@ -249,12 +253,19 @@ pub fn get_route<L: Deref>(our_node_id: &PublicKey, network: &NetworkGraph, targ
249
253
{
250
254
let mut total_fee = $starting_fee_msat as u64 ;
251
255
252
- let mut available_msat = $capacity_sats;
256
+ let mut available_capacity_msat = $capacity_sats;
253
257
if let Some ( htlc_maximum_msat) = $directional_info. htlc_maximum_msat {
254
258
if let Some ( capacity_sats) = $capacity_sats {
255
- available_msat = Some ( cmp:: min( capacity_sats * 1000 , htlc_maximum_msat) ) ;
259
+ available_capacity_msat = Some ( cmp:: min( capacity_sats * 1000 , htlc_maximum_msat) ) ;
256
260
} else {
257
- available_msat = Some ( htlc_maximum_msat) ;
261
+ available_capacity_msat = Some ( htlc_maximum_msat) ;
262
+ }
263
+ }
264
+
265
+ if let Some ( max_available_capacity_msat) = available_capacity_msat {
266
+ if let Some ( used_amount_msat) = used_channels_with_amounts_msat. get( & $chan_id. clone( ) ) {
267
+ // Take into account amounts used in previous mpp iterations.
268
+ available_capacity_msat = Some ( max_available_capacity_msat - used_amount_msat)
258
269
}
259
270
}
260
271
@@ -279,6 +290,7 @@ pub fn get_route<L: Deref>(our_node_id: &PublicKey, network: &NetworkGraph, targ
279
290
cltv_expiry_delta: 0 ,
280
291
} ,
281
292
None ,
293
+ $directional_info. fees,
282
294
)
283
295
} ) ;
284
296
if $src_node_id != * our_node_id {
@@ -308,13 +320,17 @@ pub fn get_route<L: Deref>(our_node_id: &PublicKey, network: &NetworkGraph, targ
308
320
fee_msat: new_fee, // This field is ignored on the last-hop anyway
309
321
cltv_expiry_delta: $directional_info. cltv_expiry_delta as u32 ,
310
322
} ;
311
- old_entry. 4 = available_msat;
323
+ old_entry. 4 = available_capacity_msat;
324
+ old_entry. 5 = $directional_info. fees;
312
325
}
313
326
}
314
327
}
315
328
} ;
316
329
}
317
330
331
+ // For MPP, just retry the same dest-to-source A*, but avoid some channels with lowest capacities.
332
+ let mut channels_to_avoid = HashSet :: new ( ) ;
333
+
318
334
macro_rules! add_entries_to_cheapest_to_target_node {
319
335
( $node: expr, $node_id: expr, $fee_to_target_msat: expr ) => {
320
336
if first_hops. is_some( ) {
@@ -332,6 +348,9 @@ pub fn get_route<L: Deref>(our_node_id: &PublicKey, network: &NetworkGraph, targ
332
348
333
349
if !features. requires_unknown_bits( ) {
334
350
for chan_id in $node. channels. iter( ) {
351
+ if channels_to_avoid. contains( chan_id) {
352
+ continue ;
353
+ }
335
354
let chan = network. get_channels( ) . get( chan_id) . unwrap( ) ;
336
355
if !chan. features. requires_unknown_bits( ) {
337
356
if chan. node_one == * $node_id {
@@ -385,53 +404,160 @@ pub fn get_route<L: Deref>(our_node_id: &PublicKey, network: &NetworkGraph, targ
385
404
}
386
405
}
387
406
388
- while let Some ( RouteGraphNode { pubkey, lowest_fee_to_node, .. } ) = targets. pop ( ) {
389
- if pubkey == * our_node_id {
390
- let mut res = vec ! ( dist. remove( & our_node_id) . unwrap( ) . 3 ) ;
391
- loop {
392
- if let Some ( & ( _, ref features) ) = first_hop_targets. get ( & res. last ( ) . unwrap ( ) . pubkey ) {
393
- res. last_mut ( ) . unwrap ( ) . node_features = features. to_context ( ) ;
394
- } else if let Some ( node) = network. get_nodes ( ) . get ( & res. last ( ) . unwrap ( ) . pubkey ) {
395
- if let Some ( node_info) = node. announcement_info . as_ref ( ) {
396
- res. last_mut ( ) . unwrap ( ) . node_features = node_info. features . clone ( ) ;
407
+ let mut payment_paths = Vec :: new ( ) ;
408
+ let mut tries_left = 10 ;
409
+ let mut found_amount_msat = 0 ;
410
+ // For every MPP start with the same pre-filled data.
411
+ let prefilled_targets = targets. clone ( ) ;
412
+ let prefilled_dist = dist. clone ( ) ;
413
+
414
+ ' route_finding: loop {
415
+ targets = prefilled_targets. clone ( ) ;
416
+ dist = prefilled_dist. clone ( ) ;
417
+ ' path_finding: while let Some ( RouteGraphNode { pubkey, lowest_fee_to_node, .. } ) = targets. pop ( ) {
418
+ if pubkey == * our_node_id {
419
+ // TODO: currently pretending that always have blockchain/amount data for channel capacities.
420
+ // Should handle None better.
421
+ let mut new_entry = dist. remove ( & our_node_id) . unwrap ( ) ;
422
+ let mut res = vec ! ( new_entry. 3 . clone( ) ) ;
423
+ let mut path_bottleneck_msat = None ;
424
+ let mut cur_path_fees = Vec :: new ( ) ;
425
+ let mut smallest_channel_id = None ;
426
+ loop {
427
+ if let Some ( & ( _, ref features) ) = first_hop_targets. get ( & res. last ( ) . unwrap ( ) . pubkey ) {
428
+ res. last_mut ( ) . unwrap ( ) . node_features = features. to_context ( ) ;
429
+ } else if let Some ( node) = network. get_nodes ( ) . get ( & res. last ( ) . unwrap ( ) . pubkey ) {
430
+ if let Some ( node_info) = node. announcement_info . as_ref ( ) {
431
+ res. last_mut ( ) . unwrap ( ) . node_features = node_info. features . clone ( ) ;
432
+ } else {
433
+ res. last_mut ( ) . unwrap ( ) . node_features = NodeFeatures :: empty ( ) ;
434
+ }
397
435
} else {
398
- res. last_mut ( ) . unwrap ( ) . node_features = NodeFeatures :: empty ( ) ;
436
+ // We should be able to fill in features for everything except the last
437
+ // hop, if the last hop was provided via a BOLT 11 invoice (though we
438
+ // should be able to extend it further as BOLT 11 does have feature
439
+ // flags for the last hop node itself).
440
+ assert ! ( res. last( ) . unwrap( ) . pubkey == * target) ;
441
+ }
442
+
443
+
444
+ if let Some ( current_hop_bottleneck_msat) = new_entry. 4 {
445
+ if let Some ( full_path_bottleneck_msat) = path_bottleneck_msat {
446
+ if current_hop_bottleneck_msat < full_path_bottleneck_msat {
447
+ path_bottleneck_msat = Some ( current_hop_bottleneck_msat) ;
448
+ // Store smallest channel per path to avoid hitting it while looking
449
+ // for another path (per MPP).
450
+ smallest_channel_id = Some ( res. last ( ) . unwrap ( ) . short_channel_id ) ;
451
+ }
452
+ }
453
+ }
454
+
455
+ if let Some ( bottleneck_amount_msat) = path_bottleneck_msat {
456
+ found_amount_msat += bottleneck_amount_msat;
399
457
}
400
- } else {
401
- // We should be able to fill in features for everything except the last
402
- // hop, if the last hop was provided via a BOLT 11 invoice (though we
403
- // should be able to extend it further as BOLT 11 does have feature
404
- // flags for the last hop node itself).
405
- assert ! ( res. last( ) . unwrap( ) . pubkey == * target) ;
458
+
459
+ if res. last ( ) . unwrap ( ) . pubkey == * target {
460
+ break ;
461
+ }
462
+
463
+ new_entry = match dist. remove ( & res. last ( ) . unwrap ( ) . pubkey ) {
464
+ Some ( hop) => hop,
465
+ None => return Err ( LightningError { err : "Failed to find a non-fee-overflowing path to the given destination" . to_owned ( ) , action : ErrorAction :: IgnoreError } ) ,
466
+ } ;
467
+ res. last_mut ( ) . unwrap ( ) . fee_msat = new_entry. 3 . fee_msat ;
468
+ res. last_mut ( ) . unwrap ( ) . cltv_expiry_delta = new_entry. 3 . cltv_expiry_delta ;
469
+ res. push ( new_entry. 3 ) ;
470
+ cur_path_fees. push ( new_entry. 5 ) ;
406
471
}
407
- if res. last ( ) . unwrap ( ) . pubkey == * target {
408
- break ;
472
+ res. last_mut ( ) . unwrap ( ) . fee_msat = final_value_msat;
473
+ res. last_mut ( ) . unwrap ( ) . cltv_expiry_delta = final_cltv;
474
+
475
+ // Recompute fee considering the bottleneck.
476
+ // Also remember used amounts per channels to avoid relying on them in the next MPP iterations.
477
+ let mut recomputed_fee_msat = 0 ;
478
+ let mut total_proportional_fee_millionths = 0 ;
479
+ let mut i = 0 ;
480
+ for fees in cur_path_fees. clone ( ) {
481
+ let current_hop_amount_msat = path_bottleneck_msat. unwrap ( ) . checked_add ( recomputed_fee_msat) ;
482
+ let proportional_fee_millions = current_hop_amount_msat. unwrap ( ) . checked_mul ( fees. proportional_millionths as u64 ) ;
483
+ let mut hop_to_update = res. get_mut ( i) . unwrap ( ) ;
484
+ if let Some ( new_fee) = proportional_fee_millions. and_then ( |part| {
485
+ ( fees. base_msat as u64 ) . checked_add ( part / 1000000 ) } ) {
486
+
487
+ recomputed_fee_msat += new_fee;
488
+ total_proportional_fee_millionths += fees. proportional_millionths ;
489
+ hop_to_update. fee_msat = new_fee;
490
+ }
491
+
492
+ * used_channels_with_amounts_msat. entry ( hop_to_update. short_channel_id ) . or_insert ( 0 ) += path_bottleneck_msat. unwrap ( ) ;
493
+ i += 1 ;
409
494
}
410
495
411
- let new_entry = match dist. remove ( & res. last ( ) . unwrap ( ) . pubkey ) {
412
- Some ( hop) => hop. 3 ,
413
- None => return Err ( LightningError { err : "Failed to find a non-fee-overflowing path to the given destination" . to_owned ( ) , action : ErrorAction :: IgnoreError } ) ,
414
- } ;
415
- res. last_mut ( ) . unwrap ( ) . fee_msat = new_entry. fee_msat ;
416
- res. last_mut ( ) . unwrap ( ) . cltv_expiry_delta = new_entry. cltv_expiry_delta ;
417
- res. push ( new_entry) ;
496
+ payment_paths. push ( ( res, path_bottleneck_msat, total_proportional_fee_millionths, recomputed_fee_msat) ) ;
497
+ if let Some ( chan_id_to_avoid) = smallest_channel_id {
498
+ channels_to_avoid. insert ( chan_id_to_avoid) ;
499
+ }
500
+ break ' path_finding;
501
+ }
502
+
503
+ match network. get_nodes ( ) . get ( & pubkey) {
504
+ None => { } ,
505
+ Some ( node) => {
506
+ add_entries_to_cheapest_to_target_node ! ( node, & pubkey, lowest_fee_to_node) ;
507
+ } ,
418
508
}
419
- res. last_mut ( ) . unwrap ( ) . fee_msat = final_value_msat;
420
- res. last_mut ( ) . unwrap ( ) . cltv_expiry_delta = final_cltv;
421
- let route = Route { paths : vec ! [ res] } ;
422
- log_trace ! ( logger, "Got route: {}" , log_route!( route) ) ;
423
- return Ok ( route) ;
424
509
}
510
+ tries_left -= 1 ;
425
511
426
- match network. get_nodes ( ) . get ( & pubkey) {
427
- None => { } ,
428
- Some ( node) => {
429
- add_entries_to_cheapest_to_target_node ! ( node, & pubkey, lowest_fee_to_node) ;
430
- } ,
512
+ if tries_left == 0 || found_amount_msat >= recommended_available_msat {
513
+ break ' route_finding;
431
514
}
432
515
}
433
516
434
- Err ( LightningError { err : "Failed to find a path to the given destination" . to_owned ( ) , action : ErrorAction :: IgnoreError } )
517
+ if payment_paths. len ( ) == 0 {
518
+ return Err ( LightningError { err : "Failed to find a path to the given destination" . to_owned ( ) , action : ErrorAction :: IgnoreError } ) ;
519
+ }
520
+
521
+ // Sort by total fees and take the best paths.
522
+ payment_paths. sort_by_key ( |k| k. 3 ) ;
523
+ if payment_paths. len ( ) > 50 {
524
+ payment_paths. resize ( 50 , ( Vec :: new ( ) , None , 0 , 0 ) ) ;
525
+ }
526
+
527
+ let mut drawn_routes_with_fees = Vec :: new ( ) ;
528
+ for i in 0 ..10 {
529
+ let mut cur_route_with_fees = Vec :: new ( ) ;
530
+ let mut aggregate_amount_msat = 0 ;
531
+ let cur_payment_paths = & payment_paths[ i..i+10 ] ; // TODO: real random sampling to combine paths into random routes
532
+ let mut aggregate_fee_msat = 0 ;
533
+
534
+
535
+ for ( path, path_bottleneck_msat, total_proportional_fee_millionths, fee_msat) in cur_payment_paths {
536
+ cur_route_with_fees. push ( ( path, path_bottleneck_msat, total_proportional_fee_millionths, fee_msat) ) ;
537
+ aggregate_amount_msat += path_bottleneck_msat. unwrap ( ) ;
538
+ aggregate_fee_msat += fee_msat;
539
+ if aggregate_amount_msat > final_value_msat {
540
+ break ;
541
+ }
542
+ }
543
+ cur_route_with_fees. sort_by_key ( |k| k. 2 ) ; // sort by total proportional fee.
544
+ drawn_routes_with_fees. push ( ( cur_route_with_fees, aggregate_fee_msat) )
545
+ }
546
+
547
+
548
+ // TODO reduce amount in the last path (sorted by proportional fee) to not overshoot and adjust fees.
549
+
550
+ // Select the best route by total fee.
551
+ drawn_routes_with_fees. sort_by_key ( |k| k. 1 ) ;
552
+ let mut selected_paths = Vec :: < Vec :: < RouteHop > > :: new ( ) ;
553
+ for ( path, amount, _, _) in & drawn_routes_with_fees. first ( ) . unwrap ( ) . 0 {
554
+ selected_paths. push ( path. to_vec ( ) ) ;
555
+ }
556
+
557
+ // TODO add amount per path so that the caller knows how to distribute the amount being sent among MPPs.
558
+ let route = Route { paths : selected_paths } ;
559
+ log_trace ! ( logger, "Got route: {}" , log_route!( route) ) ;
560
+ return Ok ( route) ;
435
561
}
436
562
437
563
#[ cfg( test) ]
0 commit comments