@@ -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_update_time : Duration ,
479
482
}
480
483
/// Container for live and historical liquidity bounds for each channel.
481
484
#[ derive( Clone ) ]
@@ -745,6 +748,29 @@ 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
+ /// 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 ,
748
774
}
749
775
750
776
impl Default for ProbabilisticScoringFeeParameters {
@@ -760,6 +786,7 @@ impl Default for ProbabilisticScoringFeeParameters {
760
786
historical_liquidity_penalty_multiplier_msat : 10_000 ,
761
787
historical_liquidity_penalty_amount_multiplier_msat : 1_250 ,
762
788
linear_success_probability : false ,
789
+ probing_diversity_penalty_msat : 0 ,
763
790
}
764
791
}
765
792
}
@@ -814,6 +841,7 @@ impl ProbabilisticScoringFeeParameters {
814
841
anti_probing_penalty_msat : 0 ,
815
842
considered_impossible_penalty_msat : 0 ,
816
843
linear_success_probability : true ,
844
+ probing_diversity_penalty_msat : 0 ,
817
845
}
818
846
}
819
847
}
@@ -927,6 +955,7 @@ impl<G: Deref<Target = NetworkGraph<L>>, L: Deref> ProbabilisticScorer<G, L> whe
927
955
network_graph,
928
956
logger,
929
957
channel_liquidities : ChannelLiquidities :: new ( ) ,
958
+ last_update_time : Duration :: from_secs ( 0 ) ,
930
959
}
931
960
}
932
961
@@ -1383,7 +1412,7 @@ DirectedChannelLiquidity< L, HT, T> {
1383
1412
/// Returns a liquidity penalty for routing the given HTLC `amount_msat` through the channel in
1384
1413
/// this direction.
1385
1414
fn penalty_msat (
1386
- & self , amount_msat : u64 , inflight_htlc_msat : u64 ,
1415
+ & self , amount_msat : u64 , inflight_htlc_msat : u64 , last_update_time : Duration ,
1387
1416
score_params : & ProbabilisticScoringFeeParameters ,
1388
1417
) -> u64 {
1389
1418
let total_inflight_amount_msat = amount_msat. saturating_add ( inflight_htlc_msat) ;
@@ -1465,6 +1494,18 @@ DirectedChannelLiquidity< L, HT, T> {
1465
1494
}
1466
1495
}
1467
1496
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
+
1468
1509
res
1469
1510
}
1470
1511
@@ -1616,11 +1657,12 @@ impl<G: Deref<Target = NetworkGraph<L>>, L: Deref> ScoreLookUp for Probabilistic
1616
1657
}
1617
1658
1618
1659
let capacity_msat = usage. effective_capacity . as_msat ( ) ;
1660
+ let time = self . last_update_time ;
1619
1661
self . channel_liquidities
1620
1662
. get ( scid)
1621
1663
. unwrap_or ( & ChannelLiquidity :: new ( Duration :: ZERO ) )
1622
1664
. 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)
1624
1666
. saturating_add ( anti_probing_penalty_msat)
1625
1667
. saturating_add ( base_penalty_msat)
1626
1668
}
@@ -1666,6 +1708,7 @@ impl<G: Deref<Target = NetworkGraph<L>>, L: Deref> ScoreUpdate for Probabilistic
1666
1708
}
1667
1709
if at_failed_channel { break ; }
1668
1710
}
1711
+ self . last_update_time = duration_since_epoch;
1669
1712
}
1670
1713
1671
1714
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
1693
1736
hop. short_channel_id) ;
1694
1737
}
1695
1738
}
1739
+ self . last_update_time = duration_since_epoch;
1696
1740
}
1697
1741
1698
1742
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
1705
1749
1706
1750
fn time_passed ( & mut self , duration_since_epoch : Duration ) {
1707
1751
self . channel_liquidities . time_passed ( duration_since_epoch, self . decay_params ) ;
1752
+ self . last_update_time = duration_since_epoch;
1708
1753
}
1709
1754
}
1710
1755
@@ -2361,11 +2406,16 @@ ReadableArgs<(ProbabilisticScoringDecayParameters, G, L)> for ProbabilisticScore
2361
2406
) -> Result < Self , DecodeError > {
2362
2407
let ( decay_params, network_graph, logger) = args;
2363
2408
let channel_liquidities = ChannelLiquidities :: read ( r) ?;
2409
+ let mut last_update_time = Duration :: from_secs ( 0 ) ;
2410
+ for ( _, liq) in channel_liquidities. 0 . iter ( ) {
2411
+ last_update_time = cmp:: max ( last_update_time, liq. last_updated ) ;
2412
+ }
2364
2413
Ok ( Self {
2365
2414
decay_params,
2366
2415
network_graph,
2367
2416
logger,
2368
2417
channel_liquidities,
2418
+ last_update_time,
2369
2419
} )
2370
2420
}
2371
2421
}
@@ -4060,6 +4110,52 @@ mod tests {
4060
4110
combined_scorer. scorer . estimated_channel_liquidity_range ( 42 , & target_node_id ( ) ) ;
4061
4111
assert_eq ! ( liquidity_range. unwrap( ) , ( 0 , 0 ) ) ;
4062
4112
}
4113
+
4114
+ #[ test]
4115
+ fn probes_for_diversity ( ) {
4116
+ // Tests the probing_diversity_penalty_msat is applied
4117
+ let logger = TestLogger :: new ( ) ;
4118
+ let network_graph = network_graph ( & logger) ;
4119
+ let params = ProbabilisticScoringFeeParameters {
4120
+ probing_diversity_penalty_msat : 1_000_000 ,
4121
+ ..ProbabilisticScoringFeeParameters :: zero_penalty ( )
4122
+ } ;
4123
+ let decay_params = ProbabilisticScoringDecayParameters {
4124
+ liquidity_offset_half_life : Duration :: from_secs ( 10 ) ,
4125
+ ..ProbabilisticScoringDecayParameters :: zero_penalty ( )
4126
+ } ;
4127
+ let mut scorer = ProbabilisticScorer :: new ( decay_params, & network_graph, & logger) ;
4128
+ let source = source_node_id ( ) ;
4129
+
4130
+ let usage = ChannelUsage {
4131
+ amount_msat : 512 ,
4132
+ inflight_htlc_msat : 0 ,
4133
+ effective_capacity : EffectiveCapacity :: Total { capacity_msat : 1_024 , htlc_maximum_msat : 1_024 } ,
4134
+ } ;
4135
+ let channel = network_graph. read_only ( ) . channel ( 42 ) . unwrap ( ) . to_owned ( ) ;
4136
+ let ( info, _) = channel. as_directed_from ( & source) . unwrap ( ) ;
4137
+ let candidate = CandidateRouteHop :: PublicHop ( PublicHopCandidate {
4138
+ info,
4139
+ short_channel_id : 42 ,
4140
+ } ) ;
4141
+
4142
+ // Apply some update to set the last-update time to now
4143
+ scorer. payment_path_failed ( & payment_path_for_amount ( 1000 ) , 42 , Duration :: ZERO ) ;
4144
+
4145
+ // If no time has passed, we get the full probing_diversity_penalty_msat
4146
+ assert_eq ! ( scorer. channel_penalty_msat( & candidate, usage, & params) , 1_000_000 ) ;
4147
+
4148
+ // As time passes the penalty decreases.
4149
+ scorer. time_passed ( Duration :: from_secs ( 1 ) ) ;
4150
+ assert_eq ! ( scorer. channel_penalty_msat( & candidate, usage, & params) , 999_976 ) ;
4151
+
4152
+ scorer. time_passed ( Duration :: from_secs ( 2 ) ) ;
4153
+ assert_eq ! ( scorer. channel_penalty_msat( & candidate, usage, & params) , 999_953 ) ;
4154
+
4155
+ // Once we've gotten halfway through the day our penalty is 1/4 the configured value.
4156
+ scorer. time_passed ( Duration :: from_secs ( 86400 /2 ) ) ;
4157
+ assert_eq ! ( scorer. channel_penalty_msat( & candidate, usage, & params) , 250_000 ) ;
4158
+ }
4063
4159
}
4064
4160
4065
4161
#[ cfg( ldk_bench) ]
0 commit comments