Skip to content

Commit

Permalink
feat(rpc): limit block_range by 100_000 per eth_getLogs request (#5243)
Browse files Browse the repository at this point in the history
Co-authored-by: Matthias Seitz <matthias.seitz@outlook.de>
Co-authored-by: Alexey Shekhirin <a.shekhirin@gmail.com>
  • Loading branch information
3 people authored Nov 2, 2023
1 parent 24fade3 commit 0449c5e
Show file tree
Hide file tree
Showing 9 changed files with 200 additions and 29 deletions.
4 changes: 3 additions & 1 deletion bin/reth/src/args/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ pub use stage_args::StageEnum;
mod gas_price_oracle_args;
pub use gas_price_oracle_args::GasPriceOracleArgs;

/// TxPoolArgs for congiguring the transaction pool
/// TxPoolArgs for configuring the transaction pool
mod txpool_args;
pub use txpool_args::TxPoolArgs;

Expand All @@ -44,3 +44,5 @@ mod pruning_args;
pub use pruning_args::PruningArgs;

pub mod utils;

pub mod types;
47 changes: 42 additions & 5 deletions bin/reth/src/args/rpc_server_args.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! clap [Args](clap::Args) for RPC related arguments.
use crate::{
args::GasPriceOracleArgs,
args::{types::ZeroAsNone, GasPriceOracleArgs},
cli::{
components::{RethNodeComponents, RethRpcComponents, RethRpcServerHandles},
config::RethRpcConfig,
Expand Down Expand Up @@ -140,9 +140,13 @@ pub struct RpcServerArgs {
#[arg(long, value_name = "COUNT", default_value_t = constants::DEFAULT_MAX_TRACING_REQUESTS)]
pub rpc_max_tracing_requests: u32,

/// Maximum number of logs that can be returned in a single response.
#[arg(long, value_name = "COUNT", default_value_t = constants::DEFAULT_MAX_LOGS_PER_RESPONSE)]
pub rpc_max_logs_per_response: usize,
/// Maximum number of blocks that could be scanned per filter request. (0 = entire chain)
#[arg(long, value_name = "COUNT", default_value_t = ZeroAsNone::new(constants::DEFAULT_MAX_BLOCKS_PER_FILTER))]
pub rpc_max_blocks_per_filter: ZeroAsNone,

/// Maximum number of logs that can be returned in a single response. (0 = no limit)
#[arg(long, value_name = "COUNT", default_value_t = ZeroAsNone::new(constants::DEFAULT_MAX_LOGS_PER_RESPONSE as u64))]
pub rpc_max_logs_per_response: ZeroAsNone,

/// Maximum gas limit for `eth_call` and call tracing RPC methods.
#[arg(
Expand Down Expand Up @@ -326,7 +330,8 @@ impl RethRpcConfig for RpcServerArgs {
fn eth_config(&self) -> EthConfig {
EthConfig::default()
.max_tracing_requests(self.rpc_max_tracing_requests)
.max_logs_per_response(self.rpc_max_logs_per_response)
.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)
.rpc_gas_cap(self.rpc_gas_cap)
.gpo_config(self.gas_price_oracle_config())
}
Expand Down Expand Up @@ -598,4 +603,36 @@ mod tests {
);
assert_eq!(config.ipc_endpoint().unwrap().path(), constants::DEFAULT_IPC_ENDPOINT);
}

#[test]
fn test_zero_filter_limits() {
let args = CommandParser::<RpcServerArgs>::parse_from([
"reth",
"--rpc-max-blocks-per-filter",
"0",
"--rpc-max-logs-per-response",
"0",
])
.args;

let config = args.eth_config().filter_config();
assert_eq!(config.max_blocks_per_filter, Some(u64::MAX));
assert_eq!(config.max_logs_per_response, Some(usize::MAX));
}

#[test]
fn test_custom_filter_limits() {
let args = CommandParser::<RpcServerArgs>::parse_from([
"reth",
"--rpc-max-blocks-per-filter",
"100",
"--rpc-max-logs-per-response",
"200",
])
.args;

let config = args.eth_config().filter_config();
assert_eq!(config.max_blocks_per_filter, Some(100));
assert_eq!(config.max_logs_per_response, Some(200));
}
}
49 changes: 49 additions & 0 deletions bin/reth/src/args/types.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
//! Additional helper types for CLI parsing.
use std::{fmt, str::FromStr};

/// A helper type that maps `0` to `None` when parsing CLI arguments.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ZeroAsNone(pub Option<u64>);

impl ZeroAsNone {
/// Returns the inner value.
pub const fn new(value: u64) -> Self {
Self(Some(value))
}

/// Returns the inner value or `u64::MAX` if `None`.
pub fn unwrap_or_max(self) -> u64 {
self.0.unwrap_or(u64::MAX)
}
}

impl fmt::Display for ZeroAsNone {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.0 {
Some(value) => write!(f, "{}", value),
None => write!(f, "0"),
}
}
}

impl FromStr for ZeroAsNone {
type Err = std::num::ParseIntError;

fn from_str(s: &str) -> Result<Self, Self::Err> {
let value = s.parse::<u64>()?;
Ok(Self(if value == 0 { None } else { Some(value) }))
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_zero_parse() {
let val = "0".parse::<ZeroAsNone>().unwrap();
assert_eq!(val, ZeroAsNone(None));
assert_eq!(val.unwrap_or_max(), u64::MAX);
}
}
17 changes: 7 additions & 10 deletions crates/rpc/rpc-builder/src/auth.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::{
constants,
constants::DEFAULT_MAX_LOGS_PER_RESPONSE,
constants::{DEFAULT_MAX_BLOCKS_PER_FILTER, DEFAULT_MAX_LOGS_PER_RESPONSE},
error::{RpcError, ServerKind},
EthConfig,
};
Expand All @@ -16,7 +16,7 @@ use reth_provider::{
StateProviderFactory,
};
use reth_rpc::{
eth::{cache::EthStateCache, gas_oracle::GasPriceOracle},
eth::{cache::EthStateCache, gas_oracle::GasPriceOracle, EthFilterConfig},
AuthLayer, BlockingTaskPool, Claims, EngineEthApi, EthApi, EthFilter,
EthSubscriptionIdProvider, JwtAuthValidator, JwtSecret,
};
Expand Down Expand Up @@ -68,14 +68,11 @@ where
Box::new(executor.clone()),
BlockingTaskPool::build().expect("failed to build tracing pool"),
);
let eth_filter = EthFilter::new(
provider,
pool,
eth_cache.clone(),
DEFAULT_MAX_LOGS_PER_RESPONSE,
Box::new(executor.clone()),
EthConfig::default().stale_filter_ttl,
);
let config = EthFilterConfig::default()
.max_logs_per_response(DEFAULT_MAX_LOGS_PER_RESPONSE)
.max_blocks_per_filter(DEFAULT_MAX_BLOCKS_PER_FILTER);
let eth_filter =
EthFilter::new(provider, pool, eth_cache.clone(), config, Box::new(executor.clone()));
launch_with_eth_api(eth_api, eth_filter, engine_api, socket_addr, secret).await
}

