@@ -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}
@@ -927,6 +948,7 @@ impl<G: Deref<Target = NetworkGraph<L>>, L: Deref> ProbabilisticScorer<G, L> whe
927948 network_graph,
928949 logger,
929950 channel_liquidities : ChannelLiquidities :: new ( ) ,
951+ last_duration_since_epoch : Duration :: from_secs ( 0 ) ,
930952 }
931953 }
932954
@@ -1383,7 +1405,7 @@ DirectedChannelLiquidity< L, HT, T> {
13831405 /// Returns a liquidity penalty for routing the given HTLC `amount_msat` through the channel in
13841406 /// this direction.
13851407 fn penalty_msat (
1386- & self , amount_msat : u64 , inflight_htlc_msat : u64 ,
1408+ & self , amount_msat : u64 , inflight_htlc_msat : u64 , last_duration_since_epoch : Duration ,
13871409 score_params : & ProbabilisticScoringFeeParameters ,
13881410 ) -> u64 {
13891411 let total_inflight_amount_msat = amount_msat. saturating_add ( inflight_htlc_msat) ;
@@ -1465,6 +1487,13 @@ DirectedChannelLiquidity< L, HT, T> {
14651487 }
14661488 }
14671489
1490+ if score_params. probing_diversity_penalty_msat != 0 {
1491+ let time_since_update = last_duration_since_epoch. saturating_sub ( * self . last_datapoint ) ;
1492+ let mul = Duration :: from_secs ( 60 * 60 * 24 ) . saturating_sub ( time_since_update) . as_secs ( ) ;
1493+ let penalty = score_params. probing_diversity_penalty_msat . saturating_mul ( mul * mul) ;
1494+ res = res. saturating_add ( penalty / ( ( 60 * 60 * 24 ) * ( 60 * 60 * 24 ) ) ) ;
1495+ }
1496+
14681497 res
14691498 }
14701499
@@ -1616,11 +1645,12 @@ impl<G: Deref<Target = NetworkGraph<L>>, L: Deref> ScoreLookUp for Probabilistic
16161645 }
16171646
16181647 let capacity_msat = usage. effective_capacity . as_msat ( ) ;
1648+ let time = self . last_duration_since_epoch ;
16191649 self . channel_liquidities
16201650 . get ( scid)
16211651 . unwrap_or ( & ChannelLiquidity :: new ( Duration :: ZERO ) )
16221652 . as_directed ( & source, & target, capacity_msat)
1623- . penalty_msat ( usage. amount_msat , usage. inflight_htlc_msat , score_params)
1653+ . penalty_msat ( usage. amount_msat , usage. inflight_htlc_msat , time , score_params)
16241654 . saturating_add ( anti_probing_penalty_msat)
16251655 . saturating_add ( base_penalty_msat)
16261656 }
@@ -1666,6 +1696,7 @@ impl<G: Deref<Target = NetworkGraph<L>>, L: Deref> ScoreUpdate for Probabilistic
16661696 }
16671697 if at_failed_channel { break ; }
16681698 }
1699+ self . last_duration_since_epoch = duration_since_epoch;
16691700 }
16701701
16711702 fn payment_path_successful ( & mut self , path : & Path , duration_since_epoch : Duration ) {
@@ -1693,6 +1724,7 @@ impl<G: Deref<Target = NetworkGraph<L>>, L: Deref> ScoreUpdate for Probabilistic
16931724 hop. short_channel_id) ;
16941725 }
16951726 }
1727+ self . last_duration_since_epoch = duration_since_epoch;
16961728 }
16971729
16981730 fn probe_failed ( & mut self , path : & Path , short_channel_id : u64 , duration_since_epoch : Duration ) {
@@ -1705,6 +1737,7 @@ impl<G: Deref<Target = NetworkGraph<L>>, L: Deref> ScoreUpdate for Probabilistic
17051737
17061738 fn time_passed ( & mut self , duration_since_epoch : Duration ) {
17071739 self . channel_liquidities . time_passed ( duration_since_epoch, self . decay_params ) ;
1740+ self . last_duration_since_epoch = duration_since_epoch;
17081741 }
17091742}
17101743
@@ -2361,11 +2394,16 @@ ReadableArgs<(ProbabilisticScoringDecayParameters, G, L)> for ProbabilisticScore
23612394 ) -> Result < Self , DecodeError > {
23622395 let ( decay_params, network_graph, logger) = args;
23632396 let channel_liquidities = ChannelLiquidities :: read ( r) ?;
2397+ let mut last_duration_since_epoch = Duration :: from_secs ( 0 ) ;
2398+ for ( _, liq) in channel_liquidities. 0 . iter ( ) {
2399+ last_duration_since_epoch = cmp:: max ( last_duration_since_epoch, liq. last_updated ) ;
2400+ }
23642401 Ok ( Self {
23652402 decay_params,
23662403 network_graph,
23672404 logger,
23682405 channel_liquidities,
2406+ last_duration_since_epoch,
23692407 } )
23702408 }
23712409}
@@ -4060,6 +4098,52 @@ mod tests {
40604098 combined_scorer. scorer . estimated_channel_liquidity_range ( 42 , & target_node_id ( ) ) ;
40614099 assert_eq ! ( liquidity_range. unwrap( ) , ( 0 , 0 ) ) ;
40624100 }
4101+
4102+ #[ test]
4103+ fn probes_for_diversity ( ) {
4104+ // Tests the probing_diversity_penalty_msat is applied
4105+ let logger = TestLogger :: new ( ) ;
4106+ let network_graph = network_graph ( & logger) ;
4107+ let params = ProbabilisticScoringFeeParameters {
4108+ probing_diversity_penalty_msat : 1_000_000 ,
4109+ ..ProbabilisticScoringFeeParameters :: zero_penalty ( )
4110+ } ;
4111+ let decay_params = ProbabilisticScoringDecayParameters {
4112+ liquidity_offset_half_life : Duration :: from_secs ( 10 ) ,
4113+ ..ProbabilisticScoringDecayParameters :: zero_penalty ( )
4114+ } ;
4115+ let mut scorer = ProbabilisticScorer :: new ( decay_params, & network_graph, & logger) ;
4116+ let source = source_node_id ( ) ;
4117+
4118+ let usage = ChannelUsage {
4119+ amount_msat : 512 ,
4120+ inflight_htlc_msat : 0 ,
4121+ effective_capacity : EffectiveCapacity :: Total { capacity_msat : 1_024 , htlc_maximum_msat : 1_024 } ,
4122+ } ;
4123+ let channel = network_graph. read_only ( ) . channel ( 42 ) . unwrap ( ) . to_owned ( ) ;
4124+ let ( info, _) = channel. as_directed_from ( & source) . unwrap ( ) ;
4125+ let candidate = CandidateRouteHop :: PublicHop ( PublicHopCandidate {
4126+ info,
4127+ short_channel_id : 42 ,
4128+ } ) ;
4129+
4130+ // Apply some update to set the last-update time to now
4131+ scorer. payment_path_failed ( & payment_path_for_amount ( 1000 ) , 42 , Duration :: ZERO ) ;
4132+
4133+ // If no time has passed, we get the full probing_diversity_penalty_msat
4134+ assert_eq ! ( scorer. channel_penalty_msat( & candidate, usage, & params) , 1_000_000 ) ;
4135+
4136+ // As time passes the penalty decreases.
4137+ scorer. time_passed ( Duration :: from_secs ( 1 ) ) ;
4138+ assert_eq ! ( scorer. channel_penalty_msat( & candidate, usage, & params) , 999_976 ) ;
4139+
4140+ scorer. time_passed ( Duration :: from_secs ( 2 ) ) ;
4141+ assert_eq ! ( scorer. channel_penalty_msat( & candidate, usage, & params) , 999_953 ) ;
4142+
4143+ // Once we've gotten halfway through the day our penalty is 1/4 the configured value.
4144+ scorer. time_passed ( Duration :: from_secs ( 86400 /2 ) ) ;
4145+ assert_eq ! ( scorer. channel_penalty_msat( & candidate, usage, & params) , 250_000 ) ;
4146+ }
40634147}
40644148
40654149#[ cfg( ldk_bench) ]
0 commit comments