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