//! # use lightning::routing::gossip::NetworkGraph;
//! # use lightning::routing::router::{RouteParameters, find_route};
//! # use lightning::routing::scoring::{ProbabilisticScorer, ProbabilisticScoringParameters};
-//! # use lightning::chain::keysinterface::KeysManager;
+//! # use lightning::sign::KeysManager;
//! # use lightning::util::logger::{Logger, Record};
//! # use bitcoin::secp256k1::PublicKey;
//! #
use crate::ln::msgs::DecodeError;
use crate::routing::gossip::{EffectiveCapacity, NetworkGraph, NodeId};
-use crate::routing::router::RouteHop;
+use crate::routing::router::Path;
use crate::util::ser::{Readable, ReadableArgs, Writeable, Writer};
use crate::util::logger::Logger;
use crate::util::time::Time;
) -> u64;
/// Handles updating channel penalties after failing to route through a channel.
- fn payment_path_failed(&mut self, path: &[&RouteHop], short_channel_id: u64);
+ fn payment_path_failed(&mut self, path: &Path, short_channel_id: u64);
/// Handles updating channel penalties after successfully routing along a path.
- fn payment_path_successful(&mut self, path: &[&RouteHop]);
+ fn payment_path_successful(&mut self, path: &Path);
/// Handles updating channel penalties after a probe over the given path failed.
- fn probe_failed(&mut self, path: &[&RouteHop], short_channel_id: u64);
+ fn probe_failed(&mut self, path: &Path, short_channel_id: u64);
/// Handles updating channel penalties after a probe over the given path succeeded.
- fn probe_successful(&mut self, path: &[&RouteHop]);
+ fn probe_successful(&mut self, path: &Path);
}
impl<S: Score, T: DerefMut<Target=S> $(+ $supertrait)*> Score for T {
self.deref().channel_penalty_msat(short_channel_id, source, target, usage)
}
- fn payment_path_failed(&mut self, path: &[&RouteHop], short_channel_id: u64) {
+ fn payment_path_failed(&mut self, path: &Path, short_channel_id: u64) {
self.deref_mut().payment_path_failed(path, short_channel_id)
}
- fn payment_path_successful(&mut self, path: &[&RouteHop]) {
+ fn payment_path_successful(&mut self, path: &Path) {
self.deref_mut().payment_path_successful(path)
}
- fn probe_failed(&mut self, path: &[&RouteHop], short_channel_id: u64) {
+ fn probe_failed(&mut self, path: &Path, short_channel_id: u64) {
self.deref_mut().probe_failed(path, short_channel_id)
}
- fn probe_successful(&mut self, path: &[&RouteHop]) {
+ fn probe_successful(&mut self, path: &Path) {
self.deref_mut().probe_successful(path)
}
}
#[cfg(not(c_bindings))]
impl<'a, T> WriteableScore<'a> for T where T: LockableScore<'a> + Writeable {}
-
-/// (C-not exported)
+/// This is not exported to bindings users
impl<'a, T: 'a + Score> LockableScore<'a> for Mutex<T> {
type Locked = MutexGuard<'a, T>;
fn channel_penalty_msat(&self, scid: u64, source: &NodeId, target: &NodeId, usage: ChannelUsage) -> u64 {
self.0.channel_penalty_msat(scid, source, target, usage)
}
- fn payment_path_failed(&mut self, path: &[&RouteHop], short_channel_id: u64) {
+ fn payment_path_failed(&mut self, path: &Path, short_channel_id: u64) {
self.0.payment_path_failed(path, short_channel_id)
}
- fn payment_path_successful(&mut self, path: &[&RouteHop]) {
+ fn payment_path_successful(&mut self, path: &Path) {
self.0.payment_path_successful(path)
}
- fn probe_failed(&mut self, path: &[&RouteHop], short_channel_id: u64) {
+ fn probe_failed(&mut self, path: &Path, short_channel_id: u64) {
self.0.probe_failed(path, short_channel_id)
}
- fn probe_successful(&mut self, path: &[&RouteHop]) {
+ fn probe_successful(&mut self, path: &Path) {
self.0.probe_successful(path)
}
}
}
#[cfg(c_bindings)]
-/// (C-not exported)
+/// This is not exported to bindings users
impl<'a, T: Writeable> Writeable for RefMut<'a, T> {
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
T::write(&**self, writer)
}
#[cfg(c_bindings)]
-/// (C-not exported)
+/// This is not exported to bindings users
impl<'a, S: Writeable> Writeable for MutexGuard<'a, S> {
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
S::write(&**self, writer)
self.penalty_msat
}
- fn payment_path_failed(&mut self, _path: &[&RouteHop], _short_channel_id: u64) {}
+ fn payment_path_failed(&mut self, _path: &Path, _short_channel_id: u64) {}
- fn payment_path_successful(&mut self, _path: &[&RouteHop]) {}
+ fn payment_path_successful(&mut self, _path: &Path) {}
- fn probe_failed(&mut self, _path: &[&RouteHop], _short_channel_id: u64) {}
+ fn probe_failed(&mut self, _path: &Path, _short_channel_id: u64) {}
- fn probe_successful(&mut self, _path: &[&RouteHop]) {}
+ fn probe_successful(&mut self, _path: &Path) {}
}
impl Writeable for FixedPenaltyScorer {
/// Probabilistic [`Score`] implementation.
///
-/// (C-not exported) generally all users should use the [`ProbabilisticScorer`] type alias.
+/// This is not exported to bindings users generally all users should use the [`ProbabilisticScorer`] type alias.
pub struct ProbabilisticScorerUsingTime<G: Deref<Target = NetworkGraph<L>>, L: Deref, T: Time>
where L::Target: Logger {
params: ProbabilisticScoringParameters,
/// node. Note that a manual penalty of `u64::max_value()` means the node would not ever be
/// considered during path finding.
///
- /// (C-not exported)
+ /// This is not exported to bindings users
pub manual_node_penalties: HashMap<NodeId, u64>,
/// This penalty is applied when `htlc_maximum_msat` is equal to or larger than half of the
// Ensure the bucket index is in the range [0, 7], even if the liquidity offset is zero or
// the channel's capacity, though the second should generally never happen.
debug_assert!(liquidity_offset_msat <= capacity_msat);
- let bucket_idx: u8 = (liquidity_offset_msat.saturating_sub(1) * 8 / capacity_msat)
+ let bucket_idx: u8 = (liquidity_offset_msat * 8 / capacity_msat.saturating_add(1))
.try_into().unwrap_or(32); // 32 is bogus for 8 buckets, and will be ignored
debug_assert!(bucket_idx < 8);
if bucket_idx < 8 {
if params.historical_liquidity_penalty_multiplier_msat != 0 ||
params.historical_liquidity_penalty_amount_multiplier_msat != 0 {
let payment_amt_64th_bucket = if amount_msat < u64::max_value() / 64 {
- amount_msat * 64 / self.capacity_msat
+ amount_msat * 64 / self.capacity_msat.saturating_add(1)
} else {
// Only use 128-bit arithmetic when multiplication will overflow to avoid 128-bit
// division. This branch should only be hit in fuzz testing since the amount would
// need to be over 2.88 million BTC in practice.
- ((amount_msat as u128) * 64 / (self.capacity_msat as u128))
+ ((amount_msat as u128) * 64 / (self.capacity_msat as u128).saturating_add(1))
.try_into().unwrap_or(65)
};
#[cfg(not(fuzzing))]
.saturating_add(base_penalty_msat)
}
- fn payment_path_failed(&mut self, path: &[&RouteHop], short_channel_id: u64) {
- let amount_msat = path.split_last().map(|(hop, _)| hop.fee_msat).unwrap_or(0);
+ fn payment_path_failed(&mut self, path: &Path, short_channel_id: u64) {
+ let amount_msat = path.final_value_msat();
log_trace!(self.logger, "Scoring path through to SCID {} as having failed at {} msat", short_channel_id, amount_msat);
let network_graph = self.network_graph.read_only();
- for (hop_idx, hop) in path.iter().enumerate() {
+ for (hop_idx, hop) in path.hops.iter().enumerate() {
let target = NodeId::from_pubkey(&hop.pubkey);
let channel_directed_from_source = network_graph.channels()
.get(&hop.short_channel_id)
}
}
- fn payment_path_successful(&mut self, path: &[&RouteHop]) {
- let amount_msat = path.split_last().map(|(hop, _)| hop.fee_msat).unwrap_or(0);
+ fn payment_path_successful(&mut self, path: &Path) {
+ let amount_msat = path.final_value_msat();
log_trace!(self.logger, "Scoring path through SCID {} as having succeeded at {} msat.",
- path.split_last().map(|(hop, _)| hop.short_channel_id).unwrap_or(0), amount_msat);
+ path.hops.split_last().map(|(hop, _)| hop.short_channel_id).unwrap_or(0), amount_msat);
let network_graph = self.network_graph.read_only();
- for hop in path {
+ for hop in &path.hops {
let target = NodeId::from_pubkey(&hop.pubkey);
let channel_directed_from_source = network_graph.channels()
.get(&hop.short_channel_id)
}
}
- fn probe_failed(&mut self, path: &[&RouteHop], short_channel_id: u64) {
+ fn probe_failed(&mut self, path: &Path, short_channel_id: u64) {
self.payment_path_failed(path, short_channel_id)
}
- fn probe_successful(&mut self, path: &[&RouteHop]) {
+ fn probe_successful(&mut self, path: &Path) {
self.payment_path_failed(path, u64::max_value())
}
}
#[cfg(test)]
mod tests {
use super::{ChannelLiquidity, HistoricalBucketRangeTracker, ProbabilisticScoringParameters, ProbabilisticScorerUsingTime};
+ use crate::blinded_path::{BlindedHop, BlindedPath};
use crate::util::config::UserConfig;
use crate::util::time::Time;
use crate::util::time::tests::SinceEpoch;
use crate::ln::channelmanager;
use crate::ln::msgs::{ChannelAnnouncement, ChannelUpdate, UnsignedChannelAnnouncement, UnsignedChannelUpdate};
use crate::routing::gossip::{EffectiveCapacity, NetworkGraph, NodeId};
- use crate::routing::router::RouteHop;
+ use crate::routing::router::{BlindedTail, Path, RouteHop};
use crate::routing::scoring::{ChannelUsage, Score};
use crate::util::ser::{ReadableArgs, Writeable};
- use crate::util::test_utils::TestLogger;
+ use crate::util::test_utils::{self, TestLogger};
use bitcoin::blockdata::constants::genesis_block;
use bitcoin::hashes::Hash;
let chain_source: Option<&crate::util::test_utils::TestChainSource> = None;
network_graph.update_channel_from_announcement(
&signed_announcement, &chain_source).unwrap();
- update_channel(network_graph, short_channel_id, node_1_key, 0);
- update_channel(network_graph, short_channel_id, node_2_key, 1);
+ update_channel(network_graph, short_channel_id, node_1_key, 0, 1_000);
+ update_channel(network_graph, short_channel_id, node_2_key, 1, 0);
}
fn update_channel(
network_graph: &mut NetworkGraph<&TestLogger>, short_channel_id: u64, node_key: SecretKey,
- flags: u8
+ flags: u8, htlc_maximum_msat: u64
) {
let genesis_hash = genesis_block(Network::Testnet).header.block_hash();
let secp_ctx = Secp256k1::new();
flags,
cltv_expiry_delta: 18,
htlc_minimum_msat: 0,
- htlc_maximum_msat: 1_000,
+ htlc_maximum_msat,
fee_base_msat: 1,
fee_proportional_millionths: 0,
excess_data: Vec::new(),
}
}
- fn payment_path_for_amount(amount_msat: u64) -> Vec<RouteHop> {
- vec![
- path_hop(source_pubkey(), 41, 1),
- path_hop(target_pubkey(), 42, 2),
- path_hop(recipient_pubkey(), 43, amount_msat),
- ]
+ fn payment_path_for_amount(amount_msat: u64) -> Path {
+ Path {
+ hops: vec![
+ path_hop(source_pubkey(), 41, 1),
+ path_hop(target_pubkey(), 42, 2),
+ path_hop(recipient_pubkey(), 43, amount_msat),
+ ], blinded_tail: None,
+ }
}
#[test]
assert_eq!(scorer.channel_penalty_msat(41, &sender, &source, usage), 301);
- scorer.payment_path_failed(&failed_path.iter().collect::<Vec<_>>(), 41);
+ scorer.payment_path_failed(&failed_path, 41);
assert_eq!(scorer.channel_penalty_msat(41, &sender, &source, usage), 301);
- scorer.payment_path_successful(&successful_path.iter().collect::<Vec<_>>());
+ scorer.payment_path_successful(&successful_path);
assert_eq!(scorer.channel_penalty_msat(41, &sender, &source, usage), 301);
}
let usage = ChannelUsage { amount_msat: 750, ..usage };
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 602);
- scorer.payment_path_failed(&path.iter().collect::<Vec<_>>(), 43);
+ scorer.payment_path_failed(&path, 43);
let usage = ChannelUsage { amount_msat: 250, ..usage };
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 0);
let usage = ChannelUsage { amount_msat: 750, ..usage };
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 602);
- scorer.payment_path_failed(&path.iter().collect::<Vec<_>>(), 42);
+ scorer.payment_path_failed(&path, 42);
let usage = ChannelUsage { amount_msat: 250, ..usage };
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 300);
assert_eq!(scorer.channel_penalty_msat(43, &node_b, &node_c, usage), 128);
assert_eq!(scorer.channel_penalty_msat(44, &node_c, &node_d, usage), 128);
- scorer.payment_path_failed(&path.iter().collect::<Vec<_>>(), 43);
+ scorer.payment_path_failed(&Path { hops: path, blinded_tail: None }, 43);
assert_eq!(scorer.channel_penalty_msat(42, &node_a, &node_b, usage), 80);
// Note that a default liquidity bound is used for B -> C as no channel exists
inflight_htlc_msat: 0,
effective_capacity: EffectiveCapacity::Total { capacity_msat: 1_000, htlc_maximum_msat: 1_000 },
};
- let path = payment_path_for_amount(500);
assert_eq!(scorer.channel_penalty_msat(41, &sender, &source, usage), 128);
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 128);
assert_eq!(scorer.channel_penalty_msat(43, &target, &recipient, usage), 128);
- scorer.payment_path_successful(&path.iter().collect::<Vec<_>>());
+ scorer.payment_path_successful(&payment_path_for_amount(500));
assert_eq!(scorer.channel_penalty_msat(41, &sender, &source, usage), 128);
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 300);
let usage = ChannelUsage { amount_msat: 1_023, ..usage };
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 2_000);
- scorer.payment_path_failed(&payment_path_for_amount(768).iter().collect::<Vec<_>>(), 42);
- scorer.payment_path_failed(&payment_path_for_amount(128).iter().collect::<Vec<_>>(), 43);
+ scorer.payment_path_failed(&payment_path_for_amount(768), 42);
+ scorer.payment_path_failed(&payment_path_for_amount(128), 43);
let usage = ChannelUsage { amount_msat: 128, ..usage };
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 0);
};
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 125);
- scorer.payment_path_failed(&payment_path_for_amount(512).iter().collect::<Vec<_>>(), 42);
+ scorer.payment_path_failed(&payment_path_for_amount(512), 42);
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 281);
// An unchecked right shift 64 bits or more in DirectedChannelLiquidity::decayed_offset_msat
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 300);
// More knowledge gives higher confidence (256, 768), meaning a lower penalty.
- scorer.payment_path_failed(&payment_path_for_amount(768).iter().collect::<Vec<_>>(), 42);
- scorer.payment_path_failed(&payment_path_for_amount(256).iter().collect::<Vec<_>>(), 43);
+ scorer.payment_path_failed(&payment_path_for_amount(768), 42);
+ scorer.payment_path_failed(&payment_path_for_amount(256), 43);
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 281);
// Decaying knowledge gives less confidence (128, 896), meaning a higher penalty.
// Reducing the upper bound gives more confidence (128, 832) that the payment amount (512)
// is closer to the upper bound, meaning a higher penalty.
- scorer.payment_path_successful(&payment_path_for_amount(64).iter().collect::<Vec<_>>());
+ scorer.payment_path_successful(&payment_path_for_amount(64));
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 331);
// Increasing the lower bound gives more confidence (256, 832) that the payment amount (512)
// is closer to the lower bound, meaning a lower penalty.
- scorer.payment_path_failed(&payment_path_for_amount(256).iter().collect::<Vec<_>>(), 43);
+ scorer.payment_path_failed(&payment_path_for_amount(256), 43);
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 245);
// Further decaying affects the lower bound more than the upper bound (128, 928).
effective_capacity: EffectiveCapacity::Total { capacity_msat: 1_000, htlc_maximum_msat: 1_000 },
};
- scorer.payment_path_failed(&payment_path_for_amount(500).iter().collect::<Vec<_>>(), 42);
+ scorer.payment_path_failed(&payment_path_for_amount(500), 42);
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), u64::max_value());
SinceEpoch::advance(Duration::from_secs(10));
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 473);
- scorer.payment_path_failed(&payment_path_for_amount(250).iter().collect::<Vec<_>>(), 43);
+ scorer.payment_path_failed(&payment_path_for_amount(250), 43);
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 300);
let mut serialized_scorer = Vec::new();
effective_capacity: EffectiveCapacity::Total { capacity_msat: 1_000, htlc_maximum_msat: 1_000 },
};
- scorer.payment_path_failed(&payment_path_for_amount(500).iter().collect::<Vec<_>>(), 42);
+ scorer.payment_path_failed(&payment_path_for_amount(500), 42);
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), u64::max_value());
let mut serialized_scorer = Vec::new();
<ProbabilisticScorer>::read(&mut serialized_scorer, (params, &network_graph, &logger)).unwrap();
assert_eq!(deserialized_scorer.channel_penalty_msat(42, &source, &target, usage), 473);
- scorer.payment_path_failed(&payment_path_for_amount(250).iter().collect::<Vec<_>>(), 43);
+ scorer.payment_path_failed(&payment_path_for_amount(250), 43);
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 300);
SinceEpoch::advance(Duration::from_secs(10));
let logger = TestLogger::new();
let network_graph = network_graph(&logger);
let params = ProbabilisticScoringParameters {
+ liquidity_offset_half_life: Duration::from_secs(60 * 60),
historical_liquidity_penalty_multiplier_msat: 1024,
historical_liquidity_penalty_amount_multiplier_msat: 1024,
historical_no_updates_half_life: Duration::from_secs(10),
assert_eq!(scorer.historical_estimated_channel_liquidity_probabilities(42, &target),
None);
- scorer.payment_path_failed(&payment_path_for_amount(1).iter().collect::<Vec<_>>(), 42);
+ scorer.payment_path_failed(&payment_path_for_amount(1), 42);
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 2048);
// The "it failed" increment is 32, where the probability should lie fully in the first
// octile.
// Even after we tell the scorer we definitely have enough available liquidity, it will
// still remember that there was some failure in the past, and assign a non-0 penalty.
- scorer.payment_path_failed(&payment_path_for_amount(1000).iter().collect::<Vec<_>>(), 43);
+ scorer.payment_path_failed(&payment_path_for_amount(1000), 43);
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 198);
// The first octile should be decayed just slightly and the last octile has a new point.
assert_eq!(scorer.historical_estimated_channel_liquidity_probabilities(42, &target),
inflight_htlc_msat: 1024,
effective_capacity: EffectiveCapacity::Total { capacity_msat: 1_024, htlc_maximum_msat: 1_024 },
};
- scorer.payment_path_failed(&payment_path_for_amount(1).iter().collect::<Vec<_>>(), 42);
+ scorer.payment_path_failed(&payment_path_for_amount(1), 42);
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 409);
+
+ let usage = ChannelUsage {
+ amount_msat: 1,
+ inflight_htlc_msat: 0,
+ effective_capacity: EffectiveCapacity::MaximumHTLC { amount_msat: 0 },
+ };
+ assert_eq!(scorer.channel_penalty_msat(42, &target, &source, usage), 2048);
+
+ // Advance to decay all liquidity offsets to zero.
+ SinceEpoch::advance(Duration::from_secs(60 * 60 * 10));
+
+ // Use a path in the opposite direction, which have zero for htlc_maximum_msat. This will
+ // ensure that the effective capacity is zero to test division-by-zero edge cases.
+ let path = vec![
+ path_hop(target_pubkey(), 43, 2),
+ path_hop(source_pubkey(), 42, 1),
+ path_hop(sender_pubkey(), 41, 0),
+ ];
+ scorer.payment_path_failed(&Path { hops: path, blinded_tail: None }, 42);
}
#[test]
};
assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 0);
}
+
+ #[test]
+ fn scores_with_blinded_path() {
+ // Make sure we'll account for a blinded path's final_value_msat in scoring
+ let logger = TestLogger::new();
+ let network_graph = network_graph(&logger);
+ let params = ProbabilisticScoringParameters {
+ liquidity_penalty_multiplier_msat: 1_000,
+ liquidity_offset_half_life: Duration::from_secs(10),
+ ..ProbabilisticScoringParameters::zero_penalty()
+ };
+ let mut scorer = ProbabilisticScorer::new(params, &network_graph, &logger);
+ let source = source_node_id();
+ let target = target_node_id();
+ let usage = ChannelUsage {
+ amount_msat: 512,
+ inflight_htlc_msat: 0,
+ effective_capacity: EffectiveCapacity::Total { capacity_msat: 1_024, htlc_maximum_msat: 1_000 },
+ };
+ assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 300);
+
+ let mut path = payment_path_for_amount(768);
+ let recipient_hop = path.hops.pop().unwrap();
+ let blinded_path = BlindedPath {
+ introduction_node_id: path.hops.last().as_ref().unwrap().pubkey,
+ blinding_point: test_utils::pubkey(42),
+ blinded_hops: vec![
+ BlindedHop { blinded_node_id: test_utils::pubkey(44), encrypted_payload: Vec::new() }
+ ],
+ };
+ path.blinded_tail = Some(BlindedTail {
+ hops: blinded_path.blinded_hops,
+ blinding_point: blinded_path.blinding_point,
+ excess_final_cltv_expiry_delta: recipient_hop.cltv_expiry_delta,
+ final_value_msat: recipient_hop.fee_msat,
+ });
+
+ // Check the liquidity before and after scoring payment failures to ensure the blinded path's
+ // final value is taken into account.
+ assert!(scorer.channel_liquidities.get(&42).is_none());
+
+ scorer.payment_path_failed(&path, 42);
+ path.blinded_tail.as_mut().unwrap().final_value_msat = 256;
+ scorer.payment_path_failed(&path, 43);
+
+ let liquidity = scorer.channel_liquidities.get(&42).unwrap()
+ .as_directed(&source, &target, 0, 1_000, &scorer.params);
+ assert_eq!(liquidity.min_liquidity_msat(), 256);
+ assert_eq!(liquidity.max_liquidity_msat(), 768);
+ }
}