use crate::block_template::selector::ALPHA;
use itertools::Itertools;
use std::fmt::Display;
pub type Feerate = f64;
#[derive(Clone, Copy, Debug)]
pub struct FeerateBucket {
pub feerate: f64,
pub estimated_seconds: f64,
}
impl Display for FeerateBucket {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "({:.4}, {:.4}s)", self.feerate, self.estimated_seconds)
}
}
#[derive(Clone, Debug)]
pub struct FeerateEstimations {
pub priority_bucket: FeerateBucket,
pub normal_buckets: Vec<FeerateBucket>,
pub low_buckets: Vec<FeerateBucket>,
}
impl FeerateEstimations {
pub fn ordered_buckets(&self) -> Vec<FeerateBucket> {
std::iter::once(self.priority_bucket)
.chain(self.normal_buckets.iter().copied())
.chain(self.low_buckets.iter().copied())
.collect()
}
}
impl Display for FeerateEstimations {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "(fee/mass, secs) priority: {}, ", self.priority_bucket)?;
write!(f, "normal: {}, ", self.normal_buckets.iter().format(", "))?;
write!(f, "low: {}", self.low_buckets.iter().format(", "))
}
}
pub struct FeerateEstimatorArgs {
pub network_blocks_per_second: u64,
pub maximum_mass_per_block: u64,
}
impl FeerateEstimatorArgs {
pub fn new(network_blocks_per_second: u64, maximum_mass_per_block: u64) -> Self {
Self { network_blocks_per_second, maximum_mass_per_block }
}
pub fn network_mass_per_second(&self) -> u64 {
self.network_blocks_per_second * self.maximum_mass_per_block
}
}
#[derive(Debug, Clone)]
pub struct FeerateEstimator {
total_weight: f64,
inclusion_interval: f64,
}
impl FeerateEstimator {
pub fn new(total_weight: f64, inclusion_interval: f64) -> Self {
assert!(total_weight >= 0.0);
assert!((0f64..1f64).contains(&inclusion_interval));
Self { total_weight, inclusion_interval }
}
pub(crate) fn feerate_to_time(&self, feerate: f64) -> f64 {
let (c1, c2) = (self.inclusion_interval, self.total_weight);
c1 * c2 / feerate.powi(ALPHA) + c1
}
fn time_to_feerate(&self, time: f64) -> f64 {
let (c1, c2) = (self.inclusion_interval, self.total_weight);
assert!(c1 < time, "{c1}, {time}");
((c1 * c2 / time) / (1f64 - c1 / time)).powf(1f64 / ALPHA as f64)
}
#[inline]
fn feerate_to_time_antiderivative(&self, feerate: f64) -> f64 {
let (c1, c2) = (self.inclusion_interval, self.total_weight);
c1 * c2 / (-2f64 * feerate.powi(ALPHA - 1))
}
fn quantile(&self, lower: f64, upper: f64, frac: f64) -> f64 {
assert!((0f64..=1f64).contains(&frac));
assert!(0.0 < lower && lower <= upper, "{lower}, {upper}");
let (c1, c2) = (self.inclusion_interval, self.total_weight);
if c1 == 0.0 || c2 == 0.0 {
return lower;
}
let z1 = self.feerate_to_time_antiderivative(lower);
let z2 = self.feerate_to_time_antiderivative(upper);
let z = frac * z2 + (1f64 - frac) * z1;
((c1 * c2) / (-2f64 * z)).powf(1f64 / (ALPHA - 1) as f64)
}
pub fn calc_estimations(&self, minimum_standard_feerate: f64) -> FeerateEstimations {
let min = minimum_standard_feerate;
let high = self.time_to_feerate(1f64).max(min);
let low = self.time_to_feerate(3600f64).max(self.quantile(min, high, 0.25));
let normal = self.time_to_feerate(60f64).max(self.quantile(low, high, 0.66));
let mid = self.time_to_feerate(1800f64).max(self.quantile(min, high, 0.5));
FeerateEstimations {
priority_bucket: FeerateBucket { feerate: high, estimated_seconds: self.feerate_to_time(high) },
normal_buckets: vec![
FeerateBucket { feerate: normal, estimated_seconds: self.feerate_to_time(normal) },
FeerateBucket { feerate: mid, estimated_seconds: self.feerate_to_time(mid) },
],
low_buckets: vec![FeerateBucket { feerate: low, estimated_seconds: self.feerate_to_time(low) }],
}
}
}
#[derive(Clone, Debug)]
pub struct FeeEstimateVerbose {
pub estimations: FeerateEstimations,
pub mempool_ready_transactions_count: u64,
pub mempool_ready_transactions_total_mass: u64,
pub network_mass_per_second: u64,
pub next_block_template_feerate_min: f64,
pub next_block_template_feerate_median: f64,
pub next_block_template_feerate_max: f64,
}
#[cfg(test)]
mod tests {
use super::*;
use itertools::Itertools;
#[test]
fn test_feerate_estimations() {
let estimator = FeerateEstimator { total_weight: 1002283.659, inclusion_interval: 0.004f64 };
let estimations = estimator.calc_estimations(1.0);
let buckets = estimations.ordered_buckets();
for (i, j) in buckets.into_iter().tuple_windows() {
assert!(i.feerate >= j.feerate);
}
dbg!(estimations);
}
#[test]
fn test_min_feerate_estimations() {
let estimator = FeerateEstimator { total_weight: 0.00659, inclusion_interval: 0.004f64 };
let minimum_feerate = 0.755;
let estimations = estimator.calc_estimations(minimum_feerate);
println!("{estimations}");
let buckets = estimations.ordered_buckets();
assert!(buckets.last().unwrap().feerate >= minimum_feerate);
for (i, j) in buckets.into_iter().tuple_windows() {
assert!(i.feerate >= j.feerate);
assert!(i.estimated_seconds <= j.estimated_seconds);
}
}
#[test]
fn test_zero_values() {
let estimator = FeerateEstimator { total_weight: 0.0, inclusion_interval: 0.0 };
let minimum_feerate = 0.755;
let estimations = estimator.calc_estimations(minimum_feerate);
let buckets = estimations.ordered_buckets();
for bucket in buckets {
assert_eq!(minimum_feerate, bucket.feerate);
assert_eq!(0.0, bucket.estimated_seconds);
}
let estimator = FeerateEstimator { total_weight: 0.0, inclusion_interval: 0.1 };
let minimum_feerate = 0.755;
let estimations = estimator.calc_estimations(minimum_feerate);
let buckets = estimations.ordered_buckets();
for bucket in buckets {
assert_eq!(minimum_feerate, bucket.feerate);
assert_eq!(estimator.inclusion_interval, bucket.estimated_seconds);
}
let estimator = FeerateEstimator { total_weight: 0.1, inclusion_interval: 0.0 };
let minimum_feerate = 0.755;
let estimations = estimator.calc_estimations(minimum_feerate);
let buckets = estimations.ordered_buckets();
for bucket in buckets {
assert_eq!(minimum_feerate, bucket.feerate);
assert_eq!(0.0, bucket.estimated_seconds);
}
}
}