Skip to content

Commit b67d879

Browse files
committed
WIP mpp
1 parent 992cc63 commit b67d879

File tree

1 file changed

+168
-42
lines changed

1 file changed

+168
-42
lines changed

lightning/src/routing/router.rs

Lines changed: 168 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ use util::ser::{Writeable, Readable};
2222
use util::logger::Logger;
2323

2424
use std::cmp;
25-
use std::collections::{HashMap,BinaryHeap};
25+
use std::collections::{HashMap, BinaryHeap, HashSet};
2626
use std::ops::Deref;
2727
use std::hash::{Hash, Hasher};
2828

@@ -138,7 +138,7 @@ pub struct RouteHint {
138138
pub htlc_maximum_msat: Option<u64>,
139139
}
140140

141-
#[derive(Eq, PartialEq)]
141+
#[derive(Eq, PartialEq, Clone)]
142142
struct RouteGraphNode {
143143
pubkey: PublicKey,
144144
lowest_fee_to_peer_through_node: u64,
@@ -212,6 +212,7 @@ pub fn get_route<L: Deref>(our_node_id: &PublicKey, network: &NetworkGraph, targ
212212

213213
let mut targets = BinaryHeap::new(); //TODO: Do we care about switching to eg Fibbonaci heap?
214214
let mut dist = HashMap::with_capacity(network.get_nodes().len());
215+
let recommended_available_msat = final_value_msat * 4;
215216

216217
let mut first_hop_targets = HashMap::with_capacity(if first_hops.is_some() { first_hops.as_ref().unwrap().len() } else { 0 });
217218
if let Some(hops) = first_hops {
@@ -236,6 +237,9 @@ pub fn get_route<L: Deref>(our_node_id: &PublicKey, network: &NetworkGraph, targ
236237
}
237238
}
238239

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+
239243
macro_rules! add_entry {
240244
// Adds entry which goes from $src_node_id to $dest_node_id
241245
// 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
249253
{
250254
let mut total_fee = $starting_fee_msat as u64;
251255

252-
let mut available_msat = $capacity_sats;
256+
let mut available_capacity_msat = $capacity_sats;
253257
if let Some(htlc_maximum_msat) = $directional_info.htlc_maximum_msat {
254258
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));
256260
} 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)
258269
}
259270
}
260271

@@ -279,6 +290,7 @@ pub fn get_route<L: Deref>(our_node_id: &PublicKey, network: &NetworkGraph, targ
279290
cltv_expiry_delta: 0,
280291
},
281292
None,
293+
$directional_info.fees,
282294
)
283295
});
284296
if $src_node_id != *our_node_id {
@@ -308,13 +320,17 @@ pub fn get_route<L: Deref>(our_node_id: &PublicKey, network: &NetworkGraph, targ
308320
fee_msat: new_fee, // This field is ignored on the last-hop anyway
309321
cltv_expiry_delta: $directional_info.cltv_expiry_delta as u32,
310322
};
311-
old_entry.4 = available_msat;
323+
old_entry.4 = available_capacity_msat;
324+
old_entry.5 = $directional_info.fees;
312325
}
313326
}
314327
}
315328
};
316329
}
317330

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+
318334
macro_rules! add_entries_to_cheapest_to_target_node {
319335
( $node: expr, $node_id: expr, $fee_to_target_msat: expr ) => {
320336
if first_hops.is_some() {
@@ -332,6 +348,9 @@ pub fn get_route<L: Deref>(our_node_id: &PublicKey, network: &NetworkGraph, targ
332348

333349
if !features.requires_unknown_bits() {
334350
for chan_id in $node.channels.iter() {
351+
if channels_to_avoid.contains(chan_id) {
352+
continue;
353+
}
335354
let chan = network.get_channels().get(chan_id).unwrap();
336355
if !chan.features.requires_unknown_bits() {
337356
if chan.node_one == *$node_id {
@@ -385,53 +404,160 @@ pub fn get_route<L: Deref>(our_node_id: &PublicKey, network: &NetworkGraph, targ
385404
}
386405
}
387406

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+
}
397435
} 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;
399457
}
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);
406471
}
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;
409494
}
410495

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+
},
418508
}
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);
424509
}
510+
tries_left -= 1;
425511

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;
431514
}
432515
}
433516

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);
435561
}
436562

437563
#[cfg(test)]

0 commit comments

Comments
 (0)