1use crate::block_template::selector::ALPHA;
5use itertools::Itertools;
6use std::fmt::Display;
7
8pub type Feerate = f64;
12
13#[derive(Clone, Copy, Debug)]
14pub struct FeerateBucket {
15 pub feerate: f64,
16 pub estimated_seconds: f64,
17}
18
19impl Display for FeerateBucket {
20 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
21 write!(f, "({:.4}, {:.4}s)", self.feerate, self.estimated_seconds)
22 }
23}
24
25#[derive(Clone, Debug)]
26pub struct FeerateEstimations {
27 pub priority_bucket: FeerateBucket,
33
34 pub normal_buckets: Vec<FeerateBucket>,
40
41 pub low_buckets: Vec<FeerateBucket>,
44}
45
46impl FeerateEstimations {
47 pub fn ordered_buckets(&self) -> Vec<FeerateBucket> {
48 std::iter::once(self.priority_bucket)
49 .chain(self.normal_buckets.iter().copied())
50 .chain(self.low_buckets.iter().copied())
51 .collect()
52 }
53}
54
55impl Display for FeerateEstimations {
56 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
57 write!(f, "(fee/mass, secs) priority: {}, ", self.priority_bucket)?;
58 write!(f, "normal: {}, ", self.normal_buckets.iter().format(", "))?;
59 write!(f, "low: {}", self.low_buckets.iter().format(", "))
60 }
61}
62
63pub struct FeerateEstimatorArgs {
64 pub network_blocks_per_second: u64,
65 pub maximum_mass_per_block: u64,
66}
67
68impl FeerateEstimatorArgs {
69 pub fn new(network_blocks_per_second: u64, maximum_mass_per_block: u64) -> Self {
70 Self { network_blocks_per_second, maximum_mass_per_block }
71 }
72
73 pub fn network_mass_per_second(&self) -> u64 {
74 self.network_blocks_per_second * self.maximum_mass_per_block
75 }
76}
77
78#[derive(Debug, Clone)]
79pub struct FeerateEstimator {
80 total_weight: f64,
83
84 inclusion_interval: f64,
88}
89
90impl FeerateEstimator {
91 pub fn new(total_weight: f64, inclusion_interval: f64) -> Self {
92 assert!(total_weight >= 0.0);
93 assert!((0f64..1f64).contains(&inclusion_interval));
94 Self { total_weight, inclusion_interval }
95 }
96
97 pub(crate) fn feerate_to_time(&self, feerate: f64) -> f64 {
98 let (c1, c2) = (self.inclusion_interval, self.total_weight);
99 c1 * c2 / feerate.powi(ALPHA) + c1
100 }
101
102 fn time_to_feerate(&self, time: f64) -> f64 {
103 let (c1, c2) = (self.inclusion_interval, self.total_weight);
104 assert!(c1 < time, "{c1}, {time}");
105 ((c1 * c2 / time) / (1f64 - c1 / time)).powf(1f64 / ALPHA as f64)
106 }
107
108 #[inline]
110 fn feerate_to_time_antiderivative(&self, feerate: f64) -> f64 {
111 let (c1, c2) = (self.inclusion_interval, self.total_weight);
112 c1 * c2 / (-2f64 * feerate.powi(ALPHA - 1))
113 }
114
115 fn quantile(&self, lower: f64, upper: f64, frac: f64) -> f64 {
117 assert!((0f64..=1f64).contains(&frac));
118 assert!(0.0 < lower && lower <= upper, "{lower}, {upper}");
119 let (c1, c2) = (self.inclusion_interval, self.total_weight);
120 if c1 == 0.0 || c2 == 0.0 {
121 return lower;
123 }
124 let z1 = self.feerate_to_time_antiderivative(lower);
125 let z2 = self.feerate_to_time_antiderivative(upper);
126 let z = frac * z2 + (1f64 - frac) * z1;
129 ((c1 * c2) / (-2f64 * z)).powf(1f64 / (ALPHA - 1) as f64)
131 }
132
133 pub fn calc_estimations(&self, minimum_standard_feerate: f64) -> FeerateEstimations {
134 let min = minimum_standard_feerate;
135 let high = self.time_to_feerate(1f64).max(min);
137 let low = self.time_to_feerate(3600f64).max(self.quantile(min, high, 0.25));
139 let normal = self.time_to_feerate(60f64).max(self.quantile(low, high, 0.66));
141 let mid = self.time_to_feerate(1800f64).max(self.quantile(min, high, 0.5));
143 FeerateEstimations {
149 priority_bucket: FeerateBucket { feerate: high, estimated_seconds: self.feerate_to_time(high) },
150 normal_buckets: vec![
151 FeerateBucket { feerate: normal, estimated_seconds: self.feerate_to_time(normal) },
152 FeerateBucket { feerate: mid, estimated_seconds: self.feerate_to_time(mid) },
153 ],
154 low_buckets: vec![FeerateBucket { feerate: low, estimated_seconds: self.feerate_to_time(low) }],
155 }
156 }
157}
158
159#[derive(Clone, Debug)]
160pub struct FeeEstimateVerbose {
161 pub estimations: FeerateEstimations,
162
163 pub mempool_ready_transactions_count: u64,
164 pub mempool_ready_transactions_total_mass: u64,
165 pub network_mass_per_second: u64,
166
167 pub next_block_template_feerate_min: f64,
168 pub next_block_template_feerate_median: f64,
169 pub next_block_template_feerate_max: f64,
170}
171
172#[cfg(test)]
173mod tests {
174 use super::*;
175 use itertools::Itertools;
176
177 #[test]
178 fn test_feerate_estimations() {
179 let estimator = FeerateEstimator { total_weight: 1002283.659, inclusion_interval: 0.004f64 };
180 let estimations = estimator.calc_estimations(1.0);
181 let buckets = estimations.ordered_buckets();
182 for (i, j) in buckets.into_iter().tuple_windows() {
183 assert!(i.feerate >= j.feerate);
184 }
185 dbg!(estimations);
186 }
187
188 #[test]
189 fn test_min_feerate_estimations() {
190 let estimator = FeerateEstimator { total_weight: 0.00659, inclusion_interval: 0.004f64 };
191 let minimum_feerate = 0.755;
192 let estimations = estimator.calc_estimations(minimum_feerate);
193 println!("{estimations}");
194 let buckets = estimations.ordered_buckets();
195 assert!(buckets.last().unwrap().feerate >= minimum_feerate);
196 for (i, j) in buckets.into_iter().tuple_windows() {
197 assert!(i.feerate >= j.feerate);
198 assert!(i.estimated_seconds <= j.estimated_seconds);
199 }
200 }
201
202 #[test]
203 fn test_zero_values() {
204 let estimator = FeerateEstimator { total_weight: 0.0, inclusion_interval: 0.0 };
205 let minimum_feerate = 0.755;
206 let estimations = estimator.calc_estimations(minimum_feerate);
207 let buckets = estimations.ordered_buckets();
208 for bucket in buckets {
209 assert_eq!(minimum_feerate, bucket.feerate);
210 assert_eq!(0.0, bucket.estimated_seconds);
211 }
212
213 let estimator = FeerateEstimator { total_weight: 0.0, inclusion_interval: 0.1 };
214 let minimum_feerate = 0.755;
215 let estimations = estimator.calc_estimations(minimum_feerate);
216 let buckets = estimations.ordered_buckets();
217 for bucket in buckets {
218 assert_eq!(minimum_feerate, bucket.feerate);
219 assert_eq!(estimator.inclusion_interval, bucket.estimated_seconds);
220 }
221
222 let estimator = FeerateEstimator { total_weight: 0.1, inclusion_interval: 0.0 };
223 let minimum_feerate = 0.755;
224 let estimations = estimator.calc_estimations(minimum_feerate);
225 let buckets = estimations.ordered_buckets();
226 for bucket in buckets {
227 assert_eq!(minimum_feerate, bucket.feerate);
228 assert_eq!(0.0, bucket.estimated_seconds);
229 }
230 }
231}