@@ -323,6 +323,18 @@ where
323
323
type ScoreParams = <S :: Target as ScoreLookUp >:: ScoreParams ;
324
324
#[ rustfmt:: skip]
325
325
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
+ }
326
338
let target = match candidate. target ( ) {
327
339
Some ( target) => target,
328
340
None => return self . scorer . channel_penalty_msat ( candidate, usage, score_params) ,
@@ -356,12 +368,16 @@ pub struct InFlightHtlcs {
356
368
// key is less than its destination. See `InFlightHtlcs::used_liquidity_msat` for more
357
369
// details.
358
370
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 > ,
359
375
}
360
376
361
377
impl InFlightHtlcs {
362
378
/// Constructs an empty `InFlightHtlcs`.
363
379
pub fn new ( ) -> Self {
364
- InFlightHtlcs { unblinded_hops : new_hash_map ( ) }
380
+ InFlightHtlcs { unblinded_hops : new_hash_map ( ) , blinded_hops : new_hash_map ( ) }
365
381
}
366
382
367
383
/// Takes in a path with payer's node id and adds the path's details to `InFlightHtlcs`.
@@ -373,6 +389,19 @@ impl InFlightHtlcs {
373
389
let mut cumulative_msat = 0 ;
374
390
if let Some ( tail) = & path. blinded_tail {
375
391
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_hop = path. hops . last ( ) . unwrap ( ) ;
396
+ let intro_node = NodeId :: from_pubkey ( & last_hop. pubkey ) ;
397
+ // The amount we send into the blinded path is the sum of the blinded path final
398
+ // amount and the fee we pay in it, which is the `fee_msat` of the last hop.
399
+ let blinded_path_sent_amt = last_hop. fee_msat + cumulative_msat;
400
+ self . blinded_hops
401
+ . entry ( ( intro_node, tail. blinding_point ) )
402
+ . and_modify ( |used_liquidity_msat| * used_liquidity_msat += blinded_path_sent_amt)
403
+ . or_insert ( blinded_path_sent_amt) ;
404
+ }
376
405
}
377
406
378
407
// total_inflight_map needs to be direction-sensitive when keeping track of the HTLC value
@@ -414,6 +443,13 @@ impl InFlightHtlcs {
414
443
) -> Option < u64 > {
415
444
self . unblinded_hops . get ( & ( channel_scid, source < target) ) . map ( |v| * v)
416
445
}
446
+
447
+ /// Returns liquidity in msat given the blinded path introduction point and blinding point.
448
+ pub fn used_blinded_liquidity_msat (
449
+ & self , introduction_point : NodeId , blinding_point : PublicKey ,
450
+ ) -> Option < u64 > {
451
+ self . blinded_hops . get ( & ( introduction_point, blinding_point) ) . map ( |v| * v)
452
+ }
417
453
}
418
454
419
455
/// A hop in a route, and additional metadata about it. "Hop" is defined as a node and the channel
@@ -3890,8 +3926,9 @@ mod tests {
3890
3926
use crate :: routing:: gossip:: { EffectiveCapacity , NetworkGraph , NodeId , P2PGossipSync } ;
3891
3927
use crate :: routing:: router:: {
3892
3928
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 ,
3929
+ BlindedPathCandidate , BlindedTail , CandidateRouteHop , InFlightHtlcs , Path ,
3930
+ PaymentParameters , PublicHopCandidate , Route , RouteHint , RouteHintHop , RouteHop ,
3931
+ RouteParameters , RoutingFees , ScorerAccountingForInFlightHtlcs ,
3895
3932
DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA , MAX_PATH_LENGTH_ESTIMATE ,
3896
3933
} ;
3897
3934
use crate :: routing:: scoring:: {
@@ -3923,7 +3960,7 @@ mod tests {
3923
3960
3924
3961
use crate :: io:: Cursor ;
3925
3962
use crate :: prelude:: * ;
3926
- use crate :: sync:: Arc ;
3963
+ use crate :: sync:: { Arc , Mutex } ;
3927
3964
3928
3965
#[ rustfmt:: skip]
3929
3966
fn get_channel_details ( short_channel_id : Option < u64 > , node_id : PublicKey ,
@@ -7960,9 +7997,9 @@ mod tests {
7960
7997
7961
7998
#[ test]
7962
7999
#[ 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.
8000
+ fn one_hop_blinded_path_inflight_processing ( ) {
8001
+ // Ensure we'll score the channel that's inbound to a one-hop blinded path's introduction
8002
+ // node, and account for the blinded tail's final amount_msat.
7966
8003
let mut inflight_htlcs = InFlightHtlcs :: new ( ) ;
7967
8004
let path = Path {
7968
8005
hops : vec ! [ RouteHop {
@@ -7994,6 +8031,99 @@ mod tests {
7994
8031
inflight_htlcs. process_path ( & path, ln_test_utils:: pubkey ( 44 ) ) ;
7995
8032
assert_eq ! ( * inflight_htlcs. unblinded_hops. get( & ( 42 , true ) ) . unwrap( ) , 301 ) ;
7996
8033
assert_eq ! ( * inflight_htlcs. unblinded_hops. get( & ( 43 , false ) ) . unwrap( ) , 201 ) ;
8034
+ assert ! ( inflight_htlcs. blinded_hops. is_empty( ) ) ;
8035
+ }
8036
+
8037
+ struct UsageTrackingScorer ( Mutex < Option < ChannelUsage > > ) ;
8038
+
8039
+ impl ScoreLookUp for UsageTrackingScorer {
8040
+ type ScoreParams = ( ) ;
8041
+ fn channel_penalty_msat ( & self , _: & CandidateRouteHop , usage : ChannelUsage , _: & ( ) ) -> u64 {
8042
+ let mut inner = self . 0 . lock ( ) . unwrap ( ) ;
8043
+ assert ! ( inner. is_none( ) ) ;
8044
+ * inner = Some ( usage) ;
8045
+ 0
8046
+ }
8047
+ }
8048
+
8049
+ #[ test]
8050
+ fn blinded_path_inflight_processing ( ) {
8051
+ // Ensure we'll score the channel that's inbound to a blinded path's introduction node, and
8052
+ // account for the blinded tail's final amount_msat as well as track the blinded path
8053
+ // in-flight.
8054
+ let mut inflight_htlcs = InFlightHtlcs :: new ( ) ;
8055
+ let blinding_point = ln_test_utils:: pubkey ( 48 ) ;
8056
+ let mut blinded_hops = Vec :: new ( ) ;
8057
+ for i in 0 ..2 {
8058
+ blinded_hops. push (
8059
+ BlindedHop { blinded_node_id : ln_test_utils:: pubkey ( 49 + i as u8 ) , encrypted_payload : Vec :: new ( ) } ,
8060
+ ) ;
8061
+ }
8062
+ let intro_point = ln_test_utils:: pubkey ( 43 ) ;
8063
+ let path = Path {
8064
+ hops : vec ! [
8065
+ RouteHop {
8066
+ pubkey: ln_test_utils:: pubkey( 42 ) ,
8067
+ node_features: NodeFeatures :: empty( ) ,
8068
+ short_channel_id: 42 ,
8069
+ channel_features: ChannelFeatures :: empty( ) ,
8070
+ fee_msat: 100 ,
8071
+ cltv_expiry_delta: 0 ,
8072
+ maybe_announced_channel: false ,
8073
+ } ,
8074
+ RouteHop {
8075
+ pubkey: intro_point,
8076
+ node_features: NodeFeatures :: empty( ) ,
8077
+ short_channel_id: 43 ,
8078
+ channel_features: ChannelFeatures :: empty( ) ,
8079
+ fee_msat: 1 ,
8080
+ cltv_expiry_delta: 0 ,
8081
+ maybe_announced_channel: false ,
8082
+ }
8083
+ ] ,
8084
+ blinded_tail : Some ( BlindedTail {
8085
+ trampoline_hops : vec ! [ ] ,
8086
+ hops : blinded_hops. clone ( ) ,
8087
+ blinding_point,
8088
+ excess_final_cltv_expiry_delta : 0 ,
8089
+ final_value_msat : 200 ,
8090
+ } ) ,
8091
+ } ;
8092
+ inflight_htlcs. process_path ( & path, ln_test_utils:: pubkey ( 44 ) ) ;
8093
+ assert_eq ! ( * inflight_htlcs. unblinded_hops. get( & ( 42 , true ) ) . unwrap( ) , 301 ) ;
8094
+ assert_eq ! ( * inflight_htlcs. unblinded_hops. get( & ( 43 , false ) ) . unwrap( ) , 201 ) ;
8095
+ let intro_node_id = NodeId :: from_pubkey ( & ln_test_utils:: pubkey ( 43 ) ) ;
8096
+ assert_eq ! ( * inflight_htlcs. blinded_hops. get( & ( intro_node_id, blinding_point) ) . unwrap( ) , 201 ) ;
8097
+
8098
+ let tracking_scorer = UsageTrackingScorer ( Mutex :: new ( None ) ) ;
8099
+ let inflight_scorer =
8100
+ ScorerAccountingForInFlightHtlcs :: new ( & tracking_scorer, & inflight_htlcs) ;
8101
+
8102
+ let blinded_payinfo = BlindedPayInfo {
8103
+ fee_base_msat : 100 ,
8104
+ fee_proportional_millionths : 500 ,
8105
+ htlc_minimum_msat : 1000 ,
8106
+ htlc_maximum_msat : 100_000_000 ,
8107
+ cltv_expiry_delta : 15 ,
8108
+ features : BlindedHopFeatures :: empty ( ) ,
8109
+ } ;
8110
+ let blinded_path = BlindedPaymentPath :: from_blinded_path_and_payinfo (
8111
+ intro_point, blinding_point, blinded_hops, blinded_payinfo,
8112
+ ) ;
8113
+
8114
+ let candidate = CandidateRouteHop :: Blinded ( BlindedPathCandidate {
8115
+ source_node_id : & intro_node_id,
8116
+ hint : & blinded_path,
8117
+ hint_idx : 0 ,
8118
+ source_node_counter : 0 ,
8119
+ } ) ;
8120
+ let empty_usage = ChannelUsage {
8121
+ amount_msat : 42 ,
8122
+ inflight_htlc_msat : 0 ,
8123
+ effective_capacity : EffectiveCapacity :: HintMaxHTLC { amount_msat : 500 } ,
8124
+ } ;
8125
+ inflight_scorer. channel_penalty_msat ( & candidate, empty_usage, & ( ) ) ;
8126
+ assert_eq ! ( tracking_scorer. 0 . lock( ) . unwrap( ) . unwrap( ) . inflight_htlc_msat, 201 ) ;
7997
8127
}
7998
8128
7999
8129
#[ test]
0 commit comments