mirror of
https://github.com/hl-archive-node/nanoreth.git
synced 2025-12-06 10:59:55 +00:00
refactor: re-implement eth_feeHistory (#3288)
Co-authored-by: Matthias Seitz <matthias.seitz@outlook.de>
This commit is contained in:
11
Cargo.lock
generated
11
Cargo.lock
generated
@ -3510,15 +3510,6 @@ dependencies = [
|
|||||||
"hashbrown 0.12.3",
|
"hashbrown 0.12.3",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "lru"
|
|
||||||
version = "0.9.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "71e7d46de488603ffdd5f30afbc64fbba2378214a2c3a2fb83abf3d33126df17"
|
|
||||||
dependencies = [
|
|
||||||
"hashbrown 0.13.2",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lru"
|
name = "lru"
|
||||||
version = "0.10.0"
|
version = "0.10.0"
|
||||||
@ -5663,7 +5654,6 @@ version = "0.1.0-alpha.1"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"assert_matches",
|
"assert_matches",
|
||||||
"jsonrpsee-types",
|
"jsonrpsee-types",
|
||||||
"lru 0.9.0",
|
|
||||||
"rand 0.8.5",
|
"rand 0.8.5",
|
||||||
"reth-interfaces",
|
"reth-interfaces",
|
||||||
"reth-primitives",
|
"reth-primitives",
|
||||||
@ -5672,7 +5662,6 @@ dependencies = [
|
|||||||
"serde_json",
|
"serde_json",
|
||||||
"similar-asserts",
|
"similar-asserts",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"tokio",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|||||||
@ -437,6 +437,7 @@ impl Transaction {
|
|||||||
pub fn effective_gas_tip(&self, base_fee: Option<u64>) -> Option<u128> {
|
pub fn effective_gas_tip(&self, base_fee: Option<u64>) -> Option<u128> {
|
||||||
if let Some(base_fee) = base_fee {
|
if let Some(base_fee) = base_fee {
|
||||||
let max_fee_per_gas = self.max_fee_per_gas();
|
let max_fee_per_gas = self.max_fee_per_gas();
|
||||||
|
|
||||||
if max_fee_per_gas < base_fee as u128 {
|
if max_fee_per_gas < base_fee as u128 {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
use jsonrpsee::{core::RpcResult, proc_macros::rpc};
|
use jsonrpsee::{core::RpcResult, proc_macros::rpc};
|
||||||
use reth_primitives::{
|
use reth_primitives::{
|
||||||
serde_helper::JsonStorageKey, AccessListWithGasUsed, Address, BlockId, BlockNumberOrTag, Bytes,
|
serde_helper::{num::U64HexOrNumber, JsonStorageKey},
|
||||||
H256, H64, U256, U64,
|
AccessListWithGasUsed, Address, BlockId, BlockNumberOrTag, Bytes, H256, H64, U256, U64,
|
||||||
};
|
};
|
||||||
use reth_rpc_types::{
|
use reth_rpc_types::{
|
||||||
state::StateOverride, BlockOverrides, CallRequest, EIP1186AccountProofResponse, FeeHistory,
|
state::StateOverride, BlockOverrides, CallRequest, EIP1186AccountProofResponse, FeeHistory,
|
||||||
@ -194,8 +194,8 @@ pub trait EthApi {
|
|||||||
#[method(name = "feeHistory")]
|
#[method(name = "feeHistory")]
|
||||||
async fn fee_history(
|
async fn fee_history(
|
||||||
&self,
|
&self,
|
||||||
block_count: U64,
|
block_count: U64HexOrNumber,
|
||||||
newest_block: BlockId,
|
newest_block: BlockNumberOrTag,
|
||||||
reward_percentiles: Option<Vec<f64>>,
|
reward_percentiles: Option<Vec<f64>>,
|
||||||
) -> RpcResult<FeeHistory>;
|
) -> RpcResult<FeeHistory>;
|
||||||
|
|
||||||
|
|||||||
@ -65,7 +65,7 @@ where
|
|||||||
EthApiClient::block_number(client).await.unwrap();
|
EthApiClient::block_number(client).await.unwrap();
|
||||||
EthApiClient::get_code(client, address, None).await.unwrap();
|
EthApiClient::get_code(client, address, None).await.unwrap();
|
||||||
EthApiClient::send_raw_transaction(client, tx).await.unwrap();
|
EthApiClient::send_raw_transaction(client, tx).await.unwrap();
|
||||||
EthApiClient::fee_history(client, 0.into(), block_number.into(), None).await.unwrap();
|
EthApiClient::fee_history(client, 0.into(), block_number, None).await.unwrap();
|
||||||
EthApiClient::balance(client, address, None).await.unwrap();
|
EthApiClient::balance(client, address, None).await.unwrap();
|
||||||
EthApiClient::transaction_count(client, address, None).await.unwrap();
|
EthApiClient::transaction_count(client, address, None).await.unwrap();
|
||||||
EthApiClient::storage_at(client, address, U256::default().into(), None).await.unwrap();
|
EthApiClient::storage_at(client, address, U256::default().into(), None).await.unwrap();
|
||||||
|
|||||||
@ -18,14 +18,10 @@ reth-rlp = { workspace = true }
|
|||||||
# errors
|
# errors
|
||||||
thiserror = { workspace = true }
|
thiserror = { workspace = true }
|
||||||
|
|
||||||
# async
|
|
||||||
tokio = { workspace = true, features = ["sync"] }
|
|
||||||
|
|
||||||
# misc
|
# misc
|
||||||
serde = { workspace = true, features = ["derive"] }
|
serde = { workspace = true, features = ["derive"] }
|
||||||
serde_json = { workspace = true }
|
serde_json = { workspace = true }
|
||||||
jsonrpsee-types = { version = "0.18" }
|
jsonrpsee-types = { version = "0.18" }
|
||||||
lru = "0.9"
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
# reth
|
# reth
|
||||||
|
|||||||
@ -1,15 +1,12 @@
|
|||||||
use lru::LruCache;
|
use reth_primitives::U256;
|
||||||
use reth_primitives::{BlockNumber, H256, U256};
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::{num::NonZeroUsize, sync::Arc};
|
|
||||||
use tokio::sync::Mutex;
|
|
||||||
|
|
||||||
/// Internal struct to calculate reward percentiles
|
/// Internal struct to calculate reward percentiles
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub struct TxGasAndReward {
|
pub struct TxGasAndReward {
|
||||||
/// gas used by a block
|
/// Gas used by the transaction
|
||||||
pub gas_used: u128,
|
pub gas_used: u64,
|
||||||
/// minimum between max_priority_fee_per_gas or max_fee_per_gas - base_fee_for_block
|
/// The effective gas tip by the transaction
|
||||||
pub reward: u128,
|
pub reward: u128,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -32,16 +29,28 @@ impl Ord for TxGasAndReward {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Response type for `eth_feeHistory`
|
/// Response type for `eth_feeHistory`
|
||||||
#[derive(Debug, Clone, Default, Deserialize, Serialize)]
|
#[derive(Debug, Clone, Default, PartialEq, Deserialize, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct FeeHistory {
|
pub struct FeeHistory {
|
||||||
/// An array of block base fees per gas.
|
/// An array of block base fees per gas.
|
||||||
/// This includes the next block after the newest of the returned range,
|
/// This includes the next block after the newest of the returned range,
|
||||||
/// because this value can be derived from the newest block. Zeroes are
|
/// because this value can be derived from the newest block. Zeroes are
|
||||||
/// returned for pre-EIP-1559 blocks.
|
/// returned for pre-EIP-1559 blocks.
|
||||||
|
///
|
||||||
|
/// # Note
|
||||||
|
///
|
||||||
|
/// The `Option` is only for compatability with Erigon and Geth.
|
||||||
|
#[serde(skip_serializing_if = "Vec::is_empty")]
|
||||||
|
#[serde(default)]
|
||||||
pub base_fee_per_gas: Vec<U256>,
|
pub base_fee_per_gas: Vec<U256>,
|
||||||
/// An array of block gas used ratios. These are calculated as the ratio
|
/// An array of block gas used ratios. These are calculated as the ratio
|
||||||
/// of `gasUsed` and `gasLimit`.
|
/// of `gasUsed` and `gasLimit`.
|
||||||
|
///
|
||||||
|
/// # Note
|
||||||
|
///
|
||||||
|
/// The `Option` is only for compatability with Erigon and Geth.
|
||||||
|
#[serde(skip_serializing_if = "Vec::is_empty")]
|
||||||
|
#[serde(default)]
|
||||||
pub gas_used_ratio: Vec<f64>,
|
pub gas_used_ratio: Vec<f64>,
|
||||||
/// Lowest number block of the returned range.
|
/// Lowest number block of the returned range.
|
||||||
pub oldest_block: U256,
|
pub oldest_block: U256,
|
||||||
@ -50,29 +59,3 @@ pub struct FeeHistory {
|
|||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub reward: Option<Vec<Vec<U256>>>,
|
pub reward: Option<Vec<Vec<U256>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// LRU cache for `eth_feeHistory` RPC method. Block Number => Fee History.
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub struct FeeHistoryCache(pub Arc<Mutex<LruCache<BlockNumber, FeeHistoryCacheItem>>>);
|
|
||||||
|
|
||||||
impl FeeHistoryCache {
|
|
||||||
/// Creates a new LRU Cache that holds at most cap items.
|
|
||||||
pub fn new(cap: NonZeroUsize) -> Self {
|
|
||||||
Self(Arc::new(Mutex::new(LruCache::new(cap))))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// [FeeHistoryCache] item.
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub struct FeeHistoryCacheItem {
|
|
||||||
/// Block hash (`None` if it wasn't the oldest block in `eth_feeHistory` response where
|
|
||||||
/// cache is populated)
|
|
||||||
pub hash: Option<H256>,
|
|
||||||
/// Block base fee per gas. Zero for pre-EIP-1559 blocks.
|
|
||||||
pub base_fee_per_gas: U256,
|
|
||||||
/// Block gas used ratio. Calculated as the ratio of `gasUsed` and `gasLimit`.
|
|
||||||
pub gas_used_ratio: f64,
|
|
||||||
/// An (optional) array of effective priority fee per gas data points for a
|
|
||||||
/// block. All zeroes are returned if the block is empty.
|
|
||||||
pub reward: Option<Vec<U256>>,
|
|
||||||
}
|
|
||||||
|
|||||||
@ -20,7 +20,7 @@ mod work;
|
|||||||
pub use account::*;
|
pub use account::*;
|
||||||
pub use block::*;
|
pub use block::*;
|
||||||
pub use call::CallRequest;
|
pub use call::CallRequest;
|
||||||
pub use fee::{FeeHistory, FeeHistoryCache, FeeHistoryCacheItem, TxGasAndReward};
|
pub use fee::{FeeHistory, TxGasAndReward};
|
||||||
pub use filter::*;
|
pub use filter::*;
|
||||||
pub use index::Index;
|
pub use index::Index;
|
||||||
pub use log::Log;
|
pub use log::Log;
|
||||||
|
|||||||
@ -62,4 +62,5 @@ futures = { workspace = true }
|
|||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
jsonrpsee = { version = "0.18", features = ["client"] }
|
jsonrpsee = { version = "0.18", features = ["client"] }
|
||||||
assert_matches = "1.5.0"
|
assert_matches = "1.5.0"
|
||||||
tempfile = "3.5.0"
|
tempfile = "3.5.0"
|
||||||
|
reth-interfaces = { workspace = true, features = ["test-utils"] }
|
||||||
@ -1,15 +1,17 @@
|
|||||||
//! Contains RPC handler implementations for fee history.
|
//! Contains RPC handler implementations for fee history.
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
eth::error::{EthApiError, EthResult, RpcInvalidTransactionError},
|
eth::error::{EthApiError, EthResult},
|
||||||
EthApi,
|
EthApi,
|
||||||
};
|
};
|
||||||
use reth_network_api::NetworkInfo;
|
use reth_network_api::NetworkInfo;
|
||||||
use reth_primitives::{BlockId, BlockNumberOrTag, U256};
|
use reth_primitives::{
|
||||||
|
basefee::calculate_next_block_base_fee, BlockNumberOrTag, SealedHeader, U256,
|
||||||
|
};
|
||||||
use reth_provider::{BlockReaderIdExt, EvmEnvProvider, StateProviderFactory};
|
use reth_provider::{BlockReaderIdExt, EvmEnvProvider, StateProviderFactory};
|
||||||
use reth_rpc_types::{FeeHistory, FeeHistoryCacheItem, TxGasAndReward};
|
use reth_rpc_types::{FeeHistory, TxGasAndReward};
|
||||||
use reth_transaction_pool::TransactionPool;
|
use reth_transaction_pool::TransactionPool;
|
||||||
use std::collections::BTreeMap;
|
use tracing::debug;
|
||||||
|
|
||||||
impl<Provider, Pool, Network> EthApi<Provider, Pool, Network>
|
impl<Provider, Pool, Network> EthApi<Provider, Pool, Network>
|
||||||
where
|
where
|
||||||
@ -37,168 +39,155 @@ where
|
|||||||
/// provided.
|
/// provided.
|
||||||
pub(crate) async fn fee_history(
|
pub(crate) async fn fee_history(
|
||||||
&self,
|
&self,
|
||||||
block_count: u64,
|
mut block_count: u64,
|
||||||
newest_block: BlockId,
|
newest_block: BlockNumberOrTag,
|
||||||
reward_percentiles: Option<Vec<f64>>,
|
reward_percentiles: Option<Vec<f64>>,
|
||||||
) -> EthResult<FeeHistory> {
|
) -> EthResult<FeeHistory> {
|
||||||
if block_count == 0 {
|
if block_count == 0 {
|
||||||
return Ok(FeeHistory::default())
|
return Ok(FeeHistory::default())
|
||||||
}
|
}
|
||||||
|
|
||||||
let Some(previous_to_end_block) = self.inner.provider.block_number_for_id(newest_block)? else { return Err(EthApiError::UnknownBlockNumber)};
|
// See https://github.com/ethereum/go-ethereum/blob/2754b197c935ee63101cbbca2752338246384fec/eth/gasprice/feehistory.go#L218C8-L225
|
||||||
let end_block = previous_to_end_block + 1;
|
let max_fee_history = if reward_percentiles.is_none() {
|
||||||
|
self.gas_oracle().config().max_header_history
|
||||||
|
} else {
|
||||||
|
self.gas_oracle().config().max_block_history
|
||||||
|
};
|
||||||
|
|
||||||
|
if block_count > max_fee_history {
|
||||||
|
debug!(
|
||||||
|
requested = block_count,
|
||||||
|
truncated = max_fee_history,
|
||||||
|
"Sanitizing fee history block count"
|
||||||
|
);
|
||||||
|
block_count = max_fee_history
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(end_block) = self.provider().block_number_for_id(newest_block.into())? else {
|
||||||
|
return Err(EthApiError::UnknownBlockNumber) };
|
||||||
|
|
||||||
|
// Check that we would not be querying outside of genesis
|
||||||
if end_block < block_count {
|
if end_block < block_count {
|
||||||
return Err(EthApiError::InvalidBlockRange)
|
return Err(EthApiError::InvalidBlockRange)
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut start_block = end_block - block_count;
|
// If reward percentiles were specified, we need to validate that they are monotonically
|
||||||
|
// increasing and 0 <= p <= 100
|
||||||
if block_count == 1 {
|
//
|
||||||
start_block = previous_to_end_block;
|
// Note: The types used ensure that the percentiles are never < 0
|
||||||
}
|
if let Some(percentiles) = &reward_percentiles {
|
||||||
|
if percentiles.windows(2).any(|w| w[0] > w[1] || w[0] > 100.) {
|
||||||
// if not provided the percentiles are []
|
return Err(EthApiError::InvalidRewardPercentiles)
|
||||||
let reward_percentiles = reward_percentiles.unwrap_or_default();
|
|
||||||
|
|
||||||
// checks for rewardPercentile's sorted-ness
|
|
||||||
// check if any of rewardPercentile is greater than 100
|
|
||||||
// pre 1559 blocks, return 0 for baseFeePerGas
|
|
||||||
for window in reward_percentiles.windows(2) {
|
|
||||||
if window[0] >= window[1] {
|
|
||||||
return Err(EthApiError::InvalidRewardPercentile(window[1]))
|
|
||||||
}
|
|
||||||
|
|
||||||
if window[0] < 0.0 || window[0] > 100.0 {
|
|
||||||
return Err(EthApiError::InvalidRewardPercentile(window[0]))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut fee_history_cache = self.inner.fee_history_cache.0.lock().await;
|
// Fetch the headers and ensure we got all of them
|
||||||
|
//
|
||||||
|
// Treat a request for 1 block as a request for `newest_block..=newest_block`,
|
||||||
|
// otherwise `newest_block - 2
|
||||||
|
let start_block = end_block - block_count + 1;
|
||||||
|
let headers = self.provider().sealed_headers_range(start_block..=end_block)?;
|
||||||
|
if headers.len() != block_count as usize {
|
||||||
|
return Err(EthApiError::InvalidBlockRange)
|
||||||
|
}
|
||||||
|
|
||||||
// Sorted map that's populated in two rounds:
|
// Collect base fees, gas usage ratios and (optionally) reward percentile data
|
||||||
// 1. Cache entries until first non-cached block
|
let mut base_fee_per_gas: Vec<U256> = Vec::new();
|
||||||
// 2. Database query from the first non-cached block
|
let mut gas_used_ratio: Vec<f64> = Vec::new();
|
||||||
let mut fee_history_cache_items = BTreeMap::new();
|
let mut rewards: Vec<Vec<U256>> = Vec::new();
|
||||||
|
for header in &headers {
|
||||||
|
base_fee_per_gas
|
||||||
|
.push(U256::try_from(header.base_fee_per_gas.unwrap_or_default()).unwrap());
|
||||||
|
gas_used_ratio.push(header.gas_used as f64 / header.gas_limit as f64);
|
||||||
|
|
||||||
let mut first_non_cached_block = None;
|
// Percentiles were specified, so we need to collect reward percentile ino
|
||||||
let mut last_non_cached_block = None;
|
if let Some(percentiles) = &reward_percentiles {
|
||||||
for block in start_block..=end_block {
|
rewards.push(self.calculate_reward_percentiles(percentiles, header).await?);
|
||||||
// Check if block exists in cache, and move it to the head of the list if so
|
|
||||||
if let Some(fee_history_cache_item) = fee_history_cache.get(&block) {
|
|
||||||
fee_history_cache_items.insert(block, fee_history_cache_item.clone());
|
|
||||||
} else {
|
|
||||||
// If block doesn't exist in cache, set it as a first non-cached block to query it
|
|
||||||
// from the database
|
|
||||||
first_non_cached_block.get_or_insert(block);
|
|
||||||
// And last non-cached block, so we could query the database until we reach it
|
|
||||||
last_non_cached_block = Some(block);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we had any cache misses, query the database starting with the first non-cached block
|
// The spec states that `base_fee_per_gas` "[..] includes the next block after the newest of
|
||||||
// and ending with the last
|
// the returned range, because this value can be derived from the newest block"
|
||||||
if let (Some(start_block), Some(end_block)) =
|
//
|
||||||
(first_non_cached_block, last_non_cached_block)
|
// The unwrap is safe since we checked earlier that we got at least 1 header.
|
||||||
{
|
let last_header = headers.last().unwrap();
|
||||||
let header_range = start_block..=end_block;
|
base_fee_per_gas.push(U256::from(calculate_next_block_base_fee(
|
||||||
|
last_header.gas_used,
|
||||||
let headers = self.inner.provider.headers_range(header_range.clone())?;
|
last_header.gas_limit,
|
||||||
let transactions_by_block =
|
last_header.base_fee_per_gas.unwrap_or_default(),
|
||||||
self.inner.provider.transactions_by_block_range(header_range)?;
|
)));
|
||||||
|
|
||||||
let header_tx = headers.iter().zip(&transactions_by_block);
|
|
||||||
|
|
||||||
// We should receive exactly the amount of blocks missing from the cache
|
|
||||||
if headers.len() != (end_block - start_block + 1) as usize {
|
|
||||||
return Err(EthApiError::InvalidBlockRange)
|
|
||||||
}
|
|
||||||
|
|
||||||
// We should receive exactly the amount of blocks missing from the cache
|
|
||||||
if transactions_by_block.len() != (end_block - start_block + 1) as usize {
|
|
||||||
return Err(EthApiError::InvalidBlockRange)
|
|
||||||
}
|
|
||||||
|
|
||||||
for (header, transactions) in header_tx {
|
|
||||||
let base_fee_per_gas: U256 = header.base_fee_per_gas.
|
|
||||||
unwrap_or_default(). // Zero for pre-EIP-1559 blocks
|
|
||||||
try_into().unwrap(); // u64 -> U256 won't fail
|
|
||||||
let gas_used_ratio = header.gas_used as f64 / header.gas_limit as f64;
|
|
||||||
|
|
||||||
let mut sorter = Vec::with_capacity(transactions.len());
|
|
||||||
for transaction in transactions.iter() {
|
|
||||||
let reward = transaction
|
|
||||||
.effective_gas_tip(header.base_fee_per_gas)
|
|
||||||
.ok_or(RpcInvalidTransactionError::FeeCapTooLow)?;
|
|
||||||
|
|
||||||
sorter.push(TxGasAndReward { gas_used: header.gas_used as u128, reward })
|
|
||||||
}
|
|
||||||
|
|
||||||
sorter.sort();
|
|
||||||
|
|
||||||
let mut rewards = Vec::with_capacity(reward_percentiles.len());
|
|
||||||
let mut sum_gas_used = sorter.first().map(|tx| tx.gas_used).unwrap_or_default();
|
|
||||||
let mut tx_index = 0;
|
|
||||||
|
|
||||||
for percentile in reward_percentiles.iter() {
|
|
||||||
let threshold_gas_used = (header.gas_used as f64) * percentile / 100_f64;
|
|
||||||
while sum_gas_used < threshold_gas_used as u128 && tx_index < transactions.len()
|
|
||||||
{
|
|
||||||
tx_index += 1;
|
|
||||||
sum_gas_used += sorter[tx_index].gas_used;
|
|
||||||
}
|
|
||||||
|
|
||||||
rewards.push(U256::from(sorter[tx_index].reward));
|
|
||||||
}
|
|
||||||
|
|
||||||
let fee_history_cache_item = FeeHistoryCacheItem {
|
|
||||||
hash: None,
|
|
||||||
base_fee_per_gas,
|
|
||||||
gas_used_ratio,
|
|
||||||
reward: Some(rewards),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Insert missing cache entries in the map for further response composition from
|
|
||||||
// it
|
|
||||||
fee_history_cache_items.insert(header.number, fee_history_cache_item.clone());
|
|
||||||
// And populate the cache with new entries
|
|
||||||
fee_history_cache.push(header.number, fee_history_cache_item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// get the first block in the range from the db
|
|
||||||
let oldest_block_hash =
|
|
||||||
self.inner.provider.block_hash(start_block)?.ok_or(EthApiError::UnknownBlockNumber)?;
|
|
||||||
|
|
||||||
// Set the hash in cache items if the block is present in the cache
|
|
||||||
if let Some(cache_item) = fee_history_cache_items.get_mut(&start_block) {
|
|
||||||
cache_item.hash = Some(oldest_block_hash);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(cache_item) = fee_history_cache.get_mut(&start_block) {
|
|
||||||
cache_item.hash = Some(oldest_block_hash);
|
|
||||||
}
|
|
||||||
|
|
||||||
// `fee_history_cache_items` now contains full requested block range (populated from both
|
|
||||||
// cache and database), so we can iterate over it in order and populate the response fields
|
|
||||||
let base_fee_per_gas =
|
|
||||||
fee_history_cache_items.values().map(|item| item.base_fee_per_gas).collect();
|
|
||||||
|
|
||||||
let mut gas_used_ratio: Vec<f64> =
|
|
||||||
fee_history_cache_items.values().map(|item| item.gas_used_ratio).collect();
|
|
||||||
|
|
||||||
let mut rewards: Vec<Vec<_>> =
|
|
||||||
fee_history_cache_items.values().filter_map(|item| item.reward.clone()).collect();
|
|
||||||
|
|
||||||
// gasUsedRatio doesn't have data for next block in this case the last block
|
|
||||||
gas_used_ratio.pop();
|
|
||||||
rewards.pop();
|
|
||||||
|
|
||||||
Ok(FeeHistory {
|
Ok(FeeHistory {
|
||||||
base_fee_per_gas,
|
base_fee_per_gas,
|
||||||
gas_used_ratio,
|
gas_used_ratio,
|
||||||
oldest_block: U256::from(start_block),
|
oldest_block: U256::from(start_block),
|
||||||
reward: Some(rewards),
|
reward: reward_percentiles.map(|_| rewards),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// todo: docs
|
||||||
|
async fn calculate_reward_percentiles(
|
||||||
|
&self,
|
||||||
|
percentiles: &[f64],
|
||||||
|
header: &SealedHeader,
|
||||||
|
) -> Result<Vec<U256>, EthApiError> {
|
||||||
|
let Some(receipts) =
|
||||||
|
self.cache().get_receipts(header.hash).await? else {
|
||||||
|
// If there are no receipts, then we do not have all info on the block
|
||||||
|
return Err(EthApiError::InvalidBlockRange)
|
||||||
|
};
|
||||||
|
let Some(mut transactions): Option<Vec<_>> = self
|
||||||
|
.cache()
|
||||||
|
.get_block_transactions(header.hash).await?
|
||||||
|
.map(|txs|txs
|
||||||
|
.into_iter()
|
||||||
|
.zip(receipts.into_iter())
|
||||||
|
.scan(0, |previous_gas, (tx, receipt)| {
|
||||||
|
// Convert the cumulative gas used in the receipts
|
||||||
|
// to the gas usage by the transaction
|
||||||
|
//
|
||||||
|
// While we will sum up the gas again later, it is worth
|
||||||
|
// noting that the order of the transactions will be different,
|
||||||
|
// so the sum will also be different for each receipt.
|
||||||
|
let gas_used = receipt.cumulative_gas_used - *previous_gas;
|
||||||
|
*previous_gas = receipt.cumulative_gas_used;
|
||||||
|
|
||||||
|
Some(TxGasAndReward {
|
||||||
|
gas_used,
|
||||||
|
reward: tx.effective_gas_tip(header.base_fee_per_gas).unwrap_or_default(),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect()) else {
|
||||||
|
// If there are no transactions, then we do not have all info on the block
|
||||||
|
return Err(EthApiError::InvalidBlockRange)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Sort the transactions by their rewards in ascending order
|
||||||
|
transactions.sort_by_key(|tx| tx.reward);
|
||||||
|
|
||||||
|
// Find the transaction that corresponds to the given percentile
|
||||||
|
//
|
||||||
|
// We use a `tx_index` here that is shared across all percentiles, since we know
|
||||||
|
// the percentiles are monotonically increasing.
|
||||||
|
let mut tx_index = 0;
|
||||||
|
let mut cumulative_gas_used =
|
||||||
|
transactions.first().map(|tx| tx.gas_used).unwrap_or_default();
|
||||||
|
let mut rewards_in_block = Vec::new();
|
||||||
|
for percentile in percentiles {
|
||||||
|
// Empty blocks should return in a zero row
|
||||||
|
if transactions.is_empty() {
|
||||||
|
rewards_in_block.push(U256::ZERO);
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
let threshold = (header.gas_used as f64 * percentile / 100.) as u64;
|
||||||
|
while cumulative_gas_used < threshold && tx_index < transactions.len() - 1 {
|
||||||
|
tx_index += 1;
|
||||||
|
cumulative_gas_used += transactions[tx_index].gas_used;
|
||||||
|
}
|
||||||
|
rewards_in_block.push(U256::from(transactions[tx_index].reward));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(rewards_in_block)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,10 +14,10 @@ use reth_interfaces::Result;
|
|||||||
use reth_network_api::NetworkInfo;
|
use reth_network_api::NetworkInfo;
|
||||||
use reth_primitives::{Address, BlockId, BlockNumberOrTag, ChainInfo, H256, U256, U64};
|
use reth_primitives::{Address, BlockId, BlockNumberOrTag, ChainInfo, H256, U256, U64};
|
||||||
use reth_provider::{BlockReaderIdExt, EvmEnvProvider, StateProviderBox, StateProviderFactory};
|
use reth_provider::{BlockReaderIdExt, EvmEnvProvider, StateProviderBox, StateProviderFactory};
|
||||||
use reth_rpc_types::{FeeHistoryCache, SyncInfo, SyncStatus};
|
use reth_rpc_types::{SyncInfo, SyncStatus};
|
||||||
use reth_tasks::{TaskSpawner, TokioTaskExecutor};
|
use reth_tasks::{TaskSpawner, TokioTaskExecutor};
|
||||||
use reth_transaction_pool::TransactionPool;
|
use reth_transaction_pool::TransactionPool;
|
||||||
use std::{future::Future, num::NonZeroUsize, sync::Arc};
|
use std::{future::Future, sync::Arc};
|
||||||
use tokio::sync::oneshot;
|
use tokio::sync::oneshot;
|
||||||
|
|
||||||
mod block;
|
mod block;
|
||||||
@ -30,9 +30,6 @@ mod transactions;
|
|||||||
|
|
||||||
pub use transactions::{EthTransactions, TransactionSource};
|
pub use transactions::{EthTransactions, TransactionSource};
|
||||||
|
|
||||||
/// Cache limit of block-level fee history for `eth_feeHistory` RPC method.
|
|
||||||
const FEE_HISTORY_CACHE_LIMIT: usize = 2048;
|
|
||||||
|
|
||||||
/// `Eth` API trait.
|
/// `Eth` API trait.
|
||||||
///
|
///
|
||||||
/// Defines core functionality of the `eth` API implementation.
|
/// Defines core functionality of the `eth` API implementation.
|
||||||
@ -118,9 +115,6 @@ where
|
|||||||
gas_oracle,
|
gas_oracle,
|
||||||
starting_block: U256::from(latest_block),
|
starting_block: U256::from(latest_block),
|
||||||
task_spawner,
|
task_spawner,
|
||||||
fee_history_cache: FeeHistoryCache::new(
|
|
||||||
NonZeroUsize::new(FEE_HISTORY_CACHE_LIMIT).unwrap(),
|
|
||||||
),
|
|
||||||
};
|
};
|
||||||
Self { inner: Arc::new(inner) }
|
Self { inner: Arc::new(inner) }
|
||||||
}
|
}
|
||||||
@ -290,6 +284,4 @@ struct EthApiInner<Provider, Pool, Network> {
|
|||||||
starting_block: U256,
|
starting_block: U256,
|
||||||
/// The type that can spawn tasks which would otherwise block.
|
/// The type that can spawn tasks which would otherwise block.
|
||||||
task_spawner: Box<dyn TaskSpawner>,
|
task_spawner: Box<dyn TaskSpawner>,
|
||||||
/// The cache for fee history entries,
|
|
||||||
fee_history_cache: FeeHistoryCache,
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,8 +12,8 @@ use crate::{
|
|||||||
use jsonrpsee::core::RpcResult as Result;
|
use jsonrpsee::core::RpcResult as Result;
|
||||||
use reth_network_api::NetworkInfo;
|
use reth_network_api::NetworkInfo;
|
||||||
use reth_primitives::{
|
use reth_primitives::{
|
||||||
serde_helper::JsonStorageKey, AccessListWithGasUsed, Address, BlockId, BlockNumberOrTag, Bytes,
|
serde_helper::{num::U64HexOrNumber, JsonStorageKey},
|
||||||
H256, H64, U256, U64,
|
AccessListWithGasUsed, Address, BlockId, BlockNumberOrTag, Bytes, H256, H64, U256, U64,
|
||||||
};
|
};
|
||||||
use reth_provider::{
|
use reth_provider::{
|
||||||
BlockIdReader, BlockReader, BlockReaderIdExt, EvmEnvProvider, HeaderProvider,
|
BlockIdReader, BlockReader, BlockReaderIdExt, EvmEnvProvider, HeaderProvider,
|
||||||
@ -295,8 +295,8 @@ where
|
|||||||
/// Handler for: `eth_feeHistory`
|
/// Handler for: `eth_feeHistory`
|
||||||
async fn fee_history(
|
async fn fee_history(
|
||||||
&self,
|
&self,
|
||||||
block_count: U64,
|
block_count: U64HexOrNumber,
|
||||||
newest_block: BlockId,
|
newest_block: BlockNumberOrTag,
|
||||||
reward_percentiles: Option<Vec<f64>>,
|
reward_percentiles: Option<Vec<f64>>,
|
||||||
) -> Result<FeeHistory> {
|
) -> Result<FeeHistory> {
|
||||||
trace!(target: "rpc::eth", ?block_count, ?newest_block, ?reward_percentiles, "Serving eth_feeHistory");
|
trace!(target: "rpc::eth", ?block_count, ?newest_block, ?reward_percentiles, "Serving eth_feeHistory");
|
||||||
@ -386,50 +386,78 @@ mod tests {
|
|||||||
EthApi,
|
EthApi,
|
||||||
};
|
};
|
||||||
use jsonrpsee::types::error::INVALID_PARAMS_CODE;
|
use jsonrpsee::types::error::INVALID_PARAMS_CODE;
|
||||||
use rand::random;
|
use reth_interfaces::test_utils::{generators, generators::Rng};
|
||||||
use reth_network_api::test_utils::NoopNetwork;
|
use reth_network_api::test_utils::NoopNetwork;
|
||||||
use reth_primitives::{Block, BlockNumberOrTag, Header, TransactionSigned, H256, U256};
|
use reth_primitives::{
|
||||||
use reth_provider::test_utils::{MockEthProvider, NoopProvider};
|
basefee::calculate_next_block_base_fee, Block, BlockNumberOrTag, Header, TransactionSigned,
|
||||||
|
H256, U256,
|
||||||
|
};
|
||||||
|
use reth_provider::{
|
||||||
|
test_utils::{MockEthProvider, NoopProvider},
|
||||||
|
BlockReader, BlockReaderIdExt, EvmEnvProvider, StateProviderFactory,
|
||||||
|
};
|
||||||
use reth_rpc_api::EthApiServer;
|
use reth_rpc_api::EthApiServer;
|
||||||
use reth_transaction_pool::test_utils::testing_pool;
|
use reth_rpc_types::FeeHistory;
|
||||||
|
use reth_transaction_pool::test_utils::{testing_pool, TestPool};
|
||||||
|
|
||||||
#[tokio::test]
|
fn build_test_eth_api<
|
||||||
/// Handler for: `eth_test_fee_history`
|
P: BlockReaderIdExt
|
||||||
async fn test_fee_history() {
|
+ BlockReader
|
||||||
let cache = EthStateCache::spawn(NoopProvider::default(), Default::default());
|
+ EvmEnvProvider
|
||||||
let eth_api = EthApi::new(
|
+ StateProviderFactory
|
||||||
NoopProvider::default(),
|
+ Unpin
|
||||||
|
+ Clone
|
||||||
|
+ 'static,
|
||||||
|
>(
|
||||||
|
provider: P,
|
||||||
|
) -> EthApi<P, TestPool, NoopNetwork> {
|
||||||
|
let cache = EthStateCache::spawn(provider.clone(), Default::default());
|
||||||
|
EthApi::new(
|
||||||
|
provider.clone(),
|
||||||
testing_pool(),
|
testing_pool(),
|
||||||
NoopNetwork,
|
NoopNetwork,
|
||||||
cache.clone(),
|
cache.clone(),
|
||||||
GasPriceOracle::new(NoopProvider::default(), Default::default(), cache),
|
GasPriceOracle::new(provider, Default::default(), cache),
|
||||||
);
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Invalid block range
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_fee_history_empty() {
|
||||||
let response = <EthApi<_, _, _> as EthApiServer>::fee_history(
|
let response = <EthApi<_, _, _> as EthApiServer>::fee_history(
|
||||||
ð_api,
|
&build_test_eth_api(NoopProvider::default()),
|
||||||
1.into(),
|
1.into(),
|
||||||
BlockNumberOrTag::Latest.into(),
|
BlockNumberOrTag::Latest,
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
assert!(response.is_err());
|
assert!(response.is_err());
|
||||||
let error_object = response.unwrap_err();
|
let error_object = response.unwrap_err();
|
||||||
assert_eq!(error_object.code(), INVALID_PARAMS_CODE);
|
assert_eq!(error_object.code(), INVALID_PARAMS_CODE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handler for: `eth_test_fee_history`
|
||||||
|
// TODO: Split this into multiple tests, and add tests for percentiles.
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_fee_history() {
|
||||||
|
let mut rng = generators::rng();
|
||||||
|
|
||||||
let block_count = 10;
|
let block_count = 10;
|
||||||
let newest_block = 1337;
|
let newest_block = 1337;
|
||||||
|
|
||||||
|
// Build mock data
|
||||||
let mut oldest_block = None;
|
let mut oldest_block = None;
|
||||||
let mut gas_used_ratios = Vec::new();
|
let mut gas_used_ratios = Vec::new();
|
||||||
let mut base_fees_per_gas = Vec::new();
|
let mut base_fees_per_gas = Vec::new();
|
||||||
|
let mut last_header = None;
|
||||||
let mock_provider = MockEthProvider::default();
|
let mock_provider = MockEthProvider::default();
|
||||||
|
|
||||||
for i in (0..=block_count).rev() {
|
for i in (0..block_count).rev() {
|
||||||
let hash = H256::random();
|
let hash = H256::random();
|
||||||
let gas_limit: u64 = random();
|
let gas_limit: u64 = rng.gen();
|
||||||
let gas_used: u64 = random();
|
let gas_used: u64 = rng.gen();
|
||||||
let base_fee_per_gas: Option<u64> = random::<bool>().then(random);
|
// Note: Generates a u32 to avoid overflows later
|
||||||
|
let base_fee_per_gas: Option<u64> = rng.gen::<bool>().then(|| rng.gen::<u32>() as u64);
|
||||||
|
|
||||||
let header = Header {
|
let header = Header {
|
||||||
number: newest_block - i,
|
number: newest_block - i,
|
||||||
@ -438,10 +466,11 @@ mod tests {
|
|||||||
base_fee_per_gas,
|
base_fee_per_gas,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
last_header = Some(header.clone());
|
||||||
|
|
||||||
let mut transactions = vec![];
|
let mut transactions = vec![];
|
||||||
for _ in 0..100 {
|
for _ in 0..100 {
|
||||||
let random_fee: u128 = random();
|
let random_fee: u128 = rng.gen();
|
||||||
|
|
||||||
if let Some(base_fee_per_gas) = header.base_fee_per_gas {
|
if let Some(base_fee_per_gas) = header.base_fee_per_gas {
|
||||||
let transaction = TransactionSigned {
|
let transaction = TransactionSigned {
|
||||||
@ -480,17 +509,17 @@ mod tests {
|
|||||||
.push(base_fee_per_gas.map(|fee| U256::try_from(fee).unwrap()).unwrap_or_default());
|
.push(base_fee_per_gas.map(|fee| U256::try_from(fee).unwrap()).unwrap_or_default());
|
||||||
}
|
}
|
||||||
|
|
||||||
gas_used_ratios.pop();
|
// Add final base fee (for the next block outside of the request)
|
||||||
|
let last_header = last_header.unwrap();
|
||||||
|
base_fees_per_gas.push(U256::from(calculate_next_block_base_fee(
|
||||||
|
last_header.gas_used,
|
||||||
|
last_header.gas_limit,
|
||||||
|
last_header.base_fee_per_gas.unwrap_or_default(),
|
||||||
|
)));
|
||||||
|
|
||||||
let cache = EthStateCache::spawn(mock_provider.clone(), Default::default());
|
let eth_api = build_test_eth_api(mock_provider);
|
||||||
let eth_api = EthApi::new(
|
|
||||||
mock_provider.clone(),
|
|
||||||
testing_pool(),
|
|
||||||
NoopNetwork,
|
|
||||||
cache.clone(),
|
|
||||||
GasPriceOracle::new(mock_provider, Default::default(), cache.clone()),
|
|
||||||
);
|
|
||||||
|
|
||||||
|
// Invalid block range (request is before genesis)
|
||||||
let response = <EthApi<_, _, _> as EthApiServer>::fee_history(
|
let response = <EthApi<_, _, _> as EthApiServer>::fee_history(
|
||||||
ð_api,
|
ð_api,
|
||||||
(newest_block + 1).into(),
|
(newest_block + 1).into(),
|
||||||
@ -502,20 +531,85 @@ mod tests {
|
|||||||
let error_object = response.unwrap_err();
|
let error_object = response.unwrap_err();
|
||||||
assert_eq!(error_object.code(), INVALID_PARAMS_CODE);
|
assert_eq!(error_object.code(), INVALID_PARAMS_CODE);
|
||||||
|
|
||||||
// newest_block is finalized
|
// Invalid block range (request is in in the future)
|
||||||
|
let response = <EthApi<_, _, _> as EthApiServer>::fee_history(
|
||||||
|
ð_api,
|
||||||
|
(1).into(),
|
||||||
|
(newest_block + 1000).into(),
|
||||||
|
Some(vec![10.0]),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
assert!(response.is_err());
|
||||||
|
let error_object = response.unwrap_err();
|
||||||
|
assert_eq!(error_object.code(), INVALID_PARAMS_CODE);
|
||||||
|
|
||||||
|
// Requesting no block should result in a default response
|
||||||
|
let response = <EthApi<_, _, _> as EthApiServer>::fee_history(
|
||||||
|
ð_api,
|
||||||
|
(0).into(),
|
||||||
|
(newest_block).into(),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
response,
|
||||||
|
FeeHistory::default(),
|
||||||
|
"none: requesting no block should yield a default response"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Requesting a single block should return 1 block (+ base fee for the next block over)
|
||||||
|
let fee_history = eth_api.fee_history(1, (newest_block).into(), None).await.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
&fee_history.base_fee_per_gas,
|
||||||
|
&base_fees_per_gas[base_fees_per_gas.len() - 2..],
|
||||||
|
"one: base fee per gas is incorrect"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
fee_history.base_fee_per_gas.len(),
|
||||||
|
2,
|
||||||
|
"one: should return base fee of the next block as well"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
&fee_history.gas_used_ratio,
|
||||||
|
&gas_used_ratios[gas_used_ratios.len() - 1..],
|
||||||
|
"one: gas used ratio is incorrect"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
fee_history.oldest_block,
|
||||||
|
U256::from(newest_block),
|
||||||
|
"one: oldest block is incorrect"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
fee_history.reward.is_none(),
|
||||||
|
"one: no percentiles were requested, so there should be no rewards result"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Requesting all blocks should be ok
|
||||||
let fee_history =
|
let fee_history =
|
||||||
eth_api.fee_history(block_count, (newest_block - 1).into(), None).await.unwrap();
|
eth_api.fee_history(block_count, (newest_block).into(), None).await.unwrap();
|
||||||
|
|
||||||
assert_eq!(fee_history.base_fee_per_gas, base_fees_per_gas);
|
assert_eq!(
|
||||||
assert_eq!(fee_history.gas_used_ratio, gas_used_ratios);
|
&fee_history.base_fee_per_gas, &base_fees_per_gas,
|
||||||
assert_eq!(fee_history.oldest_block, U256::from(newest_block - block_count));
|
"all: base fee per gas is incorrect"
|
||||||
|
);
|
||||||
// newest_block is pending
|
assert_eq!(
|
||||||
let fee_history =
|
fee_history.base_fee_per_gas.len() as u64,
|
||||||
eth_api.fee_history(block_count, (newest_block - 1).into(), None).await.unwrap();
|
block_count + 1,
|
||||||
|
"all: should return base fee of the next block as well"
|
||||||
assert_eq!(fee_history.base_fee_per_gas, base_fees_per_gas);
|
);
|
||||||
assert_eq!(fee_history.gas_used_ratio, gas_used_ratios);
|
assert_eq!(
|
||||||
assert_eq!(fee_history.oldest_block, U256::from(newest_block - block_count));
|
&fee_history.gas_used_ratio, &gas_used_ratios,
|
||||||
|
"all: gas used ratio is incorrect"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
fee_history.oldest_block,
|
||||||
|
U256::from(newest_block - block_count + 1),
|
||||||
|
"all: oldest block is incorrect"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
fee_history.reward.is_none(),
|
||||||
|
"all: no percentiles were requested, so there should be no rewards result"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -65,8 +65,8 @@ pub enum EthApiError {
|
|||||||
#[error("invalid tracer config")]
|
#[error("invalid tracer config")]
|
||||||
InvalidTracerConfig,
|
InvalidTracerConfig,
|
||||||
/// Percentile array is invalid
|
/// Percentile array is invalid
|
||||||
#[error("invalid reward percentile")]
|
#[error("invalid reward percentiles")]
|
||||||
InvalidRewardPercentile(f64),
|
InvalidRewardPercentiles,
|
||||||
/// Error thrown when a spawned tracing task failed to deliver an anticipated response.
|
/// Error thrown when a spawned tracing task failed to deliver an anticipated response.
|
||||||
#[error("internal error while tracing")]
|
#[error("internal error while tracing")]
|
||||||
InternalTracingError,
|
InternalTracingError,
|
||||||
@ -101,7 +101,7 @@ impl From<EthApiError> for ErrorObject<'static> {
|
|||||||
EthApiError::Unsupported(msg) => internal_rpc_err(msg),
|
EthApiError::Unsupported(msg) => internal_rpc_err(msg),
|
||||||
EthApiError::InternalJsTracerError(msg) => internal_rpc_err(msg),
|
EthApiError::InternalJsTracerError(msg) => internal_rpc_err(msg),
|
||||||
EthApiError::InvalidParams(msg) => invalid_params_rpc_err(msg),
|
EthApiError::InvalidParams(msg) => invalid_params_rpc_err(msg),
|
||||||
EthApiError::InvalidRewardPercentile(msg) => internal_rpc_err(msg.to_string()),
|
EthApiError::InvalidRewardPercentiles => internal_rpc_err(error.to_string()),
|
||||||
err @ EthApiError::InternalTracingError => internal_rpc_err(err.to_string()),
|
err @ EthApiError::InternalTracingError => internal_rpc_err(err.to_string()),
|
||||||
err @ EthApiError::InternalEthError => internal_rpc_err(err.to_string()),
|
err @ EthApiError::InternalEthError => internal_rpc_err(err.to_string()),
|
||||||
}
|
}
|
||||||
|
|||||||
@ -111,6 +111,11 @@ where
|
|||||||
Self { provider, oracle_config, last_price: Default::default(), cache }
|
Self { provider, oracle_config, last_price: Default::default(), cache }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the configuration of the gas price oracle.
|
||||||
|
pub fn config(&self) -> &GasPriceOracleConfig {
|
||||||
|
&self.oracle_config
|
||||||
|
}
|
||||||
|
|
||||||
/// Suggests a gas price estimate based on recent blocks, using the configured percentile.
|
/// Suggests a gas price estimate based on recent blocks, using the configured percentile.
|
||||||
pub async fn suggest_tip_cap(&self) -> EthResult<U256> {
|
pub async fn suggest_tip_cap(&self) -> EthResult<U256> {
|
||||||
let header = self
|
let header = self
|
||||||
|
|||||||
Reference in New Issue
Block a user