@@ -1157,27 +1157,25 @@ fn three_f64_pow_9(a: f64, b: f64, c: f64) -> (f64, f64, f64) {
1157
1157
/// Given liquidity bounds, calculates the success probability (in the form of a numerator and
1158
1158
/// denominator) of an HTLC. This is a key assumption in our scoring models.
1159
1159
///
1160
- /// Must not return a numerator or denominator greater than 2^31 for arguments less than 2^31.
1161
- ///
1162
1160
/// `total_inflight_amount_msat` includes the amount of the HTLC and any HTLCs in flight over the
1163
1161
/// channel.
1164
1162
///
1165
1163
/// min_zero_implies_no_successes signals that a `min_liquidity_msat` of 0 means we've not
1166
1164
/// (recently) seen an HTLC successfully complete over this channel.
1167
1165
#[ inline( always) ]
1168
- fn success_probability (
1166
+ fn success_probability_float (
1169
1167
total_inflight_amount_msat : u64 , min_liquidity_msat : u64 , max_liquidity_msat : u64 ,
1170
1168
capacity_msat : u64 , params : & ProbabilisticScoringFeeParameters ,
1171
1169
min_zero_implies_no_successes : bool ,
1172
- ) -> ( u64 , u64 ) {
1170
+ ) -> ( f64 , f64 ) {
1173
1171
debug_assert ! ( min_liquidity_msat <= total_inflight_amount_msat) ;
1174
1172
debug_assert ! ( total_inflight_amount_msat < max_liquidity_msat) ;
1175
1173
debug_assert ! ( max_liquidity_msat <= capacity_msat) ;
1176
1174
1177
1175
let ( numerator, mut denominator) =
1178
1176
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 )
1181
1179
} else {
1182
1180
let capacity = capacity_msat as f64 ;
1183
1181
let min = ( min_liquidity_msat as f64 ) / capacity;
@@ -1200,6 +1198,57 @@ fn success_probability(
1200
1198
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 ) ;
1201
1199
let num = max_v - amt_v;
1202
1200
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
+ ) ;
1203
1252
1204
1253
// Because our numerator and denominator max out at 0.0078125 we need to multiply them
1205
1254
// by quite a large factor to get something useful (ideally in the 2^30 range).
@@ -1211,16 +1260,6 @@ fn success_probability(
1211
1260
( numerator, denominator)
1212
1261
} ;
1213
1262
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
-
1224
1263
( numerator, denominator)
1225
1264
}
1226
1265
@@ -1766,7 +1805,7 @@ mod bucketed_history {
1766
1805
// Because the first thing we do is check if `total_valid_points` is sufficient to consider
1767
1806
// the data here at all, and can return early if it is not, we want this to go first to
1768
1807
// avoid hitting a second cache line load entirely in that case.
1769
- total_valid_points_tracked : u64 ,
1808
+ total_valid_points_tracked : f64 ,
1770
1809
min_liquidity_offset_history : HistoricalBucketRangeTracker ,
1771
1810
max_liquidity_offset_history : HistoricalBucketRangeTracker ,
1772
1811
}
@@ -1776,7 +1815,7 @@ mod bucketed_history {
1776
1815
HistoricalLiquidityTracker {
1777
1816
min_liquidity_offset_history : HistoricalBucketRangeTracker :: new ( ) ,
1778
1817
max_liquidity_offset_history : HistoricalBucketRangeTracker :: new ( ) ,
1779
- total_valid_points_tracked : 0 ,
1818
+ total_valid_points_tracked : 0.0 ,
1780
1819
}
1781
1820
}
1782
1821
@@ -1787,7 +1826,7 @@ mod bucketed_history {
1787
1826
let mut res = HistoricalLiquidityTracker {
1788
1827
min_liquidity_offset_history,
1789
1828
max_liquidity_offset_history,
1790
- total_valid_points_tracked : 0 ,
1829
+ total_valid_points_tracked : 0.0 ,
1791
1830
} ;
1792
1831
res. recalculate_valid_point_count ( ) ;
1793
1832
res
@@ -1810,12 +1849,15 @@ mod bucketed_history {
1810
1849
}
1811
1850
1812
1851
fn recalculate_valid_point_count ( & mut self ) {
1813
- self . total_valid_points_tracked = 0 ;
1852
+ let mut total_valid_points_tracked = 0 ;
1814
1853
for ( min_idx, min_bucket) in self . min_liquidity_offset_history . buckets . iter ( ) . enumerate ( ) {
1815
1854
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;
1817
1858
}
1818
1859
}
1860
+ self . total_valid_points_tracked = total_valid_points_tracked as f64 ;
1819
1861
}
1820
1862
1821
1863
pub ( super ) fn writeable_min_offset_history ( & self ) -> & HistoricalBucketRangeTracker {
@@ -1901,20 +1943,23 @@ mod bucketed_history {
1901
1943
let mut actual_valid_points_tracked = 0 ;
1902
1944
for ( min_idx, min_bucket) in min_liquidity_offset_history_buckets. iter ( ) . enumerate ( ) {
1903
1945
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;
1905
1949
}
1906
1950
}
1907
- assert_eq ! ( total_valid_points_tracked, actual_valid_points_tracked) ;
1951
+ assert_eq ! ( total_valid_points_tracked, actual_valid_points_tracked as f64 ) ;
1908
1952
}
1909
1953
1910
1954
// If the total valid points is smaller than 1.0 (i.e. 32 in our fixed-point scheme),
1911
1955
// 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 ;
1913
1958
if total_valid_points_tracked < FULLY_DECAYED . into ( ) {
1914
1959
return None ;
1915
1960
}
1916
1961
1917
- let mut cumulative_success_prob_times_billion = 0 ;
1962
+ let mut cumulative_success_prob = 0.0f64 ;
1918
1963
// Special-case the 0th min bucket - it generally means we failed a payment, so only
1919
1964
// consider the highest (i.e. largest-offset-from-max-capacity) max bucket for all
1920
1965
// points against the 0th min bucket. This avoids the case where we fail to route
@@ -1927,16 +1972,18 @@ mod bucketed_history {
1927
1972
// max-bucket with at least BUCKET_FIXED_POINT_ONE.
1928
1973
let mut highest_max_bucket_with_points = 0 ;
1929
1974
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 ;
1931
1976
for ( max_idx, max_bucket) in max_liquidity_offset_history_buckets. iter ( ) . enumerate ( ) {
1932
1977
if * max_bucket >= BUCKET_FIXED_POINT_ONE {
1933
1978
highest_max_bucket_with_full_points = Some ( cmp:: max ( highest_max_bucket_with_full_points. unwrap_or ( 0 ) , max_idx) ) ;
1934
1979
}
1935
1980
if * max_bucket != 0 {
1936
1981
highest_max_bucket_with_points = cmp:: max ( highest_max_bucket_with_points, max_idx) ;
1937
1982
}
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 ) ;
1939
1985
}
1986
+ debug_assert ! ( total_weight as f64 <= total_valid_points_tracked) ;
1940
1987
// Use the highest max-bucket with at least BUCKET_FIXED_POINT_ONE, but if none is
1941
1988
// available use the highest max-bucket with any non-zero value. This ensures that
1942
1989
// if we have substantially decayed data we don't end up thinking the highest
@@ -1945,40 +1992,39 @@ mod bucketed_history {
1945
1992
let selected_max = highest_max_bucket_with_full_points. unwrap_or ( highest_max_bucket_with_points) ;
1946
1993
let max_bucket_end_pos = BUCKET_START_POS [ 32 - selected_max] - 1 ;
1947
1994
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 ,
1949
1996
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;
1955
1999
}
1956
2000
}
1957
2001
1958
2002
for ( min_idx, min_bucket) in min_liquidity_offset_history_buckets. iter ( ) . enumerate ( ) . skip ( 1 ) {
1959
2003
let min_bucket_start_pos = BUCKET_START_POS [ min_idx] ;
1960
2004
for ( max_idx, max_bucket) in max_liquidity_offset_history_buckets. iter ( ) . enumerate ( ) . take ( 32 - min_idx) {
1961
2005
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
+
1966
2010
if payment_pos >= max_bucket_end_pos {
1967
2011
// Success probability 0, the payment amount may be above the max liquidity
1968
2012
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;
1971
2018
} else {
1972
- let ( numerator, denominator) = success_probability ( payment_pos as u64 ,
2019
+ let ( numerator, denominator) = success_probability_float ( payment_pos as u64 ,
1973
2020
min_bucket_start_pos as u64 , max_bucket_end_pos as u64 ,
1974
2021
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;
1977
2023
}
1978
2024
}
1979
2025
}
1980
2026
1981
- Some ( cumulative_success_prob_times_billion )
2027
+ Some ( ( cumulative_success_prob * ( 1024.0 * 1024.0 * 1024.0 ) ) as u64 )
1982
2028
}
1983
2029
}
1984
2030
}
@@ -3576,9 +3622,12 @@ mod tests {
3576
3622
// Now test again with the amount in the bottom bucket.
3577
3623
amount_msat /= 2 ;
3578
3624
// 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 ) ;
3582
3631
3583
3632
// ...but once we see a failure, we consider the payment to be substantially less likely,
3584
3633
// even though not a probability of zero as we still look at the second max bucket which
0 commit comments