@@ -830,6 +830,9 @@ impl<G: Deref<Target = NetworkGraph<L>>, L: Deref, T: Time> ProbabilisticScorerU
830830 ///
831831 /// Because the datapoints are decayed slowly over time, values will eventually return to
832832 /// `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`].
833836 pub fn historical_estimated_channel_liquidity_probabilities ( & self , scid : u64 , target : & NodeId )
834837 -> Option < ( [ u16 ; 8 ] , [ u16 ; 8 ] ) > {
835838 let graph = self . network_graph . read_only ( ) ;
@@ -856,6 +859,38 @@ impl<G: Deref<Target = NetworkGraph<L>>, L: Deref, T: Time> ProbabilisticScorerU
856859 None
857860 }
858861
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+
859894 /// Marks the node with the given `node_id` as banned, i.e.,
860895 /// it will be avoided during path finding.
861896 pub fn add_banned ( & mut self , node_id : & NodeId ) {
@@ -2786,13 +2821,19 @@ mod tests {
27862821 assert_eq ! ( scorer. channel_penalty_msat( 42 , & source, & target, usage) , 47 ) ;
27872822 assert_eq ! ( scorer. historical_estimated_channel_liquidity_probabilities( 42 , & target) ,
27882823 None ) ;
2824+ assert_eq ! ( scorer. historical_estimated_payment_success_probability( 42 , & target, 42 ) ,
2825+ None ) ;
27892826
27902827 scorer. payment_path_failed ( & payment_path_for_amount ( 1 ) . iter ( ) . collect :: < Vec < _ > > ( ) , 42 ) ;
27912828 assert_eq ! ( scorer. channel_penalty_msat( 42 , & source, & target, usage) , 2048 ) ;
27922829 // The "it failed" increment is 32, where the probability should lie fully in the first
27932830 // octile.
27942831 assert_eq ! ( scorer. historical_estimated_channel_liquidity_probabilities( 42 , & target) ,
27952832 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 ) ) ;
27962837
27972838 // Even after we tell the scorer we definitely have enough available liquidity, it will
27982839 // still remember that there was some failure in the past, and assign a non-0 penalty.
@@ -2802,6 +2843,17 @@ mod tests {
28022843 assert_eq ! ( scorer. historical_estimated_channel_liquidity_probabilities( 42 , & target) ,
28032844 Some ( ( [ 31 , 0 , 0 , 0 , 0 , 0 , 0 , 32 ] , [ 31 , 0 , 0 , 0 , 0 , 0 , 0 , 32 ] ) ) ) ;
28042845
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+
28052857 // Advance the time forward 16 half-lives (which the docs claim will ensure all data is
28062858 // gone), and check that we're back to where we started.
28072859 SinceEpoch :: advance ( Duration :: from_secs ( 10 * 16 ) ) ;
@@ -2810,6 +2862,7 @@ mod tests {
28102862 // data entirely instead.
28112863 assert_eq ! ( scorer. historical_estimated_channel_liquidity_probabilities( 42 , & target) ,
28122864 Some ( ( [ 0 ; 8 ] , [ 0 ; 8 ] ) ) ) ;
2865+ assert_eq ! ( scorer. historical_estimated_payment_success_probability( 42 , & target, 1 ) , None ) ;
28132866
28142867 let usage = ChannelUsage {
28152868 amount_msat : 100 ,
0 commit comments