reth_rpc_eth_api/helpers/
fee.rs1use alloy_consensus::BlockHeader;
4use alloy_primitives::U256;
5use alloy_rpc_types_eth::{BlockNumberOrTag, FeeHistory};
6use futures::Future;
7use reth_chainspec::EthChainSpec;
8use reth_primitives_traits::BlockBody;
9use reth_provider::{BlockIdReader, ChainSpecProvider, HeaderProvider};
10use reth_rpc_eth_types::{
11 fee_history::calculate_reward_percentiles_for_block, EthApiError, FeeHistoryCache,
12 FeeHistoryEntry, GasPriceOracle, RpcInvalidTransactionError,
13};
14use tracing::debug;
15
16use crate::FromEthApiError;
17
18use super::LoadBlock;
19
20pub trait EthFees: LoadFee {
23 fn gas_price(&self) -> impl Future<Output = Result<U256, Self::Error>> + Send
27 where
28 Self: LoadBlock,
29 {
30 LoadFee::gas_price(self)
31 }
32
33 fn blob_base_fee(&self) -> impl Future<Output = Result<U256, Self::Error>> + Send
35 where
36 Self: LoadBlock,
37 {
38 LoadFee::blob_base_fee(self)
39 }
40
41 fn suggested_priority_fee(&self) -> impl Future<Output = Result<U256, Self::Error>> + Send
43 where
44 Self: 'static,
45 {
46 LoadFee::suggested_priority_fee(self)
47 }
48
49 fn fee_history(
54 &self,
55 mut block_count: u64,
56 mut newest_block: BlockNumberOrTag,
57 reward_percentiles: Option<Vec<f64>>,
58 ) -> impl Future<Output = Result<FeeHistory, Self::Error>> + Send {
59 async move {
60 if block_count == 0 {
61 return Ok(FeeHistory::default())
62 }
63
64 let max_fee_history = if reward_percentiles.is_none() {
66 self.gas_oracle().config().max_header_history
67 } else {
68 self.gas_oracle().config().max_block_history
69 };
70
71 if block_count > max_fee_history {
72 debug!(
73 requested = block_count,
74 truncated = max_fee_history,
75 "Sanitizing fee history block count"
76 );
77 block_count = max_fee_history
78 }
79
80 if newest_block.is_pending() {
81 newest_block = BlockNumberOrTag::Latest;
83 block_count = block_count.saturating_sub(1);
85 }
86
87 let end_block = self
88 .provider()
89 .block_number_for_id(newest_block.into())
90 .map_err(Self::Error::from_eth_err)?
91 .ok_or(EthApiError::HeaderNotFound(newest_block.into()))?;
92
93 let end_block_plus = end_block + 1;
95 if end_block_plus < block_count {
97 block_count = end_block_plus;
98 }
99
100 if let Some(percentiles) = &reward_percentiles {
105 if percentiles.windows(2).any(|w| w[0] > w[1] || w[0] > 100.) {
106 return Err(EthApiError::InvalidRewardPercentiles.into())
107 }
108 }
109
110 let start_block = end_block_plus - block_count;
116
117 let mut base_fee_per_gas: Vec<u128> = Vec::new();
119 let mut gas_used_ratio: Vec<f64> = Vec::new();
120
121 let mut base_fee_per_blob_gas: Vec<u128> = Vec::new();
122 let mut blob_gas_used_ratio: Vec<f64> = Vec::new();
123
124 let mut rewards: Vec<Vec<u128>> = Vec::new();
125
126 let fee_entries = self.fee_history_cache().get_history(start_block, end_block).await;
128
129 if let Some(fee_entries) = fee_entries {
130 if fee_entries.len() != block_count as usize {
131 return Err(EthApiError::InvalidBlockRange.into())
132 }
133
134 for entry in &fee_entries {
135 base_fee_per_gas.push(entry.base_fee_per_gas as u128);
136 gas_used_ratio.push(entry.gas_used_ratio);
137 base_fee_per_blob_gas.push(entry.base_fee_per_blob_gas.unwrap_or_default());
138 blob_gas_used_ratio.push(entry.blob_gas_used_ratio);
139
140 if let Some(percentiles) = &reward_percentiles {
141 let mut block_rewards = Vec::with_capacity(percentiles.len());
142 for &percentile in percentiles {
143 block_rewards.push(self.approximate_percentile(entry, percentile));
144 }
145 rewards.push(block_rewards);
146 }
147 }
148 let last_entry = fee_entries.last().expect("is not empty");
149
150 base_fee_per_gas
153 .push(last_entry.next_block_base_fee(self.provider().chain_spec()) as u128);
154
155 base_fee_per_blob_gas.push(last_entry.next_block_blob_fee().unwrap_or_default());
156 } else {
157 let headers = self.provider()
159 .sealed_headers_range(start_block..=end_block)
160 .map_err(Self::Error::from_eth_err)?;
161 if headers.len() != block_count as usize {
162 return Err(EthApiError::InvalidBlockRange.into())
163 }
164
165
166 for header in &headers {
167 base_fee_per_gas.push(header.base_fee_per_gas().unwrap_or_default() as u128);
168 gas_used_ratio.push(header.gas_used() as f64 / header.gas_limit() as f64);
169 base_fee_per_blob_gas.push(header.blob_fee().unwrap_or_default());
170 blob_gas_used_ratio.push(
171 header.blob_gas_used().unwrap_or_default() as f64
172 / alloy_eips::eip4844::MAX_DATA_GAS_PER_BLOCK as f64,
173 );
174
175 if let Some(percentiles) = &reward_percentiles {
177 let (block, receipts) = self.cache()
178 .get_block_and_receipts(header.hash())
179 .await
180 .map_err(Self::Error::from_eth_err)?
181 .ok_or(EthApiError::InvalidBlockRange)?;
182 rewards.push(
183 calculate_reward_percentiles_for_block(
184 percentiles,
185 header.gas_used(),
186 header.base_fee_per_gas().unwrap_or_default(),
187 block.body.transactions(),
188 &receipts,
189 )
190 .unwrap_or_default(),
191 );
192 }
193 }
194
195 let last_header = headers.last().expect("is present");
201 base_fee_per_gas.push(
202 last_header.next_block_base_fee(
203 self.provider()
204 .chain_spec()
205 .base_fee_params_at_timestamp(last_header.timestamp())).unwrap_or_default() as u128
206 );
207
208 base_fee_per_blob_gas.push(last_header.next_block_blob_fee().unwrap_or_default());
211 };
212
213 Ok(FeeHistory {
214 base_fee_per_gas,
215 gas_used_ratio,
216 base_fee_per_blob_gas,
217 blob_gas_used_ratio,
218 oldest_block: start_block,
219 reward: reward_percentiles.map(|_| rewards),
220 })
221 }
222 }
223
224 fn approximate_percentile(&self, entry: &FeeHistoryEntry, requested_percentile: f64) -> u128 {
227 let resolution = self.fee_history_cache().resolution();
228 let rounded_percentile =
229 (requested_percentile * resolution as f64).round() / resolution as f64;
230 let clamped_percentile = rounded_percentile.clamp(0.0, 100.0);
231
232 let index = (clamped_percentile / (1.0 / resolution as f64)).round() as usize;
234 entry.rewards.get(index).copied().unwrap_or_default()
236 }
237}
238
239pub trait LoadFee: LoadBlock {
243 fn gas_oracle(&self) -> &GasPriceOracle<Self::Provider>;
247
248 fn fee_history_cache(&self) -> &FeeHistoryCache;
252
253 fn legacy_gas_price(
256 &self,
257 gas_price: Option<U256>,
258 ) -> impl Future<Output = Result<U256, Self::Error>> + Send {
259 async move {
260 match gas_price {
261 Some(gas_price) => Ok(gas_price),
262 None => {
263 self.gas_price().await
265 }
266 }
267 }
268 }
269
270 fn eip1559_fees(
275 &self,
276 base_fee: Option<U256>,
277 max_priority_fee_per_gas: Option<U256>,
278 ) -> impl Future<Output = Result<(U256, U256), Self::Error>> + Send {
279 async move {
280 let base_fee = match base_fee {
281 Some(base_fee) => base_fee,
282 None => {
283 let base_fee = self
285 .block_with_senders(BlockNumberOrTag::Pending.into())
286 .await?
287 .ok_or(EthApiError::HeaderNotFound(BlockNumberOrTag::Pending.into()))?
288 .base_fee_per_gas()
289 .ok_or(EthApiError::InvalidTransaction(
290 RpcInvalidTransactionError::TxTypeNotSupported,
291 ))?;
292 U256::from(base_fee)
293 }
294 };
295
296 let max_priority_fee_per_gas = match max_priority_fee_per_gas {
297 Some(max_priority_fee_per_gas) => max_priority_fee_per_gas,
298 None => self.suggested_priority_fee().await?,
299 };
300 Ok((base_fee, max_priority_fee_per_gas))
301 }
302 }
303
304 fn eip4844_blob_fee(
306 &self,
307 blob_fee: Option<U256>,
308 ) -> impl Future<Output = Result<U256, Self::Error>> + Send {
309 async move {
310 match blob_fee {
311 Some(blob_fee) => Ok(blob_fee),
312 None => self.blob_base_fee().await,
313 }
314 }
315 }
316
317 fn gas_price(&self) -> impl Future<Output = Result<U256, Self::Error>> + Send {
321 let header = self.block_with_senders(BlockNumberOrTag::Latest.into());
322 let suggested_tip = self.suggested_priority_fee();
323 async move {
324 let (header, suggested_tip) = futures::try_join!(header, suggested_tip)?;
325 let base_fee = header.and_then(|h| h.base_fee_per_gas()).unwrap_or_default();
326 Ok(suggested_tip + U256::from(base_fee))
327 }
328 }
329
330 fn blob_base_fee(&self) -> impl Future<Output = Result<U256, Self::Error>> + Send {
332 async move {
333 self.block_with_senders(BlockNumberOrTag::Latest.into())
334 .await?
335 .and_then(|h| h.next_block_blob_fee())
336 .ok_or(EthApiError::ExcessBlobGasNotSet.into())
337 .map(U256::from)
338 }
339 }
340
341 fn suggested_priority_fee(&self) -> impl Future<Output = Result<U256, Self::Error>> + Send
343 where
344 Self: 'static,
345 {
346 async move { self.gas_oracle().suggest_tip_cap().await.map_err(Self::Error::from_eth_err) }
347 }
348}