@@ -1218,14 +1218,33 @@ fn nonlinear_success_probability(
12181218/// Given liquidity bounds, calculates the success probability (in the form of a numerator and
12191219/// denominator) of an HTLC. This is a key assumption in our scoring models.
12201220///
1221- /// Must not return a numerator or denominator greater than 2^31 for arguments less than 2^31.
1222- ///
12231221/// `total_inflight_amount_msat` includes the amount of the HTLC and any HTLCs in flight over the
12241222/// channel.
12251223///
12261224/// min_zero_implies_no_successes signals that a `min_liquidity_msat` of 0 means we've not
12271225/// (recently) seen an HTLC successfully complete over this channel.
12281226#[ 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.
12291248fn success_probability (
12301249 total_inflight_amount_msat : u64 , min_liquidity_msat : u64 , max_liquidity_msat : u64 ,
12311250 capacity_msat : u64 , params : & ProbabilisticScoringFeeParameters ,
@@ -1798,7 +1817,12 @@ mod bucketed_history {
17981817 // Because the first thing we do is check if `total_valid_points` is sufficient to consider
17991818 // the data here at all, and can return early if it is not, we want this to go first to
18001819 // 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 ,
18021826 min_liquidity_offset_history : HistoricalBucketRangeTracker ,
18031827 max_liquidity_offset_history : HistoricalBucketRangeTracker ,
18041828 }
@@ -1808,7 +1832,7 @@ mod bucketed_history {
18081832 HistoricalLiquidityTracker {
18091833 min_liquidity_offset_history : HistoricalBucketRangeTracker :: new ( ) ,
18101834 max_liquidity_offset_history : HistoricalBucketRangeTracker :: new ( ) ,
1811- total_valid_points_tracked : 0 ,
1835+ total_valid_points_tracked : 0.0 ,
18121836 }
18131837 }
18141838
@@ -1819,7 +1843,7 @@ mod bucketed_history {
18191843 let mut res = HistoricalLiquidityTracker {
18201844 min_liquidity_offset_history,
18211845 max_liquidity_offset_history,
1822- total_valid_points_tracked : 0 ,
1846+ total_valid_points_tracked : 0.0 ,
18231847 } ;
18241848 res. recalculate_valid_point_count ( ) ;
18251849 res
@@ -1842,12 +1866,18 @@ mod bucketed_history {
18421866 }
18431867
18441868 fn recalculate_valid_point_count ( & mut self ) {
1845- self . total_valid_points_tracked = 0 ;
1869+ let mut total_valid_points_tracked = 0 ;
18461870 for ( min_idx, min_bucket) in self . min_liquidity_offset_history . buckets . iter ( ) . enumerate ( ) {
18471871 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;
18491878 }
18501879 }
1880+ self . total_valid_points_tracked = total_valid_points_tracked as f64 ;
18511881 }
18521882
18531883 pub ( super ) fn writeable_min_offset_history ( & self ) -> & HistoricalBucketRangeTracker {
@@ -1933,20 +1963,23 @@ mod bucketed_history {
19331963 let mut actual_valid_points_tracked = 0 ;
19341964 for ( min_idx, min_bucket) in min_liquidity_offset_history_buckets. iter ( ) . enumerate ( ) {
19351965 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;
19371969 }
19381970 }
1939- assert_eq ! ( total_valid_points_tracked, actual_valid_points_tracked) ;
1971+ assert_eq ! ( total_valid_points_tracked, actual_valid_points_tracked as f64 ) ;
19401972 }
19411973
19421974 // If the total valid points is smaller than 1.0 (i.e. 32 in our fixed-point scheme),
19431975 // 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 ;
19451978 if total_valid_points_tracked < FULLY_DECAYED . into ( ) {
19461979 return None ;
19471980 }
19481981
1949- let mut cumulative_success_prob_times_billion = 0 ;
1982+ let mut cumulative_success_prob = 0.0f64 ;
19501983 // Special-case the 0th min bucket - it generally means we failed a payment, so only
19511984 // consider the highest (i.e. largest-offset-from-max-capacity) max bucket for all
19521985 // points against the 0th min bucket. This avoids the case where we fail to route
@@ -1959,16 +1992,22 @@ mod bucketed_history {
19591992 // max-bucket with at least BUCKET_FIXED_POINT_ONE.
19601993 let mut highest_max_bucket_with_points = 0 ;
19611994 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 ;
19631996 for ( max_idx, max_bucket) in max_liquidity_offset_history_buckets. iter ( ) . enumerate ( ) {
19641997 if * max_bucket >= BUCKET_FIXED_POINT_ONE {
19651998 highest_max_bucket_with_full_points = Some ( cmp:: max ( highest_max_bucket_with_full_points. unwrap_or ( 0 ) , max_idx) ) ;
19661999 }
19672000 if * max_bucket != 0 {
19682001 highest_max_bucket_with_points = cmp:: max ( highest_max_bucket_with_points, max_idx) ;
19692002 }
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;
19712009 }
2010+ debug_assert ! ( total_weight as f64 <= total_valid_points_tracked) ;
19722011 // Use the highest max-bucket with at least BUCKET_FIXED_POINT_ONE, but if none is
19732012 // available use the highest max-bucket with any non-zero value. This ensures that
19742013 // if we have substantially decayed data we don't end up thinking the highest
@@ -1977,40 +2016,43 @@ mod bucketed_history {
19772016 let selected_max = highest_max_bucket_with_full_points. unwrap_or ( highest_max_bucket_with_points) ;
19782017 let max_bucket_end_pos = BUCKET_START_POS [ 32 - selected_max] - 1 ;
19792018 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 ,
19812020 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;
19872023 }
19882024 }
19892025
19902026 for ( min_idx, min_bucket) in min_liquidity_offset_history_buckets. iter ( ) . enumerate ( ) . skip ( 1 ) {
19912027 let min_bucket_start_pos = BUCKET_START_POS [ min_idx] ;
19922028 for ( max_idx, max_bucket) in max_liquidity_offset_history_buckets. iter ( ) . enumerate ( ) . take ( 32 - min_idx) {
19932029 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;
19982030 if payment_pos >= max_bucket_end_pos {
19992031 // Success probability 0, the payment amount may be above the max liquidity
20002032 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;
20032046 } else {
2004- let ( numerator, denominator) = success_probability ( payment_pos as u64 ,
2047+ let ( numerator, denominator) = success_probability_float ( payment_pos as u64 ,
20052048 min_bucket_start_pos as u64 , max_bucket_end_pos as u64 ,
20062049 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;
20092051 }
20102052 }
20112053 }
20122054
2013- Some ( cumulative_success_prob_times_billion )
2055+ Some ( ( cumulative_success_prob * ( 1024.0 * 1024.0 * 1024.0 ) ) as u64 )
20142056 }
20152057 }
20162058}
0 commit comments