@@ -1156,27 +1156,25 @@ fn three_f64_pow_9(a: f64, b: f64, c: f64) -> (f64, f64, f64) {
11561156/// Given liquidity bounds, calculates the success probability (in the form of a numerator and
11571157/// denominator) of an HTLC. This is a key assumption in our scoring models.
11581158///
1159- /// Must not return a numerator or denominator greater than 2^31 for arguments less than 2^31.
1160- ///
11611159/// `total_inflight_amount_msat` includes the amount of the HTLC and any HTLCs in flight over the
11621160/// channel.
11631161///
11641162/// min_zero_implies_no_successes signals that a `min_liquidity_msat` of 0 means we've not
11651163/// (recently) seen an HTLC successfully complete over this channel.
11661164#[ inline( always) ]
1167- fn success_probability (
1165+ fn success_probability_float (
11681166 total_inflight_amount_msat : u64 , min_liquidity_msat : u64 , max_liquidity_msat : u64 ,
11691167 capacity_msat : u64 , params : & ProbabilisticScoringFeeParameters ,
11701168 min_zero_implies_no_successes : bool ,
1171- ) -> ( u64 , u64 ) {
1169+ ) -> ( f64 , f64 ) {
11721170 debug_assert ! ( min_liquidity_msat <= total_inflight_amount_msat) ;
11731171 debug_assert ! ( total_inflight_amount_msat < max_liquidity_msat) ;
11741172 debug_assert ! ( max_liquidity_msat <= capacity_msat) ;
11751173
11761174 let ( numerator, mut denominator) =
11771175 if params. linear_success_probability {
1178- ( max_liquidity_msat - total_inflight_amount_msat,
1179- ( max_liquidity_msat - min_liquidity_msat) . saturating_add ( 1 ) )
1176+ ( ( max_liquidity_msat - total_inflight_amount_msat) as f64 ,
1177+ ( max_liquidity_msat - min_liquidity_msat) . saturating_add ( 1 ) as f64 )
11801178 } else {
11811179 let capacity = capacity_msat as f64 ;
11821180 let min = ( min_liquidity_msat as f64 ) / capacity;
@@ -1199,6 +1197,57 @@ fn success_probability(
11991197 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 ) ;
12001198 let num = max_v - amt_v;
12011199 let den = max_v - min_v;
1200+ ( num, den)
1201+ } ;
1202+
1203+ if min_zero_implies_no_successes && min_liquidity_msat == 0 {
1204+ // If we have no knowledge of the channel, scale probability down by a multiple of ~82%.
1205+ // Note that we prefer to increase the denominator rather than decrease the numerator as
1206+ // the denominator is more likely to be larger and thus provide greater precision. This is
1207+ // mostly an overoptimization but makes a large difference in tests.
1208+ denominator = denominator * 78.0 / 64.0 ;
1209+ }
1210+
1211+ ( numerator, denominator)
1212+ }
1213+
1214+ #[ inline( always) ]
1215+ /// Identical to [`success_probability_float`] but returns integer numerator and denominators.
1216+ ///
1217+ /// Must not return a numerator or denominator greater than 2^31 for arguments less than 2^31.
1218+ fn success_probability (
1219+ total_inflight_amount_msat : u64 , min_liquidity_msat : u64 , max_liquidity_msat : u64 ,
1220+ capacity_msat : u64 , params : & ProbabilisticScoringFeeParameters ,
1221+ min_zero_implies_no_successes : bool ,
1222+ ) -> ( u64 , u64 ) {
1223+ debug_assert ! ( min_liquidity_msat <= total_inflight_amount_msat) ;
1224+ debug_assert ! ( total_inflight_amount_msat < max_liquidity_msat) ;
1225+ debug_assert ! ( max_liquidity_msat <= capacity_msat) ;
1226+
1227+ let ( numerator, denominator) =
1228+ if params. linear_success_probability {
1229+ let ( numerator, mut denominator) =
1230+ ( max_liquidity_msat - total_inflight_amount_msat,
1231+ ( max_liquidity_msat - min_liquidity_msat) . saturating_add ( 1 ) ) ;
1232+
1233+ if min_zero_implies_no_successes && min_liquidity_msat == 0 &&
1234+ denominator < u64:: max_value ( ) / 78
1235+ {
1236+ // If we have no knowledge of the channel, scale probability down by a multiple of ~82%.
1237+ // Note that we prefer to increase the denominator rather than decrease the numerator as
1238+ // the denominator is more likely to be larger and thus provide greater precision. This is
1239+ // mostly an overoptimization but makes a large difference in tests.
1240+ denominator = denominator * 78 / 64
1241+ }
1242+
1243+ ( numerator, denominator)
1244+ } else {
1245+ // We calculate the nonlinear probabilities using floats anyway, so just stub out to
1246+ // the float version and then convert to integers.
1247+ let ( num, den) = success_probability_float (
1248+ total_inflight_amount_msat, min_liquidity_msat, max_liquidity_msat, capacity_msat,
1249+ params, min_zero_implies_no_successes
1250+ ) ;
12021251
12031252 // Because our numerator and denominator max out at 0.0078125 we need to multiply them
12041253 // by quite a large factor to get something useful (ideally in the 2^30 range).
@@ -1210,16 +1259,6 @@ fn success_probability(
12101259 ( numerator, denominator)
12111260 } ;
12121261
1213- if min_zero_implies_no_successes && min_liquidity_msat == 0 &&
1214- denominator < u64:: max_value ( ) / 78
1215- {
1216- // If we have no knowledge of the channel, scale probability down by a multiple of ~82%.
1217- // Note that we prefer to increase the denominator rather than decrease the numerator as
1218- // the denominator is more likely to be larger and thus provide greater precision. This is
1219- // mostly an overoptimization but makes a large difference in tests.
1220- denominator = denominator * 78 / 64
1221- }
1222-
12231262 ( numerator, denominator)
12241263}
12251264
@@ -1765,7 +1804,7 @@ mod bucketed_history {
17651804 // Because the first thing we do is check if `total_valid_points` is sufficient to consider
17661805 // the data here at all, and can return early if it is not, we want this to go first to
17671806 // avoid hitting a second cache line load entirely in that case.
1768- total_valid_points_tracked : u64 ,
1807+ total_valid_points_tracked : f64 ,
17691808 min_liquidity_offset_history : HistoricalBucketRangeTracker ,
17701809 max_liquidity_offset_history : HistoricalBucketRangeTracker ,
17711810 }
@@ -1775,7 +1814,7 @@ mod bucketed_history {
17751814 HistoricalLiquidityTracker {
17761815 min_liquidity_offset_history : HistoricalBucketRangeTracker :: new ( ) ,
17771816 max_liquidity_offset_history : HistoricalBucketRangeTracker :: new ( ) ,
1778- total_valid_points_tracked : 0 ,
1817+ total_valid_points_tracked : 0.0 ,
17791818 }
17801819 }
17811820
@@ -1786,7 +1825,7 @@ mod bucketed_history {
17861825 let mut res = HistoricalLiquidityTracker {
17871826 min_liquidity_offset_history,
17881827 max_liquidity_offset_history,
1789- total_valid_points_tracked : 0 ,
1828+ total_valid_points_tracked : 0.0 ,
17901829 } ;
17911830 res. recalculate_valid_point_count ( ) ;
17921831 res
@@ -1809,12 +1848,15 @@ mod bucketed_history {
18091848 }
18101849
18111850 fn recalculate_valid_point_count ( & mut self ) {
1812- self . total_valid_points_tracked = 0 ;
1851+ let mut total_valid_points_tracked = 0 ;
18131852 for ( min_idx, min_bucket) in self . min_liquidity_offset_history . buckets . iter ( ) . enumerate ( ) {
18141853 for max_bucket in self . max_liquidity_offset_history . buckets . iter ( ) . take ( 32 - min_idx) {
1815- self . total_valid_points_tracked += ( * min_bucket as u64 ) * ( * max_bucket as u64 ) ;
1854+ let mut bucket_weight = ( * min_bucket as u64 ) * ( * max_bucket as u64 ) ;
1855+ bucket_weight *= bucket_weight;
1856+ total_valid_points_tracked += bucket_weight;
18161857 }
18171858 }
1859+ self . total_valid_points_tracked = total_valid_points_tracked as f64 ;
18181860 }
18191861
18201862 pub ( super ) fn writeable_min_offset_history ( & self ) -> & HistoricalBucketRangeTracker {
@@ -1900,20 +1942,23 @@ mod bucketed_history {
19001942 let mut actual_valid_points_tracked = 0 ;
19011943 for ( min_idx, min_bucket) in min_liquidity_offset_history_buckets. iter ( ) . enumerate ( ) {
19021944 for max_bucket in max_liquidity_offset_history_buckets. iter ( ) . take ( 32 - min_idx) {
1903- actual_valid_points_tracked += ( * min_bucket as u64 ) * ( * max_bucket as u64 ) ;
1945+ let mut bucket_weight = ( * min_bucket as u64 ) * ( * max_bucket as u64 ) ;
1946+ bucket_weight *= bucket_weight;
1947+ actual_valid_points_tracked += bucket_weight;
19041948 }
19051949 }
1906- assert_eq ! ( total_valid_points_tracked, actual_valid_points_tracked) ;
1950+ assert_eq ! ( total_valid_points_tracked, actual_valid_points_tracked as f64 ) ;
19071951 }
19081952
19091953 // If the total valid points is smaller than 1.0 (i.e. 32 in our fixed-point scheme),
19101954 // treat it as if we were fully decayed.
1911- const FULLY_DECAYED : u16 = BUCKET_FIXED_POINT_ONE * BUCKET_FIXED_POINT_ONE ;
1955+ const FULLY_DECAYED : f64 = BUCKET_FIXED_POINT_ONE as f64 * BUCKET_FIXED_POINT_ONE as f64 *
1956+ BUCKET_FIXED_POINT_ONE as f64 * BUCKET_FIXED_POINT_ONE as f64 ;
19121957 if total_valid_points_tracked < FULLY_DECAYED . into ( ) {
19131958 return None ;
19141959 }
19151960
1916- let mut cumulative_success_prob_times_billion = 0 ;
1961+ let mut cumulative_success_prob = 0.0f64 ;
19171962 // Special-case the 0th min bucket - it generally means we failed a payment, so only
19181963 // consider the highest (i.e. largest-offset-from-max-capacity) max bucket for all
19191964 // points against the 0th min bucket. This avoids the case where we fail to route
@@ -1926,16 +1971,18 @@ mod bucketed_history {
19261971 // max-bucket with at least BUCKET_FIXED_POINT_ONE.
19271972 let mut highest_max_bucket_with_points = 0 ;
19281973 let mut highest_max_bucket_with_full_points = None ;
1929- let mut total_max_points = 0 ; // Total points in max-buckets to consider
1974+ let mut total_weight = 0 ;
19301975 for ( max_idx, max_bucket) in max_liquidity_offset_history_buckets. iter ( ) . enumerate ( ) {
19311976 if * max_bucket >= BUCKET_FIXED_POINT_ONE {
19321977 highest_max_bucket_with_full_points = Some ( cmp:: max ( highest_max_bucket_with_full_points. unwrap_or ( 0 ) , max_idx) ) ;
19331978 }
19341979 if * max_bucket != 0 {
19351980 highest_max_bucket_with_points = cmp:: max ( highest_max_bucket_with_points, max_idx) ;
19361981 }
1937- total_max_points += * max_bucket as u64 ;
1982+ total_weight += ( * max_bucket as u64 ) * ( * max_bucket as u64 )
1983+ * ( min_liquidity_offset_history_buckets[ 0 ] as u64 ) * ( min_liquidity_offset_history_buckets[ 0 ] as u64 ) ;
19381984 }
1985+ debug_assert ! ( total_weight as f64 <= total_valid_points_tracked) ;
19391986 // Use the highest max-bucket with at least BUCKET_FIXED_POINT_ONE, but if none is
19401987 // available use the highest max-bucket with any non-zero value. This ensures that
19411988 // if we have substantially decayed data we don't end up thinking the highest
@@ -1944,40 +1991,39 @@ mod bucketed_history {
19441991 let selected_max = highest_max_bucket_with_full_points. unwrap_or ( highest_max_bucket_with_points) ;
19451992 let max_bucket_end_pos = BUCKET_START_POS [ 32 - selected_max] - 1 ;
19461993 if payment_pos < max_bucket_end_pos {
1947- let ( numerator, denominator) = success_probability ( payment_pos as u64 , 0 ,
1994+ let ( numerator, denominator) = success_probability_float ( payment_pos as u64 , 0 ,
19481995 max_bucket_end_pos as u64 , POSITION_TICKS as u64 - 1 , params, true ) ;
1949- let bucket_prob_times_billion =
1950- ( min_liquidity_offset_history_buckets[ 0 ] as u64 ) * total_max_points
1951- * 1024 * 1024 * 1024 / total_valid_points_tracked;
1952- cumulative_success_prob_times_billion += bucket_prob_times_billion *
1953- numerator / denominator;
1996+ let bucket_prob = total_weight as f64 / total_valid_points_tracked;
1997+ cumulative_success_prob += bucket_prob * numerator / denominator;
19541998 }
19551999 }
19562000
19572001 for ( min_idx, min_bucket) in min_liquidity_offset_history_buckets. iter ( ) . enumerate ( ) . skip ( 1 ) {
19582002 let min_bucket_start_pos = BUCKET_START_POS [ min_idx] ;
19592003 for ( max_idx, max_bucket) in max_liquidity_offset_history_buckets. iter ( ) . enumerate ( ) . take ( 32 - min_idx) {
19602004 let max_bucket_end_pos = BUCKET_START_POS [ 32 - max_idx] - 1 ;
1961- // Note that this multiply can only barely not overflow - two 16 bit ints plus
1962- // 30 bits is 62 bits.
1963- let bucket_prob_times_billion = ( * min_bucket as u64 ) * ( * max_bucket as u64 )
1964- * 1024 * 1024 * 1024 / total_valid_points_tracked ;
2005+ let mut bucket_weight = ( * min_bucket as u64 ) * ( * max_bucket as u64 ) ;
2006+ bucket_weight *= bucket_weight ;
2007+ debug_assert ! ( bucket_weight as f64 <= total_valid_points_tracked ) ;
2008+
19652009 if payment_pos >= max_bucket_end_pos {
19662010 // Success probability 0, the payment amount may be above the max liquidity
19672011 break ;
1968- } else if payment_pos < min_bucket_start_pos {
1969- cumulative_success_prob_times_billion += bucket_prob_times_billion;
2012+ }
2013+
2014+ let bucket_prob = bucket_weight as f64 / total_valid_points_tracked;
2015+ if payment_pos < min_bucket_start_pos {
2016+ cumulative_success_prob += bucket_prob;
19702017 } else {
1971- let ( numerator, denominator) = success_probability ( payment_pos as u64 ,
2018+ let ( numerator, denominator) = success_probability_float ( payment_pos as u64 ,
19722019 min_bucket_start_pos as u64 , max_bucket_end_pos as u64 ,
19732020 POSITION_TICKS as u64 - 1 , params, true ) ;
1974- cumulative_success_prob_times_billion += bucket_prob_times_billion *
1975- numerator / denominator;
2021+ cumulative_success_prob += bucket_prob * numerator / denominator;
19762022 }
19772023 }
19782024 }
19792025
1980- Some ( cumulative_success_prob_times_billion )
2026+ Some ( ( cumulative_success_prob * ( 1024.0 * 1024.0 * 1024.0 ) ) as u64 )
19812027 }
19822028 }
19832029}
@@ -3575,9 +3621,12 @@ mod tests {
35753621 // Now test again with the amount in the bottom bucket.
35763622 amount_msat /= 2 ;
35773623 // The new amount is entirely within the only minimum bucket with score, so the probability
3578- // we assign is 1/2.
3579- assert_eq ! ( scorer. historical_estimated_payment_success_probability( 42 , & target, amount_msat, & params, false ) ,
3580- Some ( 0.5 ) ) ;
3624+ // we assign is around 41%.
3625+ let probability =
3626+ scorer. historical_estimated_payment_success_probability ( 42 , & target, amount_msat, & params, false )
3627+ . unwrap ( ) ;
3628+ assert ! ( probability >= 0.41 ) ;
3629+ assert ! ( probability < 0.42 ) ;
35813630
35823631 // ...but once we see a failure, we consider the payment to be substantially less likely,
35833632 // even though not a probability of zero as we still look at the second max bucket which
0 commit comments