mirror of
https://github.com/hl-archive-node/nanoreth.git
synced 2025-12-06 10:59:55 +00:00
feat(rpc, storage): eth_feeHistory except reward field (#1275)
This commit is contained in:
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -4691,12 +4691,14 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"jsonrpsee-types",
|
"jsonrpsee-types",
|
||||||
|
"lru 0.9.0",
|
||||||
"reth-network-api",
|
"reth-network-api",
|
||||||
"reth-primitives",
|
"reth-primitives",
|
||||||
"reth-rlp",
|
"reth-rlp",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|||||||
@ -353,6 +353,7 @@ mod tests {
|
|||||||
hex_literal::hex, Account, Address, BlockHash, Bytes, Header, Signature, TransactionKind,
|
hex_literal::hex, Account, Address, BlockHash, Bytes, Header, Signature, TransactionKind,
|
||||||
TransactionSigned, MAINNET, U256,
|
TransactionSigned, MAINNET, U256,
|
||||||
};
|
};
|
||||||
|
use std::ops::RangeBounds;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
@ -422,6 +423,10 @@ mod tests {
|
|||||||
fn header_td(&self, _hash: &BlockHash) -> Result<Option<U256>> {
|
fn header_td(&self, _hash: &BlockHash) -> Result<Option<U256>> {
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn headers_range(&self, _range: impl RangeBounds<BlockNumber>) -> Result<Vec<Header>> {
|
||||||
|
Ok(vec![])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mock_tx(nonce: u64) -> TransactionSignedEcRecovered {
|
fn mock_tx(nonce: u64) -> TransactionSignedEcRecovered {
|
||||||
|
|||||||
@ -25,7 +25,7 @@ pub enum Error {
|
|||||||
/// Failed to initiate a cursor.
|
/// Failed to initiate a cursor.
|
||||||
#[error("Initialization of cursor errored with code: {0:?}")]
|
#[error("Initialization of cursor errored with code: {0:?}")]
|
||||||
InitCursor(u32),
|
InitCursor(u32),
|
||||||
/// Failed to decode a key from a table..
|
/// Failed to decode a key from a table.
|
||||||
#[error("Error decoding value.")]
|
#[error("Error decoding value.")]
|
||||||
DecodeError,
|
DecodeError,
|
||||||
}
|
}
|
||||||
|
|||||||
@ -184,6 +184,15 @@ impl From<BlockHash> for BlockId {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<BlockHashOrNumber> for BlockId {
|
||||||
|
fn from(value: BlockHashOrNumber) -> Self {
|
||||||
|
match value {
|
||||||
|
BlockHashOrNumber::Hash(hash) => H256::from(hash.0).into(),
|
||||||
|
BlockHashOrNumber::Number(number) => number.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Serialize for BlockId {
|
impl Serialize for BlockId {
|
||||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
where
|
where
|
||||||
|
|||||||
@ -174,8 +174,8 @@ pub trait EthApi {
|
|||||||
#[method(name = "eth_feeHistory")]
|
#[method(name = "eth_feeHistory")]
|
||||||
async fn fee_history(
|
async fn fee_history(
|
||||||
&self,
|
&self,
|
||||||
block_count: U256,
|
block_count: U64,
|
||||||
newest_block: BlockNumber,
|
newest_block: BlockId,
|
||||||
reward_percentiles: Option<Vec<f64>>,
|
reward_percentiles: Option<Vec<f64>>,
|
||||||
) -> Result<FeeHistory>;
|
) -> Result<FeeHistory>;
|
||||||
|
|
||||||
|
|||||||
@ -26,12 +26,12 @@
|
|||||||
//!
|
//!
|
||||||
//! ```
|
//! ```
|
||||||
//! use reth_network_api::{NetworkInfo, Peers};
|
//! use reth_network_api::{NetworkInfo, Peers};
|
||||||
//! use reth_provider::{BlockProvider, StateProviderFactory};
|
//! use reth_provider::{BlockProvider, HeaderProvider, StateProviderFactory};
|
||||||
//! use reth_rpc_builder::{RethRpcModule, RpcModuleBuilder, RpcServerConfig, ServerBuilder, TransportRpcModuleConfig};
|
//! use reth_rpc_builder::{RethRpcModule, RpcModuleBuilder, RpcServerConfig, ServerBuilder, TransportRpcModuleConfig};
|
||||||
//! use reth_transaction_pool::TransactionPool;
|
//! use reth_transaction_pool::TransactionPool;
|
||||||
//! pub async fn launch<Client, Pool, Network>(client: Client, pool: Pool, network: Network)
|
//! pub async fn launch<Client, Pool, Network>(client: Client, pool: Pool, network: Network)
|
||||||
//! where
|
//! where
|
||||||
//! Client: BlockProvider + StateProviderFactory + Clone + 'static,
|
//! Client: BlockProvider + HeaderProvider + StateProviderFactory + Clone + 'static,
|
||||||
//! Pool: TransactionPool + Clone + 'static,
|
//! Pool: TransactionPool + Clone + 'static,
|
||||||
//! Network: NetworkInfo + Peers + Clone + 'static,
|
//! Network: NetworkInfo + Peers + Clone + 'static,
|
||||||
//! {
|
//! {
|
||||||
@ -60,7 +60,7 @@ use jsonrpsee::{
|
|||||||
use reth_ipc::server::IpcServer;
|
use reth_ipc::server::IpcServer;
|
||||||
pub use reth_ipc::server::{Builder as IpcServerBuilder, Endpoint};
|
pub use reth_ipc::server::{Builder as IpcServerBuilder, Endpoint};
|
||||||
use reth_network_api::{NetworkInfo, Peers};
|
use reth_network_api::{NetworkInfo, Peers};
|
||||||
use reth_provider::{BlockProvider, StateProviderFactory};
|
use reth_provider::{BlockProvider, HeaderProvider, StateProviderFactory};
|
||||||
use reth_rpc::{AdminApi, DebugApi, EthApi, NetApi, TraceApi, Web3Api};
|
use reth_rpc::{AdminApi, DebugApi, EthApi, NetApi, TraceApi, Web3Api};
|
||||||
use reth_rpc_api::servers::*;
|
use reth_rpc_api::servers::*;
|
||||||
use reth_transaction_pool::TransactionPool;
|
use reth_transaction_pool::TransactionPool;
|
||||||
@ -96,7 +96,7 @@ pub async fn launch<Client, Pool, Network>(
|
|||||||
server_config: impl Into<RpcServerConfig>,
|
server_config: impl Into<RpcServerConfig>,
|
||||||
) -> Result<RpcServerHandle, RpcError>
|
) -> Result<RpcServerHandle, RpcError>
|
||||||
where
|
where
|
||||||
Client: BlockProvider + StateProviderFactory + Clone + 'static,
|
Client: BlockProvider + HeaderProvider + StateProviderFactory + Clone + 'static,
|
||||||
Pool: TransactionPool + Clone + 'static,
|
Pool: TransactionPool + Clone + 'static,
|
||||||
Network: NetworkInfo + Peers + Clone + 'static,
|
Network: NetworkInfo + Peers + Clone + 'static,
|
||||||
{
|
{
|
||||||
@ -159,7 +159,7 @@ impl<Client, Pool, Network> RpcModuleBuilder<Client, Pool, Network> {
|
|||||||
|
|
||||||
impl<Client, Pool, Network> RpcModuleBuilder<Client, Pool, Network>
|
impl<Client, Pool, Network> RpcModuleBuilder<Client, Pool, Network>
|
||||||
where
|
where
|
||||||
Client: BlockProvider + StateProviderFactory + Clone + 'static,
|
Client: BlockProvider + HeaderProvider + StateProviderFactory + Clone + 'static,
|
||||||
Pool: TransactionPool + Clone + 'static,
|
Pool: TransactionPool + Clone + 'static,
|
||||||
Network: NetworkInfo + Peers + Clone + 'static,
|
Network: NetworkInfo + Peers + Clone + 'static,
|
||||||
{
|
{
|
||||||
@ -260,7 +260,7 @@ impl RpcModuleSelection {
|
|||||||
network: Network,
|
network: Network,
|
||||||
) -> RpcModule<()>
|
) -> RpcModule<()>
|
||||||
where
|
where
|
||||||
Client: BlockProvider + StateProviderFactory + Clone + 'static,
|
Client: BlockProvider + HeaderProvider + StateProviderFactory + Clone + 'static,
|
||||||
Pool: TransactionPool + Clone + 'static,
|
Pool: TransactionPool + Clone + 'static,
|
||||||
Network: NetworkInfo + Peers + Clone + 'static,
|
Network: NetworkInfo + Peers + Clone + 'static,
|
||||||
{
|
{
|
||||||
@ -399,7 +399,7 @@ where
|
|||||||
|
|
||||||
impl<Client, Pool, Network> RethModuleRegistry<Client, Pool, Network>
|
impl<Client, Pool, Network> RethModuleRegistry<Client, Pool, Network>
|
||||||
where
|
where
|
||||||
Client: BlockProvider + StateProviderFactory + Clone + 'static,
|
Client: BlockProvider + HeaderProvider + StateProviderFactory + Clone + 'static,
|
||||||
Pool: TransactionPool + Clone + 'static,
|
Pool: TransactionPool + Clone + 'static,
|
||||||
Network: NetworkInfo + Peers + Clone + 'static,
|
Network: NetworkInfo + Peers + Clone + 'static,
|
||||||
{
|
{
|
||||||
|
|||||||
@ -66,6 +66,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();
|
||||||
|
|
||||||
// Unimplemented
|
// Unimplemented
|
||||||
assert!(is_unimplemented(EthApiClient::syncing(client).await.err().unwrap()));
|
assert!(is_unimplemented(EthApiClient::syncing(client).await.err().unwrap()));
|
||||||
@ -125,9 +126,6 @@ where
|
|||||||
EthApiClient::estimate_gas(client, call_request.clone(), None).await.err().unwrap()
|
EthApiClient::estimate_gas(client, call_request.clone(), None).await.err().unwrap()
|
||||||
));
|
));
|
||||||
assert!(is_unimplemented(EthApiClient::gas_price(client).await.err().unwrap()));
|
assert!(is_unimplemented(EthApiClient::gas_price(client).await.err().unwrap()));
|
||||||
assert!(is_unimplemented(
|
|
||||||
EthApiClient::fee_history(client, U256::default(), block_number, None).await.err().unwrap()
|
|
||||||
));
|
|
||||||
assert!(is_unimplemented(EthApiClient::max_priority_fee_per_gas(client).await.err().unwrap()));
|
assert!(is_unimplemented(EthApiClient::max_priority_fee_per_gas(client).await.err().unwrap()));
|
||||||
assert!(is_unimplemented(EthApiClient::is_mining(client).await.err().unwrap()));
|
assert!(is_unimplemented(EthApiClient::is_mining(client).await.err().unwrap()));
|
||||||
assert!(is_unimplemented(EthApiClient::hashrate(client).await.err().unwrap()));
|
assert!(is_unimplemented(EthApiClient::hashrate(client).await.err().unwrap()));
|
||||||
|
|||||||
@ -17,8 +17,12 @@ reth-network-api = { path = "../../net/network-api"}
|
|||||||
# errors
|
# errors
|
||||||
thiserror = "1.0"
|
thiserror = "1.0"
|
||||||
|
|
||||||
|
# async
|
||||||
|
tokio = { version = "1", features = ["sync"] }
|
||||||
|
|
||||||
# misc
|
# misc
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
bytes = "1.2"
|
bytes = "1.2"
|
||||||
jsonrpsee-types = { version = "0.16" }
|
jsonrpsee-types = { version = "0.16" }
|
||||||
|
lru = "0.9"
|
||||||
|
|||||||
@ -1,8 +1,11 @@
|
|||||||
use reth_primitives::U256;
|
use lru::LruCache;
|
||||||
|
use reth_primitives::{BlockNumber, H256, U256};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::{num::NonZeroUsize, sync::Arc};
|
||||||
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
/// Response type for `eth_feeHistory`
|
/// Response type for `eth_feeHistory`
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
#[derive(Debug, Clone, Default, 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.
|
||||||
@ -20,3 +23,29 @@ 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>>,
|
||||||
|
}
|
||||||
|
|||||||
@ -17,7 +17,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;
|
pub use fee::{FeeHistory, FeeHistoryCache, FeeHistoryCacheItem};
|
||||||
pub use filter::*;
|
pub use filter::*;
|
||||||
pub use index::Index;
|
pub use index::Index;
|
||||||
pub use log::Log;
|
pub use log::Log;
|
||||||
|
|||||||
@ -16,8 +16,8 @@ reth-rpc-api = { path = "../rpc-api" }
|
|||||||
reth-rlp = { path = "../../rlp" }
|
reth-rlp = { path = "../../rlp" }
|
||||||
reth-rpc-types = { path = "../rpc-types" }
|
reth-rpc-types = { path = "../rpc-types" }
|
||||||
reth-provider = { path = "../../storage/provider", features = ["test-utils"] }
|
reth-provider = { path = "../../storage/provider", features = ["test-utils"] }
|
||||||
reth-transaction-pool = { path = "../../transaction-pool", features=["test-utils"]}
|
reth-transaction-pool = { path = "../../transaction-pool", features = ["test-utils"]}
|
||||||
reth-network-api = { path = "../../net/network-api" }
|
reth-network-api = { path = "../../net/network-api", features = ["test-utils"] }
|
||||||
reth-rpc-engine-api = { path = "../rpc-engine-api" }
|
reth-rpc-engine-api = { path = "../rpc-engine-api" }
|
||||||
reth-tasks = { path = "../../tasks" }
|
reth-tasks = { path = "../../tasks" }
|
||||||
|
|
||||||
|
|||||||
@ -12,7 +12,9 @@ use reth_primitives::{
|
|||||||
Address, ChainInfo, TransactionSigned, H256, U64,
|
Address, ChainInfo, TransactionSigned, H256, U64,
|
||||||
};
|
};
|
||||||
use reth_provider::{BlockProvider, StateProviderFactory};
|
use reth_provider::{BlockProvider, StateProviderFactory};
|
||||||
|
use std::num::NonZeroUsize;
|
||||||
|
|
||||||
|
use reth_rpc_types::FeeHistoryCache;
|
||||||
use reth_transaction_pool::TransactionPool;
|
use reth_transaction_pool::TransactionPool;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
@ -21,6 +23,9 @@ mod server;
|
|||||||
mod state;
|
mod state;
|
||||||
mod transactions;
|
mod transactions;
|
||||||
|
|
||||||
|
/// 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.
|
||||||
@ -55,13 +60,19 @@ pub trait EthApiSpec: Send + Sync {
|
|||||||
pub struct EthApi<Client, Pool, Network> {
|
pub struct EthApi<Client, Pool, Network> {
|
||||||
/// All nested fields bundled together.
|
/// All nested fields bundled together.
|
||||||
inner: Arc<EthApiInner<Client, Pool, Network>>,
|
inner: Arc<EthApiInner<Client, Pool, Network>>,
|
||||||
|
fee_history_cache: FeeHistoryCache,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Client, Pool, Network> EthApi<Client, Pool, Network> {
|
impl<Client, Pool, Network> EthApi<Client, Pool, Network> {
|
||||||
/// Creates a new, shareable instance.
|
/// Creates a new, shareable instance.
|
||||||
pub fn new(client: Client, pool: Pool, network: Network) -> Self {
|
pub fn new(client: Client, pool: Pool, network: Network) -> Self {
|
||||||
let inner = EthApiInner { client, pool, network, signers: Default::default() };
|
let inner = EthApiInner { client, pool, network, signers: Default::default() };
|
||||||
Self { inner: Arc::new(inner) }
|
Self {
|
||||||
|
inner: Arc::new(inner),
|
||||||
|
fee_history_cache: FeeHistoryCache::new(
|
||||||
|
NonZeroUsize::new(FEE_HISTORY_CACHE_LIMIT).unwrap(),
|
||||||
|
),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the inner `Client`
|
/// Returns the inner `Client`
|
||||||
|
|||||||
@ -2,22 +2,23 @@
|
|||||||
//! Handles RPC requests for the `eth_` namespace.
|
//! Handles RPC requests for the `eth_` namespace.
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
eth::api::EthApi,
|
eth::{api::EthApi, error::EthApiError},
|
||||||
result::{internal_rpc_err, ToRpcResult},
|
result::{internal_rpc_err, ToRpcResult},
|
||||||
};
|
};
|
||||||
use jsonrpsee::core::RpcResult as Result;
|
use jsonrpsee::core::RpcResult as Result;
|
||||||
use reth_primitives::{
|
use reth_primitives::{
|
||||||
rpc::{transaction::eip2930::AccessListWithGasUsed, BlockId, BlockNumber},
|
rpc::{transaction::eip2930::AccessListWithGasUsed, BlockId, BlockNumber},
|
||||||
Address, Bytes, H256, H64, U256, U64,
|
Address, Bytes, Header, H256, H64, U256, U64,
|
||||||
};
|
};
|
||||||
use reth_provider::{BlockProvider, StateProviderFactory};
|
use reth_provider::{BlockProvider, HeaderProvider, StateProviderFactory};
|
||||||
use reth_rpc_api::EthApiServer;
|
use reth_rpc_api::EthApiServer;
|
||||||
use reth_rpc_types::{
|
use reth_rpc_types::{
|
||||||
CallRequest, EIP1186AccountProofResponse, FeeHistory, Index, RichBlock, SyncStatus,
|
CallRequest, EIP1186AccountProofResponse, FeeHistory, FeeHistoryCacheItem, Index, RichBlock,
|
||||||
TransactionReceipt, TransactionRequest, Work,
|
SyncStatus, TransactionReceipt, TransactionRequest, Work,
|
||||||
};
|
};
|
||||||
use reth_transaction_pool::TransactionPool;
|
use reth_transaction_pool::TransactionPool;
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
use super::EthApiSpec;
|
use super::EthApiSpec;
|
||||||
|
|
||||||
@ -26,7 +27,7 @@ impl<Client, Pool, Network> EthApiServer for EthApi<Client, Pool, Network>
|
|||||||
where
|
where
|
||||||
Self: EthApiSpec,
|
Self: EthApiSpec,
|
||||||
Pool: TransactionPool + 'static,
|
Pool: TransactionPool + 'static,
|
||||||
Client: BlockProvider + StateProviderFactory + 'static,
|
Client: BlockProvider + HeaderProvider + StateProviderFactory + 'static,
|
||||||
Network: 'static,
|
Network: 'static,
|
||||||
{
|
{
|
||||||
async fn protocol_version(&self) -> Result<U64> {
|
async fn protocol_version(&self) -> Result<U64> {
|
||||||
@ -178,13 +179,110 @@ where
|
|||||||
Err(internal_rpc_err("unimplemented"))
|
Err(internal_rpc_err("unimplemented"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FeeHistory is calculated based on lazy evaluation of fees for historical blocks, and further
|
||||||
|
// caching of it in the LRU cache.
|
||||||
|
// When new RPC call is executed, the cache gets locked, we check it for the historical fees
|
||||||
|
// according to the requested block range, and fill any cache misses (in both RPC response
|
||||||
|
// and cache itself) with the actual data queried from the database.
|
||||||
|
// To minimize the number of database seeks required to query the missing data, we calculate the
|
||||||
|
// first non-cached block number and last non-cached block number. After that, we query this
|
||||||
|
// range of consecutive blocks from the database.
|
||||||
async fn fee_history(
|
async fn fee_history(
|
||||||
&self,
|
&self,
|
||||||
_block_count: U256,
|
block_count: U64,
|
||||||
_newest_block: BlockNumber,
|
newest_block: BlockId,
|
||||||
_reward_percentiles: Option<Vec<f64>>,
|
_reward_percentiles: Option<Vec<f64>>,
|
||||||
) -> Result<FeeHistory> {
|
) -> Result<FeeHistory> {
|
||||||
Err(internal_rpc_err("unimplemented"))
|
let block_count = block_count.as_u64();
|
||||||
|
|
||||||
|
if block_count == 0 {
|
||||||
|
return Ok(FeeHistory::default())
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(end_block) = self.inner.client.block_number_for_id(newest_block).to_rpc_result()? else { return Err(EthApiError::UnknownBlockNumber.into())};
|
||||||
|
|
||||||
|
if end_block < block_count {
|
||||||
|
return Err(EthApiError::InvalidBlockRange.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
let start_block = end_block - block_count;
|
||||||
|
|
||||||
|
let mut fee_history_cache = self.fee_history_cache.0.lock().await;
|
||||||
|
|
||||||
|
// Sorted map that's populated in two rounds:
|
||||||
|
// 1. Cache entries until first non-cached block
|
||||||
|
// 2. Database query from the first non-cached block
|
||||||
|
let mut fee_history_cache_items =
|
||||||
|
BTreeMap::<reth_primitives::BlockNumber, FeeHistoryCacheItem>::new();
|
||||||
|
|
||||||
|
let mut first_non_cached_block = None;
|
||||||
|
let mut last_non_cached_block = None;
|
||||||
|
for block in start_block..=end_block {
|
||||||
|
// 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
|
||||||
|
// and ending with the last
|
||||||
|
if let (Some(start_block), Some(end_block)) =
|
||||||
|
(first_non_cached_block, last_non_cached_block)
|
||||||
|
{
|
||||||
|
let headers: Vec<Header> =
|
||||||
|
self.inner.client.headers_range(start_block..=end_block).to_rpc_result()?;
|
||||||
|
|
||||||
|
// 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.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
for header in headers {
|
||||||
|
let base_fee_per_gas = 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 fee_history_cache_item = FeeHistoryCacheItem {
|
||||||
|
hash: None,
|
||||||
|
base_fee_per_gas,
|
||||||
|
gas_used_ratio,
|
||||||
|
reward: None, // TODO: calculate rewards per transaction
|
||||||
|
};
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let oldest_block_hash =
|
||||||
|
self.inner.client.block_hash(start_block.try_into().unwrap()).to_rpc_result()?.unwrap();
|
||||||
|
|
||||||
|
fee_history_cache_items.get_mut(&start_block).unwrap().hash = Some(oldest_block_hash);
|
||||||
|
fee_history_cache.get_mut(&start_block).unwrap().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
|
||||||
|
Ok(FeeHistory {
|
||||||
|
base_fee_per_gas: fee_history_cache_items
|
||||||
|
.values()
|
||||||
|
.map(|item| item.base_fee_per_gas)
|
||||||
|
.collect(),
|
||||||
|
gas_used_ratio: fee_history_cache_items
|
||||||
|
.values()
|
||||||
|
.map(|item| item.gas_used_ratio)
|
||||||
|
.collect(),
|
||||||
|
oldest_block: U256::from_be_bytes(oldest_block_hash.0),
|
||||||
|
reward: None,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn max_priority_fee_per_gas(&self) -> Result<U256> {
|
async fn max_priority_fee_per_gas(&self) -> Result<U256> {
|
||||||
@ -240,3 +338,77 @@ where
|
|||||||
Err(internal_rpc_err("unimplemented"))
|
Err(internal_rpc_err("unimplemented"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use jsonrpsee::{
|
||||||
|
core::{error::Error as RpcError, RpcResult},
|
||||||
|
types::error::{CallError, INVALID_PARAMS_CODE},
|
||||||
|
};
|
||||||
|
use rand::random;
|
||||||
|
use reth_network_api::test_utils::NoopNetwork;
|
||||||
|
use reth_primitives::{rpc::BlockNumber, Block, Header, H256, U256};
|
||||||
|
use reth_provider::test_utils::{MockEthProvider, NoopProvider};
|
||||||
|
use reth_rpc_api::EthApiServer;
|
||||||
|
use reth_transaction_pool::test_utils::testing_pool;
|
||||||
|
|
||||||
|
use crate::EthApi;
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_fee_history() {
|
||||||
|
let eth_api = EthApi::new(NoopProvider::default(), testing_pool(), NoopNetwork::default());
|
||||||
|
|
||||||
|
let response = eth_api.fee_history(1.into(), BlockNumber::Latest.into(), None).await;
|
||||||
|
assert!(matches!(response, RpcResult::Err(RpcError::Call(CallError::Custom(_)))));
|
||||||
|
let Err(RpcError::Call(CallError::Custom(error_object))) = response else { unreachable!() };
|
||||||
|
assert_eq!(error_object.code(), INVALID_PARAMS_CODE);
|
||||||
|
|
||||||
|
let block_count = 10;
|
||||||
|
let newest_block = 1337;
|
||||||
|
|
||||||
|
let mut oldest_block = None;
|
||||||
|
let mut gas_used_ratios = Vec::new();
|
||||||
|
let mut base_fees_per_gas = Vec::new();
|
||||||
|
|
||||||
|
let mock_provider = MockEthProvider::default();
|
||||||
|
|
||||||
|
for i in (0..=block_count).rev() {
|
||||||
|
let hash = H256::random();
|
||||||
|
let gas_limit: u64 = random();
|
||||||
|
let gas_used: u64 = random();
|
||||||
|
let base_fee_per_gas: Option<u64> =
|
||||||
|
if random::<bool>() { Some(random()) } else { None };
|
||||||
|
|
||||||
|
let header = Header {
|
||||||
|
number: newest_block - i,
|
||||||
|
gas_limit,
|
||||||
|
gas_used,
|
||||||
|
base_fee_per_gas,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
mock_provider.add_block(hash, Block { header: header.clone(), ..Default::default() });
|
||||||
|
mock_provider.add_header(hash, header);
|
||||||
|
|
||||||
|
oldest_block.get_or_insert(hash);
|
||||||
|
gas_used_ratios.push(gas_used as f64 / gas_limit as f64);
|
||||||
|
base_fees_per_gas
|
||||||
|
.push(base_fee_per_gas.map(|fee| U256::try_from(fee).unwrap()).unwrap_or_default());
|
||||||
|
}
|
||||||
|
|
||||||
|
let eth_api = EthApi::new(mock_provider, testing_pool(), NoopNetwork::default());
|
||||||
|
|
||||||
|
let response =
|
||||||
|
eth_api.fee_history((newest_block + 1).into(), newest_block.into(), None).await;
|
||||||
|
assert!(matches!(response, RpcResult::Err(RpcError::Call(CallError::Custom(_)))));
|
||||||
|
let Err(RpcError::Call(CallError::Custom(error_object))) = response else { unreachable!() };
|
||||||
|
assert_eq!(error_object.code(), INVALID_PARAMS_CODE);
|
||||||
|
|
||||||
|
let fee_history =
|
||||||
|
eth_api.fee_history(block_count.into(), newest_block.into(), None).await.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(fee_history.base_fee_per_gas, base_fees_per_gas);
|
||||||
|
assert_eq!(fee_history.gas_used_ratio, gas_used_ratios);
|
||||||
|
assert_eq!(fee_history.oldest_block, U256::from_be_bytes(oldest_block.unwrap().0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -22,8 +22,9 @@ pub(crate) enum EthApiError {
|
|||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
PoolError(GethTxPoolError),
|
PoolError(GethTxPoolError),
|
||||||
#[error("Unknown block number")]
|
#[error("Unknown block number")]
|
||||||
// TODO return -32602 here
|
|
||||||
UnknownBlockNumber,
|
UnknownBlockNumber,
|
||||||
|
#[error("Invalid block range")]
|
||||||
|
InvalidBlockRange,
|
||||||
/// Other internal error
|
/// Other internal error
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
Internal(#[from] reth_interfaces::Error),
|
Internal(#[from] reth_interfaces::Error),
|
||||||
@ -32,7 +33,7 @@ pub(crate) enum EthApiError {
|
|||||||
impl From<EthApiError> for RpcError {
|
impl From<EthApiError> for RpcError {
|
||||||
fn from(value: EthApiError) -> Self {
|
fn from(value: EthApiError) -> Self {
|
||||||
match value {
|
match value {
|
||||||
EthApiError::UnknownBlockNumber => {
|
EthApiError::UnknownBlockNumber | EthApiError::InvalidBlockRange => {
|
||||||
rpc_err(INVALID_PARAMS_CODE, value.to_string(), None)
|
rpc_err(INVALID_PARAMS_CODE, value.to_string(), None)
|
||||||
}
|
}
|
||||||
err => internal_rpc_err(err.to_string()),
|
err => internal_rpc_err(err.to_string()),
|
||||||
|
|||||||
@ -6,8 +6,10 @@ use reth_db::{
|
|||||||
};
|
};
|
||||||
use reth_interfaces::Result;
|
use reth_interfaces::Result;
|
||||||
use reth_primitives::{rpc::BlockId, Block, BlockHash, BlockNumber, ChainInfo, Header, H256, U256};
|
use reth_primitives::{rpc::BlockId, Block, BlockHash, BlockNumber, ChainInfo, Header, H256, U256};
|
||||||
|
use std::ops::RangeBounds;
|
||||||
|
|
||||||
mod state;
|
mod state;
|
||||||
|
use reth_db::cursor::DbCursorRO;
|
||||||
pub use state::{
|
pub use state::{
|
||||||
chain::ChainState,
|
chain::ChainState,
|
||||||
historical::{HistoricalStateProvider, HistoricalStateProviderRef},
|
historical::{HistoricalStateProvider, HistoricalStateProviderRef},
|
||||||
@ -63,6 +65,18 @@ impl<DB: Database> HeaderProvider for ShareableDatabase<DB> {
|
|||||||
}
|
}
|
||||||
})?
|
})?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn headers_range(&self, range: impl RangeBounds<BlockNumber>) -> Result<Vec<Header>> {
|
||||||
|
self.db
|
||||||
|
.view(|tx| {
|
||||||
|
let mut cursor = tx.cursor_read::<tables::Headers>()?;
|
||||||
|
cursor
|
||||||
|
.walk_range(range)?
|
||||||
|
.map(|result| result.map(|(_, header)| header).map_err(Into::into))
|
||||||
|
.collect::<Result<Vec<_>>>()
|
||||||
|
})?
|
||||||
|
.map_err(Into::into)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<DB: Database> BlockHashProvider for ShareableDatabase<DB> {
|
impl<DB: Database> BlockHashProvider for ShareableDatabase<DB> {
|
||||||
|
|||||||
@ -1,4 +1,7 @@
|
|||||||
use crate::{AccountProvider, BlockHashProvider, BlockProvider, HeaderProvider, StateProvider};
|
use crate::{
|
||||||
|
AccountProvider, BlockHashProvider, BlockProvider, HeaderProvider, StateProvider,
|
||||||
|
StateProviderFactory,
|
||||||
|
};
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use reth_interfaces::Result;
|
use reth_interfaces::Result;
|
||||||
use reth_primitives::{
|
use reth_primitives::{
|
||||||
@ -7,7 +10,7 @@ use reth_primitives::{
|
|||||||
Account, Address, Block, BlockHash, Bytes, ChainInfo, Header, StorageKey, StorageValue, H256,
|
Account, Address, Block, BlockHash, Bytes, ChainInfo, Header, StorageKey, StorageValue, H256,
|
||||||
U256,
|
U256,
|
||||||
};
|
};
|
||||||
use std::{collections::HashMap, sync::Arc};
|
use std::{collections::HashMap, ops::RangeBounds, sync::Arc};
|
||||||
|
|
||||||
/// A mock implementation for Provider interfaces.
|
/// A mock implementation for Provider interfaces.
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default)]
|
||||||
@ -106,6 +109,14 @@ impl HeaderProvider for MockEthProvider {
|
|||||||
.fold(target.difficulty, |td, h| td + h.difficulty)
|
.fold(target.difficulty, |td, h| td + h.difficulty)
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn headers_range(
|
||||||
|
&self,
|
||||||
|
range: impl RangeBounds<reth_primitives::BlockNumber>,
|
||||||
|
) -> Result<Vec<Header>> {
|
||||||
|
let lock = self.headers.lock();
|
||||||
|
Ok(lock.values().filter(|header| range.contains(&header.number)).cloned().collect())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BlockHashProvider for MockEthProvider {
|
impl BlockHashProvider for MockEthProvider {
|
||||||
@ -185,3 +196,23 @@ impl StateProvider for MockEthProvider {
|
|||||||
Ok(lock.get(&account).and_then(|account| account.storage.get(&storage_key)).cloned())
|
Ok(lock.get(&account).and_then(|account| account.storage.get(&storage_key)).cloned())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl StateProviderFactory for MockEthProvider {
|
||||||
|
type HistorySP<'a> = &'a MockEthProvider where Self: 'a;
|
||||||
|
type LatestSP<'a> = &'a MockEthProvider where Self: 'a;
|
||||||
|
|
||||||
|
fn latest(&self) -> Result<Self::LatestSP<'_>> {
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn history_by_block_number(
|
||||||
|
&self,
|
||||||
|
_block: reth_primitives::BlockNumber,
|
||||||
|
) -> Result<Self::HistorySP<'_>> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn history_by_block_hash(&self, _block: BlockHash) -> Result<Self::HistorySP<'_>> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -7,6 +7,7 @@ use reth_primitives::{
|
|||||||
rpc::BlockId, Account, Address, Block, BlockHash, BlockNumber, Bytes, ChainInfo, Header,
|
rpc::BlockId, Account, Address, Block, BlockHash, BlockNumber, Bytes, ChainInfo, Header,
|
||||||
StorageKey, StorageValue, H256, U256,
|
StorageKey, StorageValue, H256, U256,
|
||||||
};
|
};
|
||||||
|
use std::ops::RangeBounds;
|
||||||
|
|
||||||
/// Supports various api interfaces for testing purposes.
|
/// Supports various api interfaces for testing purposes.
|
||||||
#[derive(Debug, Clone, Default, Copy)]
|
#[derive(Debug, Clone, Default, Copy)]
|
||||||
@ -51,6 +52,10 @@ impl HeaderProvider for NoopProvider {
|
|||||||
fn header_td(&self, _hash: &BlockHash) -> Result<Option<U256>> {
|
fn header_td(&self, _hash: &BlockHash) -> Result<Option<U256>> {
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn headers_range(&self, _range: impl RangeBounds<BlockNumber>) -> Result<Vec<Header>> {
|
||||||
|
Ok(vec![])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AccountProvider for NoopProvider {
|
impl AccountProvider for NoopProvider {
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
use auto_impl::auto_impl;
|
use auto_impl::auto_impl;
|
||||||
use reth_interfaces::Result;
|
use reth_interfaces::Result;
|
||||||
use reth_primitives::{BlockHash, BlockHashOrNumber, Header, U256};
|
use reth_primitives::{BlockHash, BlockHashOrNumber, BlockNumber, Header, U256};
|
||||||
|
use std::ops::RangeBounds;
|
||||||
|
|
||||||
/// Client trait for fetching `Header` related data.
|
/// Client trait for fetching `Header` related data.
|
||||||
#[auto_impl(&)]
|
#[auto_impl(&)]
|
||||||
@ -26,4 +27,7 @@ pub trait HeaderProvider: Send + Sync {
|
|||||||
|
|
||||||
/// Get total difficulty by block hash.
|
/// Get total difficulty by block hash.
|
||||||
fn header_td(&self, hash: &BlockHash) -> Result<Option<U256>>;
|
fn header_td(&self, hash: &BlockHash) -> Result<Option<U256>>;
|
||||||
|
|
||||||
|
/// Get headers in range of block hashes or numbers
|
||||||
|
fn headers_range(&self, range: impl RangeBounds<BlockNumber>) -> Result<Vec<Header>>;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user