@@ -476,6 +476,9 @@ where L::Target: Logger {
476
476
network_graph : G ,
477
477
logger : L ,
478
478
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 ,
479
482
}
480
483
/// Container for live and historical liquidity bounds for each channel.
481
484
#[ derive( Clone ) ]
@@ -745,6 +748,22 @@ pub struct ProbabilisticScoringFeeParameters {
745
748
///
746
749
/// Default value: false
747
750
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 ,
748
767
}
749
768
750
769
impl Default for ProbabilisticScoringFeeParameters {
@@ -760,6 +779,7 @@ impl Default for ProbabilisticScoringFeeParameters {
760
779
historical_liquidity_penalty_multiplier_msat : 10_000 ,
761
780
historical_liquidity_penalty_amount_multiplier_msat : 1_250 ,
762
781
linear_success_probability : false ,
782
+ probing_diversity_penalty_msat : 0 ,
763
783
}
764
784
}
765
785
}
@@ -814,6 +834,7 @@ impl ProbabilisticScoringFeeParameters {
814
834
anti_probing_penalty_msat : 0 ,
815
835
considered_impossible_penalty_msat : 0 ,
816
836
linear_success_probability : true ,
837
+ probing_diversity_penalty_msat : 0 ,
817
838
}
818
839
}
819
840
}
@@ -959,6 +980,7 @@ impl<G: Deref<Target = NetworkGraph<L>>, L: Deref> ProbabilisticScorer<G, L> whe
959
980
network_graph,
960
981
logger,
961
982
channel_liquidities : ChannelLiquidities :: new ( ) ,
983
+ last_duration_since_epoch : Duration :: from_secs ( 0 ) ,
962
984
}
963
985
}
964
986
@@ -1416,7 +1438,7 @@ DirectedChannelLiquidity< L, HT, T> {
1416
1438
/// Returns a liquidity penalty for routing the given HTLC `amount_msat` through the channel in
1417
1439
/// this direction.
1418
1440
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 ,
1420
1442
score_params : & ProbabilisticScoringFeeParameters ,
1421
1443
) -> u64 {
1422
1444
let total_inflight_amount_msat = amount_msat. saturating_add ( inflight_htlc_msat) ;
@@ -1498,6 +1520,13 @@ DirectedChannelLiquidity< L, HT, T> {
1498
1520
}
1499
1521
}
1500
1522
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
+
1501
1530
res
1502
1531
}
1503
1532
@@ -1649,11 +1678,12 @@ impl<G: Deref<Target = NetworkGraph<L>>, L: Deref> ScoreLookUp for Probabilistic
1649
1678
}
1650
1679
1651
1680
let capacity_msat = usage. effective_capacity . as_msat ( ) ;
1681
+ let time = self . last_duration_since_epoch ;
1652
1682
self . channel_liquidities
1653
1683
. get ( scid)
1654
1684
. unwrap_or ( & ChannelLiquidity :: new ( Duration :: ZERO ) )
1655
1685
. 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)
1657
1687
. saturating_add ( anti_probing_penalty_msat)
1658
1688
. saturating_add ( base_penalty_msat)
1659
1689
}
@@ -1699,6 +1729,7 @@ impl<G: Deref<Target = NetworkGraph<L>>, L: Deref> ScoreUpdate for Probabilistic
1699
1729
}
1700
1730
if at_failed_channel { break ; }
1701
1731
}
1732
+ self . last_duration_since_epoch = duration_since_epoch;
1702
1733
}
1703
1734
1704
1735
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
1726
1757
hop. short_channel_id) ;
1727
1758
}
1728
1759
}
1760
+ self . last_duration_since_epoch = duration_since_epoch;
1729
1761
}
1730
1762
1731
1763
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
1738
1770
1739
1771
fn time_passed ( & mut self , duration_since_epoch : Duration ) {
1740
1772
self . channel_liquidities . time_passed ( duration_since_epoch, self . decay_params ) ;
1773
+ self . last_duration_since_epoch = duration_since_epoch;
1741
1774
}
1742
1775
}
1743
1776
@@ -2394,11 +2427,16 @@ ReadableArgs<(ProbabilisticScoringDecayParameters, G, L)> for ProbabilisticScore
2394
2427
) -> Result < Self , DecodeError > {
2395
2428
let ( decay_params, network_graph, logger) = args;
2396
2429
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
+ }
2397
2434
Ok ( Self {
2398
2435
decay_params,
2399
2436
network_graph,
2400
2437
logger,
2401
2438
channel_liquidities,
2439
+ last_duration_since_epoch,
2402
2440
} )
2403
2441
}
2404
2442
}
@@ -4099,6 +4137,52 @@ mod tests {
4099
4137
combined_scorer. scorer . estimated_channel_liquidity_range ( 42 , & target_node_id ( ) ) ;
4100
4138
assert_eq ! ( liquidity_range. unwrap( ) , ( 0 , 0 ) ) ;
4101
4139
}
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
+ }
4102
4186
}
4103
4187
4104
4188
#[ cfg( ldk_bench) ]
0 commit comments