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