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;
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 suggested_priority_fee(&self) -> impl Future<Output = Result<U256, Self::Error>> + Send
48 where
49 Self: 'static,
50 {
51 LoadFee::suggested_priority_fee(self)
52 }
53
54 fn fee_history(
59 &self,
60 mut block_count: u64,
61 mut newest_block: BlockNumberOrTag,
62 reward_percentiles: Option<Vec<f64>>,
63 ) -> impl Future<Output = Result<FeeHistory, Self::Error>> + Send {
64 async move {
65 if block_count == 0 {
66 return Ok(FeeHistory::default())
67 }
68
69 if reward_percentiles.as_ref().map(|perc| perc.len() as u64) >
71 Some(self.gas_oracle().config().max_reward_percentile_count)
72 {
73 return Err(EthApiError::InvalidRewardPercentiles.into())
74 }
75
76 let max_fee_history = if reward_percentiles.is_none() {
78 self.gas_oracle().config().max_header_history
79 } else {
80 self.gas_oracle().config().max_block_history
81 };
82
83 if block_count > max_fee_history {
84 debug!(
85 requested = block_count,
86 truncated = max_fee_history,
87 "Sanitizing fee history block count"
88 );
89 block_count = max_fee_history
90 }
91
92 if newest_block.is_pending() {
93 newest_block = BlockNumberOrTag::Latest;
95 }
96
97 if let BlockNumberOrTag::Number(requested) = newest_block {
99 let latest_block =
100 self.provider().best_block_number().map_err(Self::Error::from_eth_err)?;
101 if requested > latest_block {
102 return Err(
103 EthApiError::RequestBeyondHead { requested, head: latest_block }.into()
104 )
105 }
106 }
107
108 let end_block = self
109 .provider()
110 .block_number_for_id(newest_block.into())
111 .map_err(Self::Error::from_eth_err)?
112 .ok_or(EthApiError::HeaderNotFound(newest_block.into()))?;
113
114 let end_block_plus = end_block + 1;
116 if end_block_plus < block_count {
118 block_count = end_block_plus;
119 }
120
121 if let Some(percentiles) = &reward_percentiles &&
126 (percentiles.iter().any(|p| *p < 0.0 || *p > 100.0) ||
127 percentiles.windows(2).any(|w| w[0] > w[1]))
128 {
129 return Err(EthApiError::InvalidRewardPercentiles.into())
130 }
131
132 let start_block = end_block_plus - block_count;
138
139 let mut base_fee_per_gas: Vec<u128> = Vec::new();
141 let mut gas_used_ratio: Vec<f64> = Vec::new();
142
143 let mut base_fee_per_blob_gas: Vec<u128> = Vec::new();
144 let mut blob_gas_used_ratio: Vec<f64> = Vec::new();
145
146 let mut rewards: Vec<Vec<u128>> = Vec::new();
147
148 let fee_entries = self.fee_history_cache().get_history(start_block, end_block).await;
150
151 if let Some(fee_entries) = fee_entries {
152 if fee_entries.len() != block_count as usize {
153 return Err(EthApiError::InvalidBlockRange.into())
154 }
155
156 for entry in &fee_entries {
157 base_fee_per_gas
158 .push(entry.header.base_fee_per_gas().unwrap_or_default() as u128);
159 gas_used_ratio.push(entry.gas_used_ratio);
160 base_fee_per_blob_gas.push(entry.base_fee_per_blob_gas.unwrap_or_default());
161 blob_gas_used_ratio.push(entry.blob_gas_used_ratio);
162
163 if let Some(percentiles) = &reward_percentiles {
164 let mut block_rewards = Vec::with_capacity(percentiles.len());
165 for &percentile in percentiles {
166 block_rewards.push(self.approximate_percentile(entry, percentile));
167 }
168 rewards.push(block_rewards);
169 }
170 }
171 let last_entry = fee_entries.last().expect("is not empty");
172
173 base_fee_per_gas.push(
176 self.provider()
177 .chain_spec()
178 .next_block_base_fee(&last_entry.header, last_entry.header.timestamp())
179 .unwrap_or_default() as u128,
180 );
181
182 base_fee_per_blob_gas.push(last_entry.next_block_blob_fee().unwrap_or_default());
183 } else {
184 let headers = self.provider()
186 .sealed_headers_range(start_block..=end_block)
187 .map_err(Self::Error::from_eth_err)?;
188 if headers.len() != block_count as usize {
189 return Err(EthApiError::InvalidBlockRange.into())
190 }
191
192 let chain_spec = self.provider().chain_spec();
193 for header in &headers {
194 base_fee_per_gas.push(header.base_fee_per_gas().unwrap_or_default() as u128);
195 gas_used_ratio.push(header.gas_used() as f64 / header.gas_limit() as f64);
196
197 let blob_params = chain_spec
198 .blob_params_at_timestamp(header.timestamp())
199 .unwrap_or_else(BlobParams::cancun);
200
201 base_fee_per_blob_gas.push(header.blob_fee(blob_params).unwrap_or_default());
202 blob_gas_used_ratio.push(
203 checked_blob_gas_used_ratio(
204 header.blob_gas_used().unwrap_or_default(),
205 blob_params.max_blob_gas_per_block(),
206 )
207 );
208
209 if let Some(percentiles) = &reward_percentiles {
211 let (block, receipts) = self.cache()
212 .get_block_and_receipts(header.hash())
213 .await
214 .map_err(Self::Error::from_eth_err)?
215 .ok_or(EthApiError::InvalidBlockRange)?;
216 rewards.push(
217 calculate_reward_percentiles_for_block(
218 percentiles,
219 header.gas_used(),
220 header.base_fee_per_gas().unwrap_or_default(),
221 block.body().transactions(),
222 &receipts,
223 )
224 .unwrap_or_default(),
225 );
226 }
227 }
228
229 let last_header = headers.last().expect("is present");
235 base_fee_per_gas.push(
236 chain_spec
237 .next_block_base_fee(last_header.header(), last_header.timestamp())
238 .unwrap_or_default() as u128,
239 );
240 base_fee_per_blob_gas.push(
243 last_header
244 .maybe_next_block_blob_fee(
245 chain_spec.blob_params_at_timestamp(last_header.timestamp())
246 ).unwrap_or_default()
247 );
248 };
249
250 Ok(FeeHistory {
251 base_fee_per_gas,
252 gas_used_ratio,
253 base_fee_per_blob_gas,
254 blob_gas_used_ratio,
255 oldest_block: start_block,
256 reward: reward_percentiles.map(|_| rewards),
257 })
258 }
259 }
260
261 fn approximate_percentile(
264 &self,
265 entry: &FeeHistoryEntry<ProviderHeader<Self::Provider>>,
266 requested_percentile: f64,
267 ) -> u128 {
268 let resolution = self.fee_history_cache().resolution();
269 let rounded_percentile =
270 (requested_percentile * resolution as f64).round() / resolution as f64;
271 let clamped_percentile = rounded_percentile.clamp(0.0, 100.0);
272
273 let index = (clamped_percentile / (1.0 / resolution as f64)).round() as usize;
275 entry.rewards.get(index).copied().unwrap_or_default()
277 }
278}
279
280pub trait LoadFee: LoadBlock
284where
285 Self::Provider: BlockReaderIdExt,
286{
287 fn gas_oracle(&self) -> &GasPriceOracle<Self::Provider>;
291
292 fn fee_history_cache(&self) -> &FeeHistoryCache<ProviderHeader<Self::Provider>>;
296
297 fn legacy_gas_price(
300 &self,
301 gas_price: Option<U256>,
302 ) -> impl Future<Output = Result<U256, Self::Error>> + Send {
303 async move {
304 match gas_price {
305 Some(gas_price) => Ok(gas_price),
306 None => {
307 self.gas_price().await
309 }
310 }
311 }
312 }
313
314 fn eip1559_fees(
319 &self,
320 base_fee: Option<U256>,
321 max_priority_fee_per_gas: Option<U256>,
322 ) -> impl Future<Output = Result<(U256, U256), Self::Error>> + Send {
323 async move {
324 let base_fee = match base_fee {
325 Some(base_fee) => base_fee,
326 None => {
327 let base_fee = self
329 .recovered_block(BlockNumberOrTag::Pending.into())
330 .await?
331 .ok_or(EthApiError::HeaderNotFound(BlockNumberOrTag::Pending.into()))?
332 .base_fee_per_gas()
333 .ok_or(EthApiError::InvalidTransaction(
334 RpcInvalidTransactionError::TxTypeNotSupported,
335 ))?;
336 U256::from(base_fee)
337 }
338 };
339
340 let max_priority_fee_per_gas = match max_priority_fee_per_gas {
341 Some(max_priority_fee_per_gas) => max_priority_fee_per_gas,
342 None => self.suggested_priority_fee().await?,
343 };
344 Ok((base_fee, max_priority_fee_per_gas))
345 }
346 }
347
348 fn eip4844_blob_fee(
350 &self,
351 blob_fee: Option<U256>,
352 ) -> impl Future<Output = Result<U256, Self::Error>> + Send {
353 async move {
354 match blob_fee {
355 Some(blob_fee) => Ok(blob_fee),
356 None => self.blob_base_fee().await,
357 }
358 }
359 }
360
361 fn gas_price(&self) -> impl Future<Output = Result<U256, Self::Error>> + Send {
365 async move {
366 let header = self.provider().latest_header().map_err(Self::Error::from_eth_err)?;
367 let suggested_tip = self.suggested_priority_fee().await?;
368 let base_fee = header.and_then(|h| h.base_fee_per_gas()).unwrap_or_default();
369 Ok(suggested_tip + U256::from(base_fee))
370 }
371 }
372
373 fn blob_base_fee(&self) -> impl Future<Output = Result<U256, Self::Error>> + Send {
375 async move {
376 self.provider()
377 .latest_header()
378 .map_err(Self::Error::from_eth_err)?
379 .and_then(|h| {
380 h.maybe_next_block_blob_fee(
381 self.provider().chain_spec().blob_params_at_timestamp(h.timestamp()),
382 )
383 })
384 .ok_or(EthApiError::ExcessBlobGasNotSet.into())
385 .map(U256::from)
386 }
387 }
388
389 fn suggested_priority_fee(&self) -> impl Future<Output = Result<U256, Self::Error>> + Send
391 where
392 Self: 'static,
393 {
394 async move { self.gas_oracle().suggest_tip_cap().await.map_err(Self::Error::from_eth_err) }
395 }
396}