use std::{net::SocketAddr, path::PathBuf};
use jsonrpsee::server::ServerBuilder;
use reth_node_core::{args::RpcServerArgs, utils::get_or_create_jwt_secret_from_path};
use reth_rpc::ValidationApiConfig;
use reth_rpc_eth_types::{EthConfig, EthStateCacheConfig, GasPriceOracleConfig};
use reth_rpc_layer::{JwtError, JwtSecret};
use reth_rpc_server_types::RpcModuleSelection;
use tower::layer::util::Identity;
use tracing::{debug, warn};
use crate::{
auth::AuthServerConfig, error::RpcError, IpcServerBuilder, RpcModuleConfig, RpcServerConfig,
TransportRpcModuleConfig,
};
pub trait RethRpcServerConfig {
fn is_ipc_enabled(&self) -> bool;
fn ipc_path(&self) -> &str;
fn eth_config(&self) -> EthConfig;
fn flashbots_config(&self) -> ValidationApiConfig;
fn state_cache_config(&self) -> EthStateCacheConfig;
fn rpc_max_request_size_bytes(&self) -> u32;
fn rpc_max_response_size_bytes(&self) -> u32;
fn gas_price_oracle_config(&self) -> GasPriceOracleConfig;
fn transport_rpc_module_config(&self) -> TransportRpcModuleConfig;
fn http_ws_server_builder(&self) -> ServerBuilder<Identity, Identity>;
fn ipc_server_builder(&self) -> IpcServerBuilder<Identity, Identity>;
fn rpc_server_config(&self) -> RpcServerConfig;
fn auth_server_config(&self, jwt_secret: JwtSecret) -> Result<AuthServerConfig, RpcError>;
fn auth_jwt_secret(&self, default_jwt_path: PathBuf) -> Result<JwtSecret, JwtError>;
fn rpc_secret_key(&self) -> Option<JwtSecret>;
}
impl RethRpcServerConfig for RpcServerArgs {
fn is_ipc_enabled(&self) -> bool {
!self.ipcdisable
}
fn ipc_path(&self) -> &str {
self.ipcpath.as_str()
}
fn eth_config(&self) -> EthConfig {
EthConfig::default()
.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)
.rpc_max_simulate_blocks(self.rpc_max_simulate_blocks)
.state_cache(self.state_cache_config())
.gpo_config(self.gas_price_oracle_config())
.proof_permits(self.rpc_proof_permits)
}
fn flashbots_config(&self) -> ValidationApiConfig {
ValidationApiConfig { disallow: self.builder_disallow.clone().unwrap_or_default() }
}
fn state_cache_config(&self) -> EthStateCacheConfig {
EthStateCacheConfig {
max_blocks: self.rpc_state_cache.max_blocks,
max_receipts: self.rpc_state_cache.max_receipts,
max_headers: self.rpc_state_cache.max_headers,
max_concurrent_db_requests: self.rpc_state_cache.max_concurrent_db_requests,
}
}
fn rpc_max_request_size_bytes(&self) -> u32 {
self.rpc_max_request_size.get().saturating_mul(1024 * 1024)
}
fn rpc_max_response_size_bytes(&self) -> u32 {
self.rpc_max_response_size.get().saturating_mul(1024 * 1024)
}
fn gas_price_oracle_config(&self) -> GasPriceOracleConfig {
self.gas_price_oracle.gas_price_oracle_config()
}
fn transport_rpc_module_config(&self) -> TransportRpcModuleConfig {
let mut config = TransportRpcModuleConfig::default()
.with_config(RpcModuleConfig::new(self.eth_config(), self.flashbots_config()));
if self.http {
config = config.with_http(
self.http_api
.clone()
.unwrap_or_else(|| RpcModuleSelection::standard_modules().into()),
);
}
if self.ws {
config = config.with_ws(
self.ws_api
.clone()
.unwrap_or_else(|| RpcModuleSelection::standard_modules().into()),
);
}
if self.is_ipc_enabled() {
config = config.with_ipc(RpcModuleSelection::default_ipc_modules());
}
config
}
fn http_ws_server_builder(&self) -> ServerBuilder<Identity, Identity> {
ServerBuilder::new()
.max_connections(self.rpc_max_connections.get())
.max_request_body_size(self.rpc_max_request_size_bytes())
.max_response_body_size(self.rpc_max_response_size_bytes())
.max_subscriptions_per_connection(self.rpc_max_subscriptions_per_connection.get())
}
fn ipc_server_builder(&self) -> IpcServerBuilder<Identity, Identity> {
IpcServerBuilder::default()
.max_subscriptions_per_connection(self.rpc_max_subscriptions_per_connection.get())
.max_request_body_size(self.rpc_max_request_size_bytes())
.max_response_body_size(self.rpc_max_response_size_bytes())
.max_connections(self.rpc_max_connections.get())
}
fn rpc_server_config(&self) -> RpcServerConfig {
let mut config = RpcServerConfig::default().with_jwt_secret(self.rpc_secret_key());
if self.http_api.is_some() && !self.http {
warn!(
target: "reth::cli",
"The --http.api flag is set but --http is not enabled. HTTP RPC API will not be exposed."
);
}
if self.http {
let socket_address = SocketAddr::new(self.http_addr, self.http_port);
config = config
.with_http_address(socket_address)
.with_http(self.http_ws_server_builder())
.with_http_cors(self.http_corsdomain.clone())
.with_ws_cors(self.ws_allowed_origins.clone());
}
if self.ws {
let socket_address = SocketAddr::new(self.ws_addr, self.ws_port);
config = config.with_ws_address(socket_address).with_ws(self.http_ws_server_builder());
}
if self.is_ipc_enabled() {
config =
config.with_ipc(self.ipc_server_builder()).with_ipc_endpoint(self.ipcpath.clone());
}
config
}
fn auth_server_config(&self, jwt_secret: JwtSecret) -> Result<AuthServerConfig, RpcError> {
let address = SocketAddr::new(self.auth_addr, self.auth_port);
let mut builder = AuthServerConfig::builder(jwt_secret).socket_addr(address);
if self.auth_ipc {
builder = builder
.ipc_endpoint(self.auth_ipc_path.clone())
.with_ipc_config(self.ipc_server_builder());
}
Ok(builder.build())
}
fn auth_jwt_secret(&self, default_jwt_path: PathBuf) -> Result<JwtSecret, JwtError> {
match self.auth_jwtsecret.as_ref() {
Some(fpath) => {
debug!(target: "reth::cli", user_path=?fpath, "Reading JWT auth secret file");
JwtSecret::from_file(fpath)
}
None => get_or_create_jwt_secret_from_path(&default_jwt_path),
}
}
fn rpc_secret_key(&self) -> Option<JwtSecret> {
self.rpc_jwtsecret
}
}
#[cfg(test)]
mod tests {
use clap::{Args, Parser};
use reth_node_core::args::RpcServerArgs;
use reth_rpc_eth_types::RPC_DEFAULT_GAS_CAP;
use reth_rpc_server_types::{constants, RethRpcModule, RpcModuleSelection};
use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4};
use crate::config::RethRpcServerConfig;
#[derive(Parser)]
struct CommandParser<T: Args> {
#[command(flatten)]
args: T,
}
#[test]
fn test_rpc_gas_cap() {
let args = CommandParser::<RpcServerArgs>::parse_from(["reth"]).args;
let config = args.eth_config();
assert_eq!(config.rpc_gas_cap, u64::from(RPC_DEFAULT_GAS_CAP));
let args =
CommandParser::<RpcServerArgs>::parse_from(["reth", "--rpc.gascap", "1000"]).args;
let config = args.eth_config();
assert_eq!(config.rpc_gas_cap, 1000);
let args = CommandParser::<RpcServerArgs>::try_parse_from(["reth", "--rpc.gascap", "0"]);
assert!(args.is_err());
}
#[test]
fn test_transport_rpc_module_config() {
let args = CommandParser::<RpcServerArgs>::parse_from([
"reth",
"--http.api",
"eth,admin,debug",
"--http",
"--ws",
])
.args;
let config = args.transport_rpc_module_config();
let expected = [RethRpcModule::Eth, RethRpcModule::Admin, RethRpcModule::Debug];
assert_eq!(config.http().cloned().unwrap().into_selection(), expected.into());
assert_eq!(
config.ws().cloned().unwrap().into_selection(),
RpcModuleSelection::standard_modules()
);
}
#[test]
fn test_transport_rpc_module_trim_config() {
let args = CommandParser::<RpcServerArgs>::parse_from([
"reth",
"--http.api",
" eth, admin, debug",
"--http",
"--ws",
])
.args;
let config = args.transport_rpc_module_config();
let expected = [RethRpcModule::Eth, RethRpcModule::Admin, RethRpcModule::Debug];
assert_eq!(config.http().cloned().unwrap().into_selection(), expected.into());
assert_eq!(
config.ws().cloned().unwrap().into_selection(),
RpcModuleSelection::standard_modules()
);
}
#[test]
fn test_unique_rpc_modules() {
let args = CommandParser::<RpcServerArgs>::parse_from([
"reth",
"--http.api",
" eth, admin, debug, eth,admin",
"--http",
"--ws",
])
.args;
let config = args.transport_rpc_module_config();
let expected = [RethRpcModule::Eth, RethRpcModule::Admin, RethRpcModule::Debug];
assert_eq!(config.http().cloned().unwrap().into_selection(), expected.into());
assert_eq!(
config.ws().cloned().unwrap().into_selection(),
RpcModuleSelection::standard_modules()
);
}
#[test]
fn test_rpc_server_config() {
let args = CommandParser::<RpcServerArgs>::parse_from([
"reth",
"--http.api",
"eth,admin,debug",
"--http",
"--ws",
"--ws.addr",
"127.0.0.1",
"--ws.port",
"8888",
])
.args;
let config = args.rpc_server_config();
assert_eq!(
config.http_address().unwrap(),
SocketAddr::V4(SocketAddrV4::new(
Ipv4Addr::LOCALHOST,
constants::DEFAULT_HTTP_RPC_PORT
))
);
assert_eq!(
config.ws_address().unwrap(),
SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), 8888))
);
assert_eq!(config.ipc_endpoint().unwrap(), 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));
}
}