@@ -1218,14 +1218,33 @@ fn nonlinear_success_probability(
1218
1218
/// Given liquidity bounds, calculates the success probability (in the form of a numerator and
1219
1219
/// denominator) of an HTLC. This is a key assumption in our scoring models.
1220
1220
///
1221
- /// Must not return a numerator or denominator greater than 2^31 for arguments less than 2^31.
1222
- ///
1223
1221
/// `total_inflight_amount_msat` includes the amount of the HTLC and any HTLCs in flight over the
1224
1222
/// channel.
1225
1223
///
1226
1224
/// min_zero_implies_no_successes signals that a `min_liquidity_msat` of 0 means we've not
1227
1225
/// (recently) seen an HTLC successfully complete over this channel.
1228
1226
#[ inline( always) ]
1227
+ fn success_probability_float (
1228
+ total_inflight_amount_msat : u64 , min_liquidity_msat : u64 , max_liquidity_msat : u64 ,
1229
+ capacity_msat : u64 , params : & ProbabilisticScoringFeeParameters ,
1230
+ min_zero_implies_no_successes : bool ,
1231
+ ) -> ( f64 , f64 ) {
1232
+ debug_assert ! ( min_liquidity_msat <= total_inflight_amount_msat) ;
1233
+ debug_assert ! ( total_inflight_amount_msat < max_liquidity_msat) ;
1234
+ debug_assert ! ( max_liquidity_msat <= capacity_msat) ;
1235
+
1236
+ if params. linear_success_probability {
1237
+ let ( numerator, denominator) = linear_success_probability ( total_inflight_amount_msat, min_liquidity_msat, max_liquidity_msat, min_zero_implies_no_successes) ;
1238
+ ( numerator as f64 , denominator as f64 )
1239
+ } else {
1240
+ nonlinear_success_probability ( total_inflight_amount_msat, min_liquidity_msat, max_liquidity_msat, capacity_msat, min_zero_implies_no_successes)
1241
+ }
1242
+ }
1243
+
1244
+ #[ inline( always) ]
1245
+ /// Identical to [`success_probability_float`] but returns integer numerator and denominators.
1246
+ ///
1247
+ /// Must not return a numerator or denominator greater than 2^31 for arguments less than 2^31.
1229
1248
fn success_probability (
1230
1249
total_inflight_amount_msat : u64 , min_liquidity_msat : u64 , max_liquidity_msat : u64 ,
1231
1250
capacity_msat : u64 , params : & ProbabilisticScoringFeeParameters ,
@@ -1798,7 +1817,12 @@ mod bucketed_history {
1798
1817
// Because the first thing we do is check if `total_valid_points` is sufficient to consider
1799
1818
// the data here at all, and can return early if it is not, we want this to go first to
1800
1819
// avoid hitting a second cache line load entirely in that case.
1801
- total_valid_points_tracked : u64 ,
1820
+ //
1821
+ // Note that we store it as an `f64` rather than a `u64` (potentially losing some
1822
+ // precision) because we ultimately need the value as an `f64` when dividing bucket weights
1823
+ // by it. Storing it as an `f64` avoids doing the additional int -> float conversion in the
1824
+ // hot score-calculation path.
1825
+ total_valid_points_tracked : f64 ,
1802
1826
min_liquidity_offset_history : HistoricalBucketRangeTracker ,
1803
1827
max_liquidity_offset_history : HistoricalBucketRangeTracker ,
1804
1828
}
@@ -1808,7 +1832,7 @@ mod bucketed_history {
1808
1832
HistoricalLiquidityTracker {
1809
1833
min_liquidity_offset_history : HistoricalBucketRangeTracker :: new ( ) ,
1810
1834
max_liquidity_offset_history : HistoricalBucketRangeTracker :: new ( ) ,
1811
- total_valid_points_tracked : 0 ,
1835
+ total_valid_points_tracked : 0.0 ,
1812
1836
}
1813
1837
}
1814
1838
@@ -1819,7 +1843,7 @@ mod bucketed_history {
1819
1843
let mut res = HistoricalLiquidityTracker {
1820
1844
min_liquidity_offset_history,
1821
1845
max_liquidity_offset_history,
1822
- total_valid_points_tracked : 0 ,
1846
+ total_valid_points_tracked : 0.0 ,
1823
1847
} ;
1824
1848
res. recalculate_valid_point_count ( ) ;
1825
1849
res
@@ -1842,12 +1866,18 @@ mod bucketed_history {
1842
1866
}
1843
1867
1844
1868
fn recalculate_valid_point_count ( & mut self ) {
1845
- self . total_valid_points_tracked = 0 ;
1869
+ let mut total_valid_points_tracked = 0 ;
1846
1870
for ( min_idx, min_bucket) in self . min_liquidity_offset_history . buckets . iter ( ) . enumerate ( ) {
1847
1871
for max_bucket in self . max_liquidity_offset_history . buckets . iter ( ) . take ( 32 - min_idx) {
1848
- self . total_valid_points_tracked += ( * min_bucket as u64 ) * ( * max_bucket as u64 ) ;
1872
+ // In testing, raising the weights of buckets to a high power led to better
1873
+ // scoring results. Thus, we raise the bucket weights to the 4th power here (by
1874
+ // squaring the result of multiplying the weights).
1875
+ let mut bucket_weight = ( * min_bucket as u64 ) * ( * max_bucket as u64 ) ;
1876
+ bucket_weight *= bucket_weight;
1877
+ total_valid_points_tracked += bucket_weight;
1849
1878
}
1850
1879
}
1880
+ self . total_valid_points_tracked = total_valid_points_tracked as f64 ;
1851
1881
}
1852
1882
1853
1883
pub ( super ) fn writeable_min_offset_history ( & self ) -> & HistoricalBucketRangeTracker {
@@ -1933,20 +1963,23 @@ mod bucketed_history {
1933
1963
let mut actual_valid_points_tracked = 0 ;
1934
1964
for ( min_idx, min_bucket) in min_liquidity_offset_history_buckets. iter ( ) . enumerate ( ) {
1935
1965
for max_bucket in max_liquidity_offset_history_buckets. iter ( ) . take ( 32 - min_idx) {
1936
- actual_valid_points_tracked += ( * min_bucket as u64 ) * ( * max_bucket as u64 ) ;
1966
+ let mut bucket_weight = ( * min_bucket as u64 ) * ( * max_bucket as u64 ) ;
1967
+ bucket_weight *= bucket_weight;
1968
+ actual_valid_points_tracked += bucket_weight;
1937
1969
}
1938
1970
}
1939
- assert_eq ! ( total_valid_points_tracked, actual_valid_points_tracked) ;
1971
+ assert_eq ! ( total_valid_points_tracked, actual_valid_points_tracked as f64 ) ;
1940
1972
}
1941
1973
1942
1974
// If the total valid points is smaller than 1.0 (i.e. 32 in our fixed-point scheme),
1943
1975
// treat it as if we were fully decayed.
1944
- const FULLY_DECAYED : u16 = BUCKET_FIXED_POINT_ONE * BUCKET_FIXED_POINT_ONE ;
1976
+ const FULLY_DECAYED : f64 = BUCKET_FIXED_POINT_ONE as f64 * BUCKET_FIXED_POINT_ONE as f64 *
1977
+ BUCKET_FIXED_POINT_ONE as f64 * BUCKET_FIXED_POINT_ONE as f64 ;
1945
1978
if total_valid_points_tracked < FULLY_DECAYED . into ( ) {
1946
1979
return None ;
1947
1980
}
1948
1981
1949
- let mut cumulative_success_prob_times_billion = 0 ;
1982
+ let mut cumulative_success_prob = 0.0f64 ;
1950
1983
// Special-case the 0th min bucket - it generally means we failed a payment, so only
1951
1984
// consider the highest (i.e. largest-offset-from-max-capacity) max bucket for all
1952
1985
// points against the 0th min bucket. This avoids the case where we fail to route
@@ -1959,16 +1992,22 @@ mod bucketed_history {
1959
1992
// max-bucket with at least BUCKET_FIXED_POINT_ONE.
1960
1993
let mut highest_max_bucket_with_points = 0 ;
1961
1994
let mut highest_max_bucket_with_full_points = None ;
1962
- let mut total_max_points = 0 ; // Total points in max-buckets to consider
1995
+ let mut total_weight = 0 ;
1963
1996
for ( max_idx, max_bucket) in max_liquidity_offset_history_buckets. iter ( ) . enumerate ( ) {
1964
1997
if * max_bucket >= BUCKET_FIXED_POINT_ONE {
1965
1998
highest_max_bucket_with_full_points = Some ( cmp:: max ( highest_max_bucket_with_full_points. unwrap_or ( 0 ) , max_idx) ) ;
1966
1999
}
1967
2000
if * max_bucket != 0 {
1968
2001
highest_max_bucket_with_points = cmp:: max ( highest_max_bucket_with_points, max_idx) ;
1969
2002
}
1970
- total_max_points += * max_bucket as u64 ;
2003
+ // In testing, raising the weights of buckets to a high power led to better
2004
+ // scoring results. Thus, we raise the bucket weights to the 4th power here (by
2005
+ // squaring the result of multiplying the weights), matching the logic in
2006
+ // `recalculate_valid_point_count`.
2007
+ let bucket_weight = ( * max_bucket as u64 ) * ( min_liquidity_offset_history_buckets[ 0 ] as u64 ) ;
2008
+ total_weight += bucket_weight * bucket_weight;
1971
2009
}
2010
+ debug_assert ! ( total_weight as f64 <= total_valid_points_tracked) ;
1972
2011
// Use the highest max-bucket with at least BUCKET_FIXED_POINT_ONE, but if none is
1973
2012
// available use the highest max-bucket with any non-zero value. This ensures that
1974
2013
// if we have substantially decayed data we don't end up thinking the highest
@@ -1977,40 +2016,43 @@ mod bucketed_history {
1977
2016
let selected_max = highest_max_bucket_with_full_points. unwrap_or ( highest_max_bucket_with_points) ;
1978
2017
let max_bucket_end_pos = BUCKET_START_POS [ 32 - selected_max] - 1 ;
1979
2018
if payment_pos < max_bucket_end_pos {
1980
- let ( numerator, denominator) = success_probability ( payment_pos as u64 , 0 ,
2019
+ let ( numerator, denominator) = success_probability_float ( payment_pos as u64 , 0 ,
1981
2020
max_bucket_end_pos as u64 , POSITION_TICKS as u64 - 1 , params, true ) ;
1982
- let bucket_prob_times_billion =
1983
- ( min_liquidity_offset_history_buckets[ 0 ] as u64 ) * total_max_points
1984
- * 1024 * 1024 * 1024 / total_valid_points_tracked;
1985
- cumulative_success_prob_times_billion += bucket_prob_times_billion *
1986
- numerator / denominator;
2021
+ let bucket_prob = total_weight as f64 / total_valid_points_tracked;
2022
+ cumulative_success_prob += bucket_prob * numerator / denominator;
1987
2023
}
1988
2024
}
1989
2025
1990
2026
for ( min_idx, min_bucket) in min_liquidity_offset_history_buckets. iter ( ) . enumerate ( ) . skip ( 1 ) {
1991
2027
let min_bucket_start_pos = BUCKET_START_POS [ min_idx] ;
1992
2028
for ( max_idx, max_bucket) in max_liquidity_offset_history_buckets. iter ( ) . enumerate ( ) . take ( 32 - min_idx) {
1993
2029
let max_bucket_end_pos = BUCKET_START_POS [ 32 - max_idx] - 1 ;
1994
- // Note that this multiply can only barely not overflow - two 16 bit ints plus
1995
- // 30 bits is 62 bits.
1996
- let bucket_prob_times_billion = ( * min_bucket as u64 ) * ( * max_bucket as u64 )
1997
- * 1024 * 1024 * 1024 / total_valid_points_tracked;
1998
2030
if payment_pos >= max_bucket_end_pos {
1999
2031
// Success probability 0, the payment amount may be above the max liquidity
2000
2032
break ;
2001
- } else if payment_pos < min_bucket_start_pos {
2002
- cumulative_success_prob_times_billion += bucket_prob_times_billion;
2033
+ }
2034
+
2035
+ // In testing, raising the weights of buckets to a high power led to better
2036
+ // scoring results. Thus, we raise the bucket weights to the 4th power here (by
2037
+ // squaring the result of multiplying the weights), matching the logic in
2038
+ // `recalculate_valid_point_count`.
2039
+ let mut bucket_weight = ( * min_bucket as u64 ) * ( * max_bucket as u64 ) ;
2040
+ bucket_weight *= bucket_weight;
2041
+ debug_assert ! ( bucket_weight as f64 <= total_valid_points_tracked) ;
2042
+ let bucket_prob = bucket_weight as f64 / total_valid_points_tracked;
2043
+
2044
+ if payment_pos < min_bucket_start_pos {
2045
+ cumulative_success_prob += bucket_prob;
2003
2046
} else {
2004
- let ( numerator, denominator) = success_probability ( payment_pos as u64 ,
2047
+ let ( numerator, denominator) = success_probability_float ( payment_pos as u64 ,
2005
2048
min_bucket_start_pos as u64 , max_bucket_end_pos as u64 ,
2006
2049
POSITION_TICKS as u64 - 1 , params, true ) ;
2007
- cumulative_success_prob_times_billion += bucket_prob_times_billion *
2008
- numerator / denominator;
2050
+ cumulative_success_prob += bucket_prob * numerator / denominator;
2009
2051
}
2010
2052
}
2011
2053
}
2012
2054
2013
- Some ( cumulative_success_prob_times_billion )
2055
+ Some ( ( cumulative_success_prob * ( 1024.0 * 1024.0 * 1024.0 ) ) as u64 )
2014
2056
}
2015
2057
}
2016
2058
}
0 commit comments