Skip to content

Commit 24183e4

Browse files
committed
Pass EffectiveCapacity through to scorer instead of a u64
There is little reason to take the `EffectiveCapacity`, which has a bunch of information about the type of channel, and distill it down to a `u64` when scoring channels. Instead, we here pass the full information we know, in the form of the original `EffectiveCapacity`. This does create more branching in the main router loop, which appears to have a very slight (1-2%) performance loss, but that may well be within noise. Much more importantly, this resolves a panic in our log approximation where we can accidentally call `log(0)` when the channel's effective capacity is `u64::max_value()`.
1 parent f6fa8e9 commit 24183e4

File tree

4 files changed

+220
-172
lines changed

4 files changed

+220
-172
lines changed

lightning-invoice/src/payment.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
//! # use lightning::ln::channelmanager::{ChannelDetails, PaymentId, PaymentSendFailure};
4040
//! # use lightning::ln::msgs::LightningError;
4141
//! # use lightning::routing::scoring::Score;
42-
//! # use lightning::routing::network_graph::NodeId;
42+
//! # use lightning::routing::network_graph::{EffectiveCapacity, NodeId};
4343
//! # use lightning::routing::router::{Route, RouteHop, RouteParameters};
4444
//! # use lightning::util::events::{Event, EventHandler, EventsProvider};
4545
//! # use lightning::util::logger::{Logger, Record};
@@ -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, _send_amt: u64, _cap: EffectiveCapacity, _source: &NodeId, _target: &NodeId
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]) {}
@@ -532,7 +532,7 @@ mod tests {
532532
use lightning::ln::features::{ChannelFeatures, NodeFeatures, InitFeatures};
533533
use lightning::ln::functional_test_utils::*;
534534
use lightning::ln::msgs::{ChannelMessageHandler, ErrorAction, LightningError};
535-
use lightning::routing::network_graph::NodeId;
535+
use lightning::routing::network_graph::{EffectiveCapacity, NodeId};
536536
use lightning::routing::router::{PaymentParameters, Route, RouteHop};
537537
use lightning::util::test_utils::TestLogger;
538538
use lightning::util::errors::APIError;
@@ -1327,7 +1327,7 @@ mod tests {
13271327

13281328
impl Score for TestScorer {
13291329
fn channel_penalty_msat(
1330-
&self, _short_channel_id: u64, _send_amt: u64, _chan_amt: u64, _source: &NodeId, _target: &NodeId
1330+
&self, _short_channel_id: u64, _send_amt: u64, _capacity: EffectiveCapacity, _source: &NodeId, _target: &NodeId
13311331
) -> u64 { 0 }
13321332

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

lightning/src/routing/network_graph.rs

Lines changed: 46 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -758,7 +758,7 @@ impl<'a> DirectedChannelInfo<'a> {
758758
})
759759
.or_else(|| capacity_msat.map(|capacity_msat|
760760
EffectiveCapacity::Total { capacity_msat }))
761-
.unwrap_or(EffectiveCapacity::Unknown)
761+
.unwrap_or(EffectiveCapacity::Unknown { previously_used_msat: 0 })
762762
}
763763

764764
/// Returns `Some` if [`ChannelUpdateInfo`] is available in the direction.
@@ -808,6 +808,7 @@ impl<'a> fmt::Debug for DirectedChannelInfoWithUpdate<'a> {
808808
///
809809
/// While this may be smaller than the actual channel capacity, amounts greater than
810810
/// [`Self::as_msat`] should not be routed through the channel.
811+
#[derive(Copy, Clone, PartialEq)]
811812
pub enum EffectiveCapacity {
812813
/// The available liquidity in the channel known from being a channel counterparty, and thus a
813814
/// direct hop.
@@ -831,22 +832,62 @@ pub enum EffectiveCapacity {
831832
Infinite,
832833
/// A capacity that is unknown possibly because either the chain state is unavailable to know
833834
/// the total capacity or the `htlc_maximum_msat` was not advertised on the gossip network.
834-
Unknown,
835+
Unknown {
836+
/// An amount which should be considered "already used". Used during routing to keep track
837+
/// of how much we're already considering routing through this channel.
838+
previously_used_msat: u64,
839+
},
835840
}
836841

837842
/// The presumed channel capacity denominated in millisatoshi for [`EffectiveCapacity::Unknown`] to
838843
/// use when making routing decisions.
839844
pub const UNKNOWN_CHANNEL_CAPACITY_MSAT: u64 = 250_000 * 1000;
840845

841846
impl EffectiveCapacity {
842-
/// Returns the effective capacity denominated in millisatoshi.
843-
pub fn as_msat(&self) -> u64 {
847+
#[inline]
848+
pub(crate) fn as_msat_without_bounds(&self) -> u64 {
844849
match self {
845850
EffectiveCapacity::ExactLiquidity { liquidity_msat } => *liquidity_msat,
846851
EffectiveCapacity::MaximumHTLC { amount_msat } => *amount_msat,
847852
EffectiveCapacity::Total { capacity_msat } => *capacity_msat,
848853
EffectiveCapacity::Infinite => u64::max_value(),
849-
EffectiveCapacity::Unknown => UNKNOWN_CHANNEL_CAPACITY_MSAT,
854+
EffectiveCapacity::Unknown { previously_used_msat } => UNKNOWN_CHANNEL_CAPACITY_MSAT.saturating_sub(*previously_used_msat),
855+
}
856+
}
857+
858+
/// Returns the effective capacity denominated in millisatoshi.
859+
#[inline]
860+
pub fn as_msat_with_default(&self) -> Option<u64> {
861+
match self {
862+
EffectiveCapacity::ExactLiquidity { liquidity_msat } => Some(*liquidity_msat),
863+
EffectiveCapacity::MaximumHTLC { amount_msat } => Some(*amount_msat),
864+
EffectiveCapacity::Total { capacity_msat } => Some(*capacity_msat),
865+
EffectiveCapacity::Infinite => None,
866+
EffectiveCapacity::Unknown { previously_used_msat } => Some(UNKNOWN_CHANNEL_CAPACITY_MSAT.saturating_sub(*previously_used_msat)),
867+
}
868+
}
869+
870+
/// Returns a new [`EffectiveCapacity`] which is reduced by the given number of millisatoshis
871+
#[inline]
872+
pub fn checked_sub(&self, msats: u64) -> Option<EffectiveCapacity> {
873+
match self {
874+
Self::ExactLiquidity { liquidity_msat } => match liquidity_msat.checked_sub(msats) {
875+
Some(liquidity_msat) => Some(Self::ExactLiquidity { liquidity_msat }),
876+
None => None,
877+
},
878+
Self::MaximumHTLC { amount_msat } => match amount_msat.checked_sub(msats) {
879+
Some(amount_msat) => Some(Self::MaximumHTLC { amount_msat }),
880+
None => None,
881+
},
882+
Self::Total { capacity_msat } => match capacity_msat.checked_sub(msats) {
883+
Some(capacity_msat) => Some(Self::Total { capacity_msat }),
884+
None => None,
885+
},
886+
Self::Infinite => Some(Self::Infinite),
887+
Self::Unknown { previously_used_msat } => match previously_used_msat.checked_add(msats) {
888+
Some(previously_used_msat) => Some(Self::Unknown { previously_used_msat }),
889+
None => None,
890+
},
850891
}
851892
}
852893
}

lightning/src/routing/router.rs

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -854,7 +854,7 @@ where L::Target: Logger {
854854
let short_channel_id = $candidate.short_channel_id();
855855
let available_liquidity_msat = bookkept_channels_liquidity_available_msat
856856
.entry(short_channel_id)
857-
.or_insert_with(|| $candidate.effective_capacity().as_msat());
857+
.or_insert_with(|| $candidate.effective_capacity());
858858

859859
// It is tricky to substract $next_hops_fee_msat from available liquidity here.
860860
// It may be misleading because we might later choose to reduce the value transferred
@@ -863,7 +863,7 @@ where L::Target: Logger {
863863
// fees caused by one expensive channel, but then this channel could have been used
864864
// if the amount being transferred over this path is lower.
865865
// We do this for now, but this is a subject for removal.
866-
if let Some(available_value_contribution_msat) = available_liquidity_msat.checked_sub($next_hops_fee_msat) {
866+
if let Some(available_value_contribution) = available_liquidity_msat.checked_sub($next_hops_fee_msat) {
867867

868868
// Routing Fragmentation Mitigation heuristic:
869869
//
@@ -886,6 +886,7 @@ where L::Target: Logger {
886886
final_value_msat
887887
};
888888
// Verify the liquidity offered by this channel complies to the minimal contribution.
889+
let available_value_contribution_msat = available_value_contribution.as_msat_without_bounds();
889890
let contributes_sufficient_value = available_value_contribution_msat >= minimal_value_contribution_msat;
890891

891892
// Do not consider candidates that exceed the maximum total cltv expiry limit.
@@ -1220,9 +1221,8 @@ where L::Target: Logger {
12201221
short_channel_id: hop.short_channel_id,
12211222
})
12221223
.unwrap_or_else(|| CandidateRouteHop::PrivateHop { hint: hop });
1223-
let capacity_msat = candidate.effective_capacity().as_msat();
12241224
aggregate_next_hops_path_penalty_msat = aggregate_next_hops_path_penalty_msat
1225-
.checked_add(scorer.channel_penalty_msat(hop.short_channel_id, final_value_msat, capacity_msat, &source, &target))
1225+
.checked_add(scorer.channel_penalty_msat(hop.short_channel_id, path_value_msat, candidate.effective_capacity(), &source, &target))
12261226
.unwrap_or_else(|| u64::max_value());
12271227

12281228
aggregate_next_hops_cltv_delta = aggregate_next_hops_cltv_delta
@@ -1382,12 +1382,13 @@ where L::Target: Logger {
13821382
let mut spent_on_hop_msat = value_contribution_msat;
13831383
let next_hops_fee_msat = payment_hop.next_hops_fee_msat;
13841384
spent_on_hop_msat += next_hops_fee_msat;
1385-
if spent_on_hop_msat == *channel_liquidity_available_msat {
1385+
if spent_on_hop_msat == channel_liquidity_available_msat.as_msat_without_bounds() {
13861386
// If this path used all of this channel's available liquidity, we know
13871387
// this path will not be selected again in the next loop iteration.
13881388
prevented_redundant_path_selection = true;
13891389
}
1390-
*channel_liquidity_available_msat -= spent_on_hop_msat;
1390+
*channel_liquidity_available_msat = channel_liquidity_available_msat
1391+
.checked_sub(spent_on_hop_msat).unwrap_or(EffectiveCapacity::ExactLiquidity { liquidity_msat: 0 });
13911392
}
13921393
if !prevented_redundant_path_selection {
13931394
// If we weren't capped by hitting a liquidity limit on a channel in the path,
@@ -1396,7 +1397,7 @@ where L::Target: Logger {
13961397
let victim_scid = payment_path.hops[(payment_path.hops.len() - 1) / 2].0.candidate.short_channel_id();
13971398
log_trace!(logger, "Disabling channel {} for future path building iterations to avoid duplicates.", victim_scid);
13981399
let victim_liquidity = bookkept_channels_liquidity_available_msat.get_mut(&victim_scid).unwrap();
1399-
*victim_liquidity = 0;
1400+
*victim_liquidity = EffectiveCapacity::ExactLiquidity { liquidity_msat: 0 };
14001401
}
14011402

14021403
// Track the total amount all our collected paths allow to send so that we:
@@ -1664,7 +1665,7 @@ fn add_random_cltv_offset(route: &mut Route, payment_params: &PaymentParameters,
16641665

16651666
#[cfg(test)]
16661667
mod tests {
1667-
use routing::network_graph::{NetworkGraph, NetGraphMsgHandler, NodeId};
1668+
use routing::network_graph::{EffectiveCapacity, NetworkGraph, NetGraphMsgHandler, NodeId};
16681669
use routing::router::{get_route, add_random_cltv_offset, PaymentParameters, Route, RouteHint, RouteHintHop, RouteHop, RoutingFees, DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA};
16691670
use routing::scoring::Score;
16701671
use chain::transaction::OutPoint;
@@ -5001,7 +5002,7 @@ mod tests {
50015002
fn write<W: Writer>(&self, _w: &mut W) -> Result<(), ::io::Error> { unimplemented!() }
50025003
}
50035004
impl Score for BadChannelScorer {
5004-
fn channel_penalty_msat(&self, short_channel_id: u64, _send_amt: u64, _capacity_msat: u64, _source: &NodeId, _target: &NodeId) -> u64 {
5005+
fn channel_penalty_msat(&self, short_channel_id: u64, _send_amt: u64, _capacity: EffectiveCapacity, _source: &NodeId, _target: &NodeId) -> u64 {
50055006
if short_channel_id == self.short_channel_id { u64::max_value() } else { 0 }
50065007
}
50075008

@@ -5019,7 +5020,7 @@ mod tests {
50195020
}
50205021

50215022
impl Score for BadNodeScorer {
5022-
fn channel_penalty_msat(&self, _short_channel_id: u64, _send_amt: u64, _capacity_msat: u64, _source: &NodeId, target: &NodeId) -> u64 {
5023+
fn channel_penalty_msat(&self, _short_channel_id: u64, _send_amt: u64, _capacity: EffectiveCapacity, _source: &NodeId, target: &NodeId) -> u64 {
50235024
if *target == self.node_id { u64::max_value() } else { 0 }
50245025
}
50255026

0 commit comments

Comments
 (0)