1use crate::mempool::model::frontier::selectors::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 #[allow(clippy::arithmetic_side_effects, reason = "Both values are relatively small.")]
74 pub fn network_mass_per_second(&self) -> u64 {
75 self.network_blocks_per_second * self.maximum_mass_per_block
76 }
77}
78
79#[derive(Debug, Clone)]
80pub struct FeerateEstimator {
81 total_weight: f64,
84
85 inclusion_interval: f64,
89
90 target_time_per_block_seconds: f64,
91}
92
93impl FeerateEstimator {
94 pub fn new(total_weight: f64, inclusion_interval: f64, target_time_per_block_seconds: f64) -> Self {
95 assert!(total_weight >= 0.0);
96 assert!((0f64..1f64).contains(&inclusion_interval));
97 Self { total_weight, inclusion_interval, target_time_per_block_seconds }
98 }
99
100 pub(crate) fn feerate_to_time(&self, feerate: f64) -> f64 {
101 let (c1, c2) = (self.inclusion_interval, self.total_weight);
102 c1 * c2 / feerate.powi(ALPHA) + c1
103 }
104
105 fn time_to_feerate(&self, time: f64) -> f64 {
106 let (c1, c2) = (self.inclusion_interval, self.total_weight);
107 assert!(c1 < time, "{c1}, {time}");
108 ((c1 * c2 / time) / (1f64 - c1 / time)).powf(1f64 / ALPHA as f64)
109 }
110
111 #[inline]
113 fn feerate_to_time_antiderivative(&self, feerate: f64) -> f64 {
114 let (c1, c2) = (self.inclusion_interval, self.total_weight);
115 c1 * c2 / (-2f64 * feerate.powi(ALPHA - 1))
116 }
117
118 fn quantile(&self, lower: f64, upper: f64, frac: f64) -> f64 {
120 assert!((0f64..=1f64).contains(&frac));
121 if lower == upper {
122 return lower;
124 }
125 assert!(0.0 < lower && lower <= upper, "{lower}, {upper}");
126 let (c1, c2) = (self.inclusion_interval, self.total_weight);
127 if c1 == 0.0 || c2 == 0.0 {
128 return lower;
130 }
131 let z1 = self.feerate_to_time_antiderivative(lower);
132 let z2 = self.feerate_to_time_antiderivative(upper);
133 let z = frac * z2 + (1f64 - frac) * z1;
136 ((c1 * c2) / (-2f64 * z)).powf(1f64 / (ALPHA - 1) as f64)
138 }
139
140 pub fn calc_estimations(&self, minimum_standard_feerate: f64) -> FeerateEstimations {
141 let min = minimum_standard_feerate;
142 let high = self.time_to_feerate(self.target_time_per_block_seconds).max(min);
144 let low = self.time_to_feerate(3600f64).max(self.quantile(min, high, 0.25));
146 let normal = self.time_to_feerate(60f64).max(self.quantile(low, high, 0.66));
148 let mid = self.time_to_feerate(1800f64).max(self.quantile(min, high, 0.5));
150 FeerateEstimations {
156 priority_bucket: FeerateBucket { feerate: high, estimated_seconds: self.feerate_to_time(high) },
157 normal_buckets: vec![
158 FeerateBucket { feerate: normal, estimated_seconds: self.feerate_to_time(normal) },
159 FeerateBucket { feerate: mid, estimated_seconds: self.feerate_to_time(mid) },
160 ],
161 low_buckets: vec![FeerateBucket { feerate: low, estimated_seconds: self.feerate_to_time(low) }],
162 }
163 }
164}
165
166#[derive(Clone, Debug)]
167pub struct FeeEstimateVerbose {
168 pub estimations: FeerateEstimations,
169
170 pub mempool_ready_transactions_count: u64,
171 pub mempool_ready_transactions_total_mass: u64,
172 pub network_mass_per_second: u64,
173
174 pub next_block_template_feerate_min: f64,
175 pub next_block_template_feerate_median: f64,
176 pub next_block_template_feerate_max: f64,
177}
178
179#[cfg(test)]
180mod tests {
181 use super::*;
182 use itertools::Itertools;
183
184 #[test]
185 fn test_feerate_estimations() {
186 let estimator =
187 FeerateEstimator { total_weight: 1002283.659, inclusion_interval: 0.004f64, target_time_per_block_seconds: 1.0 };
188 let estimations = estimator.calc_estimations(1.0);
189 let buckets = estimations.ordered_buckets();
190 for (i, j) in buckets.into_iter().tuple_windows() {
191 assert!(i.feerate >= j.feerate);
192 }
193 dbg!(estimations);
194 }
195
196 #[test]
197 fn test_min_feerate_estimations() {
198 let estimator = FeerateEstimator { total_weight: 0.00659, inclusion_interval: 0.004f64, target_time_per_block_seconds: 1.0 };
199 let minimum_feerate = 0.755;
200 let estimations = estimator.calc_estimations(minimum_feerate);
201 println!("{estimations}");
202 let buckets = estimations.ordered_buckets();
203 assert!(buckets.last().unwrap().feerate >= minimum_feerate);
204 for (i, j) in buckets.into_iter().tuple_windows() {
205 assert!(i.feerate >= j.feerate);
206 assert!(i.estimated_seconds <= j.estimated_seconds);
207 }
208 }
209
210 #[test]
211 fn test_zero_values() {
212 let estimator = FeerateEstimator { total_weight: 0.0, inclusion_interval: 0.0, target_time_per_block_seconds: 1.0 };
213 let minimum_feerate = 0.755;
214 let estimations = estimator.calc_estimations(minimum_feerate);
215 let buckets = estimations.ordered_buckets();
216 for bucket in buckets {
217 assert_eq!(minimum_feerate, bucket.feerate);
218 assert_eq!(0.0, bucket.estimated_seconds);
219 }
220
221 let estimator = FeerateEstimator { total_weight: 0.0, inclusion_interval: 0.1, target_time_per_block_seconds: 1.0 };
222 let minimum_feerate = 0.755;
223 let estimations = estimator.calc_estimations(minimum_feerate);
224 let buckets = estimations.ordered_buckets();
225 for bucket in buckets {
226 assert_eq!(minimum_feerate, bucket.feerate);
227 assert_eq!(estimator.inclusion_interval, bucket.estimated_seconds);
228 }
229
230 let estimator = FeerateEstimator { total_weight: 0.1, inclusion_interval: 0.0, target_time_per_block_seconds: 1.0 };
231 let minimum_feerate = 0.755;
232 let estimations = estimator.calc_estimations(minimum_feerate);
233 let buckets = estimations.ordered_buckets();
234 for bucket in buckets {
235 assert_eq!(minimum_feerate, bucket.feerate);
236 assert_eq!(0.0, bucket.estimated_seconds);
237 }
238 }
239}