@@ -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_update_time : Duration ,
479482}
480483/// Container for live and historical liquidity bounds for each channel.
481484#[ derive( Clone ) ]
@@ -745,6 +748,29 @@ 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+ /// As this is a maximum value, when setting this you should consider it in relation to the
766+ /// other values set to ensure that, at maximum, we strongly avoid paths which we recently
767+ /// tried (similar to if they have a low success probability). For example, you might set this
768+ /// to be the sum of [`Self::base_penalty_msat`] and
769+ /// [`Self::historical_liquidity_penalty_multiplier_msat`] (plus some multiple of their
770+ /// corresponding `amount_multiplier`s).
771+ ///
772+ /// Default value: 0
773+ pub probing_diversity_penalty_msat : u64 ,
748774}
749775
750776impl Default for ProbabilisticScoringFeeParameters {
@@ -760,6 +786,7 @@ impl Default for ProbabilisticScoringFeeParameters {
760786 historical_liquidity_penalty_multiplier_msat : 10_000 ,
761787 historical_liquidity_penalty_amount_multiplier_msat : 1_250 ,
762788 linear_success_probability : false ,
789+ probing_diversity_penalty_msat : 0 ,
763790 }
764791 }
765792}
@@ -814,6 +841,7 @@ impl ProbabilisticScoringFeeParameters {
814841 anti_probing_penalty_msat : 0 ,
815842 considered_impossible_penalty_msat : 0 ,
816843 linear_success_probability : true ,
844+ probing_diversity_penalty_msat : 0 ,
817845 }
818846 }
819847}
@@ -901,17 +929,11 @@ struct ChannelLiquidity {
901929 /// Time when the historical liquidity bounds were last modified as an offset against the unix
902930 /// epoch.
903931 offset_history_last_updated : Duration ,
904- }
905932
906- // Check that the liquidity HashMap's entries sit on round cache lines.
907- //
908- // Specifically, the first cache line will have the key, the liquidity offsets, and the total
909- // points tracked in the historical tracker.
910- //
911- // The next two cache lines will have the historical points, which we only access last during
912- // scoring, followed by the last_updated `Duration`s (which we do not need during scoring).
913- const _LIQUIDITY_MAP_SIZING_CHECK: usize = 192 - :: core:: mem:: size_of :: < ( u64 , ChannelLiquidity ) > ( ) ;
914- const _LIQUIDITY_MAP_SIZING_CHECK_2: usize = :: core:: mem:: size_of :: < ( u64 , ChannelLiquidity ) > ( ) - 192 ;
933+ /// The last time when the liquidity bounds were updated with new payment information (i.e.
934+ /// ignoring decays).
935+ last_datapoint_time : Duration ,
936+ }
915937
916938/// A snapshot of [`ChannelLiquidity`] in one direction assuming a certain channel capacity.
917939struct DirectedChannelLiquidity < L : Deref < Target = u64 > , HT : Deref < Target = HistoricalLiquidityTracker > , T : Deref < Target = Duration > > {
@@ -921,6 +943,7 @@ struct DirectedChannelLiquidity<L: Deref<Target = u64>, HT: Deref<Target = Histo
921943 capacity_msat : u64 ,
922944 last_updated : T ,
923945 offset_history_last_updated : T ,
946+ last_datapoint_time : T ,
924947}
925948
926949impl < G : Deref < Target = NetworkGraph < L > > , L : Deref > ProbabilisticScorer < G , L > where L :: Target : Logger {
@@ -932,6 +955,7 @@ impl<G: Deref<Target = NetworkGraph<L>>, L: Deref> ProbabilisticScorer<G, L> whe
932955 network_graph,
933956 logger,
934957 channel_liquidities : ChannelLiquidities :: new ( ) ,
958+ last_update_time : Duration :: from_secs ( 0 ) ,
935959 }
936960 }
937961
@@ -1163,6 +1187,7 @@ impl ChannelLiquidity {
11631187 liquidity_history : HistoricalLiquidityTracker :: new ( ) ,
11641188 last_updated,
11651189 offset_history_last_updated : last_updated,
1190+ last_datapoint_time : last_updated,
11661191 }
11671192 }
11681193
@@ -1195,6 +1220,7 @@ impl ChannelLiquidity {
11951220 capacity_msat,
11961221 last_updated : & self . last_updated ,
11971222 offset_history_last_updated : & self . offset_history_last_updated ,
1223+ last_datapoint_time : & self . last_datapoint_time ,
11981224 }
11991225 }
12001226
@@ -1218,6 +1244,7 @@ impl ChannelLiquidity {
12181244 capacity_msat,
12191245 last_updated : & mut self . last_updated ,
12201246 offset_history_last_updated : & mut self . offset_history_last_updated ,
1247+ last_datapoint_time : & mut self . last_datapoint_time ,
12211248 }
12221249 }
12231250
@@ -1385,7 +1412,7 @@ DirectedChannelLiquidity< L, HT, T> {
13851412 /// Returns a liquidity penalty for routing the given HTLC `amount_msat` through the channel in
13861413 /// this direction.
13871414 fn penalty_msat (
1388- & self , amount_msat : u64 , inflight_htlc_msat : u64 ,
1415+ & self , amount_msat : u64 , inflight_htlc_msat : u64 , last_update_time : Duration ,
13891416 score_params : & ProbabilisticScoringFeeParameters ,
13901417 ) -> u64 {
13911418 let total_inflight_amount_msat = amount_msat. saturating_add ( inflight_htlc_msat) ;
@@ -1467,6 +1494,18 @@ DirectedChannelLiquidity< L, HT, T> {
14671494 }
14681495 }
14691496
1497+ if score_params. probing_diversity_penalty_msat != 0 {
1498+ // We use `last_update_time` as a stand-in for the current time as we don't want to
1499+ // fetch the current time in every score call (slowing things down substantially on
1500+ // some platforms where a syscall is required), don't want to add an unnecessary `std`
1501+ // requirement. Assuming we're probing somewhat regularly, it should reliably be close
1502+ // to the current time, (and using the last the last time we probed is also fine here).
1503+ let time_since_update = last_update_time. saturating_sub ( * self . last_datapoint_time ) ;
1504+ let mul = Duration :: from_secs ( 60 * 60 * 24 ) . saturating_sub ( time_since_update) . as_secs ( ) ;
1505+ let penalty = score_params. probing_diversity_penalty_msat . saturating_mul ( mul * mul) ;
1506+ res = res. saturating_add ( penalty / ( ( 60 * 60 * 24 ) * ( 60 * 60 * 24 ) ) ) ;
1507+ }
1508+
14701509 res
14711510 }
14721511
@@ -1564,6 +1603,7 @@ DirectedChannelLiquidity<L, HT, T> {
15641603 * self . max_liquidity_offset_msat = 0 ;
15651604 }
15661605 * self . last_updated = duration_since_epoch;
1606+ * self . last_datapoint_time = duration_since_epoch;
15671607 }
15681608
15691609 /// Adjusts the upper bound of the channel liquidity balance in this direction.
@@ -1573,6 +1613,7 @@ DirectedChannelLiquidity<L, HT, T> {
15731613 * self . min_liquidity_offset_msat = 0 ;
15741614 }
15751615 * self . last_updated = duration_since_epoch;
1616+ * self . last_datapoint_time = duration_since_epoch;
15761617 }
15771618}
15781619
@@ -1616,11 +1657,12 @@ impl<G: Deref<Target = NetworkGraph<L>>, L: Deref> ScoreLookUp for Probabilistic
16161657 }
16171658
16181659 let capacity_msat = usage. effective_capacity . as_msat ( ) ;
1660+ let time = self . last_update_time ;
16191661 self . channel_liquidities
16201662 . get ( scid)
16211663 . unwrap_or ( & ChannelLiquidity :: new ( Duration :: ZERO ) )
16221664 . as_directed ( & source, & target, capacity_msat)
1623- . penalty_msat ( usage. amount_msat , usage. inflight_htlc_msat , score_params)
1665+ . penalty_msat ( usage. amount_msat , usage. inflight_htlc_msat , time , score_params)
16241666 . saturating_add ( anti_probing_penalty_msat)
16251667 . saturating_add ( base_penalty_msat)
16261668 }
@@ -1666,6 +1708,7 @@ impl<G: Deref<Target = NetworkGraph<L>>, L: Deref> ScoreUpdate for Probabilistic
16661708 }
16671709 if at_failed_channel { break ; }
16681710 }
1711+ self . last_update_time = duration_since_epoch;
16691712 }
16701713
16711714 fn payment_path_successful ( & mut self , path : & Path , duration_since_epoch : Duration ) {
@@ -1693,6 +1736,7 @@ impl<G: Deref<Target = NetworkGraph<L>>, L: Deref> ScoreUpdate for Probabilistic
16931736 hop. short_channel_id) ;
16941737 }
16951738 }
1739+ self . last_update_time = duration_since_epoch;
16961740 }
16971741
16981742 fn probe_failed ( & mut self , path : & Path , short_channel_id : u64 , duration_since_epoch : Duration ) {
@@ -1705,6 +1749,7 @@ impl<G: Deref<Target = NetworkGraph<L>>, L: Deref> ScoreUpdate for Probabilistic
17051749
17061750 fn time_passed ( & mut self , duration_since_epoch : Duration ) {
17071751 self . channel_liquidities . time_passed ( duration_since_epoch, self . decay_params ) ;
1752+ self . last_update_time = duration_since_epoch;
17081753 }
17091754}
17101755
@@ -2383,11 +2428,16 @@ ReadableArgs<(ProbabilisticScoringDecayParameters, G, L)> for ProbabilisticScore
23832428 ) -> Result < Self , DecodeError > {
23842429 let ( decay_params, network_graph, logger) = args;
23852430 let channel_liquidities = ChannelLiquidities :: read ( r) ?;
2431+ let mut last_update_time = Duration :: from_secs ( 0 ) ;
2432+ for ( _, liq) in channel_liquidities. 0 . iter ( ) {
2433+ last_update_time = cmp:: max ( last_update_time, liq. last_updated ) ;
2434+ }
23862435 Ok ( Self {
23872436 decay_params,
23882437 network_graph,
23892438 logger,
23902439 channel_liquidities,
2440+ last_update_time,
23912441 } )
23922442 }
23932443}
@@ -2404,6 +2454,7 @@ impl Writeable for ChannelLiquidity {
24042454 ( 5 , self . liquidity_history. writeable_min_offset_history( ) , required) ,
24052455 ( 7 , self . liquidity_history. writeable_max_offset_history( ) , required) ,
24062456 ( 9 , self . offset_history_last_updated, required) ,
2457+ ( 11 , self . last_datapoint_time, required) ,
24072458 } ) ;
24082459 Ok ( ( ) )
24092460 }
@@ -2420,6 +2471,7 @@ impl Readable for ChannelLiquidity {
24202471 let mut max_liquidity_offset_history: Option < HistoricalBucketRangeTracker > = None ;
24212472 let mut last_updated = Duration :: from_secs ( 0 ) ;
24222473 let mut offset_history_last_updated = None ;
2474+ let mut last_datapoint_time = None ;
24232475 read_tlv_fields ! ( r, {
24242476 ( 0 , min_liquidity_offset_msat, required) ,
24252477 ( 1 , legacy_min_liq_offset_history, option) ,
@@ -2429,6 +2481,7 @@ impl Readable for ChannelLiquidity {
24292481 ( 5 , min_liquidity_offset_history, option) ,
24302482 ( 7 , max_liquidity_offset_history, option) ,
24312483 ( 9 , offset_history_last_updated, option) ,
2484+ ( 11 , last_datapoint_time, option) ,
24322485 } ) ;
24332486
24342487 if min_liquidity_offset_history. is_none ( ) {
@@ -2453,6 +2506,7 @@ impl Readable for ChannelLiquidity {
24532506 ) ,
24542507 last_updated,
24552508 offset_history_last_updated : offset_history_last_updated. unwrap_or ( last_updated) ,
2509+ last_datapoint_time : last_datapoint_time. unwrap_or ( last_updated) ,
24562510 } )
24572511 }
24582512}
@@ -2626,19 +2680,20 @@ mod tests {
26262680 let logger = TestLogger :: new ( ) ;
26272681 let last_updated = Duration :: ZERO ;
26282682 let offset_history_last_updated = Duration :: ZERO ;
2683+ let last_datapoint_time = Duration :: ZERO ;
26292684 let network_graph = network_graph ( & logger) ;
26302685 let decay_params = ProbabilisticScoringDecayParameters :: default ( ) ;
26312686 let mut scorer = ProbabilisticScorer :: new ( decay_params, & network_graph, & logger)
26322687 . with_channel ( 42 ,
26332688 ChannelLiquidity {
26342689 min_liquidity_offset_msat : 700 , max_liquidity_offset_msat : 100 ,
2635- last_updated, offset_history_last_updated,
2690+ last_updated, offset_history_last_updated, last_datapoint_time ,
26362691 liquidity_history : HistoricalLiquidityTracker :: new ( ) ,
26372692 } )
26382693 . with_channel ( 43 ,
26392694 ChannelLiquidity {
26402695 min_liquidity_offset_msat : 700 , max_liquidity_offset_msat : 100 ,
2641- last_updated, offset_history_last_updated,
2696+ last_updated, offset_history_last_updated, last_datapoint_time ,
26422697 liquidity_history : HistoricalLiquidityTracker :: new ( ) ,
26432698 } ) ;
26442699 let source = source_node_id ( ) ;
@@ -2705,13 +2760,14 @@ mod tests {
27052760 let logger = TestLogger :: new ( ) ;
27062761 let last_updated = Duration :: ZERO ;
27072762 let offset_history_last_updated = Duration :: ZERO ;
2763+ let last_datapoint_time = Duration :: ZERO ;
27082764 let network_graph = network_graph ( & logger) ;
27092765 let decay_params = ProbabilisticScoringDecayParameters :: default ( ) ;
27102766 let mut scorer = ProbabilisticScorer :: new ( decay_params, & network_graph, & logger)
27112767 . with_channel ( 42 ,
27122768 ChannelLiquidity {
27132769 min_liquidity_offset_msat : 200 , max_liquidity_offset_msat : 400 ,
2714- last_updated, offset_history_last_updated,
2770+ last_updated, offset_history_last_updated, last_datapoint_time ,
27152771 liquidity_history : HistoricalLiquidityTracker :: new ( ) ,
27162772 } ) ;
27172773 let source = source_node_id ( ) ;
@@ -2765,13 +2821,14 @@ mod tests {
27652821 let logger = TestLogger :: new ( ) ;
27662822 let last_updated = Duration :: ZERO ;
27672823 let offset_history_last_updated = Duration :: ZERO ;
2824+ let last_datapoint_time = Duration :: ZERO ;
27682825 let network_graph = network_graph ( & logger) ;
27692826 let decay_params = ProbabilisticScoringDecayParameters :: default ( ) ;
27702827 let mut scorer = ProbabilisticScorer :: new ( decay_params, & network_graph, & logger)
27712828 . with_channel ( 42 ,
27722829 ChannelLiquidity {
27732830 min_liquidity_offset_msat : 200 , max_liquidity_offset_msat : 400 ,
2774- last_updated, offset_history_last_updated,
2831+ last_updated, offset_history_last_updated, last_datapoint_time ,
27752832 liquidity_history : HistoricalLiquidityTracker :: new ( ) ,
27762833 } ) ;
27772834 let source = source_node_id ( ) ;
@@ -2877,6 +2934,7 @@ mod tests {
28772934 let logger = TestLogger :: new ( ) ;
28782935 let last_updated = Duration :: ZERO ;
28792936 let offset_history_last_updated = Duration :: ZERO ;
2937+ let last_datapoint_time = Duration :: ZERO ;
28802938 let network_graph = network_graph ( & logger) ;
28812939 let params = ProbabilisticScoringFeeParameters {
28822940 liquidity_penalty_multiplier_msat : 1_000 ,
@@ -2890,7 +2948,7 @@ mod tests {
28902948 . with_channel ( 42 ,
28912949 ChannelLiquidity {
28922950 min_liquidity_offset_msat : 40 , max_liquidity_offset_msat : 40 ,
2893- last_updated, offset_history_last_updated,
2951+ last_updated, offset_history_last_updated, last_datapoint_time ,
28942952 liquidity_history : HistoricalLiquidityTracker :: new ( ) ,
28952953 } ) ;
28962954 let source = source_node_id ( ) ;
@@ -4074,6 +4132,52 @@ mod tests {
40744132 combined_scorer. scorer . estimated_channel_liquidity_range ( 42 , & target_node_id ( ) ) ;
40754133 assert_eq ! ( liquidity_range. unwrap( ) , ( 0 , 0 ) ) ;
40764134 }
4135+
4136+ #[ test]
4137+ fn probes_for_diversity ( ) {
4138+ // Tests the probing_diversity_penalty_msat is applied
4139+ let logger = TestLogger :: new ( ) ;
4140+ let network_graph = network_graph ( & logger) ;
4141+ let params = ProbabilisticScoringFeeParameters {
4142+ probing_diversity_penalty_msat : 1_000_000 ,
4143+ ..ProbabilisticScoringFeeParameters :: zero_penalty ( )
4144+ } ;
4145+ let decay_params = ProbabilisticScoringDecayParameters {
4146+ liquidity_offset_half_life : Duration :: from_secs ( 10 ) ,
4147+ ..ProbabilisticScoringDecayParameters :: zero_penalty ( )
4148+ } ;
4149+ let mut scorer = ProbabilisticScorer :: new ( decay_params, & network_graph, & logger) ;
4150+ let source = source_node_id ( ) ;
4151+
4152+ let usage = ChannelUsage {
4153+ amount_msat : 512 ,
4154+ inflight_htlc_msat : 0 ,
4155+ effective_capacity : EffectiveCapacity :: Total { capacity_msat : 1_024 , htlc_maximum_msat : 1_024 } ,
4156+ } ;
4157+ let channel = network_graph. read_only ( ) . channel ( 42 ) . unwrap ( ) . to_owned ( ) ;
4158+ let ( info, _) = channel. as_directed_from ( & source) . unwrap ( ) ;
4159+ let candidate = CandidateRouteHop :: PublicHop ( PublicHopCandidate {
4160+ info,
4161+ short_channel_id : 42 ,
4162+ } ) ;
4163+
4164+ // Apply some update to set the last-update time to now
4165+ scorer. payment_path_failed ( & payment_path_for_amount ( 1000 ) , 42 , Duration :: ZERO ) ;
4166+
4167+ // If no time has passed, we get the full probing_diversity_penalty_msat
4168+ assert_eq ! ( scorer. channel_penalty_msat( & candidate, usage, & params) , 1_000_000 ) ;
4169+
4170+ // As time passes the penalty decreases.
4171+ scorer. time_passed ( Duration :: from_secs ( 1 ) ) ;
4172+ assert_eq ! ( scorer. channel_penalty_msat( & candidate, usage, & params) , 999_976 ) ;
4173+
4174+ scorer. time_passed ( Duration :: from_secs ( 2 ) ) ;
4175+ assert_eq ! ( scorer. channel_penalty_msat( & candidate, usage, & params) , 999_953 ) ;
4176+
4177+ // Once we've gotten halfway through the day our penalty is 1/4 the configured value.
4178+ scorer. time_passed ( Duration :: from_secs ( 86400 /2 ) ) ;
4179+ assert_eq ! ( scorer. channel_penalty_msat( & candidate, usage, & params) , 250_000 ) ;
4180+ }
40774181}
40784182
40794183#[ cfg( ldk_bench) ]
0 commit comments