X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=blobdiff_plain;f=lightning%2Fsrc%2Frouting%2Fscoring.rs;h=e60e4879b3d5e25ca121ed6d1a543b120317282f;hb=3a643df99797ee2dd5cc19a6f9d090212b1c7963;hp=e2d1107566e55e577d16a11a54dd0a9f8a777c17;hpb=a58b81aa1eeef4c546465508f07a0a0af5f4eb8c;p=rust-lightning diff --git a/lightning/src/routing/scoring.rs b/lightning/src/routing/scoring.rs index e2d11075..e60e4879 100644 --- a/lightning/src/routing/scoring.rs +++ b/lightning/src/routing/scoring.rs @@ -56,7 +56,7 @@ 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; @@ -99,16 +99,16 @@ pub trait Score $(: $supertrait)* { ) -> 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 $(+ $supertrait)*> Score for T { @@ -118,19 +118,19 @@ impl $(+ $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) } } @@ -165,8 +165,7 @@ pub trait WriteableScore<'a>: LockableScore<'a> + Writeable {} #[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 { type Locked = MutexGuard<'a, T>; @@ -196,16 +195,16 @@ impl<'a, T: Score + 'a> Score for MultiThreadedScoreLock<'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) } } @@ -244,7 +243,7 @@ impl MultiThreadedLockableScore { } #[cfg(c_bindings)] -/// (C-not exported) +/// This is not exported to bindings users impl<'a, T: Writeable> Writeable for RefMut<'a, T> { fn write(&self, writer: &mut W) -> Result<(), io::Error> { T::write(&**self, writer) @@ -252,7 +251,7 @@ impl<'a, T: Writeable> Writeable for RefMut<'a, T> { } #[cfg(c_bindings)] -/// (C-not exported) +/// This is not exported to bindings users impl<'a, S: Writeable> Writeable for MutexGuard<'a, S> { fn write(&self, writer: &mut W) -> Result<(), io::Error> { S::write(&**self, writer) @@ -291,13 +290,13 @@ impl Score for FixedPenaltyScorer { 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 { @@ -363,7 +362,7 @@ pub type ProbabilisticScorer = ProbabilisticScorerUsingTime::>, L: Deref, T: Time> where L::Target: Logger { params: ProbabilisticScoringParameters, @@ -510,7 +509,7 @@ pub struct 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, /// This penalty is applied when `htlc_maximum_msat` is equal to or larger than half of the @@ -575,7 +574,7 @@ impl HistoricalBucketRangeTracker { // 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 { @@ -1034,12 +1033,12 @@ impl, BRT: Deref, 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))] @@ -1129,6 +1128,7 @@ impl, BRT: DerefMut, BRT: DerefMut, BRT: DerefMut, BRT: DerefMut, BRT: DerefMut, BRT: DerefMut>, L: Deref, T: Time> Score for Probabilis .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) @@ -1270,12 +1272,12 @@ impl>, L: Deref, T: Time> Score for Probabilis } } - 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) @@ -1296,11 +1298,11 @@ impl>, L: Deref, T: Time> Score for Probabilis } } - 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()) } } @@ -1700,6 +1702,7 @@ impl Readable for ChannelLiquidity { #[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; @@ -1707,10 +1710,10 @@ mod tests { 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; @@ -1814,13 +1817,13 @@ mod tests { 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(); @@ -1831,7 +1834,7 @@ mod tests { 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(), @@ -1856,12 +1859,14 @@ mod tests { } } - fn payment_path_for_amount(amount_msat: u64) -> Vec { - 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] @@ -2159,10 +2164,10 @@ mod tests { assert_eq!(scorer.channel_penalty_msat(41, &sender, &source, usage), 301); - scorer.payment_path_failed(&failed_path.iter().collect::>(), 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::>()); + scorer.payment_path_successful(&successful_path); assert_eq!(scorer.channel_penalty_msat(41, &sender, &source, usage), 301); } @@ -2190,7 +2195,7 @@ mod tests { 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::>(), 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); @@ -2225,7 +2230,7 @@ mod tests { 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::>(), 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); @@ -2285,7 +2290,7 @@ mod tests { 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::>(), 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 @@ -2311,13 +2316,12 @@ mod tests { 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::>()); + 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); @@ -2347,8 +2351,8 @@ mod tests { 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::>(), 42); - scorer.payment_path_failed(&payment_path_for_amount(128).iter().collect::>(), 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); @@ -2423,7 +2427,7 @@ mod tests { }; assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 125); - scorer.payment_path_failed(&payment_path_for_amount(512).iter().collect::>(), 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 @@ -2456,8 +2460,8 @@ mod tests { 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::>(), 42); - scorer.payment_path_failed(&payment_path_for_amount(256).iter().collect::>(), 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. @@ -2466,12 +2470,12 @@ mod tests { // 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::>()); + 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::>(), 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). @@ -2498,13 +2502,13 @@ mod tests { effective_capacity: EffectiveCapacity::Total { capacity_msat: 1_000, htlc_maximum_msat: 1_000 }, }; - scorer.payment_path_failed(&payment_path_for_amount(500).iter().collect::>(), 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::>(), 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(); @@ -2535,7 +2539,7 @@ mod tests { effective_capacity: EffectiveCapacity::Total { capacity_msat: 1_000, htlc_maximum_msat: 1_000 }, }; - scorer.payment_path_failed(&payment_path_for_amount(500).iter().collect::>(), 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(); @@ -2548,7 +2552,7 @@ mod tests { ::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::>(), 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)); @@ -2751,6 +2755,7 @@ mod tests { 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), @@ -2770,7 +2775,7 @@ mod tests { assert_eq!(scorer.historical_estimated_channel_liquidity_probabilities(42, &target), None); - scorer.payment_path_failed(&payment_path_for_amount(1).iter().collect::>(), 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. @@ -2779,7 +2784,7 @@ mod tests { // 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::>(), 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), @@ -2799,8 +2804,27 @@ mod tests { 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::>(), 42); - assert_eq!(scorer.channel_penalty_msat(42, &source, &target, usage), 2048); + 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] @@ -2847,4 +2871,54 @@ mod tests { }; 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); + } }