Expand Down
3 changes: 3 additions & 0 deletions crates/rpc/rpc-builder/src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ pub const DEFAULT_WS_RPC_PORT: u16 = 8546;
/// The default port for the auth server.
pub const DEFAULT_AUTH_PORT: u16 = 8551;

/// The default maximum block range allowed to filter
pub const DEFAULT_MAX_BLOCKS_PER_FILTER: u64 = 100_000;

/// The default maximum of logs in a single response.
pub const DEFAULT_MAX_LOGS_PER_RESPONSE: usize = 20_000;

Expand Down
25 changes: 23 additions & 2 deletions crates/rpc/rpc-builder/src/eth.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
use crate::constants::{DEFAULT_MAX_LOGS_PER_RESPONSE, DEFAULT_MAX_TRACING_REQUESTS};
use crate::constants::{
DEFAULT_MAX_BLOCKS_PER_FILTER, DEFAULT_MAX_LOGS_PER_RESPONSE, DEFAULT_MAX_TRACING_REQUESTS,
};
use reth_rpc::{
eth::{
cache::{EthStateCache, EthStateCacheConfig},
gas_oracle::GasPriceOracleConfig,
RPC_DEFAULT_GAS_CAP,
EthFilterConfig, RPC_DEFAULT_GAS_CAP,
},
BlockingTaskPool, EthApi, EthFilter, EthPubSub,
};
Expand Down Expand Up @@ -33,6 +35,8 @@ pub struct EthConfig {
pub gas_oracle: GasPriceOracleConfig,
/// The maximum number of tracing calls that can be executed in concurrently.
pub max_tracing_requests: u32,
/// Maximum number of blocks that could be scanned per filter request in `eth_getLogs` calls.
pub max_blocks_per_filter: u64,
/// Maximum number of logs that can be returned in a single response in `eth_getLogs` calls.
pub max_logs_per_response: usize,
/// Gas limit for `eth_call` and call tracing RPC methods.
Expand All @@ -44,6 +48,16 @@ pub struct EthConfig {
pub stale_filter_ttl: std::time::Duration,
}

impl EthConfig {
/// Returns the filter config for the `eth_filter` handler.
pub fn filter_config(&self) -> EthFilterConfig {
EthFilterConfig::default()
.max_blocks_per_filter(self.max_blocks_per_filter)
.max_logs_per_response(self.max_logs_per_response)
.stale_filter_ttl(self.stale_filter_ttl)
}
}

/// Default value for stale filter ttl
const DEFAULT_STALE_FILTER_TTL: std::time::Duration = std::time::Duration::from_secs(5 * 60);

Expand All @@ -53,6 +67,7 @@ impl Default for EthConfig {
cache: EthStateCacheConfig::default(),
gas_oracle: GasPriceOracleConfig::default(),
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,
rpc_gas_cap: RPC_DEFAULT_GAS_CAP.into(),
stale_filter_ttl: DEFAULT_STALE_FILTER_TTL,
Expand All @@ -79,6 +94,12 @@ impl EthConfig {
self
}

/// Configures the maximum block length to scan per `eth_getLogs` request
pub fn max_blocks_per_filter(mut self, max_blocks: u64) -> Self {
self.max_blocks_per_filter = max_blocks;
self
}

/// Configures the maximum number of logs per response
pub fn max_logs_per_response(mut self, max_logs: usize) -> Self {
self.max_logs_per_response = max_logs;
Expand Down
3 changes: 1 addition & 2 deletions crates/rpc/rpc-builder/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1043,9 +1043,8 @@ where
self.provider.clone(),
self.pool.clone(),
cache.clone(),
self.config.eth.max_logs_per_response,
self.config.eth.filter_config(),
executor.clone(),
self.config.eth.stale_filter_ttl,
);

let pubsub = EthPubSub::with_spawner(
Expand Down
Loading

0 comments on commit 0449c5e

Please sign in to comment.