diff --git a/Cargo.lock b/Cargo.lock index 7fcee2731..acd81f9f0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4691,12 +4691,14 @@ version = "0.1.0" dependencies = [ "bytes", "jsonrpsee-types", + "lru 0.9.0", "reth-network-api", "reth-primitives", "reth-rlp", "serde", "serde_json", "thiserror", + "tokio", ] [[package]] diff --git a/crates/consensus/src/validation.rs b/crates/consensus/src/validation.rs index 9114b4264..553263585 100644 --- a/crates/consensus/src/validation.rs +++ b/crates/consensus/src/validation.rs @@ -353,6 +353,7 @@ mod tests { hex_literal::hex, Account, Address, BlockHash, Bytes, Header, Signature, TransactionKind, TransactionSigned, MAINNET, U256, }; + use std::ops::RangeBounds; use super::*; @@ -422,6 +423,10 @@ mod tests { fn header_td(&self, _hash: &BlockHash) -> Result> { Ok(None) } + + fn headers_range(&self, _range: impl RangeBounds) -> Result> { + Ok(vec![]) + } } fn mock_tx(nonce: u64) -> TransactionSignedEcRecovered { diff --git a/crates/interfaces/src/db.rs b/crates/interfaces/src/db.rs index 157425e67..9c86b43f3 100644 --- a/crates/interfaces/src/db.rs +++ b/crates/interfaces/src/db.rs @@ -25,7 +25,7 @@ pub enum Error { /// Failed to initiate a cursor. #[error("Initialization of cursor errored with code: {0:?}")] InitCursor(u32), - /// Failed to decode a key from a table.. + /// Failed to decode a key from a table. #[error("Error decoding value.")] DecodeError, } diff --git a/crates/primitives/src/block.rs b/crates/primitives/src/block.rs index 241a8cf2a..f34737a20 100644 --- a/crates/primitives/src/block.rs +++ b/crates/primitives/src/block.rs @@ -184,6 +184,15 @@ impl From for BlockId { } } +impl From 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 { fn serialize(&self, serializer: S) -> Result where diff --git a/crates/rpc/rpc-api/src/eth.rs b/crates/rpc/rpc-api/src/eth.rs index c207220bc..c0305f890 100644 --- a/crates/rpc/rpc-api/src/eth.rs +++ b/crates/rpc/rpc-api/src/eth.rs @@ -174,8 +174,8 @@ pub trait EthApi { #[method(name = "eth_feeHistory")] async fn fee_history( &self, - block_count: U256, - newest_block: BlockNumber, + block_count: U64, + newest_block: BlockId, reward_percentiles: Option>, ) -> Result; diff --git a/crates/rpc/rpc-builder/src/lib.rs b/crates/rpc/rpc-builder/src/lib.rs index e9564bf79..1c5f27015 100644 --- a/crates/rpc/rpc-builder/src/lib.rs +++ b/crates/rpc/rpc-builder/src/lib.rs @@ -26,12 +26,12 @@ //! //! ``` //! 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_transaction_pool::TransactionPool; //! pub async fn launch(client: Client, pool: Pool, network: Network) //! where -//! Client: BlockProvider + StateProviderFactory + Clone + 'static, +//! Client: BlockProvider + HeaderProvider + StateProviderFactory + Clone + 'static, //! Pool: TransactionPool + Clone + 'static, //! Network: NetworkInfo + Peers + Clone + 'static, //! { @@ -60,7 +60,7 @@ use jsonrpsee::{ use reth_ipc::server::IpcServer; pub use reth_ipc::server::{Builder as IpcServerBuilder, Endpoint}; 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_api::servers::*; use reth_transaction_pool::TransactionPool; @@ -96,7 +96,7 @@ pub async fn launch( server_config: impl Into, ) -> Result where - Client: BlockProvider + StateProviderFactory + Clone + 'static, + Client: BlockProvider + HeaderProvider + StateProviderFactory + Clone + 'static, Pool: TransactionPool + Clone + 'static, Network: NetworkInfo + Peers + Clone + 'static, { @@ -159,7 +159,7 @@ impl RpcModuleBuilder { impl RpcModuleBuilder where - Client: BlockProvider + StateProviderFactory + Clone + 'static, + Client: BlockProvider + HeaderProvider + StateProviderFactory + Clone + 'static, Pool: TransactionPool + Clone + 'static, Network: NetworkInfo + Peers + Clone + 'static, { @@ -260,7 +260,7 @@ impl RpcModuleSelection { network: Network, ) -> RpcModule<()> where - Client: BlockProvider + StateProviderFactory + Clone + 'static, + Client: BlockProvider + HeaderProvider + StateProviderFactory + Clone + 'static, Pool: TransactionPool + Clone + 'static, Network: NetworkInfo + Peers + Clone + 'static, { @@ -399,7 +399,7 @@ where impl RethModuleRegistry where - Client: BlockProvider + StateProviderFactory + Clone + 'static, + Client: BlockProvider + HeaderProvider + StateProviderFactory + Clone + 'static, Pool: TransactionPool + Clone + 'static, Network: NetworkInfo + Peers + Clone + 'static, { diff --git a/crates/rpc/rpc-builder/tests/it/http.rs b/crates/rpc/rpc-builder/tests/it/http.rs index eb3933b37..3fa79f051 100644 --- a/crates/rpc/rpc-builder/tests/it/http.rs +++ b/crates/rpc/rpc-builder/tests/it/http.rs @@ -66,6 +66,7 @@ where EthApiClient::block_number(client).await.unwrap(); EthApiClient::get_code(client, address, None).await.unwrap(); EthApiClient::send_raw_transaction(client, tx).await.unwrap(); + EthApiClient::fee_history(client, 0.into(), block_number.into(), None).await.unwrap(); // Unimplemented 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() )); 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::is_mining(client).await.err().unwrap())); assert!(is_unimplemented(EthApiClient::hashrate(client).await.err().unwrap())); diff --git a/crates/rpc/rpc-types/Cargo.toml b/crates/rpc/rpc-types/Cargo.toml index 5f3def212..e0a3aba29 100644 --- a/crates/rpc/rpc-types/Cargo.toml +++ b/crates/rpc/rpc-types/Cargo.toml @@ -17,8 +17,12 @@ reth-network-api = { path = "../../net/network-api"} # errors thiserror = "1.0" +# async +tokio = { version = "1", features = ["sync"] } + # misc serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" bytes = "1.2" jsonrpsee-types = { version = "0.16" } +lru = "0.9" diff --git a/crates/rpc/rpc-types/src/eth/fee.rs b/crates/rpc/rpc-types/src/eth/fee.rs index 256113151..d5dbf8861 100644 --- a/crates/rpc/rpc-types/src/eth/fee.rs +++ b/crates/rpc/rpc-types/src/eth/fee.rs @@ -1,8 +1,11 @@ -use reth_primitives::U256; +use lru::LruCache; +use reth_primitives::{BlockNumber, H256, U256}; use serde::{Deserialize, Serialize}; +use std::{num::NonZeroUsize, sync::Arc}; +use tokio::sync::Mutex; /// Response type for `eth_feeHistory` -#[derive(Debug, Clone, Deserialize, Serialize)] +#[derive(Debug, Clone, Default, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct FeeHistory { /// An array of block base fees per gas. @@ -20,3 +23,29 @@ pub struct FeeHistory { #[serde(default)] pub reward: Option>>, } + +/// LRU cache for `eth_feeHistory` RPC method. Block Number => Fee History. +#[derive(Clone, Debug)] +pub struct FeeHistoryCache(pub Arc>>); + +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, + /// 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>, +} diff --git a/crates/rpc/rpc-types/src/eth/mod.rs b/crates/rpc/rpc-types/src/eth/mod.rs index 2ea643335..ecca4cdb1 100644 --- a/crates/rpc/rpc-types/src/eth/mod.rs +++ b/crates/rpc/rpc-types/src/eth/mod.rs @@ -17,7 +17,7 @@ mod work; pub use account::*; pub use block::*; pub use call::CallRequest; -pub use fee::FeeHistory; +pub use fee::{FeeHistory, FeeHistoryCache, FeeHistoryCacheItem}; pub use filter::*; pub use index::Index; pub use log::Log; diff --git a/crates/rpc/rpc/Cargo.toml b/crates/rpc/rpc/Cargo.toml index 3558a774b..4719d79ae 100644 --- a/crates/rpc/rpc/Cargo.toml +++ b/crates/rpc/rpc/Cargo.toml @@ -16,8 +16,8 @@ reth-rpc-api = { path = "../rpc-api" } reth-rlp = { path = "../../rlp" } reth-rpc-types = { path = "../rpc-types" } reth-provider = { path = "../../storage/provider", features = ["test-utils"] } -reth-transaction-pool = { path = "../../transaction-pool", features=["test-utils"]} -reth-network-api = { path = "../../net/network-api" } +reth-transaction-pool = { path = "../../transaction-pool", features = ["test-utils"]} +reth-network-api = { path = "../../net/network-api", features = ["test-utils"] } reth-rpc-engine-api = { path = "../rpc-engine-api" } reth-tasks = { path = "../../tasks" } diff --git a/crates/rpc/rpc/src/eth/api/mod.rs b/crates/rpc/rpc/src/eth/api/mod.rs index 2173a2091..fe5ee63b0 100644 --- a/crates/rpc/rpc/src/eth/api/mod.rs +++ b/crates/rpc/rpc/src/eth/api/mod.rs @@ -12,7 +12,9 @@ use reth_primitives::{ Address, ChainInfo, TransactionSigned, H256, U64, }; use reth_provider::{BlockProvider, StateProviderFactory}; +use std::num::NonZeroUsize; +use reth_rpc_types::FeeHistoryCache; use reth_transaction_pool::TransactionPool; use std::sync::Arc; @@ -21,6 +23,9 @@ mod server; mod state; mod transactions; +/// Cache limit of block-level fee history for `eth_feeHistory` RPC method. +const FEE_HISTORY_CACHE_LIMIT: usize = 2048; + /// `Eth` API trait. /// /// Defines core functionality of the `eth` API implementation. @@ -55,13 +60,19 @@ pub trait EthApiSpec: Send + Sync { pub struct EthApi { /// All nested fields bundled together. inner: Arc>, + fee_history_cache: FeeHistoryCache, } impl EthApi { /// Creates a new, shareable instance. pub fn new(client: Client, pool: Pool, network: Network) -> Self { 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` diff --git a/crates/rpc/rpc/src/eth/api/server.rs b/crates/rpc/rpc/src/eth/api/server.rs index 67b71afca..12f0c77c4 100644 --- a/crates/rpc/rpc/src/eth/api/server.rs +++ b/crates/rpc/rpc/src/eth/api/server.rs @@ -2,22 +2,23 @@ //! Handles RPC requests for the `eth_` namespace. use crate::{ - eth::api::EthApi, + eth::{api::EthApi, error::EthApiError}, result::{internal_rpc_err, ToRpcResult}, }; use jsonrpsee::core::RpcResult as Result; use reth_primitives::{ 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_types::{ - CallRequest, EIP1186AccountProofResponse, FeeHistory, Index, RichBlock, SyncStatus, - TransactionReceipt, TransactionRequest, Work, + CallRequest, EIP1186AccountProofResponse, FeeHistory, FeeHistoryCacheItem, Index, RichBlock, + SyncStatus, TransactionReceipt, TransactionRequest, Work, }; use reth_transaction_pool::TransactionPool; use serde_json::Value; +use std::collections::BTreeMap; use super::EthApiSpec; @@ -26,7 +27,7 @@ impl EthApiServer for EthApi where Self: EthApiSpec, Pool: TransactionPool + 'static, - Client: BlockProvider + StateProviderFactory + 'static, + Client: BlockProvider + HeaderProvider + StateProviderFactory + 'static, Network: 'static, { async fn protocol_version(&self) -> Result { @@ -178,13 +179,110 @@ where 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( &self, - _block_count: U256, - _newest_block: BlockNumber, + block_count: U64, + newest_block: BlockId, _reward_percentiles: Option>, ) -> Result { - 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::::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
= + 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 { @@ -240,3 +338,77 @@ where 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 = + if random::() { 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)); + } +} diff --git a/crates/rpc/rpc/src/eth/error.rs b/crates/rpc/rpc/src/eth/error.rs index dfde5fcf9..63c1358a4 100644 --- a/crates/rpc/rpc/src/eth/error.rs +++ b/crates/rpc/rpc/src/eth/error.rs @@ -22,8 +22,9 @@ pub(crate) enum EthApiError { #[error(transparent)] PoolError(GethTxPoolError), #[error("Unknown block number")] - // TODO return -32602 here UnknownBlockNumber, + #[error("Invalid block range")] + InvalidBlockRange, /// Other internal error #[error(transparent)] Internal(#[from] reth_interfaces::Error), @@ -32,7 +33,7 @@ pub(crate) enum EthApiError { impl From for RpcError { fn from(value: EthApiError) -> Self { match value { - EthApiError::UnknownBlockNumber => { + EthApiError::UnknownBlockNumber | EthApiError::InvalidBlockRange => { rpc_err(INVALID_PARAMS_CODE, value.to_string(), None) } err => internal_rpc_err(err.to_string()), diff --git a/crates/storage/provider/src/providers/mod.rs b/crates/storage/provider/src/providers/mod.rs index fb81b15b2..bccbe7350 100644 --- a/crates/storage/provider/src/providers/mod.rs +++ b/crates/storage/provider/src/providers/mod.rs @@ -6,8 +6,10 @@ use reth_db::{ }; use reth_interfaces::Result; use reth_primitives::{rpc::BlockId, Block, BlockHash, BlockNumber, ChainInfo, Header, H256, U256}; +use std::ops::RangeBounds; mod state; +use reth_db::cursor::DbCursorRO; pub use state::{ chain::ChainState, historical::{HistoricalStateProvider, HistoricalStateProviderRef}, @@ -63,6 +65,18 @@ impl HeaderProvider for ShareableDatabase { } })? } + + fn headers_range(&self, range: impl RangeBounds) -> Result> { + self.db + .view(|tx| { + let mut cursor = tx.cursor_read::()?; + cursor + .walk_range(range)? + .map(|result| result.map(|(_, header)| header).map_err(Into::into)) + .collect::>>() + })? + .map_err(Into::into) + } } impl BlockHashProvider for ShareableDatabase { diff --git a/crates/storage/provider/src/test_utils/mock.rs b/crates/storage/provider/src/test_utils/mock.rs index 2a1f703f2..39792d622 100644 --- a/crates/storage/provider/src/test_utils/mock.rs +++ b/crates/storage/provider/src/test_utils/mock.rs @@ -1,4 +1,7 @@ -use crate::{AccountProvider, BlockHashProvider, BlockProvider, HeaderProvider, StateProvider}; +use crate::{ + AccountProvider, BlockHashProvider, BlockProvider, HeaderProvider, StateProvider, + StateProviderFactory, +}; use parking_lot::Mutex; use reth_interfaces::Result; use reth_primitives::{ @@ -7,7 +10,7 @@ use reth_primitives::{ Account, Address, Block, BlockHash, Bytes, ChainInfo, Header, StorageKey, StorageValue, H256, U256, }; -use std::{collections::HashMap, sync::Arc}; +use std::{collections::HashMap, ops::RangeBounds, sync::Arc}; /// A mock implementation for Provider interfaces. #[derive(Debug, Clone, Default)] @@ -106,6 +109,14 @@ impl HeaderProvider for MockEthProvider { .fold(target.difficulty, |td, h| td + h.difficulty) })) } + + fn headers_range( + &self, + range: impl RangeBounds, + ) -> Result> { + let lock = self.headers.lock(); + Ok(lock.values().filter(|header| range.contains(&header.number)).cloned().collect()) + } } 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()) } } + +impl StateProviderFactory for MockEthProvider { + type HistorySP<'a> = &'a MockEthProvider where Self: 'a; + type LatestSP<'a> = &'a MockEthProvider where Self: 'a; + + fn latest(&self) -> Result> { + Ok(self) + } + + fn history_by_block_number( + &self, + _block: reth_primitives::BlockNumber, + ) -> Result> { + todo!() + } + + fn history_by_block_hash(&self, _block: BlockHash) -> Result> { + todo!() + } +} diff --git a/crates/storage/provider/src/test_utils/noop.rs b/crates/storage/provider/src/test_utils/noop.rs index 59f427635..a146e8321 100644 --- a/crates/storage/provider/src/test_utils/noop.rs +++ b/crates/storage/provider/src/test_utils/noop.rs @@ -7,6 +7,7 @@ use reth_primitives::{ rpc::BlockId, Account, Address, Block, BlockHash, BlockNumber, Bytes, ChainInfo, Header, StorageKey, StorageValue, H256, U256, }; +use std::ops::RangeBounds; /// Supports various api interfaces for testing purposes. #[derive(Debug, Clone, Default, Copy)] @@ -51,6 +52,10 @@ impl HeaderProvider for NoopProvider { fn header_td(&self, _hash: &BlockHash) -> Result> { Ok(None) } + + fn headers_range(&self, _range: impl RangeBounds) -> Result> { + Ok(vec![]) + } } impl AccountProvider for NoopProvider { diff --git a/crates/storage/provider/src/traits/header.rs b/crates/storage/provider/src/traits/header.rs index c6b3bbb99..7c678924c 100644 --- a/crates/storage/provider/src/traits/header.rs +++ b/crates/storage/provider/src/traits/header.rs @@ -1,6 +1,7 @@ use auto_impl::auto_impl; 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. #[auto_impl(&)] @@ -26,4 +27,7 @@ pub trait HeaderProvider: Send + Sync { /// Get total difficulty by block hash. fn header_td(&self, hash: &BlockHash) -> Result>; + + /// Get headers in range of block hashes or numbers + fn headers_range(&self, range: impl RangeBounds) -> Result>; }