Skip to content

Commit 0c6974b

Browse files
authored
Merge pull request #1456 from jkczyz/2022-04-effective-capacity
Use `EffectiveCapacity` in `Score` trait
2 parents 36817e0 + 4715d90 commit 0c6974b

File tree

4 files changed

+531
-208
lines changed

4 files changed

+531
-208
lines changed

lightning-invoice/src/payment.rs

+4-3
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,9 @@
3838
//! # use lightning::ln::{PaymentHash, PaymentPreimage, PaymentSecret};
3939
//! # use lightning::ln::channelmanager::{ChannelDetails, PaymentId, PaymentSendFailure};
4040
//! # use lightning::ln::msgs::LightningError;
41-
//! # use lightning::routing::scoring::Score;
4241
//! # use lightning::routing::network_graph::NodeId;
4342
//! # use lightning::routing::router::{Route, RouteHop, RouteParameters};
43+
//! # use lightning::routing::scoring::{ChannelUsage, Score};
4444
//! # use lightning::util::events::{Event, EventHandler, EventsProvider};
4545
//! # use lightning::util::logger::{Logger, Record};
4646
//! # use lightning::util::ser::{Writeable, Writer};
@@ -90,7 +90,7 @@
9090
//! # }
9191
//! # impl Score for FakeScorer {
9292
//! # fn channel_penalty_msat(
93-
//! # &self, _short_channel_id: u64, _send_amt: u64, _chan_amt: u64, _source: &NodeId, _target: &NodeId
93+
//! # &self, _short_channel_id: u64, _source: &NodeId, _target: &NodeId, _usage: ChannelUsage
9494
//! # ) -> u64 { 0 }
9595
//! # fn payment_path_failed(&mut self, _path: &[&RouteHop], _short_channel_id: u64) {}
9696
//! # fn payment_path_successful(&mut self, _path: &[&RouteHop]) {}
@@ -604,6 +604,7 @@ mod tests {
604604
use lightning::ln::msgs::{ChannelMessageHandler, ErrorAction, LightningError};
605605
use lightning::routing::network_graph::NodeId;
606606
use lightning::routing::router::{PaymentParameters, Route, RouteHop};
607+
use lightning::routing::scoring::ChannelUsage;
607608
use lightning::util::test_utils::TestLogger;
608609
use lightning::util::errors::APIError;
609610
use lightning::util::events::{Event, EventsProvider, MessageSendEvent, MessageSendEventsProvider};
@@ -1444,7 +1445,7 @@ mod tests {
14441445

14451446
impl Score for TestScorer {
14461447
fn channel_penalty_msat(
1447-
&self, _short_channel_id: u64, _send_amt: u64, _chan_amt: u64, _source: &NodeId, _target: &NodeId
1448+
&self, _short_channel_id: u64, _source: &NodeId, _target: &NodeId, _usage: ChannelUsage
14481449
) -> u64 { 0 }
14491450

14501451
fn payment_path_failed(&mut self, actual_path: &[&RouteHop], actual_short_channel_id: u64) {

lightning/src/routing/network_graph.rs

+40-17
Original file line numberDiff line numberDiff line change
@@ -695,7 +695,7 @@ impl ChannelInfo {
695695
return None;
696696
}
697697
};
698-
Some((DirectedChannelInfo { channel: self, direction }, source))
698+
Some((DirectedChannelInfo::new(self, direction), source))
699699
}
700700

701701
/// Returns a [`DirectedChannelInfo`] for the channel directed from the given `source` to a
@@ -710,7 +710,7 @@ impl ChannelInfo {
710710
return None;
711711
}
712712
};
713-
Some((DirectedChannelInfo { channel: self, direction }, target))
713+
Some((DirectedChannelInfo::new(self, direction), target))
714714
}
715715
}
716716

