test: basic rpc testing support (#1222)

This commit is contained in:
Matthias Seitz
2023-02-08 16:51:24 +01:00
committed by GitHub
parent f56bb5a022
commit 88a838b9f4
8 changed files with 205 additions and 19 deletions

1
Cargo.lock generated
View File

@ -4639,6 +4639,7 @@ dependencies = [
"jsonrpsee", "jsonrpsee",
"reth-ipc", "reth-ipc",
"reth-network-api", "reth-network-api",
"reth-primitives",
"reth-provider", "reth-provider",
"reth-rpc", "reth-rpc",
"reth-rpc-api", "reth-rpc-api",

View File

@ -1,31 +1,32 @@
use jsonrpsee::{core::RpcResult as Result, proc_macros::rpc}; use jsonrpsee::{core::RpcResult, proc_macros::rpc};
use reth_primitives::NodeRecord; use reth_primitives::NodeRecord;
use reth_rpc_types::NodeInfo; use reth_rpc_types::NodeInfo;
/// Admin namespace rpc interface that gives access to several non-standard RPC methods. /// Admin namespace rpc interface that gives access to several non-standard RPC methods.
#[rpc(server)] #[cfg_attr(not(feature = "client"), rpc(server))]
#[cfg_attr(feature = "client", rpc(server, client))]
#[async_trait::async_trait] #[async_trait::async_trait]
pub trait AdminApi { pub trait AdminApi {
/// Adds the given node record to the peerset. /// Adds the given node record to the peerset.
#[method(name = "admin_addPeer")] #[method(name = "admin_addPeer")]
fn add_peer(&self, record: NodeRecord) -> Result<bool>; fn add_peer(&self, record: NodeRecord) -> RpcResult<bool>;
/// Disconnects from a remote node if the connection exists. /// Disconnects from a remote node if the connection exists.
/// ///
/// Returns true if the peer was successfully removed. /// Returns true if the peer was successfully removed.
#[method(name = "admin_removePeer")] #[method(name = "admin_removePeer")]
fn remove_peer(&self, record: NodeRecord) -> Result<bool>; fn remove_peer(&self, record: NodeRecord) -> RpcResult<bool>;
/// Adds the given node record to the trusted peerset. /// Adds the given node record to the trusted peerset.
#[method(name = "admin_addTrustedPeer")] #[method(name = "admin_addTrustedPeer")]
fn add_trusted_peer(&self, record: NodeRecord) -> Result<bool>; fn add_trusted_peer(&self, record: NodeRecord) -> RpcResult<bool>;
/// Removes a remote node from the trusted peer set, but it does not disconnect it /// Removes a remote node from the trusted peer set, but it does not disconnect it
/// automatically. /// automatically.
/// ///
/// Returns true if the peer was successfully removed. /// Returns true if the peer was successfully removed.
#[method(name = "admin_removeTrustedPeer")] #[method(name = "admin_removeTrustedPeer")]
fn remove_trusted_peer(&self, record: NodeRecord) -> Result<bool>; fn remove_trusted_peer(&self, record: NodeRecord) -> RpcResult<bool>;
/// Creates an RPC subscription which serves events received from the network. /// Creates an RPC subscription which serves events received from the network.
#[subscription( #[subscription(
@ -33,9 +34,9 @@ pub trait AdminApi {
unsubscribe = "admin_peerEvents_unsubscribe", unsubscribe = "admin_peerEvents_unsubscribe",
item = String item = String
)] )]
fn subscribe(&self); fn subscribe_peer_events(&self);
/// Returns the ENR of the node. /// Returns the ENR of the node.
#[method(name = "admin_nodeInfo")] #[method(name = "admin_nodeInfo")]
async fn node_info(&self) -> Result<NodeInfo>; async fn node_info(&self) -> RpcResult<NodeInfo>;
} }

View File

@ -30,3 +30,16 @@ pub mod servers {
trace::TraceApiServer, web3::Web3ApiServer, trace::TraceApiServer, web3::Web3ApiServer,
}; };
} }
/// re-export of all client traits
#[cfg(feature = "client")]
pub use clients::*;
/// Aggregates all client traits.
#[cfg(feature = "client")]
pub mod clients {
pub use crate::{
admin::AdminApiClient, debug::DebugApiClient, engine::EngineApiClient, eth::EthApiClient,
net::NetApiClient, trace::TraceApiClient, web3::Web3ApiClient,
};
}

