@@ -475,6 +475,9 @@ where L::Target: Logger {
475475 network_graph : G ,
476476 logger : L ,
477477 channel_liquidities : HashMap < u64 , ChannelLiquidity > ,
478+ /// The last time we were given via a [`ScoreUpdate`] method. This does not imply that we've
479+ /// decayed every liquidity bound up to that time.
480+ last_duration_since_epoch : Duration ,
478481}
479482
480483/// Parameters for configuring [`ProbabilisticScorer`].
@@ -637,6 +640,22 @@ pub struct ProbabilisticScoringFeeParameters {
637640 ///
638641 /// Default value: false
639642 pub linear_success_probability : bool ,
643+
644+ /// In order to ensure we have knowledge for as many paths as possible, when probing it makes
645+ /// sense to bias away from channels for which we have very recent data.
646+ ///
647+ /// This value is a penalty that is applied based on the last time that we updated the bounds
648+ /// on the available liquidity in a channel. The specified value is the maximum penalty that
649+ /// will be applied.
650+ ///
651+ /// It obviously does not make sense to assign a non-0 value here unless you are using the
652+ /// pathfinding result for background probing.
653+ ///
654+ /// Specifically, the following penalty is applied
655+ /// `probing_diversity_penalty_msat * max(0, (86400 - current time + last update))^2 / 86400^2` is
656+ ///
657+ /// Default value: 0
658+ pub probing_diversity_penalty_msat : u64 ,
640659}
641660
642661impl Default for ProbabilisticScoringFeeParameters {
@@ -652,6 +671,7 @@ impl Default for ProbabilisticScoringFeeParameters {
652671 historical_liquidity_penalty_multiplier_msat : 10_000 ,
653672 historical_liquidity_penalty_amount_multiplier_msat : 64 ,
654673 linear_success_probability : false ,
674+ probing_diversity_penalty_msat : 0 ,
655675 }
656676 }
657677}
@@ -706,6 +726,7 @@ impl ProbabilisticScoringFeeParameters {
706726 anti_probing_penalty_msat : 0 ,
707727 considered_impossible_penalty_msat : 0 ,
708728 linear_success_probability : true ,
729+ probing_diversity_penalty_msat : 0 ,
709730 }
710731 }
711732}
@@ -850,6 +871,7 @@ impl<G: Deref<Target = NetworkGraph<L>>, L: Deref> ProbabilisticScorer<G, L> whe
850871 network_graph,
851872 logger,
852873 channel_liquidities : new_hash_map ( ) ,
874+ last_duration_since_epoch : Duration :: from_secs ( 0 ) ,
853875 }
854876 }
855877
@@ -1172,7 +1194,7 @@ DirectedChannelLiquidity< L, HT, T> {
11721194 /// Returns a liquidity penalty for routing the given HTLC `amount_msat` through the channel in
11731195 /// this direction.
11741196 fn penalty_msat (
1175- & self , amount_msat : u64 , inflight_htlc_msat : u64 ,
1197+ & self , amount_msat : u64 , inflight_htlc_msat : u64 , last_duration_since_epoch : Duration ,
11761198 score_params : & ProbabilisticScoringFeeParameters ,
11771199 ) -> u64 {
11781200 let total_inflight_amount_msat = amount_msat. saturating_add ( inflight_htlc_msat) ;
@@ -1247,6 +1269,13 @@ DirectedChannelLiquidity< L, HT, T> {
12471269 }
12481270 }
12491271
1272+ if score_params. probing_diversity_penalty_msat != 0 {
1273+ let time_since_update = last_duration_since_epoch. saturating_sub ( * self . last_datapoint ) ;
1274+ let mul = Duration :: from_secs ( 60 * 60 * 24 ) . saturating_sub ( time_since_update) . as_secs ( ) ;
1275+ let penalty = score_params. probing_diversity_penalty_msat . saturating_mul ( mul * mul) ;
1276+ res = res. saturating_add ( penalty / ( ( 60 * 60 * 24 ) * ( 60 * 60 * 24 ) ) ) ;
1277+ }
1278+
12501279 res
12511280 }
12521281
@@ -1398,11 +1427,12 @@ impl<G: Deref<Target = NetworkGraph<L>>, L: Deref> ScoreLookUp for Probabilistic
13981427 }
13991428
14001429 let capacity_msat = usage. effective_capacity . as_msat ( ) ;
1430+ let time = self . last_duration_since_epoch ;
14011431 self . channel_liquidities
14021432 . get ( scid)
14031433 . unwrap_or ( & ChannelLiquidity :: new ( Duration :: ZERO ) )
14041434 . as_directed ( & source, & target, capacity_msat)
1405- . penalty_msat ( usage. amount_msat , usage. inflight_htlc_msat , score_params)
1435+ . penalty_msat ( usage. amount_msat , usage. inflight_htlc_msat , time , score_params)
14061436 . saturating_add ( anti_probing_penalty_msat)
14071437 . saturating_add ( base_penalty_msat)
14081438 }
@@ -1448,6 +1478,7 @@ impl<G: Deref<Target = NetworkGraph<L>>, L: Deref> ScoreUpdate for Probabilistic
14481478 }
14491479 if at_failed_channel { break ; }
14501480 }
1481+ self . last_duration_since_epoch = duration_since_epoch;
14511482 }
14521483
14531484 fn payment_path_successful ( & mut self , path : & Path , duration_since_epoch : Duration ) {
@@ -1475,6 +1506,7 @@ impl<G: Deref<Target = NetworkGraph<L>>, L: Deref> ScoreUpdate for Probabilistic
14751506 hop. short_channel_id) ;
14761507 }
14771508 }
1509+ self . last_duration_since_epoch = duration_since_epoch;
14781510 }
14791511
14801512 fn probe_failed ( & mut self , path : & Path , short_channel_id : u64 , duration_since_epoch : Duration ) {
@@ -1506,6 +1538,7 @@ impl<G: Deref<Target = NetworkGraph<L>>, L: Deref> ScoreUpdate for Probabilistic
15061538 liquidity. min_liquidity_offset_msat != 0 || liquidity. max_liquidity_offset_msat != 0 ||
15071539 liquidity. liquidity_history . has_datapoints ( )
15081540 } ) ;
1541+ self . last_duration_since_epoch = duration_since_epoch;
15091542 }
15101543}
15111544
@@ -1939,15 +1972,20 @@ ReadableArgs<(ProbabilisticScoringDecayParameters, G, L)> for ProbabilisticScore
19391972 r : & mut R , args : ( ProbabilisticScoringDecayParameters , G , L )
19401973 ) -> Result < Self , DecodeError > {
19411974 let ( decay_params, network_graph, logger) = args;
1942- let mut channel_liquidities = new_hash_map ( ) ;
1975+ let mut channel_liquidities: HashMap < u64 , ChannelLiquidity > = new_hash_map ( ) ;
19431976 read_tlv_fields ! ( r, {
19441977 ( 0 , channel_liquidities, required) ,
19451978 } ) ;
1979+ let mut last_duration_since_epoch = Duration :: from_secs ( 0 ) ;
1980+ for ( _, liq) in channel_liquidities. iter ( ) {
1981+ last_duration_since_epoch = cmp:: max ( last_duration_since_epoch, liq. last_updated ) ;
1982+ }
19461983 Ok ( Self {
19471984 decay_params,
19481985 network_graph,
19491986 logger,
19501987 channel_liquidities,
1988+ last_duration_since_epoch,
19511989 } )
19521990 }
19531991}
@@ -3542,6 +3580,52 @@ mod tests {
35423580 assert_eq ! ( scorer. historical_estimated_payment_success_probability( 42 , & target, amount_msat, & params) ,
35433581 Some ( 0.0 ) ) ;
35443582 }
3583+
3584+ #[ test]
3585+ fn probes_for_diversity ( ) {
3586+ // Tests the probing_diversity_penalty_msat is applied
3587+ let logger = TestLogger :: new ( ) ;
3588+ let network_graph = network_graph ( & logger) ;
3589+ let params = ProbabilisticScoringFeeParameters {
3590+ probing_diversity_penalty_msat : 1_000_000 ,
3591+ ..ProbabilisticScoringFeeParameters :: zero_penalty ( )
3592+ } ;
3593+ let decay_params = ProbabilisticScoringDecayParameters {
3594+ liquidity_offset_half_life : Duration :: from_secs ( 10 ) ,
3595+ ..ProbabilisticScoringDecayParameters :: zero_penalty ( )
3596+ } ;
3597+ let mut scorer = ProbabilisticScorer :: new ( decay_params, & network_graph, & logger) ;
3598+ let source = source_node_id ( ) ;
3599+
3600+ let usage = ChannelUsage {
3601+ amount_msat : 512 ,
3602+ inflight_htlc_msat : 0 ,
3603+ effective_capacity : EffectiveCapacity :: Total { capacity_msat : 1_024 , htlc_maximum_msat : 1_024 } ,
3604+ } ;
3605+ let channel = network_graph. read_only ( ) . channel ( 42 ) . unwrap ( ) . to_owned ( ) ;
3606+ let ( info, _) = channel. as_directed_from ( & source) . unwrap ( ) ;
3607+ let candidate = CandidateRouteHop :: PublicHop ( PublicHopCandidate {
3608+ info,
3609+ short_channel_id : 42 ,
3610+ } ) ;
3611+
3612+ // Apply some update to set the last-update time to now
3613+ scorer. payment_path_failed ( & payment_path_for_amount ( 1000 ) , 42 , Duration :: ZERO ) ;
3614+
3615+ // If no time has passed, we get the full probing_diversity_penalty_msat
3616+ assert_eq ! ( scorer. channel_penalty_msat( & candidate, usage, & params) , 1_000_000 ) ;
3617+
3618+ // As time passes the penalty decreases.
3619+ scorer. time_passed ( Duration :: from_secs ( 1 ) ) ;
3620+ assert_eq ! ( scorer. channel_penalty_msat( & candidate, usage, & params) , 999_976 ) ;
3621+
3622+ scorer. time_passed ( Duration :: from_secs ( 2 ) ) ;
3623+ assert_eq ! ( scorer. channel_penalty_msat( & candidate, usage, & params) , 999_953 ) ;
3624+
3625+ // Once we've gotten halfway through the day our penalty is 1/4 the configured value.
3626+ scorer. time_passed ( Duration :: from_secs ( 86400 /2 ) ) ;
3627+ assert_eq ! ( scorer. channel_penalty_msat( & candidate, usage, & params) , 250_000 ) ;
3628+ }
35453629}
35463630
35473631#[ cfg( ldk_bench) ]
0 commit comments