@@ -1157,27 +1157,25 @@ fn three_f64_pow_9(a: f64, b: f64, c: f64) -> (f64, f64, f64) {
11571157/// Given liquidity bounds, calculates the success probability (in the form of a numerator and
11581158/// denominator) of an HTLC. This is a key assumption in our scoring models.
11591159///
1160- /// Must not return a numerator or denominator greater than 2^31 for arguments less than 2^31.
1161- ///
11621160/// `total_inflight_amount_msat` includes the amount of the HTLC and any HTLCs in flight over the
11631161/// channel.
11641162///
11651163/// min_zero_implies_no_successes signals that a `min_liquidity_msat` of 0 means we've not
11661164/// (recently) seen an HTLC successfully complete over this channel.
11671165#[ inline( always) ]
1168- fn success_probability (
1166+ fn success_probability_float (
11691167 total_inflight_amount_msat : u64 , min_liquidity_msat : u64 , max_liquidity_msat : u64 ,
11701168 capacity_msat : u64 , params : & ProbabilisticScoringFeeParameters ,
11711169 min_zero_implies_no_successes : bool ,
1172- ) -> ( u64 , u64 ) {
1170+ ) -> ( f64 , f64 ) {
11731171 debug_assert ! ( min_liquidity_msat <= total_inflight_amount_msat) ;
11741172 debug_assert ! ( total_inflight_amount_msat < max_liquidity_msat) ;
11751173 debug_assert ! ( max_liquidity_msat <= capacity_msat) ;
11761174
11771175 let ( numerator, mut denominator) =
11781176 if params. linear_success_probability {
1179- ( max_liquidity_msat - total_inflight_amount_msat,
1180- ( max_liquidity_msat - min_liquidity_msat) . saturating_add ( 1 ) )
1177+ ( ( max_liquidity_msat - total_inflight_amount_msat) as f64 ,
1178+ ( max_liquidity_msat - min_liquidity_msat) . saturating_add ( 1 ) as f64 )
11811179 } else {
11821180 let capacity = capacity_msat as f64 ;
11831181 let min = ( min_liquidity_msat as f64 ) / capacity;
@@ -1200,6 +1198,57 @@ fn success_probability(
12001198 let ( max_v, amt_v, min_v) = ( max_pow + max_norm / 256.0 , amt_pow + amt_norm / 256.0 , min_pow + min_norm / 256.0 ) ;
12011199 let num = max_v - amt_v;
12021200 let den = max_v - min_v;
1201+ ( num, den)
1202+ } ;
1203+
1204+ if min_zero_implies_no_successes && min_liquidity_msat == 0 {
1205+ // If we have no knowledge of the channel, scale probability down by a multiple of ~82%.
1206+ // Note that we prefer to increase the denominator rather than decrease the numerator as
1207+ // the denominator is more likely to be larger and thus provide greater precision. This is
1208+ // mostly an overoptimization but makes a large difference in tests.
1209+ denominator = denominator * 78.0 / 64.0 ;
1210+ }
1211+
1212+ ( numerator, denominator)
1213+ }
1214+
1215+ #[ inline( always) ]
1216+ /// Identical to [`success_probability_float`] but returns integer numerator and denominators.
1217+ ///
1218+ /// Must not return a numerator or denominator greater than 2^31 for arguments less than 2^31.
1219+ fn success_probability (
1220+ total_inflight_amount_msat : u64 , min_liquidity_msat : u64 , max_liquidity_msat : u64 ,
1221+ capacity_msat : u64 , params : & ProbabilisticScoringFeeParameters ,
1222+ min_zero_implies_no_successes : bool ,
1223+ ) -> ( u64 , u64 ) {
1224+ debug_assert ! ( min_liquidity_msat <= total_inflight_amount_msat) ;
1225+ debug_assert ! ( total_inflight_amount_msat < max_liquidity_msat) ;
1226+ debug_assert ! ( max_liquidity_msat <= capacity_msat) ;
1227+
1228+ let ( numerator, denominator) =
1229+ if params. linear_success_probability {
1230+ let ( numerator, mut denominator) =
1231+ ( max_liquidity_msat - total_inflight_amount_msat,
1232+ ( max_liquidity_msat - min_liquidity_msat) . saturating_add ( 1 ) ) ;
1233+
1234+ if min_zero_implies_no_successes && min_liquidity_msat == 0 &&
1235+ denominator < u64:: max_value ( ) / 78
1236+ {
1237+ // If we have no knowledge of the channel, scale probability down by a multiple of ~82%.
1238+ // Note that we prefer to increase the denominator rather than decrease the numerator as
1239+ // the denominator is more likely to be larger and thus provide greater precision. This is
1240+ // mostly an overoptimization but makes a large difference in tests.
1241+ denominator = denominator * 78 / 64
1242+ }
1243+
1244+ ( numerator, denominator)
1245+ } else {
1246+ // We calculate the nonlinear probabilities using floats anyway, so just stub out to
1247+ // the float version and then convert to integers.
1248+ let ( num, den) = success_probability_float (
1249+ total_inflight_amount_msat, min_liquidity_msat, max_liquidity_msat, capacity_msat,
1250+ params, min_zero_implies_no_successes
1251+ ) ;
12031252
12041253 // Because our numerator and denominator max out at 0.0078125 we need to multiply them
12051254 // by quite a large factor to get something useful (ideally in the 2^30 range).
@@ -1211,16 +1260,6 @@ fn success_probability(
12111260 ( numerator, denominator)
12121261 } ;
12131262
1214- if min_zero_implies_no_successes && min_liquidity_msat == 0 &&
1215- denominator < u64:: max_value ( ) / 78
1216- {
1217- // If we have no knowledge of the channel, scale probability down by a multiple of ~82%.
1218- // Note that we prefer to increase the denominator rather than decrease the numerator as
1219- // the denominator is more likely to be larger and thus provide greater precision. This is
1220- // mostly an overoptimization but makes a large difference in tests.
1221- denominator = denominator * 78 / 64
1222- }
1223-
12241263 ( numerator, denominator)
12251264}
12261265
@@ -1766,7 +1805,7 @@ mod bucketed_history {
17661805 // Because the first thing we do is check if `total_valid_points` is sufficient to consider
17671806 // the data here at all, and can return early if it is not, we want this to go first to
17681807 // avoid hitting a second cache line load entirely in that case.
1769- total_valid_points_tracked : u64 ,
1808+ total_valid_points_tracked : f64 ,
17701809 min_liquidity_offset_history : HistoricalBucketRangeTracker ,
17711810 max_liquidity_offset_history : HistoricalBucketRangeTracker ,
17721811 }
@@ -1776,7 +1815,7 @@ mod bucketed_history {
17761815 HistoricalLiquidityTracker {
17771816 min_liquidity_offset_history : HistoricalBucketRangeTracker :: new ( ) ,
17781817 max_liquidity_offset_history : HistoricalBucketRangeTracker :: new ( ) ,
1779- total_valid_points_tracked : 0 ,
1818+ total_valid_points_tracked : 0.0 ,
17801819 }
17811820 }
17821821
@@ -1787,7 +1826,7 @@ mod bucketed_history {
17871826 let mut res = HistoricalLiquidityTracker {
17881827 min_liquidity_offset_history,
17891828 max_liquidity_offset_history,
1790- total_valid_points_tracked : 0 ,
1829+ total_valid_points_tracked : 0.0 ,
17911830 } ;
17921831 res. recalculate_valid_point_count ( ) ;
17931832 res
@@ -1810,12 +1849,15 @@ mod bucketed_history {
18101849 }
18111850
18121851 fn recalculate_valid_point_count ( & mut self ) {
1813- self . total_valid_points_tracked = 0 ;
1852+ let mut total_valid_points_tracked = 0 ;
18141853 for ( min_idx, min_bucket) in self . min_liquidity_offset_history . buckets . iter ( ) . enumerate ( ) {
18151854 for max_bucket in self . max_liquidity_offset_history . buckets . iter ( ) . take ( 32 - min_idx) {
1816- self . total_valid_points_tracked += ( * min_bucket as u64 ) * ( * max_bucket as u64 ) ;
1855+ let mut bucket_weight = ( * min_bucket as u64 ) * ( * max_bucket as u64 ) ;
1856+ bucket_weight *= bucket_weight;
1857+ total_valid_points_tracked += bucket_weight;
18171858 }
18181859 }
1860+ self . total_valid_points_tracked = total_valid_points_tracked as f64 ;
18191861 }
18201862
18211863 pub ( super ) fn writeable_min_offset_history ( & self ) -> & HistoricalBucketRangeTracker {
@@ -1901,20 +1943,23 @@ mod bucketed_history {
19011943 let mut actual_valid_points_tracked = 0 ;
19021944 for ( min_idx, min_bucket) in min_liquidity_offset_history_buckets. iter ( ) . enumerate ( ) {
19031945 for max_bucket in max_liquidity_offset_history_buckets. iter ( ) . take ( 32 - min_idx) {
1904- actual_valid_points_tracked += ( * min_bucket as u64 ) * ( * max_bucket as u64 ) ;
1946+ let mut bucket_weight = ( * min_bucket as u64 ) * ( * max_bucket as u64 ) ;
1947+ bucket_weight *= bucket_weight;
1948+ actual_valid_points_tracked += bucket_weight;
19051949 }
19061950 }
1907- assert_eq ! ( total_valid_points_tracked, actual_valid_points_tracked) ;
1951+ assert_eq ! ( total_valid_points_tracked, actual_valid_points_tracked as f64 ) ;
19081952 }
19091953
19101954 // If the total valid points is smaller than 1.0 (i.e. 32 in our fixed-point scheme),
19111955 // treat it as if we were fully decayed.
1912- const FULLY_DECAYED : u16 = BUCKET_FIXED_POINT_ONE * BUCKET_FIXED_POINT_ONE ;
1956+ const FULLY_DECAYED : f64 = BUCKET_FIXED_POINT_ONE as f64 * BUCKET_FIXED_POINT_ONE as f64 *
1957+ BUCKET_FIXED_POINT_ONE as f64 * BUCKET_FIXED_POINT_ONE as f64 ;
19131958 if total_valid_points_tracked < FULLY_DECAYED . into ( ) {
19141959 return None ;
19151960 }
19161961
1917- let mut cumulative_success_prob_times_billion = 0 ;
1962+ let mut cumulative_success_prob = 0.0f64 ;
19181963 // Special-case the 0th min bucket - it generally means we failed a payment, so only
19191964 // consider the highest (i.e. largest-offset-from-max-capacity) max bucket for all
19201965 // points against the 0th min bucket. This avoids the case where we fail to route
@@ -1927,16 +1972,18 @@ mod bucketed_history {
19271972 // max-bucket with at least BUCKET_FIXED_POINT_ONE.
19281973 let mut highest_max_bucket_with_points = 0 ;
19291974 let mut highest_max_bucket_with_full_points = None ;
1930- let mut total_max_points = 0 ; // Total points in max-buckets to consider
1975+ let mut total_weight = 0 ;
19311976 for ( max_idx, max_bucket) in max_liquidity_offset_history_buckets. iter ( ) . enumerate ( ) {
19321977 if * max_bucket >= BUCKET_FIXED_POINT_ONE {
19331978 highest_max_bucket_with_full_points = Some ( cmp:: max ( highest_max_bucket_with_full_points. unwrap_or ( 0 ) , max_idx) ) ;
19341979 }
19351980 if * max_bucket != 0 {
19361981 highest_max_bucket_with_points = cmp:: max ( highest_max_bucket_with_points, max_idx) ;
19371982 }
1938- total_max_points += * max_bucket as u64 ;
1983+ total_weight += ( * max_bucket as u64 ) * ( * max_bucket as u64 )
1984+ * ( min_liquidity_offset_history_buckets[ 0 ] as u64 ) * ( min_liquidity_offset_history_buckets[ 0 ] as u64 ) ;
19391985 }
1986+ debug_assert ! ( total_weight as f64 <= total_valid_points_tracked) ;
19401987 // Use the highest max-bucket with at least BUCKET_FIXED_POINT_ONE, but if none is
19411988 // available use the highest max-bucket with any non-zero value. This ensures that
19421989 // if we have substantially decayed data we don't end up thinking the highest
@@ -1945,40 +1992,39 @@ mod bucketed_history {
19451992 let selected_max = highest_max_bucket_with_full_points. unwrap_or ( highest_max_bucket_with_points) ;
19461993 let max_bucket_end_pos = BUCKET_START_POS [ 32 - selected_max] - 1 ;
19471994 if payment_pos < max_bucket_end_pos {
1948- let ( numerator, denominator) = success_probability ( payment_pos as u64 , 0 ,
1995+ let ( numerator, denominator) = success_probability_float ( payment_pos as u64 , 0 ,
19491996 max_bucket_end_pos as u64 , POSITION_TICKS as u64 - 1 , params, true ) ;
1950- let bucket_prob_times_billion =
1951- ( min_liquidity_offset_history_buckets[ 0 ] as u64 ) * total_max_points
1952- * 1024 * 1024 * 1024 / total_valid_points_tracked;
1953- cumulative_success_prob_times_billion += bucket_prob_times_billion *
1954- numerator / denominator;
1997+ let bucket_prob = total_weight as f64 / total_valid_points_tracked;
1998+ cumulative_success_prob += bucket_prob * numerator / denominator;
19551999 }
19562000 }
19572001
19582002 for ( min_idx, min_bucket) in min_liquidity_offset_history_buckets. iter ( ) . enumerate ( ) . skip ( 1 ) {
19592003 let min_bucket_start_pos = BUCKET_START_POS [ min_idx] ;
19602004 for ( max_idx, max_bucket) in max_liquidity_offset_history_buckets. iter ( ) . enumerate ( ) . take ( 32 - min_idx) {
19612005 let max_bucket_end_pos = BUCKET_START_POS [ 32 - max_idx] - 1 ;
1962- // Note that this multiply can only barely not overflow - two 16 bit ints plus
1963- // 30 bits is 62 bits.
1964- let bucket_prob_times_billion = ( * min_bucket as u64 ) * ( * max_bucket as u64 )
1965- * 1024 * 1024 * 1024 / total_valid_points_tracked ;
2006+ let mut bucket_weight = ( * min_bucket as u64 ) * ( * max_bucket as u64 ) ;
2007+ bucket_weight *= bucket_weight ;
2008+ debug_assert ! ( bucket_weight as f64 <= total_valid_points_tracked ) ;
2009+
19662010 if payment_pos >= max_bucket_end_pos {
19672011 // Success probability 0, the payment amount may be above the max liquidity
19682012 break ;
1969- } else if payment_pos < min_bucket_start_pos {
1970- cumulative_success_prob_times_billion += bucket_prob_times_billion;
2013+ }
2014+
2015+ let bucket_prob = bucket_weight as f64 / total_valid_points_tracked;
2016+ if payment_pos < min_bucket_start_pos {
2017+ cumulative_success_prob += bucket_prob;
19712018 } else {
1972- let ( numerator, denominator) = success_probability ( payment_pos as u64 ,
2019+ let ( numerator, denominator) = success_probability_float ( payment_pos as u64 ,
19732020 min_bucket_start_pos as u64 , max_bucket_end_pos as u64 ,
19742021 POSITION_TICKS as u64 - 1 , params, true ) ;
1975- cumulative_success_prob_times_billion += bucket_prob_times_billion *
1976- numerator / denominator;
2022+ cumulative_success_prob += bucket_prob * numerator / denominator;
19772023 }
19782024 }
19792025 }
19802026
1981- Some ( cumulative_success_prob_times_billion )
2027+ Some ( ( cumulative_success_prob * ( 1024.0 * 1024.0 * 1024.0 ) ) as u64 )
19822028 }
19832029 }
19842030}
@@ -3576,9 +3622,12 @@ mod tests {
35763622 // Now test again with the amount in the bottom bucket.
35773623 amount_msat /= 2 ;
35783624 // The new amount is entirely within the only minimum bucket with score, so the probability
3579- // we assign is 1/2.
3580- assert_eq ! ( scorer. historical_estimated_payment_success_probability( 42 , & target, amount_msat, & params, false ) ,
3581- Some ( 0.5 ) ) ;
3625+ // we assign is around 41%.
3626+ let probability =
3627+ scorer. historical_estimated_payment_success_probability ( 42 , & target, amount_msat, & params, false )
3628+ . unwrap ( ) ;
3629+ assert ! ( probability >= 0.41 ) ;
3630+ assert ! ( probability < 0.42 ) ;
35823631
35833632 // ...but once we see a failure, we consider the payment to be substantially less likely,
35843633 // even though not a probability of zero as we still look at the second max bucket which
0 commit comments