@@ -830,6 +830,9 @@ impl<G: Deref<Target = NetworkGraph<L>>, L: Deref, T: Time> ProbabilisticScorerU
830
830
///
831
831
/// Because the datapoints are decayed slowly over time, values will eventually return to
832
832
/// `Some(([0; 8], [0; 8]))`.
833
+ ///
834
+ /// In order to convert this into a success probability, as used in the scoring model, see
835
+ /// [`Self::historical_estimated_payment_success_probability`].
833
836
pub fn historical_estimated_channel_liquidity_probabilities ( & self , scid : u64 , target : & NodeId )
834
837
-> Option < ( [ u16 ; 8 ] , [ u16 ; 8 ] ) > {
835
838
let graph = self . network_graph . read_only ( ) ;
@@ -856,6 +859,38 @@ impl<G: Deref<Target = NetworkGraph<L>>, L: Deref, T: Time> ProbabilisticScorerU
856
859
None
857
860
}
858
861
862
+ /// Query the probability of payment success (times 2^30) sending the given `amount_msat` over
863
+ /// the channel with `scid` towards the given `target` node, based on the historical estimated
864
+ /// liquidity bounds.
865
+ ///
866
+ /// These are the same bounds as returned by
867
+ /// [`Self::historical_estimated_channel_liquidity_probabilities`] (but not those returned by
868
+ /// [`Self::estimated_channel_liquidity_range`]).
869
+ pub fn historical_estimated_payment_success_probability (
870
+ & self , scid : u64 , target : & NodeId , amount_msat : u64 )
871
+ -> Option < u64 > {
872
+ let graph = self . network_graph . read_only ( ) ;
873
+
874
+ if let Some ( chan) = graph. channels ( ) . get ( & scid) {
875
+ if let Some ( liq) = self . channel_liquidities . get ( & scid) {
876
+ if let Some ( ( directed_info, source) ) = chan. as_directed_to ( target) {
877
+ let amt = directed_info. effective_capacity ( ) . as_msat ( ) ;
878
+ let dir_liq = liq. as_directed ( source, target, 0 , amt, & self . params ) ;
879
+
880
+ let buckets = HistoricalMinMaxBuckets {
881
+ min_liquidity_offset_history : & dir_liq. min_liquidity_offset_history ,
882
+ max_liquidity_offset_history : & dir_liq. max_liquidity_offset_history ,
883
+ } ;
884
+
885
+ return buckets. calculate_success_probability_times_billion ( T :: now ( ) ,
886
+ * dir_liq. last_updated , self . params . historical_no_updates_half_life ,
887
+ amount_msat, directed_info. effective_capacity ( ) . as_msat ( ) ) ;
888
+ }
889
+ }
890
+ }
891
+ None
892
+ }
893
+
859
894
/// Marks the node with the given `node_id` as banned, i.e.,
860
895
/// it will be avoided during path finding.
861
896
pub fn add_banned ( & mut self , node_id : & NodeId ) {
@@ -2788,13 +2823,19 @@ mod tests {
2788
2823
assert_eq ! ( scorer. channel_penalty_msat( 42 , & source, & target, usage) , 47 ) ;
2789
2824
assert_eq ! ( scorer. historical_estimated_channel_liquidity_probabilities( 42 , & target) ,
2790
2825
None ) ;
2826
+ assert_eq ! ( scorer. historical_estimated_payment_success_probability( 42 , & target, 42 ) ,
2827
+ None ) ;
2791
2828
2792
2829
scorer. payment_path_failed ( & payment_path_for_amount ( 1 ) , 42 ) ;
2793
2830
assert_eq ! ( scorer. channel_penalty_msat( 42 , & source, & target, usage) , 2048 ) ;
2794
2831
// The "it failed" increment is 32, where the probability should lie fully in the first
2795
2832
// octile.
2796
2833
assert_eq ! ( scorer. historical_estimated_channel_liquidity_probabilities( 42 , & target) ,
2797
2834
Some ( ( [ 32 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ] , [ 32 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ] ) ) ) ;
2835
+ assert_eq ! ( scorer. historical_estimated_payment_success_probability( 42 , & target, 1 ) ,
2836
+ Some ( 1024 * 1024 * 1024 ) ) ;
2837
+ assert_eq ! ( scorer. historical_estimated_payment_success_probability( 42 , & target, 500 ) ,
2838
+ Some ( 0 ) ) ;
2798
2839
2799
2840
// Even after we tell the scorer we definitely have enough available liquidity, it will
2800
2841
// still remember that there was some failure in the past, and assign a non-0 penalty.
@@ -2804,6 +2845,17 @@ mod tests {
2804
2845
assert_eq ! ( scorer. historical_estimated_channel_liquidity_probabilities( 42 , & target) ,
2805
2846
Some ( ( [ 31 , 0 , 0 , 0 , 0 , 0 , 0 , 32 ] , [ 31 , 0 , 0 , 0 , 0 , 0 , 0 , 32 ] ) ) ) ;
2806
2847
2848
+ // The exact success probability is a bit complicated and involves integer rounding, so we
2849
+ // simply check bounds here.
2850
+ let five_hundred_prob =
2851
+ scorer. historical_estimated_payment_success_probability ( 42 , & target, 500 ) . unwrap ( ) ;
2852
+ assert ! ( five_hundred_prob > 512 * 1024 * 1024 ) ; // 0.5
2853
+ assert ! ( five_hundred_prob < 532 * 1024 * 1024 ) ; // ~ 0.52
2854
+ let one_prob =
2855
+ scorer. historical_estimated_payment_success_probability ( 42 , & target, 1 ) . unwrap ( ) ;
2856
+ assert ! ( one_prob < 1024 * 1024 * 1024 ) ;
2857
+ assert ! ( one_prob > 1023 * 1024 * 1024 ) ;
2858
+
2807
2859
// Advance the time forward 16 half-lives (which the docs claim will ensure all data is
2808
2860
// gone), and check that we're back to where we started.
2809
2861
SinceEpoch :: advance ( Duration :: from_secs ( 10 * 16 ) ) ;
@@ -2812,6 +2864,7 @@ mod tests {
2812
2864
// data entirely instead.
2813
2865
assert_eq ! ( scorer. historical_estimated_channel_liquidity_probabilities( 42 , & target) ,
2814
2866
Some ( ( [ 0 ; 8 ] , [ 0 ; 8 ] ) ) ) ;
2867
+ assert_eq ! ( scorer. historical_estimated_payment_success_probability( 42 , & target, 1 ) , None ) ;
2815
2868
2816
2869
let usage = ChannelUsage {
2817
2870
amount_msat : 100 ,
0 commit comments