diff --git a/crates/rpc/ipc/src/server/mod.rs b/crates/rpc/ipc/src/server/mod.rs index e6f46fe1c5..f4718fd5cc 100644 --- a/crates/rpc/ipc/src/server/mod.rs +++ b/crates/rpc/ipc/src/server/mod.rs @@ -379,6 +379,7 @@ pub struct Builder { settings: Settings, resources: Resources, logger: L, + /// Subscription ID provider. id_provider: Arc, service_builder: tower::ServiceBuilder, } diff --git a/crates/rpc/rpc-builder/src/lib.rs b/crates/rpc/rpc-builder/src/lib.rs index a9837adff9..5d66e23870 100644 --- a/crates/rpc/rpc-builder/src/lib.rs +++ b/crates/rpc/rpc-builder/src/lib.rs @@ -58,13 +58,15 @@ use jsonrpsee::{ server::{host_filtering::AllowHosts, rpc_module::Methods}, Error as RpcError, }, - server::{Server, ServerHandle}, + server::{IdProvider, Server, ServerHandle}, RpcModule, }; use reth_ipc::server::IpcServer; use reth_network_api::{NetworkInfo, Peers}; use reth_provider::{BlockProvider, EvmEnvProvider, HeaderProvider, StateProviderFactory}; -use reth_rpc::{AdminApi, DebugApi, EthApi, EthFilter, NetApi, TraceApi, Web3Api}; +use reth_rpc::{ + AdminApi, DebugApi, EthApi, EthFilter, EthSubscriptionIdProvider, NetApi, TraceApi, Web3Api, +}; use reth_rpc_api::servers::*; use reth_transaction_pool::TransactionPool; use serde::{Deserialize, Serialize, Serializer}; @@ -670,11 +672,17 @@ impl RpcServerConfig { pub fn ipc(config: IpcServerBuilder) -> Self { Self::default().with_ipc(config) } + /// Configures the http server + /// + /// Note: this always configures an [EthSubscriptionIdProvider] [IdProvider] for convenience. + /// To set a custom [IdProvider], please use [Self::with_id_provider]. pub fn with_http(mut self, config: ServerBuilder) -> Self { - self.http_server_config = Some(config.http_only()); + self.http_server_config = + Some(config.http_only().set_id_provider(EthSubscriptionIdProvider::default())); self } + /// Configure the corsdomains pub fn with_cors(mut self, cors_domain: String) -> Self { self.http_cors_domains = Some(cors_domain); @@ -682,8 +690,12 @@ impl RpcServerConfig { } /// Configures the ws server + /// + /// Note: this always configures an [EthSubscriptionIdProvider] [IdProvider] for convenience. + /// To set a custom [IdProvider], please use [Self::with_id_provider]. pub fn with_ws(mut self, config: ServerBuilder) -> Self { - self.ws_server_config = Some(config.ws_only()); + self.ws_server_config = + Some(config.ws_only().set_id_provider(EthSubscriptionIdProvider::default())); self } @@ -704,8 +716,31 @@ impl RpcServerConfig { } /// Configures the ipc server + /// + /// Note: this always configures an [EthSubscriptionIdProvider] [IdProvider] for convenience. + /// To set a custom [IdProvider], please use [Self::with_id_provider]. pub fn with_ipc(mut self, mut config: IpcServerBuilder) -> Self { - self.ipc_server_config = Some(config); + self.ipc_server_config = Some(config.set_id_provider(EthSubscriptionIdProvider::default())); + self + } + + /// Sets a custom [IdProvider] for all configured transports. + /// + /// By default all transports use [EthSubscriptionIdProvider] + pub fn with_id_provider(mut self, id_provider: I) -> Self + where + I: IdProvider + Clone + 'static, + { + if let Some(http) = self.http_server_config { + self.http_server_config = Some(http.set_id_provider(id_provider.clone())); + } + if let Some(ws) = self.ws_server_config { + self.ws_server_config = Some(ws.set_id_provider(id_provider.clone())); + } + if let Some(ipc) = self.ipc_server_config { + self.ipc_server_config = Some(ipc.set_id_provider(id_provider)); + } + self } diff --git a/crates/rpc/rpc/src/eth/id_provider.rs b/crates/rpc/rpc/src/eth/id_provider.rs new file mode 100644 index 0000000000..0355d714d7 --- /dev/null +++ b/crates/rpc/rpc/src/eth/id_provider.rs @@ -0,0 +1,67 @@ +use jsonrpsee::types::SubscriptionId; +use std::fmt::Write; + +/// An [IdProvider](jsonrpsee::core::traits::IdProvider) for ethereum subscription ids. +/// +/// Returns new hex-string [QUANTITY](https://ethereum.org/en/developers/docs/apis/json-rpc/#quantities-encoding) ids +#[derive(Debug, Clone, Copy, Default)] +#[non_exhaustive] +pub struct EthSubscriptionIdProvider; + +impl jsonrpsee::core::traits::IdProvider for EthSubscriptionIdProvider { + fn next_id(&self) -> SubscriptionId<'static> { + to_quantity(rand::random::()) + } +} + +/// Returns a hex quantity string for the given value +/// +/// Strips all leading zeros, `0` is returned as `0x0` +#[inline(always)] +fn to_quantity(val: u128) -> SubscriptionId<'static> { + let bytes = val.to_be_bytes(); + let b = bytes.as_slice(); + let non_zero = b.iter().take_while(|b| **b == 0).count(); + let b = &b[non_zero..]; + if b.is_empty() { + return SubscriptionId::Str("0x0".into()) + } + + let mut id = String::with_capacity(2 * b.len() + 2); + id.push_str("0x"); + let first_byte = b[0]; + write!(id, "{first_byte:x}").unwrap(); + + for byte in &b[1..] { + write!(id, "{byte:02x}").unwrap(); + } + id.into() +} + +#[cfg(test)] +mod tests { + use super::*; + use reth_primitives::U128; + + #[test] + fn test_id_provider_quantity() { + let id = to_quantity(0); + assert_eq!(id, SubscriptionId::Str("0x0".into())); + let id = to_quantity(1); + assert_eq!(id, SubscriptionId::Str("0x1".into())); + + for _ in 0..1000 { + let val = rand::random::(); + let id = to_quantity(val); + match id { + SubscriptionId::Str(id) => { + let from_hex: U128 = id.parse().unwrap(); + assert_eq!(from_hex, U128::from(val)); + } + SubscriptionId::Num(_) => { + unreachable!() + } + } + } + } +} diff --git a/crates/rpc/rpc/src/eth/mod.rs b/crates/rpc/rpc/src/eth/mod.rs index 42500801a3..c4eea7869d 100644 --- a/crates/rpc/rpc/src/eth/mod.rs +++ b/crates/rpc/rpc/src/eth/mod.rs @@ -4,9 +4,11 @@ mod api; pub mod cache; pub(crate) mod error; mod filter; +mod id_provider; mod pubsub; mod signer; pub use api::{EthApi, EthApiSpec}; pub use filter::EthFilter; +pub use id_provider::EthSubscriptionIdProvider; pub use pubsub::EthPubSub; diff --git a/crates/rpc/rpc/src/lib.rs b/crates/rpc/rpc/src/lib.rs index f5c60506da..53a37d4dfd 100644 --- a/crates/rpc/rpc/src/lib.rs +++ b/crates/rpc/rpc/src/lib.rs @@ -23,7 +23,7 @@ mod web3; pub use admin::AdminApi; pub use debug::DebugApi; pub use engine::EngineApi; -pub use eth::{EthApi, EthApiSpec, EthFilter, EthPubSub}; +pub use eth::{EthApi, EthApiSpec, EthFilter, EthPubSub, EthSubscriptionIdProvider}; pub use layers::{AuthLayer, AuthValidator, JwtAuthValidator, JwtError, JwtSecret}; pub use net::NetApi; pub use trace::TraceApi;