@@ -476,6 +476,9 @@ where L::Target: Logger {
476476 network_graph : G ,
477477 logger : L ,
478478 channel_liquidities : ChannelLiquidities ,
479+ /// The last time we were given via a [`ScoreUpdate`] method. This does not imply that we've
480+ /// decayed every liquidity bound up to that time.
481+ last_duration_since_epoch : Duration ,
479482}
480483/// Container for live and historical liquidity bounds for each channel.
481484#[ derive( Clone ) ]
@@ -745,6 +748,22 @@ pub struct ProbabilisticScoringFeeParameters {
745748 ///
746749 /// Default value: false
747750 pub linear_success_probability : bool ,
751+
752+ /// In order to ensure we have knowledge for as many paths as possible, when probing it makes
753+ /// sense to bias away from channels for which we have very recent data.
754+ ///
755+ /// This value is a penalty that is applied based on the last time that we updated the bounds
756+ /// on the available liquidity in a channel. The specified value is the maximum penalty that
757+ /// will be applied.
758+ ///
759+ /// It obviously does not make sense to assign a non-0 value here unless you are using the
760+ /// pathfinding result for background probing.
761+ ///
762+ /// Specifically, the following penalty is applied
763+ /// `probing_diversity_penalty_msat * max(0, (86400 - current time + last update))^2 / 86400^2` is
764+ ///
765+ /// Default value: 0
766+ pub probing_diversity_penalty_msat : u64 ,
748767}
749768
750769impl Default for ProbabilisticScoringFeeParameters {
@@ -760,6 +779,7 @@ impl Default for ProbabilisticScoringFeeParameters {
760779 historical_liquidity_penalty_multiplier_msat : 10_000 ,
761780 historical_liquidity_penalty_amount_multiplier_msat : 1_250 ,
762781 linear_success_probability : false ,
782+ probing_diversity_penalty_msat : 0 ,
763783 }
764784 }
765785}
@@ -814,6 +834,7 @@ impl ProbabilisticScoringFeeParameters {
814834 anti_probing_penalty_msat : 0 ,
815835 considered_impossible_penalty_msat : 0 ,
816836 linear_success_probability : true ,
837+ probing_diversity_penalty_msat : 0 ,
817838 }
818839 }
819840}
@@ -959,6 +980,7 @@ impl<G: Deref<Target = NetworkGraph<L>>, L: Deref> ProbabilisticScorer<G, L> whe
959980 network_graph,
960981 logger,
961982 channel_liquidities : ChannelLiquidities :: new ( ) ,
983+ last_duration_since_epoch : Duration :: from_secs ( 0 ) ,
962984 }
963985 }
964986
@@ -1416,7 +1438,7 @@ DirectedChannelLiquidity< L, HT, T> {
14161438 /// Returns a liquidity penalty for routing the given HTLC `amount_msat` through the channel in
14171439 /// this direction.
14181440 fn penalty_msat (
1419- & self , amount_msat : u64 , inflight_htlc_msat : u64 ,
1441+ & self , amount_msat : u64 , inflight_htlc_msat : u64 , last_duration_since_epoch : Duration ,
14201442 score_params : & ProbabilisticScoringFeeParameters ,
14211443 ) -> u64 {
14221444 let total_inflight_amount_msat = amount_msat. saturating_add ( inflight_htlc_msat) ;
@@ -1498,6 +1520,13 @@ DirectedChannelLiquidity< L, HT, T> {
14981520 }
14991521 }
15001522
1523+ if score_params. probing_diversity_penalty_msat != 0 {
1524+ let time_since_update = last_duration_since_epoch. saturating_sub ( * self . last_datapoint ) ;
1525+ let mul = Duration :: from_secs ( 60 * 60 * 24 ) . saturating_sub ( time_since_update) . as_secs ( ) ;
1526+ let penalty = score_params. probing_diversity_penalty_msat . saturating_mul ( mul * mul) ;
1527+ res = res. saturating_add ( penalty / ( ( 60 * 60 * 24 ) * ( 60 * 60 * 24 ) ) ) ;
1528+ }
1529+
15011530 res
15021531 }
15031532
@@ -1649,11 +1678,12 @@ impl<G: Deref<Target = NetworkGraph<L>>, L: Deref> ScoreLookUp for Probabilistic
16491678 }
16501679
16511680 let capacity_msat = usage. effective_capacity . as_msat ( ) ;
1681+ let time = self . last_duration_since_epoch ;
16521682 self . channel_liquidities
16531683 . get ( scid)
16541684 . unwrap_or ( & ChannelLiquidity :: new ( Duration :: ZERO ) )
16551685 . as_directed ( & source, & target, capacity_msat)
1656- . penalty_msat ( usage. amount_msat , usage. inflight_htlc_msat , score_params)
1686+ . penalty_msat ( usage. amount_msat , usage. inflight_htlc_msat , time , score_params)
16571687 . saturating_add ( anti_probing_penalty_msat)
16581688 . saturating_add ( base_penalty_msat)
16591689 }
@@ -1699,6 +1729,7 @@ impl<G: Deref<Target = NetworkGraph<L>>, L: Deref> ScoreUpdate for Probabilistic
16991729 }
17001730 if at_failed_channel { break ; }
17011731 }
1732+ self . last_duration_since_epoch = duration_since_epoch;
17021733 }
17031734
17041735 fn payment_path_successful ( & mut self , path : & Path , duration_since_epoch : Duration ) {
@@ -1726,6 +1757,7 @@ impl<G: Deref<Target = NetworkGraph<L>>, L: Deref> ScoreUpdate for Probabilistic
17261757 hop. short_channel_id) ;
17271758 }
17281759 }
1760+ self . last_duration_since_epoch = duration_since_epoch;
17291761 }
17301762
17311763 fn probe_failed ( & mut self , path : & Path , short_channel_id : u64 , duration_since_epoch : Duration ) {
@@ -1738,6 +1770,7 @@ impl<G: Deref<Target = NetworkGraph<L>>, L: Deref> ScoreUpdate for Probabilistic
17381770
17391771 fn time_passed ( & mut self , duration_since_epoch : Duration ) {
17401772 self . channel_liquidities . time_passed ( duration_since_epoch, self . decay_params ) ;
1773+ self . last_duration_since_epoch = duration_since_epoch;
17411774 }
17421775}
17431776
@@ -2394,11 +2427,16 @@ ReadableArgs<(ProbabilisticScoringDecayParameters, G, L)> for ProbabilisticScore
23942427 ) -> Result < Self , DecodeError > {
23952428 let ( decay_params, network_graph, logger) = args;
23962429 let channel_liquidities = ChannelLiquidities :: read ( r) ?;
2430+ let mut last_duration_since_epoch = Duration :: from_secs ( 0 ) ;
2431+ for ( _, liq) in channel_liquidities. 0 . iter ( ) {
2432+ last_duration_since_epoch = cmp:: max ( last_duration_since_epoch, liq. last_updated ) ;
2433+ }
23972434 Ok ( Self {
23982435 decay_params,
23992436 network_graph,
24002437 logger,
24012438 channel_liquidities,
2439+ last_duration_since_epoch,
24022440 } )
24032441 }
24042442}
@@ -4099,6 +4137,52 @@ mod tests {
40994137 combined_scorer. scorer . estimated_channel_liquidity_range ( 42 , & target_node_id ( ) ) ;
41004138 assert_eq ! ( liquidity_range. unwrap( ) , ( 0 , 0 ) ) ;
41014139 }
4140+
4141+ #[ test]
4142+ fn probes_for_diversity ( ) {
4143+ // Tests the probing_diversity_penalty_msat is applied
4144+ let logger = TestLogger :: new ( ) ;
4145+ let network_graph = network_graph ( & logger) ;
4146+ let params = ProbabilisticScoringFeeParameters {
4147+ probing_diversity_penalty_msat : 1_000_000 ,
4148+ ..ProbabilisticScoringFeeParameters :: zero_penalty ( )
4149+ } ;
4150+ let decay_params = ProbabilisticScoringDecayParameters {
4151+ liquidity_offset_half_life : Duration :: from_secs ( 10 ) ,
4152+ ..ProbabilisticScoringDecayParameters :: zero_penalty ( )
4153+ } ;
4154+ let mut scorer = ProbabilisticScorer :: new ( decay_params, & network_graph, & logger) ;
4155+ let source = source_node_id ( ) ;
4156+
4157+ let usage = ChannelUsage {
4158+ amount_msat : 512 ,
4159+ inflight_htlc_msat : 0 ,
4160+ effective_capacity : EffectiveCapacity :: Total { capacity_msat : 1_024 , htlc_maximum_msat : 1_024 } ,
4161+ } ;
4162+ let channel = network_graph. read_only ( ) . channel ( 42 ) . unwrap ( ) . to_owned ( ) ;
4163+ let ( info, _) = channel. as_directed_from ( & source) . unwrap ( ) ;
4164+ let candidate = CandidateRouteHop :: PublicHop ( PublicHopCandidate {
4165+ info,
4166+ short_channel_id : 42 ,
4167+ } ) ;
4168+
4169+ // Apply some update to set the last-update time to now
4170+ scorer. payment_path_failed ( & payment_path_for_amount ( 1000 ) , 42 , Duration :: ZERO ) ;
4171+
4172+ // If no time has passed, we get the full probing_diversity_penalty_msat
4173+ assert_eq ! ( scorer. channel_penalty_msat( & candidate, usage, & params) , 1_000_000 ) ;
4174+
4175+ // As time passes the penalty decreases.
4176+ scorer. time_passed ( Duration :: from_secs ( 1 ) ) ;
4177+ assert_eq ! ( scorer. channel_penalty_msat( & candidate, usage, & params) , 999_976 ) ;
4178+
4179+ scorer. time_passed ( Duration :: from_secs ( 2 ) ) ;
4180+ assert_eq ! ( scorer. channel_penalty_msat( & candidate, usage, & params) , 999_953 ) ;
4181+
4182+ // Once we've gotten halfway through the day our penalty is 1/4 the configured value.
4183+ scorer. time_passed ( Duration :: from_secs ( 86400 /2 ) ) ;
4184+ assert_eq ! ( scorer. channel_penalty_msat( & candidate, usage, & params) , 250_000 ) ;
4185+ }
41024186}
41034187
41044188#[ cfg( ldk_bench) ]
0 commit comments