@@ -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 ) {
@@ -2786,13 +2821,19 @@ mod tests {
2786
2821
assert_eq ! ( scorer. channel_penalty_msat( 42 , & source, & target, usage) , 47 ) ;
2787
2822
assert_eq ! ( scorer. historical_estimated_channel_liquidity_probabilities( 42 , & target) ,
2788
2823
None ) ;
2824
+ assert_eq ! ( scorer. historical_estimated_payment_success_probability( 42 , & target, 42 ) ,
2825
+ None ) ;
2789
2826
2790
2827
scorer. payment_path_failed ( & payment_path_for_amount ( 1 ) . iter ( ) . collect :: < Vec < _ > > ( ) , 42 ) ;
2791
2828
assert_eq ! ( scorer. channel_penalty_msat( 42 , & source, & target, usage) , 2048 ) ;
2792
2829
// The "it failed" increment is 32, where the probability should lie fully in the first
2793
2830
// octile.
2794
2831
assert_eq ! ( scorer. historical_estimated_channel_liquidity_probabilities( 42 , & target) ,
2795
2832
Some ( ( [ 32 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ] , [ 32 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ] ) ) ) ;
2833
+ assert_eq ! ( scorer. historical_estimated_payment_success_probability( 42 , & target, 1 ) ,
2834
+ Some ( 1024 * 1024 * 1024 ) ) ;
2835
+ assert_eq ! ( scorer. historical_estimated_payment_success_probability( 42 , & target, 500 ) ,
2836
+ Some ( 0 ) ) ;
2796
2837
2797
2838
// Even after we tell the scorer we definitely have enough available liquidity, it will
2798
2839
// still remember that there was some failure in the past, and assign a non-0 penalty.
@@ -2802,6 +2843,17 @@ mod tests {
2802
2843
assert_eq ! ( scorer. historical_estimated_channel_liquidity_probabilities( 42 , & target) ,
2803
2844
Some ( ( [ 31 , 0 , 0 , 0 , 0 , 0 , 0 , 32 ] , [ 31 , 0 , 0 , 0 , 0 , 0 , 0 , 32 ] ) ) ) ;
2804
2845
2846
+ // The exact success probability is a bit complicated and involves integer rounding, so we
2847
+ // simply check bounds here.
2848
+ let five_hundred_prob =
2849
+ scorer. historical_estimated_payment_success_probability ( 42 , & target, 500 ) . unwrap ( ) ;
2850
+ assert ! ( five_hundred_prob > 512 * 1024 * 1024 ) ; // 0.5
2851
+ assert ! ( five_hundred_prob < 532 * 1024 * 1024 ) ; // ~ 0.52
2852
+ let one_prob =
2853
+ scorer. historical_estimated_payment_success_probability ( 42 , & target, 1 ) . unwrap ( ) ;
2854
+ assert ! ( one_prob < 1024 * 1024 * 1024 ) ;
2855
+ assert ! ( one_prob > 1023 * 1024 * 1024 ) ;
2856
+
2805
2857
// Advance the time forward 16 half-lives (which the docs claim will ensure all data is
2806
2858
// gone), and check that we're back to where we started.
2807
2859
SinceEpoch :: advance ( Duration :: from_secs ( 10 * 16 ) ) ;
@@ -2810,6 +2862,7 @@ mod tests {
2810
2862
// data entirely instead.
2811
2863
assert_eq ! ( scorer. historical_estimated_channel_liquidity_probabilities( 42 , & target) ,
2812
2864
Some ( ( [ 0 ; 8 ] , [ 0 ; 8 ] ) ) ) ;
2865
+ assert_eq ! ( scorer. historical_estimated_payment_success_probability( 42 , & target, 1 ) , None ) ;
2813
2866
2814
2867
let usage = ChannelUsage {
2815
2868
amount_msat : 100 ,
0 commit comments