1use std::{
2 collections::BTreeMap,
3 fmt,
4 future::Future,
5 pin::Pin,
6 sync::Arc,
7 task::{Context, Poll},
8};
9
10use alloy_consensus::Header;
11use alloy_eips::{
12 calc_next_block_base_fee, eip1559::BaseFeeParams, eip7691::MAX_BLOBS_PER_BLOCK_ELECTRA,
13 eip7840::BlobParams,
14};
15use alloy_primitives::B256;
16use anvil_core::eth::transaction::TypedTransaction;
17use futures::StreamExt;
18use parking_lot::{Mutex, RwLock};
19use revm::{context_interface::block::BlobExcessGasAndPrice, primitives::hardfork::SpecId};
20
21use crate::eth::{
22 backend::{info::StorageInfo, notifications::NewBlockNotifications},
23 error::BlockchainError,
24};
25
26pub const MAX_FEE_HISTORY_CACHE_SIZE: u64 = 2048u64;
28
29pub const INITIAL_BASE_FEE: u64 = 1_000_000_000;
31
32pub const INITIAL_GAS_PRICE: u128 = 1_875_000_000;
34
35pub const BASE_FEE_CHANGE_DENOMINATOR: u128 = 8;
37
38pub const MIN_SUGGESTED_PRIORITY_FEE: u128 = 1e9 as u128;
40
41pub fn default_elasticity() -> f64 {
42 1f64 / BaseFeeParams::ethereum().elasticity_multiplier as f64
43}
44
45#[derive(Clone, Debug)]
47pub struct FeeManager {
48 spec_id: SpecId,
50 base_fee: Arc<RwLock<u64>>,
54 is_min_priority_fee_enforced: bool,
56 blob_excess_gas_and_price: Arc<RwLock<BlobExcessGasAndPrice>>,
60 gas_price: Arc<RwLock<u128>>,
64 elasticity: Arc<RwLock<f64>>,
65}
66
67impl FeeManager {
68 pub fn new(
69 spec_id: SpecId,
70 base_fee: u64,
71 is_min_priority_fee_enforced: bool,
72 gas_price: u128,
73 blob_excess_gas_and_price: BlobExcessGasAndPrice,
74 ) -> Self {
75 Self {
76 spec_id,
77 base_fee: Arc::new(RwLock::new(base_fee)),
78 is_min_priority_fee_enforced,
79 gas_price: Arc::new(RwLock::new(gas_price)),
80 blob_excess_gas_and_price: Arc::new(RwLock::new(blob_excess_gas_and_price)),
81 elasticity: Arc::new(RwLock::new(default_elasticity())),
82 }
83 }
84
85 pub fn elasticity(&self) -> f64 {
86 *self.elasticity.read()
87 }
88
89 pub fn is_eip1559(&self) -> bool {
91 (self.spec_id as u8) >= (SpecId::LONDON as u8)
92 }
93
94 pub fn is_eip4844(&self) -> bool {
95 (self.spec_id as u8) >= (SpecId::CANCUN as u8)
96 }
97
98 pub fn blob_gas_price(&self) -> u128 {
100 if self.is_eip4844() {
101 self.base_fee_per_blob_gas()
102 } else {
103 0
104 }
105 }
106
107 pub fn base_fee(&self) -> u64 {
108 if self.is_eip1559() {
109 *self.base_fee.read()
110 } else {
111 0
112 }
113 }
114
115 pub fn is_min_priority_fee_enforced(&self) -> bool {
116 self.is_min_priority_fee_enforced
117 }
118
119 pub fn raw_gas_price(&self) -> u128 {
121 *self.gas_price.read()
122 }
123
124 pub fn excess_blob_gas_and_price(&self) -> Option<BlobExcessGasAndPrice> {
125 if self.is_eip4844() {
126 Some(*self.blob_excess_gas_and_price.read())
127 } else {
128 None
129 }
130 }
131
132 pub fn base_fee_per_blob_gas(&self) -> u128 {
133 if self.is_eip4844() {
134 self.blob_excess_gas_and_price.read().blob_gasprice
135 } else {
136 0
137 }
138 }
139
140 pub fn set_gas_price(&self, price: u128) {
142 let mut gas = self.gas_price.write();
143 *gas = price;
144 }
145
146 pub fn set_base_fee(&self, fee: u64) {
148 trace!(target: "backend::fees", "updated base fee {:?}", fee);
149 let mut base = self.base_fee.write();
150 *base = fee;
151 }
152
153 pub fn set_blob_excess_gas_and_price(&self, blob_excess_gas_and_price: BlobExcessGasAndPrice) {
155 trace!(target: "backend::fees", "updated blob base fee {:?}", blob_excess_gas_and_price);
156 let mut base = self.blob_excess_gas_and_price.write();
157 *base = blob_excess_gas_and_price;
158 }
159
160 pub fn get_next_block_base_fee_per_gas(
162 &self,
163 gas_used: u64,
164 gas_limit: u64,
165 last_fee_per_gas: u64,
166 ) -> u64 {
167 if self.base_fee() == 0 {
171 return 0
172 }
173 calculate_next_block_base_fee(gas_used, gas_limit, last_fee_per_gas)
174 }
175
176 pub fn get_next_block_blob_base_fee_per_gas(&self, excess_blob_gas: u128) -> u128 {
178 alloy_eips::eip4844::calc_blob_gasprice(excess_blob_gas as u64)
179 }
180
181 pub fn get_next_block_blob_excess_gas(&self, blob_gas_used: u64, blob_excess_gas: u64) -> u64 {
184 alloy_eips::eip4844::calc_excess_blob_gas(blob_gas_used, blob_excess_gas)
185 }
186}
187
188pub fn calculate_next_block_base_fee(gas_used: u64, gas_limit: u64, base_fee: u64) -> u64 {
190 calc_next_block_base_fee(gas_used, gas_limit, base_fee, BaseFeeParams::ethereum())
191}
192
193pub struct FeeHistoryService {
195 blob_params: BlobParams,
197 new_blocks: NewBlockNotifications,
199 cache: FeeHistoryCache,
201 fee_history_limit: u64,
203 storage_info: StorageInfo,
205}
206
207impl FeeHistoryService {
208 pub fn new(
209 blob_params: BlobParams,
210 new_blocks: NewBlockNotifications,
211 cache: FeeHistoryCache,
212 storage_info: StorageInfo,
213 ) -> Self {
214 Self {
215 blob_params,
216 new_blocks,
217 cache,
218 fee_history_limit: MAX_FEE_HISTORY_CACHE_SIZE,
219 storage_info,
220 }
221 }
222
223 pub fn fee_history_limit(&self) -> u64 {
225 self.fee_history_limit
226 }
227
228 pub(crate) fn insert_cache_entry_for_block(&self, hash: B256, header: &Header) {
230 let (result, block_number) = self.create_cache_entry(hash, header);
231 self.insert_cache_entry(result, block_number);
232 }
233
234 fn create_cache_entry(
236 &self,
237 hash: B256,
238 header: &Header,
239 ) -> (FeeHistoryCacheItem, Option<u64>) {
240 let reward_percentiles: Vec<f64> = {
243 let mut percentile: f64 = 0.0;
244 (0..=200)
245 .map(|_| {
246 let val = percentile;
247 percentile += 0.5;
248 val
249 })
250 .collect()
251 };
252
253 let mut block_number: Option<u64> = None;
254 let base_fee = header.base_fee_per_gas.map(|g| g as u128).unwrap_or_default();
255 let excess_blob_gas = header.excess_blob_gas.map(|g| g as u128);
256 let blob_gas_used = header.blob_gas_used.map(|g| g as u128);
257 let base_fee_per_blob_gas = header.blob_fee(self.blob_params);
258
259 let mut item = FeeHistoryCacheItem {
260 base_fee,
261 gas_used_ratio: 0f64,
262 blob_gas_used_ratio: 0f64,
263 rewards: Vec::new(),
264 excess_blob_gas,
265 base_fee_per_blob_gas,
266 blob_gas_used,
267 };
268
269 let current_block = self.storage_info.block(hash);
270 let current_receipts = self.storage_info.receipts(hash);
271
272 if let (Some(block), Some(receipts)) = (current_block, current_receipts) {
273 block_number = Some(block.header.number);
274
275 let gas_used = block.header.gas_used as f64;
276 let blob_gas_used = block.header.blob_gas_used.map(|g| g as f64);
277 item.gas_used_ratio = gas_used / block.header.gas_limit as f64;
278 item.blob_gas_used_ratio =
279 blob_gas_used.map(|g| g / MAX_BLOBS_PER_BLOCK_ELECTRA as f64).unwrap_or(0 as f64);
280
281 let mut transactions: Vec<(_, _)> = receipts
283 .iter()
284 .enumerate()
285 .map(|(i, receipt)| {
286 let gas_used = receipt.cumulative_gas_used();
287 let effective_reward = match block.transactions.get(i).map(|tx| &tx.transaction)
288 {
289 Some(TypedTransaction::Legacy(t)) => {
290 t.tx().gas_price.saturating_sub(base_fee)
291 }
292 Some(TypedTransaction::EIP2930(t)) => {
293 t.tx().gas_price.saturating_sub(base_fee)
294 }
295 Some(TypedTransaction::EIP1559(t)) => t
296 .tx()
297 .max_priority_fee_per_gas
298 .min(t.tx().max_fee_per_gas.saturating_sub(base_fee)),
299 Some(TypedTransaction::EIP4844(t)) => t
301 .tx()
302 .tx()
303 .max_priority_fee_per_gas
304 .min(t.tx().tx().max_fee_per_gas.saturating_sub(base_fee)),
305 Some(TypedTransaction::EIP7702(t)) => t
306 .tx()
307 .max_priority_fee_per_gas
308 .min(t.tx().max_fee_per_gas.saturating_sub(base_fee)),
309 Some(TypedTransaction::Deposit(_)) => 0,
310 None => 0,
311 };
312
313 (gas_used, effective_reward)
314 })
315 .collect();
316
317 transactions.sort_by(|(_, a), (_, b)| a.cmp(b));
319
320 item.rewards = reward_percentiles
322 .into_iter()
323 .filter_map(|p| {
324 let target_gas = (p * gas_used / 100f64) as u64;
325 let mut sum_gas = 0;
326 for (gas_used, effective_reward) in transactions.iter().copied() {
327 sum_gas += gas_used;
328 if target_gas <= sum_gas {
329 return Some(effective_reward)
330 }
331 }
332 None
333 })
334 .collect();
335 } else {
336 item.rewards = reward_percentiles.iter().map(|_| 0).collect();
337 }
338 (item, block_number)
339 }
340
341 fn insert_cache_entry(&self, item: FeeHistoryCacheItem, block_number: Option<u64>) {
342 if let Some(block_number) = block_number {
343 trace!(target: "fees", "insert new history item={:?} for {}", item, block_number);
344 let mut cache = self.cache.lock();
345 cache.insert(block_number, item);
346
347 let pop_next = block_number.saturating_sub(self.fee_history_limit);
349
350 let num_remove = (cache.len() as u64).saturating_sub(self.fee_history_limit);
351 for num in 0..num_remove {
352 let key = pop_next - num;
353 cache.remove(&key);
354 }
355 }
356 }
357}
358
359impl Future for FeeHistoryService {
361 type Output = ();
362
363 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
364 let pin = self.get_mut();
365
366 while let Poll::Ready(Some(notification)) = pin.new_blocks.poll_next_unpin(cx) {
367 pin.insert_cache_entry_for_block(notification.hash, notification.header.as_ref());
369 }
370
371 Poll::Pending
372 }
373}
374
375pub type FeeHistoryCache = Arc<Mutex<BTreeMap<u64, FeeHistoryCacheItem>>>;
376
377#[derive(Clone, Debug)]
379pub struct FeeHistoryCacheItem {
380 pub base_fee: u128,
381 pub gas_used_ratio: f64,
382 pub base_fee_per_blob_gas: Option<u128>,
383 pub blob_gas_used_ratio: f64,
384 pub excess_blob_gas: Option<u128>,
385 pub blob_gas_used: Option<u128>,
386 pub rewards: Vec<u128>,
387}
388
389#[derive(Clone, Default)]
390pub struct FeeDetails {
391 pub gas_price: Option<u128>,
392 pub max_fee_per_gas: Option<u128>,
393 pub max_priority_fee_per_gas: Option<u128>,
394 pub max_fee_per_blob_gas: Option<u128>,
395}
396
397impl FeeDetails {
398 pub fn zero() -> Self {
400 Self {
401 gas_price: Some(0),
402 max_fee_per_gas: Some(0),
403 max_priority_fee_per_gas: Some(0),
404 max_fee_per_blob_gas: None,
405 }
406 }
407
408 pub fn or_zero_fees(self) -> Self {
410 let Self { gas_price, max_fee_per_gas, max_priority_fee_per_gas, max_fee_per_blob_gas } =
411 self;
412
413 let no_fees = gas_price.is_none() && max_fee_per_gas.is_none();
414 let gas_price = if no_fees { Some(0) } else { gas_price };
415 let max_fee_per_gas = if no_fees { Some(0) } else { max_fee_per_gas };
416 let max_fee_per_blob_gas = if no_fees { None } else { max_fee_per_blob_gas };
417
418 Self { gas_price, max_fee_per_gas, max_priority_fee_per_gas, max_fee_per_blob_gas }
419 }
420
421 pub fn split(self) -> (Option<u128>, Option<u128>, Option<u128>, Option<u128>) {
423 let Self { gas_price, max_fee_per_gas, max_priority_fee_per_gas, max_fee_per_blob_gas } =
424 self;
425 (gas_price, max_fee_per_gas, max_priority_fee_per_gas, max_fee_per_blob_gas)
426 }
427
428 pub fn new(
430 request_gas_price: Option<u128>,
431 request_max_fee: Option<u128>,
432 request_priority: Option<u128>,
433 max_fee_per_blob_gas: Option<u128>,
434 ) -> Result<Self, BlockchainError> {
435 match (request_gas_price, request_max_fee, request_priority, max_fee_per_blob_gas) {
436 (gas_price, None, None, None) => {
437 Ok(Self {
439 gas_price,
440 max_fee_per_gas: gas_price,
441 max_priority_fee_per_gas: gas_price,
442 max_fee_per_blob_gas: None,
443 })
444 }
445 (_, max_fee, max_priority, None) => {
446 if let Some(max_priority) = max_priority {
449 let max_fee = max_fee.unwrap_or_default();
450 if max_priority > max_fee {
451 return Err(BlockchainError::InvalidFeeInput)
452 }
453 }
454 Ok(Self {
455 gas_price: max_fee,
456 max_fee_per_gas: max_fee,
457 max_priority_fee_per_gas: max_priority,
458 max_fee_per_blob_gas: None,
459 })
460 }
461 (_, max_fee, max_priority, max_fee_per_blob_gas) => {
462 if let Some(max_priority) = max_priority {
465 let max_fee = max_fee.unwrap_or_default();
466 if max_priority > max_fee {
467 return Err(BlockchainError::InvalidFeeInput)
468 }
469 }
470 Ok(Self {
471 gas_price: max_fee,
472 max_fee_per_gas: max_fee,
473 max_priority_fee_per_gas: max_priority,
474 max_fee_per_blob_gas,
475 })
476 }
477 }
478 }
479}
480
481impl fmt::Debug for FeeDetails {
482 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
483 write!(fmt, "Fees {{ ")?;
484 write!(fmt, "gas_price: {:?}, ", self.gas_price)?;
485 write!(fmt, "max_fee_per_gas: {:?}, ", self.max_fee_per_gas)?;
486 write!(fmt, "max_priority_fee_per_gas: {:?}, ", self.max_priority_fee_per_gas)?;
487 write!(fmt, "}}")?;
488 Ok(())
489 }
490}