@@ -739,35 +739,53 @@ impl_writeable_tlv_based!(ChannelInfo, {
739739
pub struct DirectedChannelInfo<'a> {
740740
channel: &'a ChannelInfo,
741741
direction: Option<&'a ChannelUpdateInfo>,
742+
htlc_maximum_msat: u64,
743+
effective_capacity: EffectiveCapacity,
742744
}
743745

744746
impl<'a> DirectedChannelInfo<'a> {
747+
#[inline]
748+
fn new(channel: &'a ChannelInfo, direction: Option<&'a ChannelUpdateInfo>) -> Self {
749+
let htlc_maximum_msat = direction.and_then(|direction| direction.htlc_maximum_msat);
750+
let capacity_msat = channel.capacity_sats.map(|capacity_sats| capacity_sats * 1000);
751+
752+
let (htlc_maximum_msat, effective_capacity) = match (htlc_maximum_msat, capacity_msat) {
753+
(Some(amount_msat), Some(capacity_msat)) => {
754+
let htlc_maximum_msat = cmp::min(amount_msat, capacity_msat);
755+
(htlc_maximum_msat, EffectiveCapacity::Total { capacity_msat })
756+
},
757+
(Some(amount_msat), None) => {
758+
(amount_msat, EffectiveCapacity::MaximumHTLC { amount_msat })
759+
},
760+
(None, Some(capacity_msat)) => {
761+
(capacity_msat, EffectiveCapacity::Total { capacity_msat })
762+
},
763+
(None, None) => (EffectiveCapacity::Unknown.as_msat(), EffectiveCapacity::Unknown),
764+
};
765+
766+
Self {
767+
channel, direction, htlc_maximum_msat, effective_capacity
768+
}
769+
}
770+
745771
/// Returns information for the channel.
746772
pub fn channel(&self) -> &'a ChannelInfo { self.channel }
747773

748774
/// Returns information for the direction.
749775
pub fn direction(&self) -> Option<&'a ChannelUpdateInfo> { self.direction }
750776

777+
/// Returns the maximum HTLC amount allowed over the channel in the direction.
778+
pub fn htlc_maximum_msat(&self) -> u64 {
779+
self.htlc_maximum_msat
780+
}
781+
751782
/// Returns the [`EffectiveCapacity`] of the channel in the direction.
752783
///
753784
/// This is either the total capacity from the funding transaction, if known, or the
754785
/// `htlc_maximum_msat` for the direction as advertised by the gossip network, if known,
755-
/// whichever is smaller.
786+
/// otherwise.
756787
pub fn effective_capacity(&self) -> EffectiveCapacity {
757-
let capacity_msat = self.channel.capacity_sats.map(|capacity_sats| capacity_sats * 1000);
758-
self.direction
759-
.and_then(|direction| direction.htlc_maximum_msat)
760-
.map(|max_htlc_msat| {
761-
let capacity_msat = capacity_msat.unwrap_or(u64::max_value());
762-
if max_htlc_msat < capacity_msat {
763-
EffectiveCapacity::MaximumHTLC { amount_msat: max_htlc_msat }
764-
} else {
765-
EffectiveCapacity::Total { capacity_msat }
766-
}
767-
})
768-
.or_else(|| capacity_msat.map(|capacity_msat|
769-
EffectiveCapacity::Total { capacity_msat }))
770-
.unwrap_or(EffectiveCapacity::Unknown)
788+
self.effective_capacity
771789
}
772790

773791
/// Returns `Some` if [`ChannelUpdateInfo`] is available in the direction.
@@ -805,6 +823,10 @@ impl<'a> DirectedChannelInfoWithUpdate<'a> {
805823
/// Returns the [`EffectiveCapacity`] of the channel in the direction.
806824
#[inline]
807825
pub(super) fn effective_capacity(&self) -> EffectiveCapacity { self.inner.effective_capacity() }
826+
827+
/// Returns the maximum HTLC amount allowed over the channel in the direction.
828+
#[inline]
829+
pub(super) fn htlc_maximum_msat(&self) -> u64 { self.inner.htlc_maximum_msat() }
808830
}
809831

810832
impl<'a> fmt::Debug for DirectedChannelInfoWithUpdate<'a> {
@@ -817,6 +839,7 @@ impl<'a> fmt::Debug for DirectedChannelInfoWithUpdate<'a> {
817839
///
818840
/// While this may be smaller than the actual channel capacity, amounts greater than
819841
/// [`Self::as_msat`] should not be routed through the channel.
842+
#[derive(Clone, Copy)]
820843
pub enum EffectiveCapacity {
821844
/// The available liquidity in the channel known from being a channel counterparty, and thus a
822845
/// direct hop.

lightning/src/routing/router.rs

+80-42
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use bitcoin::secp256k1::PublicKey;
1717
use ln::channelmanager::ChannelDetails;
1818
use ln::features::{ChannelFeatures, InvoiceFeatures, NodeFeatures};
1919
use ln::msgs::{DecodeError, ErrorAction, LightningError, MAX_VALUE_MSAT};
20-
use routing::scoring::Score;
20+
use routing::scoring::{ChannelUsage, Score};
2121
use routing::network_graph::{DirectedChannelInfoWithUpdate, EffectiveCapacity, NetworkGraph, ReadOnlyNetworkGraph, NodeId, RoutingFees};
2222
use util::ser::{Writeable, Readable};
2323
use util::logger::{Level, Logger};
@@ -414,6 +414,16 @@ impl<'a> CandidateRouteHop<'a> {
414414
}
415415
}
416416

417+
fn htlc_maximum_msat(&self) -> u64 {
418+
match self {
419+
CandidateRouteHop::FirstHop { details } => details.next_outbound_htlc_limit_msat,
420+
CandidateRouteHop::PublicHop { info, .. } => info.htlc_maximum_msat(),
421+
CandidateRouteHop::PrivateHop { hint } => {
422+
hint.htlc_maximum_msat.unwrap_or(u64::max_value())
423+
},
424+
}
425+
}
426+
417427
fn fees(&self) -> RoutingFees {
418428
match self {
419429
CandidateRouteHop::FirstHop { .. } => RoutingFees {
@@ -481,7 +491,8 @@ struct PathBuildingHop<'a> {
481491

482492
impl<'a> core::fmt::Debug for PathBuildingHop<'a> {
483493
fn fmt(&self, f: &mut core::fmt::Formatter) -> Result<(), core::fmt::Error> {
484-
f.debug_struct("PathBuildingHop")
494+
let mut debug_struct = f.debug_struct("PathBuildingHop");
495+
debug_struct
485496
.field("node_id", &self.node_id)
486497
.field("short_channel_id", &self.candidate.short_channel_id())
487498
.field("total_fee_msat", &self.total_fee_msat)
@@ -490,8 +501,11 @@ impl<'a> core::fmt::Debug for PathBuildingHop<'a> {
490501
.field("total_fee_msat - (next_hops_fee_msat + hop_use_fee_msat)", &(&self.total_fee_msat - (&self.next_hops_fee_msat + &self.hop_use_fee_msat)))
491502
.field("path_penalty_msat", &self.path_penalty_msat)
492503
.field("path_htlc_minimum_msat", &self.path_htlc_minimum_msat)
493-
.field("cltv_expiry_delta", &self.candidate.cltv_expiry_delta())
494-
.finish()
504+
.field("cltv_expiry_delta", &self.candidate.cltv_expiry_delta());
505+
#[cfg(all(not(feature = "_bench_unstable"), any(test, fuzzing)))]
506+
let debug_struct = debug_struct
507+
.field("value_contribution_msat", &self.value_contribution_msat);
508+
debug_struct.finish()
495509
}
496510
}
497511

@@ -830,12 +844,12 @@ where L::Target: Logger {
830844
let recommended_value_msat = final_value_msat * ROUTE_CAPACITY_PROVISION_FACTOR as u64;
831845
let mut path_value_msat = final_value_msat;
832846

833-
// We don't want multiple paths (as per MPP) share liquidity of the same channels.
834-
// This map allows paths to be aware of the channel use by other paths in the same call.
835-
// This would help to make a better path finding decisions and not "overbook" channels.
836-
// It is unaware of the directions (except for `next_outbound_htlc_limit_msat` in
837-
// `first_hops`).
838-
let mut bookkept_channels_liquidity_available_msat = HashMap::with_capacity(network_nodes.len());
847+
// Keep track of how much liquidity has been used in selected channels. Used to determine
848+
// if the channel can be used by additional MPP paths or to inform path finding decisions. It is
849+
// aware of direction *only* to ensure that the correct htlc_maximum_msat value is used. Hence,
850+
// liquidity used in one direction will not offset any used in the opposite direction.
851+
let mut used_channel_liquidities: HashMap<(u64, bool), u64> =
852+
HashMap::with_capacity(network_nodes.len());
839853

840854
// Keeping track of how much value we already collected across other paths. Helps to decide:
841855
// - how much a new path should be transferring (upper bound);
@@ -885,9 +899,7 @@ where L::Target: Logger {
885899
// - for first and last hops early in get_route
886900
if $src_node_id != $dest_node_id {
887901
let short_channel_id = $candidate.short_channel_id();
888-
let available_liquidity_msat = bookkept_channels_liquidity_available_msat
889-
.entry(short_channel_id)
890-
.or_insert_with(|| $candidate.effective_capacity().as_msat());
902+
let htlc_maximum_msat = $candidate.htlc_maximum_msat();
891903

892904
// It is tricky to subtract $next_hops_fee_msat from available liquidity here.
893905
// It may be misleading because we might later choose to reduce the value transferred
@@ -896,7 +908,14 @@ where L::Target: Logger {
896908
// fees caused by one expensive channel, but then this channel could have been used
897909
// if the amount being transferred over this path is lower.
898910
// We do this for now, but this is a subject for removal.
899-
if let Some(available_value_contribution_msat) = available_liquidity_msat.checked_sub($next_hops_fee_msat) {
911+
if let Some(mut available_value_contribution_msat) = htlc_maximum_msat.checked_sub($next_hops_fee_msat) {
912+
let used_liquidity_msat = used_channel_liquidities
913+
.get(&(short_channel_id, $src_node_id < $dest_node_id))
914+
.map_or(0, |used_liquidity_msat| {
915+
available_value_contribution_msat = available_value_contribution_msat
916+
.saturating_sub(*used_liquidity_msat);
917+
*used_liquidity_msat
918+
});
900919

901920
// Routing Fragmentation Mitigation heuristic:
902921
//
@@ -1047,9 +1066,16 @@ where L::Target: Logger {
10471066
}
10481067
}
10491068

1050-
let path_penalty_msat = $next_hops_path_penalty_msat.saturating_add(
1051-
scorer.channel_penalty_msat(short_channel_id, amount_to_transfer_over_msat,
1052-
*available_liquidity_msat, &$src_node_id, &$dest_node_id));
1069+
let channel_usage = ChannelUsage {
1070+
amount_msat: amount_to_transfer_over_msat,
1071+
inflight_htlc_msat: used_liquidity_msat,
1072+
effective_capacity: $candidate.effective_capacity(),
1073+
};
1074+
let channel_penalty_msat = scorer.channel_penalty_msat(
1075+
short_channel_id, &$src_node_id, &$dest_node_id, channel_usage
1076+
);
1077+
let path_penalty_msat = $next_hops_path_penalty_msat
1078+
.saturating_add(channel_penalty_msat);
10531079
let new_graph_node = RouteGraphNode {
10541080
node_id: $src_node_id,
10551081
lowest_fee_to_peer_through_node: total_fee_msat,
@@ -1207,9 +1233,8 @@ where L::Target: Logger {
12071233

12081234
// TODO: diversify by nodes (so that all paths aren't doomed if one node is offline).
12091235
'paths_collection: loop {
1210-
// For every new path, start from scratch, except
1211-
// bookkept_channels_liquidity_available_msat, which will improve
1212-
// the further iterations of path finding. Also don't erase first_hop_targets.
1236+
// For every new path, start from scratch, except for used_channel_liquidities, which
1237+
// helps to avoid reusing previously selected paths in future iterations.
12131238
targets.clear();
12141239
dist.clear();
12151240
hit_minimum_limit = false;
@@ -1276,16 +1301,6 @@ where L::Target: Logger {
12761301
short_channel_id: hop.short_channel_id,
12771302
})
12781303
.unwrap_or_else(|| CandidateRouteHop::PrivateHop { hint: hop });
1279-
let capacity_msat = candidate.effective_capacity().as_msat();
1280-
aggregate_next_hops_path_penalty_msat = aggregate_next_hops_path_penalty_msat
1281-
.saturating_add(scorer.channel_penalty_msat(hop.short_channel_id,
1282-
final_value_msat, capacity_msat, &source, &target));
1283-
1284-
aggregate_next_hops_cltv_delta = aggregate_next_hops_cltv_delta
1285-
.saturating_add(hop.cltv_expiry_delta as u32);
1286-
1287-
aggregate_next_hops_path_length = aggregate_next_hops_path_length
1288-
.saturating_add(1);
12891304

12901305
if !add_entry!(candidate, source, target, aggregate_next_hops_fee_msat,
12911306
path_value_msat, aggregate_next_hops_path_htlc_minimum_msat,
@@ -1297,6 +1312,25 @@ where L::Target: Logger {
12971312
hop_used = false;
12981313
}
12991314

1315+
let used_liquidity_msat = used_channel_liquidities
1316+
.get(&(hop.short_channel_id, source < target)).copied().unwrap_or(0);
1317+
let channel_usage = ChannelUsage {
1318+
amount_msat: final_value_msat + aggregate_next_hops_fee_msat,
1319+
inflight_htlc_msat: used_liquidity_msat,
1320+
effective_capacity: candidate.effective_capacity(),
1321+
};
1322+
let channel_penalty_msat = scorer.channel_penalty_msat(
1323+
hop.short_channel_id, &source, &target, channel_usage
1324+
);
1325+
aggregate_next_hops_path_penalty_msat = aggregate_next_hops_path_penalty_msat
1326+
.saturating_add(channel_penalty_msat);
1327+
1328+
aggregate_next_hops_cltv_delta = aggregate_next_hops_cltv_delta
1329+
.saturating_add(hop.cltv_expiry_delta as u32);
1330+
1331+
aggregate_next_hops_path_length = aggregate_next_hops_path_length
1332+
.saturating_add(1);
1333+
13001334
// Searching for a direct channel between last checked hop and first_hop_targets
13011335
if let Some(first_channels) = first_hop_targets.get(&NodeId::from_pubkey(&prev_hop_id)) {
13021336
for details in first_channels {
@@ -1448,26 +1482,30 @@ where L::Target: Logger {
14481482
// Remember that we used these channels so that we don't rely
14491483
// on the same liquidity in future paths.
14501484
let mut prevented_redundant_path_selection = false;
1451-
for (payment_hop, _) in payment_path.hops.iter() {
1452-
let channel_liquidity_available_msat = bookkept_channels_liquidity_available_msat.get_mut(&payment_hop.candidate.short_channel_id()).unwrap();
1453-
let mut spent_on_hop_msat = value_contribution_msat;
1454-
let next_hops_fee_msat = payment_hop.next_hops_fee_msat;
1455-
spent_on_hop_msat += next_hops_fee_msat;
1456-
if spent_on_hop_msat == *channel_liquidity_available_msat {
1485+
let prev_hop_iter = core::iter::once(&our_node_id)
1486+
.chain(payment_path.hops.iter().map(|(hop, _)| &hop.node_id));
1487+
for (prev_hop, (hop, _)) in prev_hop_iter.zip(payment_path.hops.iter()) {
1488+
let spent_on_hop_msat = value_contribution_msat + hop.next_hops_fee_msat;
1489+
let used_liquidity_msat = used_channel_liquidities
1490+
.entry((hop.candidate.short_channel_id(), *prev_hop < hop.node_id))
1491+
.and_modify(|used_liquidity_msat| *used_liquidity_msat += spent_on_hop_msat)
1492+
.or_insert(spent_on_hop_msat);
1493+
if *used_liquidity_msat == hop.candidate.htlc_maximum_msat() {
14571494
// If this path used all of this channel's available liquidity, we know
14581495
// this path will not be selected again in the next loop iteration.
14591496
prevented_redundant_path_selection = true;
14601497
}
1461-
*channel_liquidity_available_msat -= spent_on_hop_msat;
1498+
debug_assert!(*used_liquidity_msat <= hop.candidate.htlc_maximum_msat());
14621499
}
14631500
if !prevented_redundant_path_selection {
14641501
// If we weren't capped by hitting a liquidity limit on a channel in the path,
14651502
// we'll probably end up picking the same path again on the next iteration.
14661503
// Decrease the available liquidity of a hop in the middle of the path.
14671504
let victim_scid = payment_path.hops[(payment_path.hops.len()) / 2].0.candidate.short_channel_id();
1505+
let exhausted = u64::max_value();
14681506
log_trace!(logger, "Disabling channel {} for future path building iterations to avoid duplicates.", victim_scid);
1469-
let victim_liquidity = bookkept_channels_liquidity_available_msat.get_mut(&victim_scid).unwrap();
1470-
*victim_liquidity = 0;
1507+
*used_channel_liquidities.entry((victim_scid, false)).or_default() = exhausted;
1508+
*used_channel_liquidities.entry((victim_scid, true)).or_default() = exhausted;
14711509
}
14721510

14731511
// Track the total amount all our collected paths allow to send so that we:
@@ -1753,7 +1791,7 @@ mod tests {
17531791
use routing::router::{get_route, add_random_cltv_offset, default_node_features,
17541792
PaymentParameters, Route, RouteHint, RouteHintHop, RouteHop, RoutingFees,
17551793
DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA, MAX_PATH_LENGTH_ESTIMATE};
1756-
use routing::scoring::Score;
1794+
use routing::scoring::{ChannelUsage, Score};
17571795
use chain::transaction::OutPoint;
17581796
use chain::keysinterface::KeysInterface;
17591797
use ln::features::{ChannelFeatures, InitFeatures, InvoiceFeatures, NodeFeatures};
@@ -5145,7 +5183,7 @@ mod tests {
51455183
fn write<W: Writer>(&self, _w: &mut W) -> Result<(), ::io::Error> { unimplemented!() }
51465184
}
51475185
impl Score for BadChannelScorer {
5148-
fn channel_penalty_msat(&self, short_channel_id: u64, _send_amt: u64, _capacity_msat: u64, _source: &NodeId, _target: &NodeId) -> u64 {
5186+
fn channel_penalty_msat(&self, short_channel_id: u64, _: &NodeId, _: &NodeId, _: ChannelUsage) -> u64 {
51495187
if short_channel_id == self.short_channel_id { u64::max_value() } else { 0 }
51505188
}
51515189

@@ -5163,7 +5201,7 @@ mod tests {
51635201
}
51645202

51655203
impl Score for BadNodeScorer {
5166-
fn channel_penalty_msat(&self, _short_channel_id: u64, _send_amt: u64, _capacity_msat: u64, _source: &NodeId, target: &NodeId) -> u64 {
5204+
fn channel_penalty_msat(&self, _: u64, _: &NodeId, target: &NodeId, _: ChannelUsage) -> u64 {
51675205
if *target == self.node_id { u64::max_value() } else { 0 }
51685206
}
51695207

0 commit comments

Comments
 (0)