@@ -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
}
@@ -901,17 +929,11 @@ struct ChannelLiquidity {
901
929
/// Time when the historical liquidity bounds were last modified as an offset against the unix
902
930
/// epoch.
903
931
offset_history_last_updated : Duration ,
904
- }
905
932
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
+ }
915
937
916
938
/// A snapshot of [`ChannelLiquidity`] in one direction assuming a certain channel capacity.
917
939
struct 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
921
943
capacity_msat : u64 ,
922
944
last_updated : T ,
923
945
offset_history_last_updated : T ,
946
+ last_datapoint_time : T ,
924
947
}
925
948
926
949
impl < 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
932
955
network_graph,
933
956
logger,
934
957
channel_liquidities : ChannelLiquidities :: new ( ) ,
958
+ last_update_time : Duration :: from_secs ( 0 ) ,
935
959
}
936
960
}
937
961
@@ -1163,6 +1187,7 @@ impl ChannelLiquidity {
1163
1187
liquidity_history : HistoricalLiquidityTracker :: new ( ) ,
1164
1188
last_updated,
1165
1189
offset_history_last_updated : last_updated,
1190
+ last_datapoint_time : last_updated,
1166
1191
}
1167
1192
}
1168
1193
@@ -1195,6 +1220,7 @@ impl ChannelLiquidity {
1195
1220
capacity_msat,
1196
1221
last_updated : & self . last_updated ,
1197
1222
offset_history_last_updated : & self . offset_history_last_updated ,
1223
+ last_datapoint_time : & self . last_datapoint_time ,
1198
1224
}
1199
1225
}
1200
1226
@@ -1218,6 +1244,7 @@ impl ChannelLiquidity {
1218
1244
capacity_msat,
1219
1245
last_updated : & mut self . last_updated ,
1220
1246
offset_history_last_updated : & mut self . offset_history_last_updated ,
1247
+ last_datapoint_time : & mut self . last_datapoint_time ,
1221
1248
}
1222
1249
}
1223
1250
@@ -1385,7 +1412,7 @@ DirectedChannelLiquidity< L, HT, T> {
1385
1412
/// Returns a liquidity penalty for routing the given HTLC `amount_msat` through the channel in
1386
1413
/// this direction.
1387
1414
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 ,
1389
1416
score_params : & ProbabilisticScoringFeeParameters ,
1390
1417
) -> u64 {
1391
1418
let total_inflight_amount_msat = amount_msat. saturating_add ( inflight_htlc_msat) ;
@@ -1467,6 +1494,18 @@ DirectedChannelLiquidity< L, HT, T> {
1467
1494
}
1468
1495
}
1469
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
+
1470
1509
res
1471
1510
}
1472
1511
@@ -1564,6 +1603,7 @@ DirectedChannelLiquidity<L, HT, T> {
1564
1603
* self . max_liquidity_offset_msat = 0 ;
1565
1604
}
1566
1605
* self . last_updated = duration_since_epoch;
1606
+ * self . last_datapoint_time = duration_since_epoch;
1567
1607
}
1568
1608
1569
1609
/// Adjusts the upper bound of the channel liquidity balance in this direction.
@@ -1573,6 +1613,7 @@ DirectedChannelLiquidity<L, HT, T> {
1573
1613
* self . min_liquidity_offset_msat = 0 ;
1574
1614
}
1575
1615
* self . last_updated = duration_since_epoch;
1616
+ * self . last_datapoint_time = duration_since_epoch;
1576
1617
}
1577
1618
}
1578
1619
@@ -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
@@ -2383,11 +2428,16 @@ ReadableArgs<(ProbabilisticScoringDecayParameters, G, L)> for ProbabilisticScore
2383
2428
) -> Result < Self , DecodeError > {
2384
2429
let ( decay_params, network_graph, logger) = args;
2385
2430
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
+ }
2386
2435
Ok ( Self {
2387
2436
decay_params,
2388
2437
network_graph,
2389
2438
logger,
2390
2439
channel_liquidities,
2440
+ last_update_time,
2391
2441
} )
2392
2442
}
2393
2443
}
@@ -2404,6 +2454,7 @@ impl Writeable for ChannelLiquidity {
2404
2454
( 5 , self . liquidity_history. writeable_min_offset_history( ) , required) ,
2405
2455
( 7 , self . liquidity_history. writeable_max_offset_history( ) , required) ,
2406
2456
( 9 , self . offset_history_last_updated, required) ,
2457
+ ( 11 , self . last_datapoint_time, required) ,
2407
2458
} ) ;
2408
2459
Ok ( ( ) )
2409
2460
}
@@ -2420,6 +2471,7 @@ impl Readable for ChannelLiquidity {
2420
2471
let mut max_liquidity_offset_history: Option < HistoricalBucketRangeTracker > = None ;
2421
2472
let mut last_updated = Duration :: from_secs ( 0 ) ;
2422
2473
let mut offset_history_last_updated = None ;
2474
+ let mut last_datapoint_time = None ;
2423
2475
read_tlv_fields ! ( r, {
2424
2476
( 0 , min_liquidity_offset_msat, required) ,
2425
2477
( 1 , legacy_min_liq_offset_history, option) ,
@@ -2429,6 +2481,7 @@ impl Readable for ChannelLiquidity {
2429
2481
( 5 , min_liquidity_offset_history, option) ,
2430
2482
( 7 , max_liquidity_offset_history, option) ,
2431
2483
( 9 , offset_history_last_updated, option) ,
2484
+ ( 11 , last_datapoint_time, option) ,
2432
2485
} ) ;
2433
2486
2434
2487
if min_liquidity_offset_history. is_none ( ) {
@@ -2453,6 +2506,7 @@ impl Readable for ChannelLiquidity {
2453
2506
) ,
2454
2507
last_updated,
2455
2508
offset_history_last_updated : offset_history_last_updated. unwrap_or ( last_updated) ,
2509
+ last_datapoint_time : last_datapoint_time. unwrap_or ( last_updated) ,
2456
2510
} )
2457
2511
}
2458
2512
}
@@ -2626,19 +2680,20 @@ mod tests {
2626
2680
let logger = TestLogger :: new ( ) ;
2627
2681
let last_updated = Duration :: ZERO ;
2628
2682
let offset_history_last_updated = Duration :: ZERO ;
2683
+ let last_datapoint_time = Duration :: ZERO ;
2629
2684
let network_graph = network_graph ( & logger) ;
2630
2685
let decay_params = ProbabilisticScoringDecayParameters :: default ( ) ;
2631
2686
let mut scorer = ProbabilisticScorer :: new ( decay_params, & network_graph, & logger)
2632
2687
. with_channel ( 42 ,
2633
2688
ChannelLiquidity {
2634
2689
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 ,
2636
2691
liquidity_history : HistoricalLiquidityTracker :: new ( ) ,
2637
2692
} )
2638
2693
. with_channel ( 43 ,
2639
2694
ChannelLiquidity {
2640
2695
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 ,
2642
2697
liquidity_history : HistoricalLiquidityTracker :: new ( ) ,
2643
2698
} ) ;
2644
2699
let source = source_node_id ( ) ;
@@ -2705,13 +2760,14 @@ mod tests {
2705
2760
let logger = TestLogger :: new ( ) ;
2706
2761
let last_updated = Duration :: ZERO ;
2707
2762
let offset_history_last_updated = Duration :: ZERO ;
2763
+ let last_datapoint_time = Duration :: ZERO ;
2708
2764
let network_graph = network_graph ( & logger) ;
2709
2765
let decay_params = ProbabilisticScoringDecayParameters :: default ( ) ;
2710
2766
let mut scorer = ProbabilisticScorer :: new ( decay_params, & network_graph, & logger)
2711
2767
. with_channel ( 42 ,
2712
2768
ChannelLiquidity {
2713
2769
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 ,
2715
2771
liquidity_history : HistoricalLiquidityTracker :: new ( ) ,
2716
2772
} ) ;
2717
2773
let source = source_node_id ( ) ;
@@ -2765,13 +2821,14 @@ mod tests {
2765
2821
let logger = TestLogger :: new ( ) ;
2766
2822
let last_updated = Duration :: ZERO ;
2767
2823
let offset_history_last_updated = Duration :: ZERO ;
2824
+ let last_datapoint_time = Duration :: ZERO ;
2768
2825
let network_graph = network_graph ( & logger) ;
2769
2826
let decay_params = ProbabilisticScoringDecayParameters :: default ( ) ;
2770
2827
let mut scorer = ProbabilisticScorer :: new ( decay_params, & network_graph, & logger)
2771
2828
. with_channel ( 42 ,
2772
2829
ChannelLiquidity {
2773
2830
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 ,
2775
2832
liquidity_history : HistoricalLiquidityTracker :: new ( ) ,
2776
2833
} ) ;
2777
2834
let source = source_node_id ( ) ;
@@ -2877,6 +2934,7 @@ mod tests {
2877
2934
let logger = TestLogger :: new ( ) ;
2878
2935
let last_updated = Duration :: ZERO ;
2879
2936
let offset_history_last_updated = Duration :: ZERO ;
2937
+ let last_datapoint_time = Duration :: ZERO ;
2880
2938
let network_graph = network_graph ( & logger) ;
2881
2939
let params = ProbabilisticScoringFeeParameters {
2882
2940
liquidity_penalty_multiplier_msat : 1_000 ,
@@ -2890,7 +2948,7 @@ mod tests {
2890
2948
. with_channel ( 42 ,
2891
2949
ChannelLiquidity {
2892
2950
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 ,
2894
2952
liquidity_history : HistoricalLiquidityTracker :: new ( ) ,
2895
2953
} ) ;
2896
2954
let source = source_node_id ( ) ;
@@ -4074,6 +4132,52 @@ mod tests {
4074
4132
combined_scorer. scorer . estimated_channel_liquidity_range ( 42 , & target_node_id ( ) ) ;
4075
4133
assert_eq ! ( liquidity_range. unwrap( ) , ( 0 , 0 ) ) ;
4076
4134
}
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
+ }
4077
4181
}
4078
4182
4079
4183
#[ cfg( ldk_bench) ]
0 commit comments