@@ -323,6 +323,18 @@ where
323323 type ScoreParams = <S :: Target as ScoreLookUp >:: ScoreParams ;
324324 #[ rustfmt:: skip]
325325 fn channel_penalty_msat ( & self , candidate : & CandidateRouteHop , usage : ChannelUsage , score_params : & Self :: ScoreParams ) -> u64 {
326+ if let CandidateRouteHop :: Blinded ( blinded_candidate) = candidate {
327+ if let Some ( used_liquidity) = self . inflight_htlcs . used_blinded_liquidity_msat (
328+ * blinded_candidate. source_node_id , blinded_candidate. hint . blinding_point ( ) ,
329+ ) {
330+ let usage = ChannelUsage {
331+ inflight_htlc_msat : usage. inflight_htlc_msat . saturating_add ( used_liquidity) ,
332+ ..usage
333+ } ;
334+
335+ return self . scorer . channel_penalty_msat ( candidate, usage, score_params) ;
336+ }
337+ }
326338 let target = match candidate. target ( ) {
327339 Some ( target) => target,
328340 None => return self . scorer . channel_penalty_msat ( candidate, usage, score_params) ,
@@ -356,12 +368,16 @@ pub struct InFlightHtlcs {
356368 // key is less than its destination. See `InFlightHtlcs::used_liquidity_msat` for more
357369 // details.
358370 unblinded_hops : HashMap < ( u64 , bool ) , u64 > ,
371+ /// A map with liquidity value (in msat) keyed by the introduction point of a blinded path and
372+ /// the blinding point. In general blinding points should be globally unique, but just in case
373+ /// we add the introduction point as well.
374+ blinded_hops : HashMap < ( NodeId , PublicKey ) , u64 > ,
359375}
360376
361377impl InFlightHtlcs {
362378 /// Constructs an empty `InFlightHtlcs`.
363379 pub fn new ( ) -> Self {
364- InFlightHtlcs { unblinded_hops : new_hash_map ( ) }
380+ InFlightHtlcs { unblinded_hops : new_hash_map ( ) , blinded_hops : new_hash_map ( ) }
365381 }
366382
367383 /// Takes in a path with payer's node id and adds the path's details to `InFlightHtlcs`.
@@ -373,6 +389,23 @@ impl InFlightHtlcs {
373389 let mut cumulative_msat = 0 ;
374390 if let Some ( tail) = & path. blinded_tail {
375391 cumulative_msat += tail. final_value_msat ;
392+ if tail. hops . len ( ) > 1 {
393+ // Single-hop blinded paths aren't really "blinded" paths, as they terminate at the
394+ // introduction point. In that case, we don't need to track anything.
395+ let last_trampoline_hop =
396+ tail. trampoline_hops . last ( ) . map ( |hop| ( hop. pubkey , hop. fee_msat ) ) ;
397+ let last_normal_hop = path. hops . last ( ) . unwrap ( ) ;
398+ let last_hop = last_trampoline_hop
399+ . unwrap_or ( ( last_normal_hop. pubkey , last_normal_hop. fee_msat ) ) ;
400+ let intro_node = NodeId :: from_pubkey ( & last_hop. 0 ) ;
401+ // The amount we send into the blinded path is the sum of the blinded path final
402+ // amount and the fee we pay in it, which is the `fee_msat` of the last hop.
403+ let blinded_path_sent_amt = last_hop. 1 + cumulative_msat;
404+ self . blinded_hops
405+ . entry ( ( intro_node, tail. blinding_point ) )
406+ . and_modify ( |used_liquidity_msat| * used_liquidity_msat += blinded_path_sent_amt)
407+ . or_insert ( blinded_path_sent_amt) ;
408+ }
376409 }
377410
378411 // total_inflight_map needs to be direction-sensitive when keeping track of the HTLC value
@@ -414,6 +447,13 @@ impl InFlightHtlcs {
414447 ) -> Option < u64 > {
415448 self . unblinded_hops . get ( & ( channel_scid, source < target) ) . map ( |v| * v)
416449 }
450+
451+ /// Returns liquidity in msat given the blinded path introduction point and blinding point.
452+ pub fn used_blinded_liquidity_msat (
453+ & self , introduction_point : NodeId , blinding_point : PublicKey ,
454+ ) -> Option < u64 > {
455+ self . blinded_hops . get ( & ( introduction_point, blinding_point) ) . map ( |v| * v)
456+ }
417457}
418458
419459/// A hop in a route, and additional metadata about it. "Hop" is defined as a node and the channel
@@ -3890,8 +3930,9 @@ mod tests {
38903930 use crate :: routing:: gossip:: { EffectiveCapacity , NetworkGraph , NodeId , P2PGossipSync } ;
38913931 use crate :: routing:: router:: {
38923932 add_random_cltv_offset, build_route_from_hops_internal, default_node_features, get_route,
3893- BlindedTail , CandidateRouteHop , InFlightHtlcs , Path , PaymentParameters , PublicHopCandidate ,
3894- Route , RouteHint , RouteHintHop , RouteHop , RouteParameters , RoutingFees ,
3933+ BlindedPathCandidate , BlindedTail , CandidateRouteHop , InFlightHtlcs , Path ,
3934+ PaymentParameters , PublicHopCandidate , Route , RouteHint , RouteHintHop , RouteHop ,
3935+ RouteParameters , RoutingFees , ScorerAccountingForInFlightHtlcs ,
38953936 DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA , MAX_PATH_LENGTH_ESTIMATE ,
38963937 } ;
38973938 use crate :: routing:: scoring:: {
@@ -3923,7 +3964,7 @@ mod tests {
39233964
39243965 use crate :: io:: Cursor ;
39253966 use crate :: prelude:: * ;
3926- use crate :: sync:: Arc ;
3967+ use crate :: sync:: { Arc , Mutex } ;
39273968
39283969 #[ rustfmt:: skip]
39293970 fn get_channel_details ( short_channel_id : Option < u64 > , node_id : PublicKey ,
@@ -7960,9 +8001,9 @@ mod tests {
79608001
79618002 #[ test]
79628003 #[ rustfmt:: skip]
7963- fn blinded_path_inflight_processing ( ) {
7964- // Ensure we'll score the channel that's inbound to a blinded path's introduction node, and
7965- // account for the blinded tail's final amount_msat.
8004+ fn one_hop_blinded_path_inflight_processing ( ) {
8005+ // Ensure we'll score the channel that's inbound to a one-hop blinded path's introduction
8006+ // node, and account for the blinded tail's final amount_msat.
79668007 let mut inflight_htlcs = InFlightHtlcs :: new ( ) ;
79678008 let path = Path {
79688009 hops : vec ! [ RouteHop {
@@ -7994,6 +8035,106 @@ mod tests {
79948035 inflight_htlcs. process_path ( & path, ln_test_utils:: pubkey ( 44 ) ) ;
79958036 assert_eq ! ( * inflight_htlcs. unblinded_hops. get( & ( 42 , true ) ) . unwrap( ) , 301 ) ;
79968037 assert_eq ! ( * inflight_htlcs. unblinded_hops. get( & ( 43 , false ) ) . unwrap( ) , 201 ) ;
8038+ assert ! ( inflight_htlcs. blinded_hops. is_empty( ) ) ;
8039+ }
8040+
8041+ struct UsageTrackingScorer ( Mutex < Option < ChannelUsage > > ) ;
8042+
8043+ impl ScoreLookUp for UsageTrackingScorer {
8044+ type ScoreParams = ( ) ;
8045+ fn channel_penalty_msat ( & self , _: & CandidateRouteHop , usage : ChannelUsage , _: & ( ) ) -> u64 {
8046+ let mut inner = self . 0 . lock ( ) . unwrap ( ) ;
8047+ assert ! ( inner. is_none( ) ) ;
8048+ * inner = Some ( usage) ;
8049+ 0
8050+ }
8051+ }
8052+
8053+ #[ test]
8054+ fn blinded_path_inflight_processing ( ) {
8055+ // Ensure we'll score the channel that's inbound to a blinded path's introduction node, and
8056+ // account for the blinded tail's final amount_msat as well as track the blinded path
8057+ // in-flight.
8058+ let mut inflight_htlcs = InFlightHtlcs :: new ( ) ;
8059+ let blinding_point = ln_test_utils:: pubkey ( 48 ) ;
8060+ let mut blinded_hops = Vec :: new ( ) ;
8061+ for i in 0 ..2 {
8062+ blinded_hops. push ( BlindedHop {
8063+ blinded_node_id : ln_test_utils:: pubkey ( 49 + i as u8 ) ,
8064+ encrypted_payload : Vec :: new ( ) ,
8065+ } ) ;
8066+ }
8067+ let intro_point = ln_test_utils:: pubkey ( 43 ) ;
8068+ let path = Path {
8069+ hops : vec ! [
8070+ RouteHop {
8071+ pubkey: ln_test_utils:: pubkey( 42 ) ,
8072+ node_features: NodeFeatures :: empty( ) ,
8073+ short_channel_id: 42 ,
8074+ channel_features: ChannelFeatures :: empty( ) ,
8075+ fee_msat: 100 ,
8076+ cltv_expiry_delta: 0 ,
8077+ maybe_announced_channel: false ,
8078+ } ,
8079+ RouteHop {
8080+ pubkey: intro_point,
8081+ node_features: NodeFeatures :: empty( ) ,
8082+ short_channel_id: 43 ,
8083+ channel_features: ChannelFeatures :: empty( ) ,
8084+ fee_msat: 1 ,
8085+ cltv_expiry_delta: 0 ,
8086+ maybe_announced_channel: false ,
8087+ } ,
8088+ ] ,
8089+ blinded_tail : Some ( BlindedTail {
8090+ trampoline_hops : vec ! [ ] ,
8091+ hops : blinded_hops. clone ( ) ,
8092+ blinding_point,
8093+ excess_final_cltv_expiry_delta : 0 ,
8094+ final_value_msat : 200 ,
8095+ } ) ,
8096+ } ;
8097+ inflight_htlcs. process_path ( & path, ln_test_utils:: pubkey ( 44 ) ) ;
8098+ assert_eq ! ( * inflight_htlcs. unblinded_hops. get( & ( 42 , true ) ) . unwrap( ) , 301 ) ;
8099+ assert_eq ! ( * inflight_htlcs. unblinded_hops. get( & ( 43 , false ) ) . unwrap( ) , 201 ) ;
8100+ let intro_node_id = NodeId :: from_pubkey ( & ln_test_utils:: pubkey ( 43 ) ) ;
8101+ assert_eq ! (
8102+ * inflight_htlcs. blinded_hops. get( & ( intro_node_id, blinding_point) ) . unwrap( ) ,
8103+ 201
8104+ ) ;
8105+
8106+ let tracking_scorer = UsageTrackingScorer ( Mutex :: new ( None ) ) ;
8107+ let inflight_scorer =
8108+ ScorerAccountingForInFlightHtlcs :: new ( & tracking_scorer, & inflight_htlcs) ;
8109+
8110+ let blinded_payinfo = BlindedPayInfo {
8111+ fee_base_msat : 100 ,
8112+ fee_proportional_millionths : 500 ,
8113+ htlc_minimum_msat : 1000 ,
8114+ htlc_maximum_msat : 100_000_000 ,
8115+ cltv_expiry_delta : 15 ,
8116+ features : BlindedHopFeatures :: empty ( ) ,
8117+ } ;
8118+ let blinded_path = BlindedPaymentPath :: from_blinded_path_and_payinfo (
8119+ intro_point,
8120+ blinding_point,
8121+ blinded_hops,
8122+ blinded_payinfo,
8123+ ) ;
8124+
8125+ let candidate = CandidateRouteHop :: Blinded ( BlindedPathCandidate {
8126+ source_node_id : & intro_node_id,
8127+ hint : & blinded_path,
8128+ hint_idx : 0 ,
8129+ source_node_counter : 0 ,
8130+ } ) ;
8131+ let empty_usage = ChannelUsage {
8132+ amount_msat : 42 ,
8133+ inflight_htlc_msat : 0 ,
8134+ effective_capacity : EffectiveCapacity :: HintMaxHTLC { amount_msat : 500 } ,
8135+ } ;
8136+ inflight_scorer. channel_penalty_msat ( & candidate, empty_usage, & ( ) ) ;
8137+ assert_eq ! ( tracking_scorer. 0 . lock( ) . unwrap( ) . unwrap( ) . inflight_htlc_msat, 201 ) ;
79978138 }
79988139
79998140 #[ test]
0 commit comments