reth_rpc_eth_api/helpers/
fee.rs1use super::LoadBlock;
4use crate::FromEthApiError;
5use alloy_consensus::BlockHeader;
6use alloy_eips::eip7840::BlobParams;
7use alloy_primitives::U256;
8use alloy_rpc_types_eth::{BlockNumberOrTag, FeeHistory};
9use futures::{Future, StreamExt};
10use reth_chainspec::{ChainSpecProvider, EthChainSpec};
11use reth_primitives_traits::BlockBody;
12use reth_rpc_eth_types::{
13 fee_history::calculate_reward_percentiles_for_block, utils::checked_blob_gas_used_ratio,
14 EthApiError, FeeHistoryCache, FeeHistoryEntry, GasPriceOracle, RpcInvalidTransactionError,
15};
16use reth_storage_api::{
17 BlockIdReader, BlockNumReader, BlockReaderIdExt, HeaderProvider, ProviderHeader,
18};
19use tracing::debug;
20
21pub trait EthFees:
24 LoadFee<
25 Provider: ChainSpecProvider<ChainSpec: EthChainSpec<Header = ProviderHeader<Self::Provider>>>,
26>
27{
28 fn gas_price(&self) -> impl Future<Output = Result<U256, Self::Error>> + Send
32 where
33 Self: LoadBlock,
34 {
35 LoadFee::gas_price(self)
36 }
37
38 fn blob_base_fee(&self) -> impl Future<Output = Result<U256, Self::Error>> + Send
40 where
41 Self: LoadBlock,
42 {
43 LoadFee::blob_base_fee(self)
44 }
45
46 fn base_fee(&self) -> impl Future<Output = Result<Option<U256>, Self::Error>> + Send
48 where
49 Self: LoadBlock,
50 {
51 LoadFee::base_fee(self)
52 }
53
54 fn suggested_priority_fee(&self) -> impl Future<Output = Result<U256, Self::Error>> + Send
56 where
57 Self: 'static,
58 {
59 LoadFee::suggested_priority_fee(self)
60 }
61
62 fn fee_history(
67 &self,
68 mut block_count: u64,
69 mut newest_block: BlockNumberOrTag,
70 reward_percentiles: Option<Vec<f64>>,
71 ) -> impl Future<Output = Result<FeeHistory, Self::Error>> + Send {
72 async move {
73 if block_count == 0 {
74 return Ok(FeeHistory::default())
75 }
76
77 if reward_percentiles.as_ref().map(|perc| perc.len() as u64) >
79 Some(self.gas_oracle().config().max_reward_percentile_count)
80 {
81 return Err(EthApiError::InvalidRewardPercentiles.into())
82 }
83
84 if let Some(percentiles) = &reward_percentiles &&
89 (percentiles.iter().any(|p| *p < 0.0 || *p > 100.0) ||
90 percentiles.windows(2).any(|w| w[0] > w[1]))
91 {
92 return Err(EthApiError::InvalidRewardPercentiles.into())
93 }
94
95 let max_fee_history = if reward_percentiles.is_none() {
97 self.gas_oracle().config().max_header_history
98 } else {
99 self.gas_oracle().config().max_block_history
100 };
101
102 if block_count > max_fee_history {
103 debug!(
104 requested = block_count,
105 truncated = max_fee_history,
106 "Sanitizing fee history block count"
107 );
108 block_count = max_fee_history
109 }
110
111 if newest_block.is_pending() {
112 newest_block = BlockNumberOrTag::Latest;
114 }
115
116 if let BlockNumberOrTag::Number(requested) = newest_block {
118 let latest_block =
119 self.provider().best_block_number().map_err(Self::Error::from_eth_err)?;
120 if requested > latest_block {
121 return Err(
122 EthApiError::RequestBeyondHead { requested, head: latest_block }.into()
123 )
124 }
125 }
126
127 let end_block = self
128 .provider()
129 .block_number_for_id(newest_block.into())
130 .map_err(Self::Error::from_eth_err)?
131 .ok_or(EthApiError::HeaderNotFound(newest_block.into()))?;
132
133 let end_block_plus = end_block + 1;
135 if end_block_plus < block_count {
137 block_count = end_block_plus;
138 }
139
140 let start_block = end_block_plus - block_count;
146
147 let mut base_fee_per_gas: Vec<u128> = Vec::with_capacity(block_count as usize + 1);
150 let mut gas_used_ratio: Vec<f64> = Vec::with_capacity(block_count as usize);
151
152 let mut base_fee_per_blob_gas: Vec<u128> = Vec::with_capacity(block_count as usize + 1);
153 let mut blob_gas_used_ratio: Vec<f64> = Vec::with_capacity(block_count as usize);
154
155 let mut rewards: Vec<Vec<u128>> = if reward_percentiles.is_some() {
156 Vec::with_capacity(block_count as usize)
157 } else {
158 Vec::new()
159 };
160
161 let fee_entries = self.fee_history_cache().get_history(start_block, end_block).await;
163
164 if let Some(fee_entries) = fee_entries {
165 if fee_entries.len() != block_count as usize {
166 return Err(EthApiError::InvalidBlockRange.into())
167 }
168
169 for entry in &fee_entries {
170 base_fee_per_gas
171 .push(entry.header.base_fee_per_gas().unwrap_or_default() as u128);
172 gas_used_ratio.push(entry.gas_used_ratio);
173 base_fee_per_blob_gas.push(entry.base_fee_per_blob_gas.unwrap_or_default());
174 blob_gas_used_ratio.push(entry.blob_gas_used_ratio);
175
176 if let Some(percentiles) = &reward_percentiles {
177 let mut block_rewards = Vec::with_capacity(percentiles.len());
178 for &percentile in percentiles {
179 block_rewards.push(self.approximate_percentile(entry, percentile));
180 }
181 rewards.push(block_rewards);
182 }
183 }
184 let last_entry = fee_entries.last().expect("is not empty");
185
186 base_fee_per_gas.push(
189 self.provider()
190 .chain_spec()
191 .next_block_base_fee(&last_entry.header, last_entry.header.timestamp())
192 .unwrap_or_default() as u128,
193 );
194
195 base_fee_per_blob_gas.push(last_entry.next_block_blob_fee().unwrap_or_default());
196 } else {
197 let headers = self.provider()
199 .sealed_headers_range(start_block..=end_block)
200 .map_err(Self::Error::from_eth_err)?;
201 if headers.len() != block_count as usize {
202 return Err(EthApiError::InvalidBlockRange.into())
203 }
204
205 let chain_spec = self.provider().chain_spec();
206 for header in &headers {
207 base_fee_per_gas.push(header.base_fee_per_gas().unwrap_or_default() as u128);
208 gas_used_ratio.push(header.gas_used() as f64 / header.gas_limit() as f64);
209
210 let blob_params = chain_spec
211 .blob_params_at_timestamp(header.timestamp())
212 .unwrap_or_else(BlobParams::cancun);
213
214 base_fee_per_blob_gas.push(header.blob_fee(blob_params).unwrap_or_default());
215 blob_gas_used_ratio.push(checked_blob_gas_used_ratio(
216 header.blob_gas_used().unwrap_or_default(),
217 blob_params.max_blob_gas_per_block(),
218 ));
219 }
220
221 if let Some(percentiles) = reward_percentiles.as_ref().filter(|p| !p.is_empty()) {
222 let hashes: Vec<_> = headers.iter().map(|h| h.hash()).collect();
223 let mut stream =
224 futures::stream::iter(hashes)
225 .map(|hash| self.cache().get_block_and_receipts(hash))
226 .buffered(4);
227 let mut header_idx = 0;
228 while let Some(result) = stream.next().await {
229 let header = &headers[header_idx];
230 header_idx += 1;
231 let (block, receipts) = result
232 .map_err(Self::Error::from_eth_err)?
233 .ok_or(EthApiError::InvalidBlockRange)?;
234 rewards.push(
235 calculate_reward_percentiles_for_block(
236 percentiles,
237 header.gas_used(),
238 header.base_fee_per_gas().unwrap_or_default(),
239 block.body().transactions(),
240 &receipts,
241 )
242 .unwrap_or_default(),
243 );
244 }
245 }
246
247 let last_header = headers.last().expect("is present");
253 base_fee_per_gas.push(
254 chain_spec
255 .next_block_base_fee(last_header.header(), last_header.timestamp())
256 .unwrap_or_default() as u128,
257 );
258 base_fee_per_blob_gas.push(
261 last_header
262 .maybe_next_block_blob_fee(
263 chain_spec.blob_params_at_timestamp(last_header.timestamp())
264 ).unwrap_or_default()
265 );
266 };
267
268 Ok(FeeHistory {
269 base_fee_per_gas,
270 gas_used_ratio,
271 base_fee_per_blob_gas,
272 blob_gas_used_ratio,
273 oldest_block: start_block,
274 reward: reward_percentiles.map(|_| rewards),
275 })
276 }
277 }
278
279 fn approximate_percentile(
282 &self,
283 entry: &FeeHistoryEntry<ProviderHeader<Self::Provider>>,
284 requested_percentile: f64,
285 ) -> u128 {
286 let resolution = self.fee_history_cache().resolution();
287 let rounded_percentile =
288 (requested_percentile * resolution as f64).round() / resolution as f64;
289 let clamped_percentile = rounded_percentile.clamp(0.0, 100.0);
290
291 let index = (clamped_percentile / (1.0 / resolution as f64)).round() as usize;
293 entry.rewards.get(index).copied().unwrap_or_default()
295 }
296}
297
298pub trait LoadFee: LoadBlock
302where
303 Self::Provider: BlockReaderIdExt,
304{
305 fn gas_oracle(&self) -> &GasPriceOracle<Self::Provider>;
309
310 fn fee_history_cache(&self) -> &FeeHistoryCache<ProviderHeader<Self::Provider>>;
314
315 fn legacy_gas_price(
318 &self,
319 gas_price: Option<U256>,
320 ) -> impl Future<Output = Result<U256, Self::Error>> + Send {
321 async move {
322 match gas_price {
323 Some(gas_price) => Ok(gas_price),
324 None => {
325 self.gas_price().await
327 }
328 }
329 }
330 }
331
332 fn eip1559_fees(
337 &self,
338 base_fee: Option<U256>,
339 max_priority_fee_per_gas: Option<U256>,
340 ) -> impl Future<Output = Result<(U256, U256), Self::Error>> + Send {
341 async move {
342 let base_fee = match base_fee {
343 Some(base_fee) => base_fee,
344 None => {
345 let latest = self
347 .provider()
348 .latest_header()
349 .map_err(Self::Error::from_eth_err)?
350 .ok_or(EthApiError::HeaderNotFound(BlockNumberOrTag::Latest.into()))?;
351 let pending_base_fee = self
352 .provider()
353 .chain_spec()
354 .next_block_base_fee(&latest, latest.timestamp())
355 .ok_or(EthApiError::InvalidTransaction(
356 RpcInvalidTransactionError::TxTypeNotSupported,
357 ))?;
358 U256::from(pending_base_fee)
359 }
360 };
361
362 let max_priority_fee_per_gas = match max_priority_fee_per_gas {
363 Some(max_priority_fee_per_gas) => max_priority_fee_per_gas,
364 None => self.suggested_priority_fee().await?,
365 };
366 Ok((base_fee, max_priority_fee_per_gas))
367 }
368 }
369
370 fn eip4844_blob_fee(
372 &self,
373 blob_fee: Option<U256>,
374 ) -> impl Future<Output = Result<U256, Self::Error>> + Send {
375 async move {
376 match blob_fee {
377 Some(blob_fee) => Ok(blob_fee),
378 None => self.blob_base_fee().await,
379 }
380 }
381 }
382
383 fn gas_price(&self) -> impl Future<Output = Result<U256, Self::Error>> + Send {
387 async move {
388 let header = self.provider().latest_header().map_err(Self::Error::from_eth_err)?;
389 let suggested_tip = self.suggested_priority_fee().await?;
390 let base_fee = header.and_then(|h| h.base_fee_per_gas()).unwrap_or_default();
391 Ok(suggested_tip + U256::from(base_fee))
392 }
393 }
394
395 fn blob_base_fee(&self) -> impl Future<Output = Result<U256, Self::Error>> + Send {
397 async move {
398 self.provider()
399 .latest_header()
400 .map_err(Self::Error::from_eth_err)?
401 .and_then(|h| {
402 h.maybe_next_block_blob_fee(
403 self.provider().chain_spec().blob_params_at_timestamp(h.timestamp()),
404 )
405 })
406 .ok_or(EthApiError::ExcessBlobGasNotSet.into())
407 .map(U256::from)
408 }
409 }
410
411 fn base_fee(&self) -> impl Future<Output = Result<Option<U256>, Self::Error>> + Send {
413 async move {
414 let header = self
415 .provider()
416 .latest_header()
417 .map_err(Self::Error::from_eth_err)?
418 .ok_or(EthApiError::HeaderNotFound(BlockNumberOrTag::Latest.into()))?;
419 Ok(self
420 .provider()
421 .chain_spec()
422 .next_block_base_fee(&header, header.timestamp())
423 .map(U256::from))
424 }
425 }
426
427 fn suggested_priority_fee(&self) -> impl Future<Output = Result<U256, Self::Error>> + Send
429 where
430 Self: 'static,
431 {
432 async move { self.gas_oracle().suggest_tip_cap().await.map_err(Self::Error::from_eth_err) }
433 }
434}