View File

@ -22,8 +22,10 @@ serde = { version = "1.0", features = ["derive"] }
[dev-dependencies] [dev-dependencies]
reth-tracing = { path = "../../tracing" } reth-tracing = { path = "../../tracing" }
reth-primitives = { path = "../../primitives" }
reth-rpc-api = { path = "../rpc-api", features = ["client"] } reth-rpc-api = { path = "../rpc-api", features = ["client"] }
reth-transaction-pool = { path = "../../transaction-pool", features = ["test-utils"] } reth-transaction-pool = { path = "../../transaction-pool", features = ["test-utils"] }
reth-provider = { path = "../../storage/provider", features = ["test-utils"] } reth-provider = { path = "../../storage/provider", features = ["test-utils"] }
reth-network-api = { path = "../../net/network-api", features = ["test-utils"] }
tokio = { version = "1", features = ["rt", "rt-multi-thread"] } tokio = { version = "1", features = ["rt", "rt-multi-thread"] }

View File

@ -426,6 +426,21 @@ pub struct RpcServerConfig {
/// === impl RpcServerConfig === /// === impl RpcServerConfig ===
impl RpcServerConfig { impl RpcServerConfig {
/// Creates a new config with only http set
pub fn http(config: ServerBuilder) -> Self {
Self::default().with_http(config)
}
/// Creates a new config with only ws set
pub fn ws(config: ServerBuilder) -> Self {
Self::default().with_ws(config)
}
/// Creates a new config with only ipc set
pub fn ipc(config: IpcServerBuilder) -> Self {
Self::default().with_ipc(config)
}
/// Configures the http server /// Configures the http server
pub fn with_http(mut self, config: ServerBuilder) -> Self { 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());
@ -478,15 +493,23 @@ impl RpcServerConfig {
.http_ws_addr .http_ws_addr
.unwrap_or(SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, DEFAULT_RPC_PORT))); .unwrap_or(SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, DEFAULT_RPC_PORT)));
let mut local_addr = None;
let http_server = if let Some(builder) = self.http_server_config { let http_server = if let Some(builder) = self.http_server_config {
let server = builder.build(socket_addr).await?; let server = builder.build(socket_addr).await?;
if local_addr.is_none() {
local_addr = server.local_addr().ok();
}
Some(server) Some(server)
} else { } else {
None None
}; };
let ws_server = if let Some(builder) = self.ws_server_config { let ws_server = if let Some(builder) = self.ws_server_config {
let server = builder.build(socket_addr).await?; let server = builder.build(socket_addr).await.unwrap();
if local_addr.is_none() {
local_addr = server.local_addr().ok();
}
Some(server) Some(server)
} else { } else {
None None
@ -502,7 +525,7 @@ impl RpcServerConfig {
None None
}; };
Ok(RpcServer { http: http_server, ws: ws_server, ipc: ipc_server }) Ok(RpcServer { local_addr, http: http_server, ws: ws_server, ipc: ipc_server })
} }
} }
@ -530,6 +553,21 @@ pub struct TransportRpcModuleConfig {
// === impl TransportRpcModuleConfig === // === impl TransportRpcModuleConfig ===
impl TransportRpcModuleConfig { impl TransportRpcModuleConfig {
/// Creates a new config with only http set
pub fn http(http: impl Into<RpcModuleConfig>) -> Self {
Self::default().with_http(http)
}
/// Creates a new config with only ws set
pub fn ws(ws: impl Into<RpcModuleConfig>) -> Self {
Self::default().with_ws(ws)
}
/// Creates a new config with only ipc set
pub fn ipc(ipc: impl Into<RpcModuleConfig>) -> Self {
Self::default().with_ipc(ipc)
}
/// Sets the [RpcModuleConfig] for the http transport. /// Sets the [RpcModuleConfig] for the http transport.
pub fn with_http(mut self, http: impl Into<RpcModuleConfig>) -> Self { pub fn with_http(mut self, http: impl Into<RpcModuleConfig>) -> Self {
self.http = Some(http.into()); self.http = Some(http.into());
@ -576,6 +614,8 @@ impl TransportRpcModules<()> {
/// Container type for each transport ie. http, ws, and ipc server /// Container type for each transport ie. http, ws, and ipc server
pub struct RpcServer { pub struct RpcServer {
/// The address of the http/ws server
local_addr: Option<SocketAddr>,
/// http server /// http server
http: Option<Server>, http: Option<Server>,
/// ws server /// ws server
@ -587,6 +627,11 @@ pub struct RpcServer {
// === impl RpcServer === // === impl RpcServer ===
impl RpcServer { impl RpcServer {
/// Returns the [`SocketAddr`] of the http/ws server if started.
fn local_addr(&self) -> Option<SocketAddr> {
self.local_addr
}
/// Starts the configured server by spawning the servers on the tokio runtime. /// Starts the configured server by spawning the servers on the tokio runtime.
/// ///
/// This returns an [RpcServerHandle] that's connected to the server task(s) until the server is /// This returns an [RpcServerHandle] that's connected to the server task(s) until the server is
@ -596,7 +641,8 @@ impl RpcServer {
modules: TransportRpcModules<()>, modules: TransportRpcModules<()>,
) -> Result<RpcServerHandle, RpcError> { ) -> Result<RpcServerHandle, RpcError> {
let TransportRpcModules { http, ws, ipc } = modules; let TransportRpcModules { http, ws, ipc } = modules;
let mut handle = RpcServerHandle { http: None, ws: None, ipc: None }; let mut handle =
RpcServerHandle { local_addr: self.local_addr, http: None, ws: None, ipc: None };
// Start all servers // Start all servers
if let Some((server, module)) = if let Some((server, module)) =
@ -636,6 +682,8 @@ impl std::fmt::Debug for RpcServer {
#[derive(Clone)] #[derive(Clone)]
#[must_use = "Server stop if dropped"] #[must_use = "Server stop if dropped"]
pub struct RpcServerHandle { pub struct RpcServerHandle {
/// The address of the http/ws server
local_addr: Option<SocketAddr>,
http: Option<ServerHandle>, http: Option<ServerHandle>,
ws: Option<ServerHandle>, ws: Option<ServerHandle>,
ipc: Option<ServerHandle>, ipc: Option<ServerHandle>,
@ -644,6 +692,11 @@ pub struct RpcServerHandle {
// === impl RpcServerHandle === // === impl RpcServerHandle ===
impl RpcServerHandle { impl RpcServerHandle {
/// Returns the [`SocketAddr`] of the http/ws server if started.
fn local_addr(&self) -> Option<SocketAddr> {
self.local_addr
}
/// Tell the server to stop without waiting for the server to stop. /// Tell the server to stop without waiting for the server to stop.
pub fn stop(self) -> Result<(), RpcError> { pub fn stop(self) -> Result<(), RpcError> {
if let Some(handle) = self.http { if let Some(handle) = self.http {
@ -660,6 +713,35 @@ impl RpcServerHandle {
Ok(()) Ok(())
} }
/// Returns the url to the http server
pub fn http_url(&self) -> Option<String> {
self.local_addr.map(|addr| format!("http://{addr}"))
}
/// Returns the url to the ws server
pub fn ws_url(&self) -> Option<String> {
self.local_addr.map(|addr| format!("ws://{addr}"))
}
/// Returns a http client connected to the server.
pub fn http_client(&self) -> Option<jsonrpsee::http_client::HttpClient> {
let url = self.http_url()?;
let client = jsonrpsee::http_client::HttpClientBuilder::default()
.build(url)
.expect("Failed to create http client");
Some(client)
}
/// Returns a ws client connected to the server.
pub async fn ws_client(&self) -> Option<jsonrpsee::ws_client::WsClient> {
let url = self.ws_url()?;
let client = jsonrpsee::ws_client::WsClientBuilder::default()
.build(url)
.await
.expect("Failed to create ws client");
Some(client)
}
} }
impl std::fmt::Debug for RpcServerHandle { impl std::fmt::Debug for RpcServerHandle {

View File

@ -1,8 +1,48 @@
//! Standalone http tests //! Standalone http tests
use crate::utils::test_rpc_builder; use crate::utils::{launch_http, launch_http_ws, launch_ws};
use jsonrpsee::core::client::{ClientT, SubscriptionClientT};
use reth_primitives::NodeRecord;
use reth_rpc_api::clients::AdminApiClient;
use reth_rpc_builder::RethRpcModule;
async fn test_basic_admin_calls<C>(client: &C)
where
C: ClientT + SubscriptionClientT + Sync,
{
let url = "enode://6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@10.3.58.6:30303?discport=30301";
let node: NodeRecord = url.parse().unwrap();
AdminApiClient::add_peer(client, node).await.unwrap();
AdminApiClient::remove_peer(client, node).await.unwrap();
AdminApiClient::add_trusted_peer(client, node).await.unwrap();
AdminApiClient::remove_trusted_peer(client, node).await.unwrap();
AdminApiClient::node_info(client).await.unwrap();
}
#[tokio::test(flavor = "multi_thread")] #[tokio::test(flavor = "multi_thread")]
async fn test_launch_http() { async fn test_call_admin_functions_http() {
let _builder = test_rpc_builder(); reth_tracing::init_test_tracing();
let handle = launch_http(vec![RethRpcModule::Admin]).await;
let client = handle.http_client().unwrap();
test_basic_admin_calls(&client).await;
}
#[tokio::test(flavor = "multi_thread")]
async fn test_call_admin_functions_ws() {
reth_tracing::init_test_tracing();
let handle = launch_ws(vec![RethRpcModule::Admin]).await;
let client = handle.ws_client().await.unwrap();
test_basic_admin_calls(&client).await;
}
#[tokio::test(flavor = "multi_thread")]
async fn test_call_admin_functions_http_and_ws() {
reth_tracing::init_test_tracing();
let handle = launch_http_ws(vec![RethRpcModule::Admin]).await;
let client = handle.http_client().unwrap();
test_basic_admin_calls(&client).await;
} }

View File

@ -1,8 +1,55 @@
use reth_network_api::test_utils::NoopNetwork;
use reth_provider::test_utils::NoopProvider; use reth_provider::test_utils::NoopProvider;
use reth_rpc_builder::RpcModuleBuilder; use reth_rpc_builder::{
RpcModuleBuilder, RpcModuleConfig, RpcServerConfig, RpcServerHandle, TransportRpcModuleConfig,
};
use reth_transaction_pool::test_utils::{testing_pool, TestPool}; use reth_transaction_pool::test_utils::{testing_pool, TestPool};
use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4};
/// Localhost with port 0 so a free port is used.
pub fn test_address() -> SocketAddr {
SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 0))
}
/// Launches a new server with http only with the given modules
pub async fn launch_http(modules: impl Into<RpcModuleConfig>) -> RpcServerHandle {
let builder = test_rpc_builder();
let server = builder.build(TransportRpcModuleConfig::http(modules));
server
.start_server(RpcServerConfig::http(Default::default()).with_address(test_address()))
.await
.unwrap()
}
/// Launches a new server with ws only with the given modules
pub async fn launch_ws(modules: impl Into<RpcModuleConfig>) -> RpcServerHandle {
let builder = test_rpc_builder();
let server = builder.build(TransportRpcModuleConfig::ws(modules));
server
.start_server(RpcServerConfig::ws(Default::default()).with_address(test_address()))
.await
.unwrap()
}
/// Launches a new server with http and ws and with the given modules
pub async fn launch_http_ws(modules: impl Into<RpcModuleConfig>) -> RpcServerHandle {
let builder = test_rpc_builder();
let modules = modules.into();
let server = builder.build(TransportRpcModuleConfig::ws(modules.clone()).with_http(modules));
server
.start_server(
RpcServerConfig::ws(Default::default())
.with_http(Default::default())
.with_address(test_address()),
)
.await
.unwrap()
}
/// Returns an [RpcModuleBuilder] with testing components. /// Returns an [RpcModuleBuilder] with testing components.
pub fn test_rpc_builder() -> RpcModuleBuilder<NoopProvider, TestPool, ()> { pub fn test_rpc_builder() -> RpcModuleBuilder<NoopProvider, TestPool, NoopNetwork> {
RpcModuleBuilder::default().with_client(NoopProvider::default()).with_pool(testing_pool()) RpcModuleBuilder::default()
.with_client(NoopProvider::default())
.with_pool(testing_pool())
.with_network(NoopNetwork::default())
} }

View File

@ -46,7 +46,7 @@ where
Ok(true) Ok(true)
} }
fn subscribe( fn subscribe_peer_events(
&self, &self,
_subscription_sink: jsonrpsee::SubscriptionSink, _subscription_sink: jsonrpsee::SubscriptionSink,
) -> jsonrpsee::types::SubscriptionResult { ) -> jsonrpsee::types::SubscriptionResult {