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 base_fee: Arc<RwLock<u64>>,
53 is_min_priority_fee_enforced: bool,
55 blob_excess_gas_and_price: Arc<RwLock<BlobExcessGasAndPrice>>,
59 gas_price: Arc<RwLock<u128>>,
63 elasticity: Arc<RwLock<f64>>,
64}
65
66impl FeeManager {
67 pub fn new(
68 spec_id: SpecId,
69 base_fee: u64,
70 is_min_priority_fee_enforced: bool,
71 gas_price: u128,
72 blob_excess_gas_and_price: BlobExcessGasAndPrice,
73 ) -> Self {
74 Self {
75 spec_id,
76 base_fee: Arc::new(RwLock::new(base_fee)),
77 is_min_priority_fee_enforced,
78 gas_price: Arc::new(RwLock::new(gas_price)),
79 blob_excess_gas_and_price: Arc::new(RwLock::new(blob_excess_gas_and_price)),
80 elasticity: Arc::new(RwLock::new(default_elasticity())),
81 }
82 }
83
84 pub fn elasticity(&self) -> f64 {
85 *self.elasticity.read()
86 }
87
88 pub fn is_eip1559(&self) -> bool {
90 (self.spec_id as u8) >= (SpecId::LONDON as u8)
91 }
92
93 pub fn is_eip4844(&self) -> bool {
94 (self.spec_id as u8) >= (SpecId::CANCUN as u8)
95 }
96
97 pub fn blob_gas_price(&self) -> u128 {
99 if self.is_eip4844() { self.base_fee_per_blob_gas() } else { 0 }
100 }
101
102 pub fn base_fee(&self) -> u64 {
103 if self.is_eip1559() { *self.base_fee.read() } else { 0 }
104 }
105
106 pub fn is_min_priority_fee_enforced(&self) -> bool {
107 self.is_min_priority_fee_enforced
108 }
109
110 pub fn raw_gas_price(&self) -> u128 {
112 *self.gas_price.read()
113 }
114
115 pub fn excess_blob_gas_and_price(&self) -> Option<BlobExcessGasAndPrice> {
116 if self.is_eip4844() { Some(*self.blob_excess_gas_and_price.read()) } else { None }
117 }
118
119 pub fn base_fee_per_blob_gas(&self) -> u128 {
120 if self.is_eip4844() { self.blob_excess_gas_and_price.read().blob_gasprice } else { 0 }
121 }
122
123 pub fn set_gas_price(&self, price: u128) {
125 let mut gas = self.gas_price.write();
126 *gas = price;
127 }
128
129 pub fn set_base_fee(&self, fee: u64) {
131 trace!(target: "backend::fees", "updated base fee {:?}", fee);
132 let mut base = self.base_fee.write();
133 *base = fee;
134 }
135
136 pub fn set_blob_excess_gas_and_price(&self, blob_excess_gas_and_price: BlobExcessGasAndPrice) {
138 trace!(target: "backend::fees", "updated blob base fee {:?}", blob_excess_gas_and_price);
139 let mut base = self.blob_excess_gas_and_price.write();
140 *base = blob_excess_gas_and_price;
141 }
142
143 pub fn get_next_block_base_fee_per_gas(
145 &self,
146 gas_used: u64,
147 gas_limit: u64,
148 last_fee_per_gas: u64,
149 ) -> u64 {
150 if self.base_fee() == 0 {
154 return 0;
155 }
156 calculate_next_block_base_fee(gas_used, gas_limit, last_fee_per_gas)
157 }
158
159 pub fn get_next_block_blob_base_fee_per_gas(&self) -> u128 {
161 alloy_eips::eip4844::calc_blob_gasprice(
162 self.blob_excess_gas_and_price.read().excess_blob_gas,
163 )
164 }
165
166 pub fn get_next_block_blob_excess_gas(&self, blob_gas_used: u64, blob_excess_gas: u64) -> u64 {
169 alloy_eips::eip4844::calc_excess_blob_gas(blob_gas_used, blob_excess_gas)
170 }
171}
172
173pub fn calculate_next_block_base_fee(gas_used: u64, gas_limit: u64, base_fee: u64) -> u64 {
175 calc_next_block_base_fee(gas_used, gas_limit, base_fee, BaseFeeParams::ethereum())
176}
177
178pub struct FeeHistoryService {
180 blob_params: BlobParams,
182 new_blocks: NewBlockNotifications,
184 cache: FeeHistoryCache,
186 fee_history_limit: u64,
188 storage_info: StorageInfo,
190}
191
192impl FeeHistoryService {
193 pub fn new(
194 blob_params: BlobParams,
195 new_blocks: NewBlockNotifications,
196 cache: FeeHistoryCache,
197 storage_info: StorageInfo,
198 ) -> Self {
199 Self {
200 blob_params,
201 new_blocks,
202 cache,
203 fee_history_limit: MAX_FEE_HISTORY_CACHE_SIZE,
204 storage_info,
205 }
206 }
207
208 pub fn fee_history_limit(&self) -> u64 {
210 self.fee_history_limit
211 }
212
213 pub(crate) fn insert_cache_entry_for_block(&self, hash: B256, header: &Header) {
215 let (result, block_number) = self.create_cache_entry(hash, header);
216 self.insert_cache_entry(result, block_number);
217 }
218
219 fn create_cache_entry(
221 &self,
222 hash: B256,
223 header: &Header,
224 ) -> (FeeHistoryCacheItem, Option<u64>) {
225 let reward_percentiles: Vec<f64> = {
228 let mut percentile: f64 = 0.0;
229 (0..=200)
230 .map(|_| {
231 let val = percentile;
232 percentile += 0.5;
233 val
234 })
235 .collect()
236 };
237
238 let mut block_number: Option<u64> = None;
239 let base_fee = header.base_fee_per_gas.map(|g| g as u128).unwrap_or_default();
240 let excess_blob_gas = header.excess_blob_gas.map(|g| g as u128);
241 let blob_gas_used = header.blob_gas_used.map(|g| g as u128);
242 let base_fee_per_blob_gas = header.blob_fee(self.blob_params);
243
244 let mut item = FeeHistoryCacheItem {
245 base_fee,
246 gas_used_ratio: 0f64,
247 blob_gas_used_ratio: 0f64,
248 rewards: Vec::new(),
249 excess_blob_gas,
250 base_fee_per_blob_gas,
251 blob_gas_used,
252 };
253
254 let current_block = self.storage_info.block(hash);
255 let current_receipts = self.storage_info.receipts(hash);
256
257 if let (Some(block), Some(receipts)) = (current_block, current_receipts) {
258 block_number = Some(block.header.number);
259
260 let gas_used = block.header.gas_used as f64;
261 let blob_gas_used = block.header.blob_gas_used.map(|g| g as f64);
262 item.gas_used_ratio = gas_used / block.header.gas_limit as f64;
263 item.blob_gas_used_ratio =
264 blob_gas_used.map(|g| g / MAX_BLOBS_PER_BLOCK_ELECTRA as f64).unwrap_or(0 as f64);
265
266 let mut transactions: Vec<(_, _)> = receipts
268 .iter()
269 .enumerate()
270 .map(|(i, receipt)| {
271 let gas_used = receipt.cumulative_gas_used();
272 let effective_reward = match block.transactions.get(i).map(|tx| &tx.transaction)
273 {
274 Some(TypedTransaction::Legacy(t)) => {
275 t.tx().gas_price.saturating_sub(base_fee)
276 }
277 Some(TypedTransaction::EIP2930(t)) => {
278 t.tx().gas_price.saturating_sub(base_fee)
279 }
280 Some(TypedTransaction::EIP1559(t)) => t
281 .tx()
282 .max_priority_fee_per_gas
283 .min(t.tx().max_fee_per_gas.saturating_sub(base_fee)),
284 Some(TypedTransaction::EIP4844(t)) => t
286 .tx()
287 .tx()
288 .max_priority_fee_per_gas
289 .min(t.tx().tx().max_fee_per_gas.saturating_sub(base_fee)),
290 Some(TypedTransaction::EIP7702(t)) => t
291 .tx()
292 .max_priority_fee_per_gas
293 .min(t.tx().max_fee_per_gas.saturating_sub(base_fee)),
294 Some(TypedTransaction::Deposit(_)) => 0,
295 None => 0,
296 };
297
298 (gas_used, effective_reward)
299 })
300 .collect();
301
302 transactions.sort_by(|(_, a), (_, b)| a.cmp(b));
304
305 item.rewards = reward_percentiles
307 .into_iter()
308 .filter_map(|p| {
309 let target_gas = (p * gas_used / 100f64) as u64;
310 let mut sum_gas = 0;
311 for (gas_used, effective_reward) in transactions.iter().copied() {
312 sum_gas += gas_used;
313 if target_gas <= sum_gas {
314 return Some(effective_reward);
315 }
316 }
317 None
318 })
319 .collect();
320 } else {
321 item.rewards = reward_percentiles.iter().map(|_| 0).collect();
322 }
323 (item, block_number)
324 }
325
326 fn insert_cache_entry(&self, item: FeeHistoryCacheItem, block_number: Option<u64>) {
327 if let Some(block_number) = block_number {
328 trace!(target: "fees", "insert new history item={:?} for {}", item, block_number);
329 let mut cache = self.cache.lock();
330 cache.insert(block_number, item);
331
332 let pop_next = block_number.saturating_sub(self.fee_history_limit);
334
335 let num_remove = (cache.len() as u64).saturating_sub(self.fee_history_limit);
336 for num in 0..num_remove {
337 let key = pop_next - num;
338 cache.remove(&key);
339 }
340 }
341 }
342}
343
344impl Future for FeeHistoryService {
346 type Output = ();
347
348 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
349 let pin = self.get_mut();
350
351 while let Poll::Ready(Some(notification)) = pin.new_blocks.poll_next_unpin(cx) {
352 pin.insert_cache_entry_for_block(notification.hash, notification.header.as_ref());
354 }
355
356 Poll::Pending
357 }
358}
359
360pub type FeeHistoryCache = Arc<Mutex<BTreeMap<u64, FeeHistoryCacheItem>>>;
361
362#[derive(Clone, Debug)]
364pub struct FeeHistoryCacheItem {
365 pub base_fee: u128,
366 pub gas_used_ratio: f64,
367 pub base_fee_per_blob_gas: Option<u128>,
368 pub blob_gas_used_ratio: f64,
369 pub excess_blob_gas: Option<u128>,
370 pub blob_gas_used: Option<u128>,
371 pub rewards: Vec<u128>,
372}
373
374#[derive(Clone, Default)]
375pub struct FeeDetails {
376 pub gas_price: Option<u128>,
377 pub max_fee_per_gas: Option<u128>,
378 pub max_priority_fee_per_gas: Option<u128>,
379 pub max_fee_per_blob_gas: Option<u128>,
380}
381
382impl FeeDetails {
383 pub fn zero() -> Self {
385 Self {
386 gas_price: Some(0),
387 max_fee_per_gas: Some(0),
388 max_priority_fee_per_gas: Some(0),
389 max_fee_per_blob_gas: None,
390 }
391 }
392
393 pub fn or_zero_fees(self) -> Self {
395 let Self { gas_price, max_fee_per_gas, max_priority_fee_per_gas, max_fee_per_blob_gas } =
396 self;
397
398 let no_fees = gas_price.is_none() && max_fee_per_gas.is_none();
399 let gas_price = if no_fees { Some(0) } else { gas_price };
400 let max_fee_per_gas = if no_fees { Some(0) } else { max_fee_per_gas };
401 let max_fee_per_blob_gas = if no_fees { None } else { max_fee_per_blob_gas };
402
403 Self { gas_price, max_fee_per_gas, max_priority_fee_per_gas, max_fee_per_blob_gas }
404 }
405
406 pub fn split(self) -> (Option<u128>, Option<u128>, Option<u128>, Option<u128>) {
408 let Self { gas_price, max_fee_per_gas, max_priority_fee_per_gas, max_fee_per_blob_gas } =
409 self;
410 (gas_price, max_fee_per_gas, max_priority_fee_per_gas, max_fee_per_blob_gas)
411 }
412
413 pub fn new(
415 request_gas_price: Option<u128>,
416 request_max_fee: Option<u128>,
417 request_priority: Option<u128>,
418 max_fee_per_blob_gas: Option<u128>,
419 ) -> Result<Self, BlockchainError> {
420 match (request_gas_price, request_max_fee, request_priority, max_fee_per_blob_gas) {
421 (gas_price, None, None, None) => {
422 Ok(Self {
424 gas_price,
425 max_fee_per_gas: gas_price,
426 max_priority_fee_per_gas: gas_price,
427 max_fee_per_blob_gas: None,
428 })
429 }
430 (_, max_fee, max_priority, None) => {
431 if let Some(max_priority) = max_priority {
434 let max_fee = max_fee.unwrap_or_default();
435 if max_priority > max_fee {
436 return Err(BlockchainError::InvalidFeeInput);
437 }
438 }
439 Ok(Self {
440 gas_price: max_fee,
441 max_fee_per_gas: max_fee,
442 max_priority_fee_per_gas: max_priority,
443 max_fee_per_blob_gas: None,
444 })
445 }
446 (_, max_fee, max_priority, max_fee_per_blob_gas) => {
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,
460 })
461 }
462 }
463 }
464}
465
466impl fmt::Debug for FeeDetails {
467 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
468 write!(fmt, "Fees {{ ")?;
469 write!(fmt, "gas_price: {:?}, ", self.gas_price)?;
470 write!(fmt, "max_fee_per_gas: {:?}, ", self.max_fee_per_gas)?;
471 write!(fmt, "max_priority_fee_per_gas: {:?}, ", self.max_priority_fee_per_gas)?;
472 write!(fmt, "}}")?;
473 Ok(())
474 }
475}