From 4c226465a1ddd8288f0ede11e55f2458ababf94e Mon Sep 17 00:00:00 2001 From: James Date: Fri, 20 Mar 2026 13:24:26 -0400 Subject: [PATCH 1/7] refactor(node-config): remove reth deps, dead fields, and port/ipc config Strip SignetNodeConfig of reth-coupled fields (static_path, http_port, ws_port, ipc_endpoint) and methods (static_file_ro/rw, chain_spec, spec_id). Remove reth, reth-chainspec, trevm, and signet-evm dependencies from Cargo.toml. Simplify test_utils to match the reduced API surface. Co-Authored-By: Claude Opus 4.6 (1M context) --- crates/node-config/Cargo.toml | 7 +- crates/node-config/src/core.rs | 116 +-------------------------- crates/node-config/src/lib.rs | 2 +- crates/node-config/src/test_utils.rs | 15 +--- 4 files changed, 7 insertions(+), 133 deletions(-) diff --git a/crates/node-config/Cargo.toml b/crates/node-config/Cargo.toml index f906131..2df6b05 100644 --- a/crates/node-config/Cargo.toml +++ b/crates/node-config/Cargo.toml @@ -10,14 +10,11 @@ repository.workspace = true [dependencies] signet-blobber.workspace = true -signet-evm.workspace = true signet-storage.workspace = true signet-types.workspace = true init4-bin-base.workspace = true -reth.workspace = true -reth-chainspec.workspace = true alloy.workspace = true eyre.workspace = true @@ -25,12 +22,10 @@ reqwest.workspace = true serde.workspace = true tokio-util.workspace = true tracing.workspace = true -trevm.workspace = true signet-genesis.workspace = true -tempfile = { workspace = true, optional = true } [features] -test_utils = ["dep:tempfile"] +test_utils = [] postgres = ["signet-storage/postgres"] sqlite = ["signet-storage/sqlite"] diff --git a/crates/node-config/src/core.rs b/crates/node-config/src/core.rs index 185f7fd..410a4d1 100644 --- a/crates/node-config/src/core.rs +++ b/crates/node-config/src/core.rs @@ -1,23 +1,11 @@ use crate::StorageConfig; use alloy::genesis::Genesis; use init4_bin_base::utils::{calc::SlotCalculator, from_env::FromEnv}; -use reth::primitives::NodePrimitives; -use reth::providers::providers::StaticFileProvider; -use reth_chainspec::ChainSpec; use signet_blobber::BlobFetcherConfig; use signet_genesis::GenesisSpec; use signet_types::constants::{ConfigError, SignetSystemConstants}; -use std::{ - borrow::Cow, - fmt::Display, - path::PathBuf, - sync::{Arc, OnceLock}, -}; +use std::{borrow::Cow, fmt::Display, sync::OnceLock}; use tracing::warn; -use trevm::revm::primitives::hardfork::SpecId; - -/// Defines the default port for serving Signet Node JSON RPC requests over http. -pub const SIGNET_NODE_DEFAULT_HTTP_PORT: u16 = 5959u16; /// Configuration for a Signet Node instance. Contains system contract and signer /// information. @@ -28,9 +16,6 @@ pub struct SignetNodeConfig { #[from_env(infallible)] block_extractor: BlobFetcherConfig, - /// Path to the static files for reth StaticFileProviders. - #[from_env(var = "SIGNET_STATIC_PATH", desc = "Path to the static files", infallible)] - static_path: Cow<'static, str>, /// Unified storage configuration (hot + cold MDBX paths). #[from_env(infallible)] storage: StorageConfig, @@ -42,20 +27,6 @@ pub struct SignetNodeConfig { optional )] forward_url: Option>, - /// RPC port to serve JSON-RPC requests - #[from_env(var = "RPC_PORT", desc = "RPC port to serve JSON-RPC requests", optional)] - http_port: Option, - /// Websocket port to serve JSON-RPC requests - #[from_env(var = "WS_RPC_PORT", desc = "Websocket port to serve JSON-RPC requests", optional)] - ws_port: Option, - /// IPC endpoint to serve JSON-RPC requests - #[from_env( - var = "IPC_ENDPOINT", - desc = "IPC endpoint to serve JSON-RPC requests", - infallible, - optional - )] - ipc_endpoint: Option>, /// Configuration loaded from genesis file, or known genesis. genesis: GenesisSpec, @@ -82,29 +53,20 @@ impl Display for SignetNodeConfig { impl SignetNodeConfig { /// Create a new Signet Node configuration. - #[allow(clippy::too_many_arguments)] pub const fn new( block_extractor: BlobFetcherConfig, - static_path: Cow<'static, str>, storage: StorageConfig, forward_url: Option>, - rpc_port: u16, - ws_port: u16, - ipc_endpoint: Option>, genesis: GenesisSpec, slot_calculator: SlotCalculator, ) -> Self { Self { block_extractor, - static_path, storage, forward_url, - http_port: Some(rpc_port), - ws_port: Some(ws_port), - ipc_endpoint, genesis, slot_calculator, - backfill_max_blocks: None, // Uses default of 10,000 via accessor + backfill_max_blocks: None, } } @@ -133,26 +95,6 @@ impl SignetNodeConfig { self.slot_calculator } - /// Get the static path as a str. - pub fn static_path_str(&self) -> &str { - &self.static_path - } - - /// Get the static path. - pub fn static_path(&self) -> PathBuf { - self.static_path.as_ref().to_owned().into() - } - - /// Get the static file provider for read-only access. - pub fn static_file_ro(&self) -> eyre::Result> { - StaticFileProvider::read_only(self.static_path(), true).map_err(Into::into) - } - - /// Get the static file provider for read-write access. - pub fn static_file_rw(&self) -> eyre::Result> { - StaticFileProvider::read_write(self.static_path()).map_err(Into::into) - } - /// Get the storage configuration. pub const fn storage(&self) -> &StorageConfig { &self.storage @@ -167,65 +109,17 @@ impl SignetNodeConfig { .ok() } - /// Returns the port for serving JSON RPC requests for Signet Node. - pub const fn http_port(&self) -> u16 { - if let Some(port) = self.http_port { - return port; - } - SIGNET_NODE_DEFAULT_HTTP_PORT - } - - /// Set the HTTP port for serving JSON RPC requests for Signet Node. - pub const fn set_http_port(&mut self, port: u16) { - self.http_port = Some(port); - } - - /// Returns the port for serving Websocket RPC requests for Signet Node. - pub const fn ws_port(&self) -> u16 { - if let Some(port) = self.ws_port { - return port; - } - SIGNET_NODE_DEFAULT_HTTP_PORT + 1 - } - - /// Set the websocket port for serving JSON RPC requests for Signet. - pub const fn set_ws_port(&mut self, port: u16) { - self.ws_port = Some(port); - } - - /// Returns the IPC endpoint for serving JSON RPC requests for Signet, if any. - pub fn ipc_endpoint(&self) -> Option<&str> { - self.ipc_endpoint.as_deref() - } - - /// Set the IPC endpoint for serving JSON RPC requests for Signet Node. - pub fn set_ipc_endpoint(&mut self, endpoint: Cow<'static, str>) { - self.ipc_endpoint = Some(endpoint); - } - /// Returns the rollup genesis configuration if any has been loaded. pub fn genesis(&self) -> &'static Genesis { static ONCE: OnceLock> = OnceLock::new(); ONCE.get_or_init(|| self.genesis.load_genesis().expect("Failed to load genesis").rollup) } - /// Create a new chain spec for the Signet Node chain. - pub fn chain_spec(&self) -> &Arc { - static SPEC: OnceLock> = OnceLock::new(); - SPEC.get_or_init(|| Arc::new(self.genesis().clone().into())) - } - /// Get the system constants for the Signet Node chain. pub fn constants(&self) -> Result { SignetSystemConstants::try_from_genesis(self.genesis()) } - /// Get the current spec id for the Signet Node chain. - pub fn spec_id(&self, block: u64, timestamp: u64) -> SpecId { - signet_evm::EthereumHardfork::active_hardforks(&self.genesis().config, block, timestamp) - .spec_id() - } - /// Get the maximum number of blocks to process per backfill batch. /// Returns `Some(10_000)` by default if not configured, to avoid OOM /// during mainnet sync (reth's default of 500K is too aggressive). @@ -244,15 +138,11 @@ mod defaults { fn default() -> Self { Self { block_extractor: BlobFetcherConfig::new(Cow::Borrowed("")), - static_path: Cow::Borrowed(""), storage: StorageConfig::new(Cow::Borrowed(""), Cow::Borrowed("")), forward_url: None, - http_port: Some(SIGNET_NODE_DEFAULT_HTTP_PORT), - ws_port: Some(SIGNET_NODE_DEFAULT_HTTP_PORT + 1), - ipc_endpoint: None, genesis: GenesisSpec::Known(KnownChains::Test), slot_calculator: SlotCalculator::new(0, 0, 12), - backfill_max_blocks: None, // Uses default of 10,000 via accessor + backfill_max_blocks: None, } } } diff --git a/crates/node-config/src/lib.rs b/crates/node-config/src/lib.rs index abd9c7c..c3b6e8a 100644 --- a/crates/node-config/src/lib.rs +++ b/crates/node-config/src/lib.rs @@ -12,7 +12,7 @@ #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] mod core; -pub use core::{SIGNET_NODE_DEFAULT_HTTP_PORT, SignetNodeConfig}; +pub use core::SignetNodeConfig; // NB: RPC config merging (previously `merge_rpc_configs`) is now the // responsibility of the host adapter crate (e.g. `signet-host-reth`). diff --git a/crates/node-config/src/test_utils.rs b/crates/node-config/src/test_utils.rs index 4afea53..f1f887d 100644 --- a/crates/node-config/src/test_utils.rs +++ b/crates/node-config/src/test_utils.rs @@ -4,28 +4,17 @@ use signet_blobber::BlobFetcherConfig; use signet_genesis::GenesisSpec; use signet_types::constants::KnownChains; use std::borrow::Cow; -use tempfile::tempdir; /// Make a test config -pub fn test_config() -> SignetNodeConfig { - let mut tempdir = tempdir().unwrap().keep(); - tempdir.push("signet.ipc"); - - // Make a new test config with the IPC endpoint set to the tempdir. - let mut cfg = TEST_CONFIG; - cfg.set_ipc_endpoint(Cow::Owned(format!("{}", tempdir.to_string_lossy()))); - cfg +pub const fn test_config() -> SignetNodeConfig { + TEST_CONFIG } /// Test SignetNodeConfig const TEST_CONFIG: SignetNodeConfig = SignetNodeConfig::new( BlobFetcherConfig::new(Cow::Borrowed("")), - Cow::Borrowed("NOP"), StorageConfig::new(Cow::Borrowed("NOP"), Cow::Borrowed("NOP")), None, - 31391, // NOP - 31392, // NOP - Some(Cow::Borrowed("/trethNOP")), GenesisSpec::Known(KnownChains::Test), SlotCalculator::new(0, 0, 12), ); From 7cdba6ef5d2e09a1cac1ce3d993359d6379885f0 Mon Sep 17 00:00:00 2001 From: James Date: Fri, 20 Mar 2026 13:26:51 -0400 Subject: [PATCH 2/7] feat(rpc): add ServeConfigEnv with FromEnv for env-based RPC transport config Co-Authored-By: Claude Opus 4.6 (1M context) --- crates/rpc/Cargo.toml | 1 + crates/rpc/src/lib.rs | 2 +- crates/rpc/src/serve.rs | 81 ++++++++++++++++++++++++++++++++++++++++- 3 files changed, 82 insertions(+), 2 deletions(-) diff --git a/crates/rpc/Cargo.toml b/crates/rpc/Cargo.toml index 8384eb0..4a3994c 100644 --- a/crates/rpc/Cargo.toml +++ b/crates/rpc/Cargo.toml @@ -19,6 +19,7 @@ trevm = { workspace = true, features = ["call", "estimate_gas"] } signet-types.workspace = true signet-tx-cache.workspace = true signet-bundle.workspace = true +init4-bin-base.workspace = true alloy.workspace = true ajj.workspace = true tokio.workspace = true diff --git a/crates/rpc/src/lib.rs b/crates/rpc/src/lib.rs index 2aaf164..6c1226a 100644 --- a/crates/rpc/src/lib.rs +++ b/crates/rpc/src/lib.rs @@ -27,7 +27,7 @@ mod signet; pub use signet::error::SignetError; pub mod serve; -pub use serve::{RpcServerGuard, ServeConfig, ServeError}; +pub use serve::{RpcServerGuard, ServeConfig, ServeConfigEnv, ServeError}; /// Instantiate a combined router with `eth`, `debug`, and `signet` /// namespaces. diff --git a/crates/rpc/src/serve.rs b/crates/rpc/src/serve.rs index ef84236..9584b5e 100644 --- a/crates/rpc/src/serve.rs +++ b/crates/rpc/src/serve.rs @@ -10,8 +10,9 @@ use ajj::{ pubsub::{Connect, ServerShutdown}, }; use axum::http::HeaderValue; +use init4_bin_base::utils::from_env::FromEnv; use interprocess::local_socket as ls; -use std::{future::IntoFuture, net::SocketAddr}; +use std::{borrow::Cow, future::IntoFuture, net::SocketAddr}; use tokio::{runtime::Handle, task::JoinHandle}; use tower_http::cors::{AllowOrigin, Any, CorsLayer}; use tracing::error; @@ -141,6 +142,84 @@ impl ServeConfig { } } +/// Environment-based configuration for the RPC transport layer. +/// +/// Loads bind addresses and CORS settings from environment variables. +/// All fields are optional — omitting HTTP/WS fields disables that +/// transport. +/// +/// # Environment Variables +/// +/// - `SIGNET_HTTP_ADDR` – HTTP bind address (default: `0.0.0.0`) +/// - `SIGNET_HTTP_PORT` – HTTP port (enables HTTP transport) +/// - `SIGNET_HTTP_CORS` – CORS origins for HTTP +/// - `SIGNET_WS_ADDR` – WebSocket bind address (default: `0.0.0.0`) +/// - `SIGNET_WS_PORT` – WebSocket port (enables WS transport) +/// - `SIGNET_WS_CORS` – CORS origins for WebSocket +/// - `SIGNET_IPC_ENDPOINT` – IPC socket path (enables IPC transport) +#[derive(Debug, Clone, FromEnv)] +pub struct ServeConfigEnv { + /// HTTP bind address. + #[from_env(var = "SIGNET_HTTP_ADDR", desc = "HTTP bind address", infallible, optional)] + http_addr: Option>, + /// HTTP port. Setting this enables the HTTP transport. + #[from_env(var = "SIGNET_HTTP_PORT", desc = "HTTP port", optional)] + http_port: Option, + /// CORS origins for HTTP. + #[from_env(var = "SIGNET_HTTP_CORS", desc = "CORS origins for HTTP", infallible, optional)] + http_cors: Option>, + /// WebSocket bind address. + #[from_env(var = "SIGNET_WS_ADDR", desc = "WebSocket bind address", infallible, optional)] + ws_addr: Option>, + /// WebSocket port. Setting this enables the WS transport. + #[from_env(var = "SIGNET_WS_PORT", desc = "WebSocket port", optional)] + ws_port: Option, + /// CORS origins for WebSocket. + #[from_env(var = "SIGNET_WS_CORS", desc = "CORS origins for WebSocket", infallible, optional)] + ws_cors: Option>, + /// IPC endpoint path. Setting this enables IPC transport. + #[from_env(var = "SIGNET_IPC_ENDPOINT", desc = "IPC socket path", infallible, optional)] + ipc: Option>, +} + +impl From for ServeConfig { + fn from(env: ServeConfigEnv) -> Self { + let http = env + .http_port + .map(|port| { + let addr = env + .http_addr + .as_deref() + .unwrap_or("0.0.0.0") + .parse() + .unwrap_or(std::net::Ipv4Addr::UNSPECIFIED.into()); + vec![SocketAddr::new(addr, port)] + }) + .unwrap_or_default(); + + let ws = env + .ws_port + .map(|port| { + let addr = env + .ws_addr + .as_deref() + .unwrap_or("0.0.0.0") + .parse() + .unwrap_or(std::net::Ipv4Addr::UNSPECIFIED.into()); + vec![SocketAddr::new(addr, port)] + }) + .unwrap_or_default(); + + Self { + http, + http_cors: env.http_cors.map(Cow::into_owned), + ws, + ws_cors: env.ws_cors.map(Cow::into_owned), + ipc: env.ipc.map(Cow::into_owned), + } + } +} + fn make_cors(cors: Option<&str>) -> Result { let origins = match cors { None | Some("*") => AllowOrigin::any(), From 669436e4a0a9daf01234523eb50589653c9f1f93 Mon Sep 17 00:00:00 2001 From: James Date: Fri, 20 Mar 2026 13:28:52 -0400 Subject: [PATCH 3/7] feat(rpc): add StorageRpcConfigEnv with FromEnv for env-based RPC config Co-Authored-By: Claude Opus 4.6 (1M context) --- crates/rpc/src/config/mod.rs | 2 +- crates/rpc/src/config/rpc_config.rs | 101 ++++++++++++++++++++++++++++ crates/rpc/src/lib.rs | 4 +- 3 files changed, 105 insertions(+), 2 deletions(-) diff --git a/crates/rpc/src/config/mod.rs b/crates/rpc/src/config/mod.rs index 4a23104..4c157a1 100644 --- a/crates/rpc/src/config/mod.rs +++ b/crates/rpc/src/config/mod.rs @@ -7,7 +7,7 @@ mod chain_notifier; pub use chain_notifier::ChainNotifier; mod rpc_config; -pub use rpc_config::StorageRpcConfig; +pub use rpc_config::{StorageRpcConfig, StorageRpcConfigEnv}; mod ctx; pub(crate) use ctx::EvmBlockContext; diff --git a/crates/rpc/src/config/rpc_config.rs b/crates/rpc/src/config/rpc_config.rs index d2c7de1..f8ff6c5 100644 --- a/crates/rpc/src/config/rpc_config.rs +++ b/crates/rpc/src/config/rpc_config.rs @@ -1,5 +1,6 @@ //! Configuration for the storage-backed RPC server. +use init4_bin_base::utils::from_env::FromEnv; use std::time::Duration; /// Configuration for the storage-backed ETH RPC server. @@ -239,3 +240,103 @@ impl StorageRpcConfigBuilder { self.inner } } + +/// Environment-based configuration for the storage RPC server. +/// +/// All fields are optional and default to the same values as +/// [`StorageRpcConfig::default`]. +#[derive(Debug, Clone, FromEnv)] +pub struct StorageRpcConfigEnv { + /// Maximum gas for `eth_call` and `eth_estimateGas`. + #[from_env(var = "SIGNET_RPC_GAS_CAP", desc = "Max gas for eth_call", optional)] + rpc_gas_cap: Option, + /// Maximum block range per `eth_getLogs` query. + #[from_env( + var = "SIGNET_RPC_MAX_BLOCKS_PER_FILTER", + desc = "Max block range for getLogs", + optional + )] + max_blocks_per_filter: Option, + /// Maximum number of logs returned per response. + #[from_env(var = "SIGNET_RPC_MAX_LOGS", desc = "Max logs per response", optional)] + max_logs_per_response: Option, + /// Maximum seconds for a single log query. + #[from_env( + var = "SIGNET_RPC_LOG_QUERY_DEADLINE_SECS", + desc = "Max seconds for log query", + optional + )] + max_log_query_deadline_secs: Option, + /// Maximum concurrent tracing/debug requests. + #[from_env( + var = "SIGNET_RPC_MAX_TRACING_REQUESTS", + desc = "Concurrent tracing limit", + optional + )] + max_tracing_requests: Option, + /// Filter TTL in seconds. + #[from_env(var = "SIGNET_RPC_STALE_FILTER_TTL_SECS", desc = "Filter TTL in seconds", optional)] + stale_filter_ttl_secs: Option, + /// Number of recent blocks for gas oracle. + #[from_env(var = "SIGNET_RPC_GAS_ORACLE_BLOCKS", desc = "Blocks for gas oracle", optional)] + gas_oracle_block_count: Option, + /// Tip percentile for gas oracle. + #[from_env(var = "SIGNET_RPC_GAS_ORACLE_PERCENTILE", desc = "Tip percentile", optional)] + gas_oracle_percentile: Option, + /// Default gas price in wei. + #[from_env(var = "SIGNET_RPC_DEFAULT_GAS_PRICE", desc = "Default gas price in wei", optional)] + default_gas_price: Option, + /// Minimum effective tip in wei. + #[from_env(var = "SIGNET_RPC_IGNORE_PRICE", desc = "Min tip in wei", optional)] + ignore_price: Option, + /// Maximum gas price in wei. + #[from_env(var = "SIGNET_RPC_MAX_PRICE", desc = "Max gas price in wei", optional)] + max_price: Option, + /// Maximum header history for `eth_feeHistory`. + #[from_env(var = "SIGNET_RPC_MAX_HEADER_HISTORY", desc = "Max feeHistory headers", optional)] + max_header_history: Option, + /// Maximum block history for `eth_feeHistory`. + #[from_env(var = "SIGNET_RPC_MAX_BLOCK_HISTORY", desc = "Max feeHistory blocks", optional)] + max_block_history: Option, + /// Default bundle simulation timeout in milliseconds. + #[from_env(var = "SIGNET_RPC_BUNDLE_TIMEOUT_MS", desc = "Bundle sim timeout in ms", optional)] + default_bundle_timeout_ms: Option, +} + +impl From for StorageRpcConfig { + fn from(env: StorageRpcConfigEnv) -> Self { + let defaults = StorageRpcConfig::default(); + Self { + rpc_gas_cap: env.rpc_gas_cap.unwrap_or(defaults.rpc_gas_cap), + max_blocks_per_filter: env + .max_blocks_per_filter + .unwrap_or(defaults.max_blocks_per_filter), + max_logs_per_response: env + .max_logs_per_response + .map_or(defaults.max_logs_per_response, |v| v as usize), + max_log_query_deadline: env + .max_log_query_deadline_secs + .map_or(defaults.max_log_query_deadline, Duration::from_secs), + max_tracing_requests: env + .max_tracing_requests + .map_or(defaults.max_tracing_requests, |v| v as usize), + stale_filter_ttl: env + .stale_filter_ttl_secs + .map_or(defaults.stale_filter_ttl, Duration::from_secs), + gas_oracle_block_count: env + .gas_oracle_block_count + .unwrap_or(defaults.gas_oracle_block_count), + gas_oracle_percentile: env + .gas_oracle_percentile + .map_or(defaults.gas_oracle_percentile, |v| v as f64), + default_gas_price: env.default_gas_price.or(defaults.default_gas_price), + ignore_price: env.ignore_price.or(defaults.ignore_price), + max_price: env.max_price.or(defaults.max_price), + max_header_history: env.max_header_history.unwrap_or(defaults.max_header_history), + max_block_history: env.max_block_history.unwrap_or(defaults.max_block_history), + default_bundle_timeout_ms: env + .default_bundle_timeout_ms + .unwrap_or(defaults.default_bundle_timeout_ms), + } + } +} diff --git a/crates/rpc/src/lib.rs b/crates/rpc/src/lib.rs index 6c1226a..e687c91 100644 --- a/crates/rpc/src/lib.rs +++ b/crates/rpc/src/lib.rs @@ -12,7 +12,9 @@ #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] pub(crate) mod config; -pub use config::{BlockTags, ChainNotifier, StorageRpcConfig, StorageRpcCtx, SyncStatus}; +pub use config::{ + BlockTags, ChainNotifier, StorageRpcConfig, StorageRpcConfigEnv, StorageRpcCtx, SyncStatus, +}; mod eth; pub use eth::EthError; From f89b7cb5cfb6739170bfb8388b108f63d43e05ff Mon Sep 17 00:00:00 2001 From: James Date: Fri, 20 Mar 2026 13:30:15 -0400 Subject: [PATCH 4/7] feat(host-rpc): add HostRpcConfig with FromEnv for env-based host config Co-Authored-By: Claude Opus 4.6 (1M context) --- crates/host-rpc/Cargo.toml | 1 + crates/host-rpc/src/config.rs | 53 +++++++++++++++++++++++++++++++++++ crates/host-rpc/src/lib.rs | 3 ++ 3 files changed, 57 insertions(+) create mode 100644 crates/host-rpc/src/config.rs diff --git a/crates/host-rpc/Cargo.toml b/crates/host-rpc/Cargo.toml index 90e0467..be81a5a 100644 --- a/crates/host-rpc/Cargo.toml +++ b/crates/host-rpc/Cargo.toml @@ -15,6 +15,7 @@ signet-extract.workspace = true signet-types.workspace = true alloy.workspace = true +init4-bin-base.workspace = true futures-util.workspace = true metrics.workspace = true thiserror.workspace = true diff --git a/crates/host-rpc/src/config.rs b/crates/host-rpc/src/config.rs new file mode 100644 index 0000000..aa5188b --- /dev/null +++ b/crates/host-rpc/src/config.rs @@ -0,0 +1,53 @@ +use crate::{DEFAULT_BACKFILL_BATCH_SIZE, DEFAULT_BUFFER_CAPACITY, RpcHostNotifierBuilder}; +use alloy::providers::RootProvider; +use init4_bin_base::utils::{calc::SlotCalculator, from_env::FromEnv, provider::PubSubConfig}; + +/// Environment-based configuration for the RPC host notifier. +/// +/// # Environment Variables +/// +/// - `SIGNET_HOST_URL` – WebSocket or IPC URL for the host EL client (required) +/// - `SIGNET_HOST_BUFFER_CAPACITY` – Local chain view size (default: 64) +/// - `SIGNET_HOST_BACKFILL_BATCH_SIZE` – Blocks per backfill batch (default: 32) +/// +/// # Example +/// +/// ```ignore +/// use signet_host_rpc::HostRpcConfig; +/// use init4_bin_base::utils::{calc::SlotCalculator, from_env::FromEnv}; +/// +/// let config = HostRpcConfig::from_env(); +/// let slot_calculator = SlotCalculator::new(0, 1_606_824_023, 12); +/// let builder = config.into_builder(slot_calculator).await?; +/// ``` +#[derive(Debug, Clone, FromEnv)] +pub struct HostRpcConfig { + /// WebSocket or IPC connection to the host execution layer client. + #[from_env(var = "SIGNET_HOST_URL", desc = "Host EL pubsub URL (ws:// or ipc)")] + provider: PubSubConfig, + /// Local chain view buffer capacity. + #[from_env(var = "SIGNET_HOST_BUFFER_CAPACITY", desc = "Chain view buffer capacity", optional)] + buffer_capacity: Option, + /// Blocks per backfill RPC batch. + #[from_env(var = "SIGNET_HOST_BACKFILL_BATCH_SIZE", desc = "Backfill batch size", optional)] + backfill_batch_size: Option, +} + +impl HostRpcConfig { + /// Connect to the host provider and build an [`RpcHostNotifierBuilder`]. + /// + /// Uses `slot_calculator` for genesis timestamp rather than + /// duplicating that setting. + pub async fn into_builder( + self, + slot_calculator: SlotCalculator, + ) -> Result, alloy::transports::TransportError> { + let provider = self.provider.connect().await?; + Ok(RpcHostNotifierBuilder::new(provider) + .with_buffer_capacity(self.buffer_capacity.unwrap_or(DEFAULT_BUFFER_CAPACITY)) + .with_backfill_batch_size( + self.backfill_batch_size.unwrap_or(DEFAULT_BACKFILL_BATCH_SIZE), + ) + .with_genesis_timestamp(slot_calculator.start_timestamp())) + } +} diff --git a/crates/host-rpc/src/lib.rs b/crates/host-rpc/src/lib.rs index f93040b..337c657 100644 --- a/crates/host-rpc/src/lib.rs +++ b/crates/host-rpc/src/lib.rs @@ -19,6 +19,9 @@ pub(crate) const DEFAULT_BACKFILL_BATCH_SIZE: u64 = 32; mod builder; pub use builder::RpcHostNotifierBuilder; +mod config; +pub use config::HostRpcConfig; + mod error; pub use error::RpcHostError; From 781fe32c705c0b165e8fdb6b12d97387961fce3b Mon Sep 17 00:00:00 2001 From: James Date: Fri, 20 Mar 2026 13:32:18 -0400 Subject: [PATCH 5/7] fix(node-tests): adapt to SignetNodeConfig changes, inline IPC path Co-Authored-By: Claude Opus 4.6 (1M context) --- crates/node-tests/Cargo.toml | 1 + crates/node-tests/src/context.rs | 7 +++++-- crates/node-tests/tests/db.rs | 8 +++----- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/crates/node-tests/Cargo.toml b/crates/node-tests/Cargo.toml index d636f76..2cf2f71 100644 --- a/crates/node-tests/Cargo.toml +++ b/crates/node-tests/Cargo.toml @@ -29,6 +29,7 @@ signet-zenith.workspace = true alloy.workspace = true eyre.workspace = true reqwest.workspace = true +tempfile.workspace = true tokio.workspace = true tracing.workspace = true tracing-subscriber.workspace = true diff --git a/crates/node-tests/src/context.rs b/crates/node-tests/src/context.rs index 5c9d55e..d2ad443 100644 --- a/crates/node-tests/src/context.rs +++ b/crates/node-tests/src/context.rs @@ -143,6 +143,9 @@ impl SignetTestContext { pub async fn new() -> (Self, JoinHandle>) { let cfg = test_config(); let blob_source = MemoryBlobSource::new(); + let ipc_dir = tempfile::tempdir().unwrap().keep(); + let ipc_path = ipc_dir.join("signet.ipc"); + let ipc_endpoint = ipc_path.to_string_lossy().into_owned(); // set up Signet Node storage let constants = cfg.constants().unwrap(); @@ -206,7 +209,7 @@ impl SignetTestContext { http_cors: None, ws: vec![], ws_cors: None, - ipc: cfg.ipc_endpoint().map(ToOwned::to_owned), + ipc: Some(ipc_endpoint.clone()), }; let (node, mut node_status) = SignetNodeBuilder::new(cfg.clone()) @@ -238,7 +241,7 @@ impl SignetTestContext { .with_nonce_management(SimpleNonceManager::default()) .with_chain_id(constants.ru_chain_id()) .wallet(wallet) - .connect(cfg.ipc_endpoint().unwrap()) + .connect(&ipc_endpoint) .await .unwrap(); diff --git a/crates/node-tests/tests/db.rs b/crates/node-tests/tests/db.rs index 9a29a91..383c9d0 100644 --- a/crates/node-tests/tests/db.rs +++ b/crates/node-tests/tests/db.rs @@ -16,10 +16,8 @@ use tokio::sync::mpsc; #[tokio::test] async fn test_genesis() { let cfg = test_config(); - let consts = cfg.constants(); - - let chain_spec: Arc<_> = cfg.chain_spec().clone(); - assert_eq!(chain_spec.genesis().config.chain_id, consts.unwrap().ru_chain_id()); + let consts = cfg.constants().unwrap(); + assert_eq!(cfg.genesis().config.chain_id, consts.ru_chain_id()); let cancel_token = CancellationToken::new(); let hot = MemKv::new(); @@ -54,7 +52,7 @@ async fn test_genesis() { http_cors: None, ws: vec![], ws_cors: None, - ipc: cfg.ipc_endpoint().map(ToOwned::to_owned), + ipc: None, }) .with_rpc_config(StorageRpcConfig::default()) .build() From 842b1f0b0a2ccd6b192db42a9227a15ebe56afd0 Mon Sep 17 00:00:00 2001 From: James Date: Fri, 20 Mar 2026 14:29:22 -0400 Subject: [PATCH 6/7] fix(rpc): error on invalid bind addr, fix doctests, add rustdoc examples Make ServeConfigEnv-to-ServeConfig conversion fallible (TryFrom) so that malformed SIGNET_HTTP_ADDR / SIGNET_WS_ADDR values surface an error instead of silently falling back to 0.0.0.0. Replace `ignore` doctest on HostRpcConfig with a compilable `no_run` example. Add rustdoc examples to StorageRpcConfigEnv and ServeConfigEnv. Co-Authored-By: Claude Opus 4.6 (1M context) --- crates/host-rpc/src/config.rs | 8 +++-- crates/rpc/src/config/rpc_config.rs | 9 ++++++ crates/rpc/src/serve.rs | 45 +++++++++++++++++------------ 3 files changed, 41 insertions(+), 21 deletions(-) diff --git a/crates/host-rpc/src/config.rs b/crates/host-rpc/src/config.rs index aa5188b..2093ba5 100644 --- a/crates/host-rpc/src/config.rs +++ b/crates/host-rpc/src/config.rs @@ -12,13 +12,17 @@ use init4_bin_base::utils::{calc::SlotCalculator, from_env::FromEnv, provider::P /// /// # Example /// -/// ```ignore +/// ```no_run +/// # #[tokio::main] +/// # async fn main() -> Result<(), Box> { /// use signet_host_rpc::HostRpcConfig; /// use init4_bin_base::utils::{calc::SlotCalculator, from_env::FromEnv}; /// -/// let config = HostRpcConfig::from_env(); +/// let config = HostRpcConfig::from_env().unwrap(); /// let slot_calculator = SlotCalculator::new(0, 1_606_824_023, 12); /// let builder = config.into_builder(slot_calculator).await?; +/// # Ok(()) +/// # } /// ``` #[derive(Debug, Clone, FromEnv)] pub struct HostRpcConfig { diff --git a/crates/rpc/src/config/rpc_config.rs b/crates/rpc/src/config/rpc_config.rs index f8ff6c5..fea0efd 100644 --- a/crates/rpc/src/config/rpc_config.rs +++ b/crates/rpc/src/config/rpc_config.rs @@ -245,6 +245,15 @@ impl StorageRpcConfigBuilder { /// /// All fields are optional and default to the same values as /// [`StorageRpcConfig::default`]. +/// +/// # Example +/// +/// ```no_run +/// use signet_rpc::{StorageRpcConfig, StorageRpcConfigEnv}; +/// use init4_bin_base::utils::from_env::FromEnv; +/// +/// let config: StorageRpcConfig = StorageRpcConfigEnv::from_env().unwrap().into(); +/// ``` #[derive(Debug, Clone, FromEnv)] pub struct StorageRpcConfigEnv { /// Maximum gas for `eth_call` and `eth_estimateGas`. diff --git a/crates/rpc/src/serve.rs b/crates/rpc/src/serve.rs index 9584b5e..40877ac 100644 --- a/crates/rpc/src/serve.rs +++ b/crates/rpc/src/serve.rs @@ -12,7 +12,11 @@ use ajj::{ use axum::http::HeaderValue; use init4_bin_base::utils::from_env::FromEnv; use interprocess::local_socket as ls; -use std::{borrow::Cow, future::IntoFuture, net::SocketAddr}; +use std::{ + borrow::Cow, + future::IntoFuture, + net::{AddrParseError, SocketAddr}, +}; use tokio::{runtime::Handle, task::JoinHandle}; use tower_http::cors::{AllowOrigin, Any, CorsLayer}; use tracing::error; @@ -157,6 +161,15 @@ impl ServeConfig { /// - `SIGNET_WS_PORT` – WebSocket port (enables WS transport) /// - `SIGNET_WS_CORS` – CORS origins for WebSocket /// - `SIGNET_IPC_ENDPOINT` – IPC socket path (enables IPC transport) +/// +/// # Example +/// +/// ```no_run +/// use signet_rpc::{ServeConfig, ServeConfigEnv}; +/// use init4_bin_base::utils::from_env::FromEnv; +/// +/// let config: ServeConfig = ServeConfigEnv::from_env().unwrap().try_into().unwrap(); +/// ``` #[derive(Debug, Clone, FromEnv)] pub struct ServeConfigEnv { /// HTTP bind address. @@ -182,41 +195,35 @@ pub struct ServeConfigEnv { ipc: Option>, } -impl From for ServeConfig { - fn from(env: ServeConfigEnv) -> Self { +impl TryFrom for ServeConfig { + type Error = AddrParseError; + + fn try_from(env: ServeConfigEnv) -> Result { let http = env .http_port .map(|port| { - let addr = env - .http_addr - .as_deref() - .unwrap_or("0.0.0.0") - .parse() - .unwrap_or(std::net::Ipv4Addr::UNSPECIFIED.into()); - vec![SocketAddr::new(addr, port)] + let addr = env.http_addr.as_deref().unwrap_or("0.0.0.0").parse()?; + Ok(vec![SocketAddr::new(addr, port)]) }) + .transpose()? .unwrap_or_default(); let ws = env .ws_port .map(|port| { - let addr = env - .ws_addr - .as_deref() - .unwrap_or("0.0.0.0") - .parse() - .unwrap_or(std::net::Ipv4Addr::UNSPECIFIED.into()); - vec![SocketAddr::new(addr, port)] + let addr = env.ws_addr.as_deref().unwrap_or("0.0.0.0").parse()?; + Ok(vec![SocketAddr::new(addr, port)]) }) + .transpose()? .unwrap_or_default(); - Self { + Ok(Self { http, http_cors: env.http_cors.map(Cow::into_owned), ws, ws_cors: env.ws_cors.map(Cow::into_owned), ipc: env.ipc.map(Cow::into_owned), - } + }) } } From 9fac6fd3b6ebacc07236be96d83010132831348e Mon Sep 17 00:00:00 2001 From: James Date: Mon, 23 Mar 2026 13:57:58 -0400 Subject: [PATCH 7/7] fix(rpc): document env var defaults and add 0-to-disable for gas oracle prices Address review feedback: the three gas oracle price fields (default_gas_price, ignore_price, max_price) now treat 0 as a sentinel to disable the guardrail (mapping to None), rather than silently overriding None with the default. All FromEnv desc strings across StorageRpcConfigEnv, ServeConfigEnv, and HostRpcConfig now document the default value or behavior when unset. Co-Authored-By: Claude Opus 4.6 (1M context) --- crates/host-rpc/src/config.rs | 14 ++++- crates/rpc/src/config/rpc_config.rs | 94 +++++++++++++++++++++++------ crates/rpc/src/serve.rs | 47 ++++++++++++--- 3 files changed, 126 insertions(+), 29 deletions(-) diff --git a/crates/host-rpc/src/config.rs b/crates/host-rpc/src/config.rs index 2093ba5..682304e 100644 --- a/crates/host-rpc/src/config.rs +++ b/crates/host-rpc/src/config.rs @@ -27,13 +27,21 @@ use init4_bin_base::utils::{calc::SlotCalculator, from_env::FromEnv, provider::P #[derive(Debug, Clone, FromEnv)] pub struct HostRpcConfig { /// WebSocket or IPC connection to the host execution layer client. - #[from_env(var = "SIGNET_HOST_URL", desc = "Host EL pubsub URL (ws:// or ipc)")] + #[from_env(var = "SIGNET_HOST_URL", desc = "Host EL pubsub URL (ws:// or ipc) [required]")] provider: PubSubConfig, /// Local chain view buffer capacity. - #[from_env(var = "SIGNET_HOST_BUFFER_CAPACITY", desc = "Chain view buffer capacity", optional)] + #[from_env( + var = "SIGNET_HOST_BUFFER_CAPACITY", + desc = "Chain view buffer capacity [default: 64]", + optional + )] buffer_capacity: Option, /// Blocks per backfill RPC batch. - #[from_env(var = "SIGNET_HOST_BACKFILL_BATCH_SIZE", desc = "Backfill batch size", optional)] + #[from_env( + var = "SIGNET_HOST_BACKFILL_BATCH_SIZE", + desc = "Backfill batch size [default: 32]", + optional + )] backfill_batch_size: Option, } diff --git a/crates/rpc/src/config/rpc_config.rs b/crates/rpc/src/config/rpc_config.rs index fea0efd..c9daac0 100644 --- a/crates/rpc/src/config/rpc_config.rs +++ b/crates/rpc/src/config/rpc_config.rs @@ -78,7 +78,9 @@ pub struct StorageRpcConfig { /// Default gas price returned when no recent transactions exist. /// - /// Reth defaults to 1 Gwei. Set to `None` to return zero. + /// Reth defaults to 1 Gwei. Set to `None` to disable (returns + /// zero). When configured via environment variable, set to `0` to + /// disable. /// /// Default: `Some(1_000_000_000)` (1 Gwei). pub default_gas_price: Option, @@ -86,13 +88,18 @@ pub struct StorageRpcConfig { /// Minimum effective tip to include in the oracle sample. /// /// Tips below this threshold are discarded, matching reth's - /// `ignore_price` behavior. + /// `ignore_price` behavior. Set to `None` to include all tips. + /// When configured via environment variable, set to `0` to + /// disable. /// /// Default: `Some(2)` (2 wei). pub ignore_price: Option, /// Maximum gas price the oracle will ever suggest. /// + /// Set to `None` for no cap. When configured via environment + /// variable, set to `0` to disable. + /// /// Default: `Some(500_000_000_000)` (500 Gwei). pub max_price: Option, @@ -257,61 +264,110 @@ impl StorageRpcConfigBuilder { #[derive(Debug, Clone, FromEnv)] pub struct StorageRpcConfigEnv { /// Maximum gas for `eth_call` and `eth_estimateGas`. - #[from_env(var = "SIGNET_RPC_GAS_CAP", desc = "Max gas for eth_call", optional)] + #[from_env( + var = "SIGNET_RPC_GAS_CAP", + desc = "Max gas for eth_call [default: 30000000]", + optional + )] rpc_gas_cap: Option, /// Maximum block range per `eth_getLogs` query. #[from_env( var = "SIGNET_RPC_MAX_BLOCKS_PER_FILTER", - desc = "Max block range for getLogs", + desc = "Max block range for getLogs [default: 10000]", optional )] max_blocks_per_filter: Option, /// Maximum number of logs returned per response. - #[from_env(var = "SIGNET_RPC_MAX_LOGS", desc = "Max logs per response", optional)] + #[from_env( + var = "SIGNET_RPC_MAX_LOGS", + desc = "Max logs per response [default: 20000]", + optional + )] max_logs_per_response: Option, /// Maximum seconds for a single log query. #[from_env( var = "SIGNET_RPC_LOG_QUERY_DEADLINE_SECS", - desc = "Max seconds for log query", + desc = "Max seconds for log query [default: 10]", optional )] max_log_query_deadline_secs: Option, /// Maximum concurrent tracing/debug requests. #[from_env( var = "SIGNET_RPC_MAX_TRACING_REQUESTS", - desc = "Concurrent tracing limit", + desc = "Concurrent tracing limit [default: 25]", optional )] max_tracing_requests: Option, /// Filter TTL in seconds. - #[from_env(var = "SIGNET_RPC_STALE_FILTER_TTL_SECS", desc = "Filter TTL in seconds", optional)] + #[from_env( + var = "SIGNET_RPC_STALE_FILTER_TTL_SECS", + desc = "Filter TTL in seconds [default: 300]", + optional + )] stale_filter_ttl_secs: Option, /// Number of recent blocks for gas oracle. - #[from_env(var = "SIGNET_RPC_GAS_ORACLE_BLOCKS", desc = "Blocks for gas oracle", optional)] + #[from_env( + var = "SIGNET_RPC_GAS_ORACLE_BLOCKS", + desc = "Blocks for gas oracle [default: 20]", + optional + )] gas_oracle_block_count: Option, /// Tip percentile for gas oracle. - #[from_env(var = "SIGNET_RPC_GAS_ORACLE_PERCENTILE", desc = "Tip percentile", optional)] + #[from_env( + var = "SIGNET_RPC_GAS_ORACLE_PERCENTILE", + desc = "Tip percentile [default: 60]", + optional + )] gas_oracle_percentile: Option, /// Default gas price in wei. - #[from_env(var = "SIGNET_RPC_DEFAULT_GAS_PRICE", desc = "Default gas price in wei", optional)] + #[from_env( + var = "SIGNET_RPC_DEFAULT_GAS_PRICE", + desc = "Default gas price in wei, 0 to disable [default: 1000000000]", + optional + )] default_gas_price: Option, /// Minimum effective tip in wei. - #[from_env(var = "SIGNET_RPC_IGNORE_PRICE", desc = "Min tip in wei", optional)] + #[from_env( + var = "SIGNET_RPC_IGNORE_PRICE", + desc = "Min tip in wei, 0 to disable [default: 2]", + optional + )] ignore_price: Option, /// Maximum gas price in wei. - #[from_env(var = "SIGNET_RPC_MAX_PRICE", desc = "Max gas price in wei", optional)] + #[from_env( + var = "SIGNET_RPC_MAX_PRICE", + desc = "Max gas price in wei, 0 to disable [default: 500000000000]", + optional + )] max_price: Option, /// Maximum header history for `eth_feeHistory`. - #[from_env(var = "SIGNET_RPC_MAX_HEADER_HISTORY", desc = "Max feeHistory headers", optional)] + #[from_env( + var = "SIGNET_RPC_MAX_HEADER_HISTORY", + desc = "Max feeHistory headers [default: 1024]", + optional + )] max_header_history: Option, /// Maximum block history for `eth_feeHistory`. - #[from_env(var = "SIGNET_RPC_MAX_BLOCK_HISTORY", desc = "Max feeHistory blocks", optional)] + #[from_env( + var = "SIGNET_RPC_MAX_BLOCK_HISTORY", + desc = "Max feeHistory blocks [default: 1024]", + optional + )] max_block_history: Option, /// Default bundle simulation timeout in milliseconds. - #[from_env(var = "SIGNET_RPC_BUNDLE_TIMEOUT_MS", desc = "Bundle sim timeout in ms", optional)] + #[from_env( + var = "SIGNET_RPC_BUNDLE_TIMEOUT_MS", + desc = "Bundle sim timeout in ms [default: 1000]", + optional + )] default_bundle_timeout_ms: Option, } +/// Map `0` to `None`, preserving all other values. +const fn nonzero(v: u128) -> Option { + if v == 0 { None } else { Some(v) } +} + impl From for StorageRpcConfig { fn from(env: StorageRpcConfigEnv) -> Self { let defaults = StorageRpcConfig::default(); @@ -338,9 +394,9 @@ impl From for StorageRpcConfig { gas_oracle_percentile: env .gas_oracle_percentile .map_or(defaults.gas_oracle_percentile, |v| v as f64), - default_gas_price: env.default_gas_price.or(defaults.default_gas_price), - ignore_price: env.ignore_price.or(defaults.ignore_price), - max_price: env.max_price.or(defaults.max_price), + default_gas_price: env.default_gas_price.map_or(defaults.default_gas_price, nonzero), + ignore_price: env.ignore_price.map_or(defaults.ignore_price, nonzero), + max_price: env.max_price.map_or(defaults.max_price, nonzero), max_header_history: env.max_header_history.unwrap_or(defaults.max_header_history), max_block_history: env.max_block_history.unwrap_or(defaults.max_block_history), default_bundle_timeout_ms: env diff --git a/crates/rpc/src/serve.rs b/crates/rpc/src/serve.rs index 40877ac..fabbf1f 100644 --- a/crates/rpc/src/serve.rs +++ b/crates/rpc/src/serve.rs @@ -173,25 +173,58 @@ impl ServeConfig { #[derive(Debug, Clone, FromEnv)] pub struct ServeConfigEnv { /// HTTP bind address. - #[from_env(var = "SIGNET_HTTP_ADDR", desc = "HTTP bind address", infallible, optional)] + #[from_env( + var = "SIGNET_HTTP_ADDR", + desc = "HTTP bind address [default: 0.0.0.0]", + infallible, + optional + )] http_addr: Option>, /// HTTP port. Setting this enables the HTTP transport. - #[from_env(var = "SIGNET_HTTP_PORT", desc = "HTTP port", optional)] + #[from_env( + var = "SIGNET_HTTP_PORT", + desc = "HTTP port; unset to disable HTTP transport [default: disabled]", + optional + )] http_port: Option, /// CORS origins for HTTP. - #[from_env(var = "SIGNET_HTTP_CORS", desc = "CORS origins for HTTP", infallible, optional)] + #[from_env( + var = "SIGNET_HTTP_CORS", + desc = "CORS origins for HTTP [default: unset]", + infallible, + optional + )] http_cors: Option>, /// WebSocket bind address. - #[from_env(var = "SIGNET_WS_ADDR", desc = "WebSocket bind address", infallible, optional)] + #[from_env( + var = "SIGNET_WS_ADDR", + desc = "WebSocket bind address [default: 0.0.0.0]", + infallible, + optional + )] ws_addr: Option>, /// WebSocket port. Setting this enables the WS transport. - #[from_env(var = "SIGNET_WS_PORT", desc = "WebSocket port", optional)] + #[from_env( + var = "SIGNET_WS_PORT", + desc = "WebSocket port; unset to disable WS transport [default: disabled]", + optional + )] ws_port: Option, /// CORS origins for WebSocket. - #[from_env(var = "SIGNET_WS_CORS", desc = "CORS origins for WebSocket", infallible, optional)] + #[from_env( + var = "SIGNET_WS_CORS", + desc = "CORS origins for WebSocket [default: unset]", + infallible, + optional + )] ws_cors: Option>, /// IPC endpoint path. Setting this enables IPC transport. - #[from_env(var = "SIGNET_IPC_ENDPOINT", desc = "IPC socket path", infallible, optional)] + #[from_env( + var = "SIGNET_IPC_ENDPOINT", + desc = "IPC socket path; unset to disable IPC transport [default: disabled]", + infallible, + optional + )] ipc: Option>, }