From a7caf0d28486c2b2d45e94ec0d68b422620075b9 Mon Sep 17 00:00:00 2001 From: Roman Krasiuk Date: Wed, 3 Jul 2024 13:43:26 -0700 Subject: [PATCH] feat(rpc): enable historical proofs (#9273) --- book/cli/reth/node.md | 5 +++ crates/node/core/src/args/rpc_server.rs | 11 ++++++ crates/rpc/rpc-builder/src/config.rs | 1 + crates/rpc/rpc-builder/src/eth.rs | 13 ++++++- crates/rpc/rpc-eth-api/src/core.rs | 10 +----- crates/rpc/rpc-eth-api/src/helpers/state.rs | 26 +++++++------- crates/rpc/rpc-eth-types/src/error.rs | 4 +++ crates/rpc/rpc-server-types/src/constants.rs | 6 ++++ crates/rpc/rpc/src/eth/core.rs | 35 +++++++++++++------ crates/rpc/rpc/src/eth/helpers/state.rs | 11 ++++-- crates/rpc/rpc/src/eth/helpers/transaction.rs | 2 ++ 11 files changed, 87 insertions(+), 37 deletions(-) diff --git a/book/cli/reth/node.md b/book/cli/reth/node.md index 575fe18cc..c27d7251c 100644 --- a/book/cli/reth/node.md +++ b/book/cli/reth/node.md @@ -313,6 +313,11 @@ RPC: [default: 50000000] + --rpc.eth-proof-window + The maximum proof window for historical proof generation. This value allows for generating historical proofs up to configured number of blocks from current tip (up to `tip - window`) + + [default: 0] + RPC State Cache: --rpc-cache.max-blocks Max number of blocks in cache diff --git a/crates/node/core/src/args/rpc_server.rs b/crates/node/core/src/args/rpc_server.rs index bad1e2421..9b562329b 100644 --- a/crates/node/core/src/args/rpc_server.rs +++ b/crates/node/core/src/args/rpc_server.rs @@ -156,6 +156,16 @@ pub struct RpcServerArgs { )] pub rpc_gas_cap: u64, + /// The maximum proof window for historical proof generation. + /// This value allows for generating historical proofs up to + /// configured number of blocks from current tip (up to `tip - window`). + #[arg( + long = "rpc.eth-proof-window", + default_value_t = constants::DEFAULT_ETH_PROOF_WINDOW, + value_parser = RangedU64ValueParser::::new().range(..=constants::MAX_ETH_PROOF_WINDOW) + )] + pub rpc_eth_proof_window: u64, + /// State cache configuration. #[command(flatten)] pub rpc_state_cache: RpcStateCacheArgs, @@ -286,6 +296,7 @@ impl Default for RpcServerArgs { rpc_max_blocks_per_filter: constants::DEFAULT_MAX_BLOCKS_PER_FILTER.into(), rpc_max_logs_per_response: (constants::DEFAULT_MAX_LOGS_PER_RESPONSE as u64).into(), rpc_gas_cap: constants::gas_oracle::RPC_DEFAULT_GAS_CAP, + rpc_eth_proof_window: constants::DEFAULT_ETH_PROOF_WINDOW, gas_price_oracle: GasPriceOracleArgs::default(), rpc_state_cache: RpcStateCacheArgs::default(), } diff --git a/crates/rpc/rpc-builder/src/config.rs b/crates/rpc/rpc-builder/src/config.rs index e2becedba..837b80a99 100644 --- a/crates/rpc/rpc-builder/src/config.rs +++ b/crates/rpc/rpc-builder/src/config.rs @@ -91,6 +91,7 @@ impl RethRpcServerConfig for RpcServerArgs { .max_tracing_requests(self.rpc_max_tracing_requests) .max_blocks_per_filter(self.rpc_max_blocks_per_filter.unwrap_or_max()) .max_logs_per_response(self.rpc_max_logs_per_response.unwrap_or_max() as usize) + .eth_proof_window(self.rpc_eth_proof_window) .rpc_gas_cap(self.rpc_gas_cap) .state_cache(self.state_cache_config()) .gpo_config(self.gas_price_oracle_config()) diff --git a/crates/rpc/rpc-builder/src/eth.rs b/crates/rpc/rpc-builder/src/eth.rs index 43e20b1ea..8e897b771 100644 --- a/crates/rpc/rpc-builder/src/eth.rs +++ b/crates/rpc/rpc-builder/src/eth.rs @@ -13,7 +13,8 @@ use reth_rpc_eth_types::{ GasPriceOracleConfig, RPC_DEFAULT_GAS_CAP, }; use reth_rpc_server_types::constants::{ - default_max_tracing_requests, DEFAULT_MAX_BLOCKS_PER_FILTER, DEFAULT_MAX_LOGS_PER_RESPONSE, + default_max_tracing_requests, DEFAULT_ETH_PROOF_WINDOW, DEFAULT_MAX_BLOCKS_PER_FILTER, + DEFAULT_MAX_LOGS_PER_RESPONSE, }; use reth_tasks::{pool::BlockingTaskPool, TaskSpawner}; use reth_transaction_pool::TransactionPool; @@ -141,6 +142,8 @@ pub struct EthConfig { pub cache: EthStateCacheConfig, /// Settings for the gas price oracle pub gas_oracle: GasPriceOracleConfig, + /// The maximum number of blocks into the past for generating state proofs. + pub eth_proof_window: u64, /// The maximum number of tracing calls that can be executed in concurrently. pub max_tracing_requests: usize, /// Maximum number of blocks that could be scanned per filter request in `eth_getLogs` calls. @@ -173,6 +176,7 @@ impl Default for EthConfig { Self { cache: EthStateCacheConfig::default(), gas_oracle: GasPriceOracleConfig::default(), + eth_proof_window: DEFAULT_ETH_PROOF_WINDOW, max_tracing_requests: default_max_tracing_requests(), max_blocks_per_filter: DEFAULT_MAX_BLOCKS_PER_FILTER, max_logs_per_response: DEFAULT_MAX_LOGS_PER_RESPONSE, @@ -219,6 +223,12 @@ impl EthConfig { self.rpc_gas_cap = rpc_gas_cap; self } + + /// Configures the maximum proof window for historical proof generation. + pub const fn eth_proof_window(mut self, window: u64) -> Self { + self.eth_proof_window = window; + self + } } /// Context for building the `eth` namespace API. @@ -269,6 +279,7 @@ impl EthApiBuild { ctx.cache.clone(), gas_oracle, ctx.config.rpc_gas_cap, + ctx.config.eth_proof_window, Box::new(ctx.executor.clone()), BlockingTaskPool::build().expect("failed to build blocking task pool"), fee_history_cache, diff --git a/crates/rpc/rpc-eth-api/src/core.rs b/crates/rpc/rpc-eth-api/src/core.rs index 1f5ced83d..cf11c6d31 100644 --- a/crates/rpc/rpc-eth-api/src/core.rs +++ b/crates/rpc/rpc-eth-api/src/core.rs @@ -4,7 +4,6 @@ use alloy_dyn_abi::TypedData; use jsonrpsee::{core::RpcResult, proc_macros::rpc}; use reth_primitives::{Address, BlockId, BlockNumberOrTag, Bytes, B256, B64, U256, U64}; -use reth_rpc_eth_types::EthApiError; use reth_rpc_server_types::{result::internal_rpc_err, ToRpcResult}; use reth_rpc_types::{ serde_helpers::JsonStorageKey, @@ -715,13 +714,6 @@ where block_number: Option, ) -> RpcResult { trace!(target: "rpc::eth", ?address, ?keys, ?block_number, "Serving eth_getProof"); - let res = EthState::get_proof(self, address, keys, block_number)?.await; - - Ok(res.map_err(|e| match e { - EthApiError::InvalidBlockRange => { - internal_rpc_err("eth_getProof is unimplemented for historical blocks") - } - _ => e.into(), - })?) + Ok(EthState::get_proof(self, address, keys, block_number)?.await?) } } diff --git a/crates/rpc/rpc-eth-api/src/helpers/state.rs b/crates/rpc/rpc-eth-api/src/helpers/state.rs index 34424e94f..1f07f35ae 100644 --- a/crates/rpc/rpc-eth-api/src/helpers/state.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/state.rs @@ -3,7 +3,7 @@ use futures::Future; use reth_evm::ConfigureEvmEnv; -use reth_primitives::{Address, BlockId, BlockNumberOrTag, Bytes, Header, B256, U256}; +use reth_primitives::{Address, BlockId, Bytes, Header, B256, U256}; use reth_provider::{ BlockIdReader, ChainSpecProvider, StateProvider, StateProviderBox, StateProviderFactory, }; @@ -19,6 +19,9 @@ use super::{EthApiSpec, LoadPendingBlock, SpawnBlocking}; /// Helper methods for `eth_` methods relating to state (accounts). pub trait EthState: LoadState + SpawnBlocking { + /// Returns the maximum number of blocks into the past for generating state proofs. + fn max_proof_window(&self) -> u64; + /// Returns the number of transactions sent from an address at the given block identifier. /// /// If this is [`BlockNumberOrTag::Pending`](reth_primitives::BlockNumberOrTag) then this will @@ -90,19 +93,14 @@ pub trait EthState: LoadState + SpawnBlocking { let chain_info = self.chain_info()?; let block_id = block_id.unwrap_or_default(); - // if we are trying to create a proof for the latest block, but have a BlockId as input - // that is not BlockNumberOrTag::Latest, then we need to figure out whether or not the - // BlockId corresponds to the latest block - let is_latest_block = match block_id { - BlockId::Number(BlockNumberOrTag::Number(num)) => num == chain_info.best_number, - BlockId::Hash(hash) => hash == chain_info.best_hash.into(), - BlockId::Number(BlockNumberOrTag::Latest) => true, - _ => false, - }; - - // TODO: remove when HistoricalStateProviderRef::proof is implemented - if !is_latest_block { - return Err(EthApiError::InvalidBlockRange) + // Check whether the distance to the block exceeds the maximum configured window. + let block_number = self + .provider() + .block_number_for_id(block_id)? + .ok_or(EthApiError::UnknownBlockNumber)?; + let max_window = self.max_proof_window(); + if chain_info.best_number.saturating_sub(block_number) > max_window { + return Err(EthApiError::ExceedsMaxProofWindow) } Ok(self.spawn_tracing(move |this| { diff --git a/crates/rpc/rpc-eth-types/src/error.rs b/crates/rpc/rpc-eth-types/src/error.rs index 4ddbf9a38..7cb302a53 100644 --- a/crates/rpc/rpc-eth-types/src/error.rs +++ b/crates/rpc/rpc-eth-types/src/error.rs @@ -54,6 +54,9 @@ pub enum EthApiError { /// When an invalid block range is provided #[error("invalid block range")] InvalidBlockRange, + /// Thrown when the target block for proof computation exceeds the maximum configured window. + #[error("distance to target block exceeds maximum proof window")] + ExceedsMaxProofWindow, /// An internal error where prevrandao is not set in the evm's environment #[error("prevrandao not in the EVM's environment after merge")] PrevrandaoNotSet, @@ -143,6 +146,7 @@ impl From for jsonrpsee_types::error::ErrorObject<'static> { EthApiError::InvalidTransactionSignature | EthApiError::EmptyRawTransactionData | EthApiError::InvalidBlockRange | + EthApiError::ExceedsMaxProofWindow | EthApiError::ConflictingFeeFieldsInRequest | EthApiError::Signing(_) | EthApiError::BothStateAndStateDiffInOverride(_) | diff --git a/crates/rpc/rpc-server-types/src/constants.rs b/crates/rpc/rpc-server-types/src/constants.rs index 807d96a91..f1af5fb26 100644 --- a/crates/rpc/rpc-server-types/src/constants.rs +++ b/crates/rpc/rpc-server-types/src/constants.rs @@ -42,6 +42,12 @@ pub const DEFAULT_ENGINE_API_IPC_ENDPOINT: &str = r"\\.\pipe\reth_engine_api.ipc #[cfg(not(windows))] pub const DEFAULT_ENGINE_API_IPC_ENDPOINT: &str = "/tmp/reth_engine_api.ipc"; +/// The default eth historical proof window. +pub const DEFAULT_ETH_PROOF_WINDOW: u64 = 0; + +/// Maximum eth historical proof window. Equivalent to roughly one month of data. +pub const MAX_ETH_PROOF_WINDOW: u64 = 216_000; + /// GPO specific constants pub mod gas_oracle { use alloy_primitives::U256; diff --git a/crates/rpc/rpc/src/eth/core.rs b/crates/rpc/rpc/src/eth/core.rs index e876faca8..083352554 100644 --- a/crates/rpc/rpc/src/eth/core.rs +++ b/crates/rpc/rpc/src/eth/core.rs @@ -5,7 +5,7 @@ use std::sync::Arc; use derive_more::Deref; use reth_primitives::{BlockNumberOrTag, U256}; -use reth_provider::{BlockReaderIdExt, ChainSpecProvider}; +use reth_provider::BlockReaderIdExt; use reth_rpc_eth_api::{ helpers::{transaction::UpdateRawTxForwarder, EthSigner, SpawnBlocking}, RawTransactionForwarder, @@ -31,18 +31,9 @@ pub struct EthApi { pub(super) inner: Arc>, } -impl EthApi { - /// Sets a forwarder for `eth_sendRawTransaction` - /// - /// Note: this might be removed in the future in favor of a more generic approach. - pub fn set_eth_raw_transaction_forwarder(&self, forwarder: Arc) { - self.inner.raw_transaction_forwarder.write().replace(forwarder); - } -} - impl EthApi where - Provider: BlockReaderIdExt + ChainSpecProvider, + Provider: BlockReaderIdExt, { /// Creates a new, shareable instance using the default tokio task spawner. #[allow(clippy::too_many_arguments)] @@ -53,6 +44,7 @@ where eth_cache: EthStateCache, gas_oracle: GasPriceOracle, gas_cap: impl Into, + eth_proof_window: u64, blocking_task_pool: BlockingTaskPool, fee_history_cache: FeeHistoryCache, evm_config: EvmConfig, @@ -65,6 +57,7 @@ where eth_cache, gas_oracle, gas_cap.into().into(), + eth_proof_window, Box::::default(), blocking_task_pool, fee_history_cache, @@ -82,6 +75,7 @@ where eth_cache: EthStateCache, gas_oracle: GasPriceOracle, gas_cap: u64, + eth_proof_window: u64, task_spawner: Box, blocking_task_pool: BlockingTaskPool, fee_history_cache: FeeHistoryCache, @@ -104,6 +98,7 @@ where eth_cache, gas_oracle, gas_cap, + eth_proof_window, starting_block: U256::from(latest_block), task_spawner, pending_block: Default::default(), @@ -115,6 +110,15 @@ where Self { inner: Arc::new(inner) } } +} + +impl EthApi { + /// Sets a forwarder for `eth_sendRawTransaction` + /// + /// Note: this might be removed in the future in favor of a more generic approach. + pub fn set_eth_raw_transaction_forwarder(&self, forwarder: Arc) { + self.inner.raw_transaction_forwarder.write().replace(forwarder); + } /// Returns the state cache frontend pub fn cache(&self) -> &EthStateCache { @@ -131,6 +135,11 @@ where self.inner.gas_cap } + /// The maximum number of blocks into the past for generating state proofs. + pub fn eth_proof_window(&self) -> u64 { + self.inner.eth_proof_window + } + /// Returns the inner `Provider` pub fn provider(&self) -> &Provider { &self.inner.provider @@ -208,6 +217,8 @@ pub struct EthApiInner { gas_oracle: GasPriceOracle, /// Maximum gas limit for `eth_call` and call tracing RPC methods. gas_cap: u64, + /// The maximum number of blocks into the past for generating state proofs. + eth_proof_window: u64, /// The block number at which the node started starting_block: U256, /// The type that can spawn tasks which would otherwise block. @@ -330,6 +341,7 @@ mod tests { use reth_rpc_eth_types::{ EthStateCache, FeeHistoryCache, FeeHistoryCacheConfig, GasPriceOracle, }; + use reth_rpc_server_types::constants::DEFAULT_ETH_PROOF_WINDOW; use reth_rpc_types::FeeHistory; use reth_tasks::pool::BlockingTaskPool; use reth_testing_utils::{generators, generators::Rng}; @@ -361,6 +373,7 @@ mod tests { cache.clone(), GasPriceOracle::new(provider, Default::default(), cache), ETHEREUM_BLOCK_GAS_LIMIT, + DEFAULT_ETH_PROOF_WINDOW, BlockingTaskPool::build().expect("failed to build tracing pool"), fee_history_cache, evm_config, diff --git a/crates/rpc/rpc/src/eth/helpers/state.rs b/crates/rpc/rpc/src/eth/helpers/state.rs index dd28c6465..369bd6ba7 100644 --- a/crates/rpc/rpc/src/eth/helpers/state.rs +++ b/crates/rpc/rpc/src/eth/helpers/state.rs @@ -8,9 +8,13 @@ use reth_rpc_eth_types::EthStateCache; use crate::EthApi; -impl EthState for EthApi where - Self: LoadState + SpawnBlocking +impl EthState for EthApi +where + Self: LoadState + SpawnBlocking, { + fn max_proof_window(&self) -> u64 { + self.eth_proof_window() + } } impl LoadState for EthApi @@ -47,6 +51,7 @@ mod tests { use reth_rpc_eth_types::{ EthStateCache, FeeHistoryCache, FeeHistoryCacheConfig, GasPriceOracle, }; + use reth_rpc_server_types::constants::DEFAULT_ETH_PROOF_WINDOW; use reth_tasks::pool::BlockingTaskPool; use reth_transaction_pool::test_utils::testing_pool; @@ -66,6 +71,7 @@ mod tests { cache.clone(), GasPriceOracle::new(NoopProvider::default(), Default::default(), cache.clone()), ETHEREUM_BLOCK_GAS_LIMIT, + DEFAULT_ETH_PROOF_WINDOW, BlockingTaskPool::build().expect("failed to build tracing pool"), FeeHistoryCache::new(cache, FeeHistoryCacheConfig::default()), evm_config, @@ -91,6 +97,7 @@ mod tests { cache.clone(), GasPriceOracle::new(mock_provider, Default::default(), cache.clone()), ETHEREUM_BLOCK_GAS_LIMIT, + DEFAULT_ETH_PROOF_WINDOW, BlockingTaskPool::build().expect("failed to build tracing pool"), FeeHistoryCache::new(cache, FeeHistoryCacheConfig::default()), evm_config, diff --git a/crates/rpc/rpc/src/eth/helpers/transaction.rs b/crates/rpc/rpc/src/eth/helpers/transaction.rs index 9d86be1b2..13e3dbd5c 100644 --- a/crates/rpc/rpc/src/eth/helpers/transaction.rs +++ b/crates/rpc/rpc/src/eth/helpers/transaction.rs @@ -68,6 +68,7 @@ mod tests { use reth_rpc_eth_types::{ EthStateCache, FeeHistoryCache, FeeHistoryCacheConfig, GasPriceOracle, }; + use reth_rpc_server_types::constants::DEFAULT_ETH_PROOF_WINDOW; use reth_tasks::pool::BlockingTaskPool; use reth_transaction_pool::{test_utils::testing_pool, TransactionPool}; @@ -91,6 +92,7 @@ mod tests { cache.clone(), GasPriceOracle::new(noop_provider, Default::default(), cache.clone()), ETHEREUM_BLOCK_GAS_LIMIT, + DEFAULT_ETH_PROOF_WINDOW, BlockingTaskPool::build().expect("failed to build tracing pool"), fee_history_cache, evm_config,