@@ -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 ) {
@@ -2788,13 +2823,19 @@ mod tests {
27882823 assert_eq ! ( scorer. channel_penalty_msat( 42 , & source, & target, usage) , 47 ) ;
27892824 assert_eq ! ( scorer. historical_estimated_channel_liquidity_probabilities( 42 , & target) ,
27902825 None ) ;
2826+ assert_eq ! ( scorer. historical_estimated_payment_success_probability( 42 , & target, 42 ) ,
2827+ None ) ;
27912828
27922829 scorer. payment_path_failed ( & payment_path_for_amount ( 1 ) , 42 ) ;
27932830 assert_eq ! ( scorer. channel_penalty_msat( 42 , & source, & target, usage) , 2048 ) ;
27942831 // The "it failed" increment is 32, where the probability should lie fully in the first
27952832 // octile.
27962833 assert_eq ! ( scorer. historical_estimated_channel_liquidity_probabilities( 42 , & target) ,
27972834 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 ) ) ;
27982839
27992840 // Even after we tell the scorer we definitely have enough available liquidity, it will
28002841 // still remember that there was some failure in the past, and assign a non-0 penalty.
@@ -2804,6 +2845,17 @@ mod tests {
28042845 assert_eq ! ( scorer. historical_estimated_channel_liquidity_probabilities( 42 , & target) ,
28052846 Some ( ( [ 31 , 0 , 0 , 0 , 0 , 0 , 0 , 32 ] , [ 31 , 0 , 0 , 0 , 0 , 0 , 0 , 32 ] ) ) ) ;
28062847
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+
28072859 // Advance the time forward 16 half-lives (which the docs claim will ensure all data is
28082860 // gone), and check that we're back to where we started.
28092861 SinceEpoch :: advance ( Duration :: from_secs ( 10 * 16 ) ) ;
@@ -2812,6 +2864,7 @@ mod tests {
28122864 // data entirely instead.
28132865 assert_eq ! ( scorer. historical_estimated_channel_liquidity_probabilities( 42 , & target) ,
28142866 Some ( ( [ 0 ; 8 ] , [ 0 ; 8 ] ) ) ) ;
2867+ assert_eq ! ( scorer. historical_estimated_payment_success_probability( 42 , & target, 1 ) , None ) ;
28152868
28162869 let usage = ChannelUsage {
28172870 amount_msat : 100 ,
0 commit comments