@@ -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
}
@@ -927,6 +948,7 @@ impl<G: Deref<Target = NetworkGraph<L>>, L: Deref> ProbabilisticScorer<G, L> whe
927
948
network_graph,
928
949
logger,
929
950
channel_liquidities : ChannelLiquidities :: new ( ) ,
951
+ last_duration_since_epoch : Duration :: from_secs ( 0 ) ,
930
952
}
931
953
}
932
954
@@ -1383,7 +1405,7 @@ DirectedChannelLiquidity< L, HT, T> {
1383
1405
/// Returns a liquidity penalty for routing the given HTLC `amount_msat` through the channel in
1384
1406
/// this direction.
1385
1407
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 ,
1387
1409
score_params : & ProbabilisticScoringFeeParameters ,
1388
1410
) -> u64 {
1389
1411
let total_inflight_amount_msat = amount_msat. saturating_add ( inflight_htlc_msat) ;
@@ -1465,6 +1487,13 @@ DirectedChannelLiquidity< L, HT, T> {
1465
1487
}
1466
1488
}
1467
1489
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
+
1468
1497
res
1469
1498
}
1470
1499
@@ -1616,11 +1645,12 @@ impl<G: Deref<Target = NetworkGraph<L>>, L: Deref> ScoreLookUp for Probabilistic
1616
1645
}
1617
1646
1618
1647
let capacity_msat = usage. effective_capacity . as_msat ( ) ;
1648
+ let time = self . last_duration_since_epoch ;
1619
1649
self . channel_liquidities
1620
1650
. get ( scid)
1621
1651
. unwrap_or ( & ChannelLiquidity :: new ( Duration :: ZERO ) )
1622
1652
. 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)
1624
1654
. saturating_add ( anti_probing_penalty_msat)
1625
1655
. saturating_add ( base_penalty_msat)
1626
1656
}
@@ -1666,6 +1696,7 @@ impl<G: Deref<Target = NetworkGraph<L>>, L: Deref> ScoreUpdate for Probabilistic
1666
1696
}
1667
1697
if at_failed_channel { break ; }
1668
1698
}
1699
+ self . last_duration_since_epoch = duration_since_epoch;
1669
1700
}
1670
1701
1671
1702
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
1693
1724
hop. short_channel_id) ;
1694
1725
}
1695
1726
}
1727
+ self . last_duration_since_epoch = duration_since_epoch;
1696
1728
}
1697
1729
1698
1730
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
1705
1737
1706
1738
fn time_passed ( & mut self , duration_since_epoch : Duration ) {
1707
1739
self . channel_liquidities . time_passed ( duration_since_epoch, self . decay_params ) ;
1740
+ self . last_duration_since_epoch = duration_since_epoch;
1708
1741
}
1709
1742
}
1710
1743
@@ -2361,11 +2394,16 @@ ReadableArgs<(ProbabilisticScoringDecayParameters, G, L)> for ProbabilisticScore
2361
2394
) -> Result < Self , DecodeError > {
2362
2395
let ( decay_params, network_graph, logger) = args;
2363
2396
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
+ }
2364
2401
Ok ( Self {
2365
2402
decay_params,
2366
2403
network_graph,
2367
2404
logger,
2368
2405
channel_liquidities,
2406
+ last_duration_since_epoch,
2369
2407
} )
2370
2408
}
2371
2409
}
@@ -4060,6 +4098,52 @@ mod tests {
4060
4098
combined_scorer. scorer . estimated_channel_liquidity_range ( 42 , & target_node_id ( ) ) ;
4061
4099
assert_eq ! ( liquidity_range. unwrap( ) , ( 0 , 0 ) ) ;
4062
4100
}
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
+ }
4063
4147
}
4064
4148
4065
4149
#[ cfg( ldk_bench) ]
0 commit comments