From 5a1074712a9a800c0f88bf8e31f28fb0fb54f321 Mon Sep 17 00:00:00 2001
From: Niklas Adolfsson <niklasadolfsson1@gmail.com>
Date: Wed, 3 May 2023 17:18:25 +0200
Subject: [PATCH] rpc server: break legacy CLI options and remove "backward
 compatible HTTP server" (#13384)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* jsonrpsee v0.16

* breaking: remove old CLI configs

* remove patch.crates-io

* fix bad merge

* fix clippy

* fix bad merge

* fix grumbles

* Update client/service/src/lib.rs

Co-authored-by: Bastian Köcher <git@kchr.de>

* revert block_in_place

* add issue link in todo

* Update client/cli/src/config.rs

Co-authored-by: Dmitry Markin <dmitry@markin.tech>

* grumbles: add ipv6 loopback address

* Revert "grumbles: add ipv6 loopback address"

This reverts commit 3a0b1ece6c4e36055d666896c29d1da55ffa1c4f.

* remove nits

* bump zombienet version

* adress grumbles: provide structopt default_val_t

* remove duplicate from structopt

* bump zombienet v1.3.47

* bump zombienet version

---------

Co-authored-by: Bastian Köcher <git@kchr.de>
Co-authored-by: Dmitry Markin <dmitry@markin.tech>
Co-authored-by: Javier Viola <javier@parity.io>
---
 substrate/.gitlab-ci.yml                      |   2 +-
 .../bin/node/cli/benches/block_production.rs  |  16 +-
 .../bin/node/cli/benches/transaction_pool.rs  |  16 +-
 substrate/client/cli/src/commands/run_cmd.rs  |  98 +++-------
 substrate/client/cli/src/config.rs            |  69 ++-----
 substrate/client/cli/src/runner.rs            |  16 +-
 substrate/client/rpc-servers/src/lib.rs       | 178 ++++++------------
 substrate/client/service/src/config.rs        |  26 +--
 substrate/client/service/src/lib.rs           | 105 ++---------
 substrate/client/service/test/src/lib.rs      |  16 +-
 substrate/test-utils/cli/src/lib.rs           |   2 +-
 11 files changed, 150 insertions(+), 394 deletions(-)

diff --git a/substrate/.gitlab-ci.yml b/substrate/.gitlab-ci.yml
index 508f082d8c3..cc950865ce0 100644
--- a/substrate/.gitlab-ci.yml
+++ b/substrate/.gitlab-ci.yml
@@ -61,7 +61,7 @@ variables:
 
   NEXTEST_FAILURE_OUTPUT: immediate-final
   NEXTEST_SUCCESS_OUTPUT: final
-  ZOMBIENET_IMAGE: "docker.io/paritytech/zombienet:v1.3.43"
+  ZOMBIENET_IMAGE: "docker.io/paritytech/zombienet:v1.3.48"
 
 default:
   retry:
diff --git a/substrate/bin/node/cli/benches/block_production.rs b/substrate/bin/node/cli/benches/block_production.rs
index c7f0cd20efd..36e8076925e 100644
--- a/substrate/bin/node/cli/benches/block_production.rs
+++ b/substrate/bin/node/cli/benches/block_production.rs
@@ -84,18 +84,14 @@ fn new_node(tokio_handle: Handle) -> node_cli::service::NewFullBase {
 			offchain_worker: execution_strategy,
 			other: execution_strategy,
 		},
-		rpc_http: None,
-		rpc_ws: None,
-		rpc_ipc: None,
-		rpc_ws_max_connections: None,
+		rpc_addr: None,
+		rpc_max_connections: Default::default(),
 		rpc_cors: None,
 		rpc_methods: Default::default(),
-		rpc_max_payload: None,
-		rpc_max_request_size: None,
-		rpc_max_response_size: None,
-		rpc_id_provider: None,
-		rpc_max_subs_per_conn: None,
-		ws_max_out_buffer_capacity: None,
+		rpc_max_request_size: Default::default(),
+		rpc_max_response_size: Default::default(),
+		rpc_id_provider: Default::default(),
+		rpc_max_subs_per_conn: Default::default(),
 		prometheus_config: None,
 		telemetry_endpoints: None,
 		default_heap_pages: None,
diff --git a/substrate/bin/node/cli/benches/transaction_pool.rs b/substrate/bin/node/cli/benches/transaction_pool.rs
index abee9c84624..14d99c37a4a 100644
--- a/substrate/bin/node/cli/benches/transaction_pool.rs
+++ b/substrate/bin/node/cli/benches/transaction_pool.rs
@@ -78,18 +78,14 @@ fn new_node(tokio_handle: Handle) -> node_cli::service::NewFullBase {
 			offchain_worker: sc_client_api::ExecutionStrategy::NativeWhenPossible,
 			other: sc_client_api::ExecutionStrategy::NativeWhenPossible,
 		},
-		rpc_http: None,
-		rpc_ws: None,
-		rpc_ipc: None,
-		rpc_ws_max_connections: None,
+		rpc_addr: None,
+		rpc_max_connections: Default::default(),
 		rpc_cors: None,
 		rpc_methods: Default::default(),
-		rpc_max_payload: None,
-		rpc_max_request_size: None,
-		rpc_max_response_size: None,
-		rpc_id_provider: None,
-		rpc_max_subs_per_conn: None,
-		ws_max_out_buffer_capacity: None,
+		rpc_max_request_size: Default::default(),
+		rpc_max_response_size: Default::default(),
+		rpc_id_provider: Default::default(),
+		rpc_max_subs_per_conn: Default::default(),
 		prometheus_config: None,
 		telemetry_endpoints: None,
 		default_heap_pages: None,
diff --git a/substrate/client/cli/src/commands/run_cmd.rs b/substrate/client/cli/src/commands/run_cmd.rs
index 7cc1e6c730c..8176643d73c 100644
--- a/substrate/client/cli/src/commands/run_cmd.rs
+++ b/substrate/client/cli/src/commands/run_cmd.rs
@@ -65,7 +65,7 @@ pub struct RunCmd {
 	/// RPC methods to expose.
 	/// - `unsafe`: Exposes every RPC method.
 	/// - `safe`: Exposes only a safe subset of RPC methods, denying unsafe RPC methods.
-	/// - `auto`: Acts as `safe` if RPC is served externally, e.g. when `--{rpc,ws}-external` is
+	/// - `auto`: Acts as `safe` if RPC is served externally, e.g. when `--rpc--external` is
 	///   passed, otherwise acts as `unsafe`.
 	#[arg(
 		long,
@@ -77,58 +77,25 @@ pub struct RunCmd {
 	)]
 	pub rpc_methods: RpcMethods,
 
-	/// Listen to all Websocket interfaces.
-	/// Default is local. Note: not all RPC methods are safe to be exposed publicly. Use an RPC
-	/// proxy server to filter out dangerous methods. More details:
-	/// <https://docs.substrate.io/main-docs/build/custom-rpc/#public-rpcs>.
-	/// Use `--unsafe-ws-external` to suppress the warning if you understand the risks.
-	#[arg(long)]
-	pub ws_external: bool,
-
-	/// Listen to all Websocket interfaces.
-	/// Same as `--ws-external` but doesn't warn you about it.
-	#[arg(long)]
-	pub unsafe_ws_external: bool,
-
-	/// DEPRECATED, this has no affect anymore. Use `rpc_max_request_size` or
-	/// `rpc_max_response_size` instead.
-	#[arg(long)]
-	pub rpc_max_payload: Option<usize>,
-
 	/// Set the the maximum RPC request payload size for both HTTP and WS in megabytes.
-	/// Default is 15MiB.
-	#[arg(long)]
-	pub rpc_max_request_size: Option<usize>,
+	#[arg(long, default_value_t = 15)]
+	pub rpc_max_request_size: u32,
 
 	/// Set the the maximum RPC response payload size for both HTTP and WS in megabytes.
-	/// Default is 15MiB.
-	#[arg(long)]
-	pub rpc_max_response_size: Option<usize>,
+	#[arg(long, default_value_t = 15)]
+	pub rpc_max_response_size: u32,
 
 	/// Set the the maximum concurrent subscriptions per connection.
-	/// Default is 1024.
-	#[arg(long)]
-	pub rpc_max_subscriptions_per_connection: Option<usize>,
+	#[arg(long, default_value_t = 1024)]
+	pub rpc_max_subscriptions_per_connection: u32,
 
-	/// DEPRECATED, IPC support has been removed.
-	#[arg(long, value_name = "PATH")]
-	pub ipc_path: Option<String>,
+	/// Specify JSON-RPC server TCP port.
+	#[arg(long, value_name = "PORT", default_value_t = 9944)]
+	pub rpc_port: u16,
 
-	/// Specify HTTP RPC server TCP port.
-	#[arg(long, value_name = "PORT")]
-	pub rpc_port: Option<u16>,
-
-	/// Specify WebSockets RPC server TCP port.
-	#[arg(long, value_name = "PORT")]
-	pub ws_port: Option<u16>,
-
-	/// Maximum number of WS RPC server connections.
-	#[arg(long, value_name = "COUNT")]
-	pub ws_max_connections: Option<usize>,
-
-	/// DEPRECATED, this has no affect anymore. Use `rpc_max_response_size` instead.
-	#[arg(long)]
-	pub ws_max_out_buffer_capacity: Option<usize>,
+	/// Maximum number of RPC server connections.
+	#[arg(long, value_name = "COUNT", default_value_t = 100)]
+	pub rpc_max_connections: u32,
 
 	/// Specify browser Origins allowed to access the HTTP & WS RPC servers.
 	/// A comma-separated list of origins (protocol://domain or special `null`
@@ -344,8 +311,8 @@ impl CliConfiguration for RunCmd {
 		Ok(self.no_grandpa)
 	}
 
-	fn rpc_ws_max_connections(&self) -> Result<Option<usize>> {
-		Ok(self.ws_max_connections)
+	fn rpc_max_connections(&self) -> Result<u32> {
+		Ok(self.rpc_max_connections)
 	}
 
 	fn rpc_cors(&self, is_dev: bool) -> Result<Option<Vec<String>>> {
@@ -369,7 +336,7 @@ impl CliConfiguration for RunCmd {
 			.into())
 	}
 
-	fn rpc_http(&self, default_listen_port: u16) -> Result<Option<SocketAddr>> {
+	fn rpc_addr(&self, _default_listen_port: u16) -> Result<Option<SocketAddr>> {
 		let interface = rpc_interface(
 			self.rpc_external,
 			self.unsafe_rpc_external,
@@ -377,48 +344,25 @@ impl CliConfiguration for RunCmd {
 			self.validator,
 		)?;
 
-		Ok(Some(SocketAddr::new(interface, self.rpc_port.unwrap_or(default_listen_port))))
-	}
-
-	fn rpc_ipc(&self) -> Result<Option<String>> {
-		Ok(self.ipc_path.clone())
-	}
-
-	fn rpc_ws(&self, default_listen_port: u16) -> Result<Option<SocketAddr>> {
-		let interface = rpc_interface(
-			self.ws_external,
-			self.unsafe_ws_external,
-			self.rpc_methods,
-			self.validator,
-		)?;
-
-		Ok(Some(SocketAddr::new(interface, self.ws_port.unwrap_or(default_listen_port))))
+		Ok(Some(SocketAddr::new(interface, self.rpc_port)))
 	}
 
 	fn rpc_methods(&self) -> Result<sc_service::config::RpcMethods> {
 		Ok(self.rpc_methods.into())
 	}
 
-	fn rpc_max_payload(&self) -> Result<Option<usize>> {
-		Ok(self.rpc_max_payload)
-	}
-
-	fn rpc_max_request_size(&self) -> Result<Option<usize>> {
+	fn rpc_max_request_size(&self) -> Result<u32> {
 		Ok(self.rpc_max_request_size)
 	}
 
-	fn rpc_max_response_size(&self) -> Result<Option<usize>> {
+	fn rpc_max_response_size(&self) -> Result<u32> {
 		Ok(self.rpc_max_response_size)
 	}
 
-	fn rpc_max_subscriptions_per_connection(&self) -> Result<Option<usize>> {
+	fn rpc_max_subscriptions_per_connection(&self) -> Result<u32> {
 		Ok(self.rpc_max_subscriptions_per_connection)
 	}
 
-	fn ws_max_out_buffer_capacity(&self) -> Result<Option<usize>> {
-		Ok(self.ws_max_out_buffer_capacity)
-	}
-
 	fn transaction_pool(&self, is_dev: bool) -> Result<TransactionPoolOptions> {
 		Ok(self.pool_config.transaction_pool(is_dev))
 	}
@@ -475,7 +419,7 @@ fn rpc_interface(
 ) -> Result<IpAddr> {
 	if is_external && is_validator && rpc_methods != RpcMethods::Unsafe {
 		return Err(Error::Input(
-			"--rpc-external and --ws-external options shouldn't be used if the node is running as \
+			"--rpc-external option shouldn't be used if the node is running as \
 			 a validator. Use `--unsafe-rpc-external` or `--rpc-methods=unsafe` if you understand \
 			 the risks. See the options description for more information."
 				.to_owned(),
diff --git a/substrate/client/cli/src/config.rs b/substrate/client/cli/src/config.rs
index 063b2c39839..92b1eea7ae2 100644
--- a/substrate/client/cli/src/config.rs
+++ b/substrate/client/cli/src/config.rs
@@ -57,20 +57,13 @@ pub trait DefaultConfigurationValues {
 		30333
 	}
 
-	/// The port Substrate should listen on for websocket connections.
+	/// The port Substrate should listen on for JSON-RPC connections.
 	///
 	/// By default this is `9944`.
-	fn rpc_ws_listen_port() -> u16 {
+	fn rpc_listen_port() -> u16 {
 		9944
 	}
 
-	/// The port Substrate should listen on for http connections.
-	///
-	/// By default this is `9933`.
-	fn rpc_http_listen_port() -> u16 {
-		9933
-	}
-
 	/// The port Substrate should listen on for prometheus connections.
 	///
 	/// By default this is `9615`.
@@ -302,24 +295,8 @@ pub trait CliConfiguration<DCV: DefaultConfigurationValues = ()>: Sized {
 			.unwrap_or_default())
 	}
 
-	/// Get the RPC HTTP address (`None` if disabled).
-	///
-	/// By default this is `None`.
-	fn rpc_http(&self, _default_listen_port: u16) -> Result<Option<SocketAddr>> {
-		Ok(None)
-	}
-
-	/// Get the RPC IPC path (`None` if disabled).
-	///
-	/// By default this is `None`.
-	fn rpc_ipc(&self) -> Result<Option<String>> {
-		Ok(None)
-	}
-
-	/// Get the RPC websocket address (`None` if disabled).
-	///
-	/// By default this is `None`.
-	fn rpc_ws(&self, _default_listen_port: u16) -> Result<Option<SocketAddr>> {
+	/// Get the RPC address.
+	fn rpc_addr(&self, _default_listen_port: u16) -> Result<Option<SocketAddr>> {
 		Ok(None)
 	}
 
@@ -331,11 +308,9 @@ pub trait CliConfiguration<DCV: DefaultConfigurationValues = ()>: Sized {
 		Ok(Default::default())
 	}
 
-	/// Get the RPC websockets maximum connections (`None` if unlimited).
-	///
-	/// By default this is `None`.
-	fn rpc_ws_max_connections(&self) -> Result<Option<usize>> {
-		Ok(None)
+	/// Get the maximum number of RPC server connections.
+	fn rpc_max_connections(&self) -> Result<u32> {
+		Ok(Default::default())
 	}
 
 	/// Get the RPC cors (`None` if disabled)
@@ -345,29 +320,19 @@ pub trait CliConfiguration<DCV: DefaultConfigurationValues = ()>: Sized {
 		Ok(Some(Vec::new()))
 	}
 
-	/// Get maximum RPC payload.
-	fn rpc_max_payload(&self) -> Result<Option<usize>> {
-		Ok(None)
-	}
-
 	/// Get maximum RPC request payload size.
-	fn rpc_max_request_size(&self) -> Result<Option<usize>> {
-		Ok(None)
+	fn rpc_max_request_size(&self) -> Result<u32> {
+		Ok(Default::default())
 	}
 
 	/// Get maximum RPC response payload size.
-	fn rpc_max_response_size(&self) -> Result<Option<usize>> {
-		Ok(None)
+	fn rpc_max_response_size(&self) -> Result<u32> {
+		Ok(Default::default())
 	}
 
 	/// Get maximum number of subscriptions per connection.
-	fn rpc_max_subscriptions_per_connection(&self) -> Result<Option<usize>> {
-		Ok(None)
-	}
-
-	/// Get maximum WS output buffer capacity.
-	fn ws_max_out_buffer_capacity(&self) -> Result<Option<usize>> {
-		Ok(None)
+	fn rpc_max_subscriptions_per_connection(&self) -> Result<u32> {
+		Ok(Default::default())
 	}
 
 	/// Get the prometheus configuration (`None` if disabled)
@@ -532,18 +497,14 @@ pub trait CliConfiguration<DCV: DefaultConfigurationValues = ()>: Sized {
 			wasm_method: self.wasm_method()?,
 			wasm_runtime_overrides: self.wasm_runtime_overrides(),
 			execution_strategies: self.execution_strategies(is_dev, is_validator)?,
-			rpc_http: self.rpc_http(DCV::rpc_http_listen_port())?,
-			rpc_ws: self.rpc_ws(DCV::rpc_ws_listen_port())?,
-			rpc_ipc: self.rpc_ipc()?,
+			rpc_addr: self.rpc_addr(DCV::rpc_listen_port())?,
 			rpc_methods: self.rpc_methods()?,
-			rpc_ws_max_connections: self.rpc_ws_max_connections()?,
+			rpc_max_connections: self.rpc_max_connections()?,
 			rpc_cors: self.rpc_cors(is_dev)?,
-			rpc_max_payload: self.rpc_max_payload()?,
 			rpc_max_request_size: self.rpc_max_request_size()?,
 			rpc_max_response_size: self.rpc_max_response_size()?,
 			rpc_id_provider: None,
 			rpc_max_subs_per_conn: self.rpc_max_subscriptions_per_connection()?,
-			ws_max_out_buffer_capacity: self.ws_max_out_buffer_capacity()?,
 			prometheus_config: self
 				.prometheus_config(DCV::prometheus_listen_port(), &chain_spec)?,
 			telemetry_endpoints,
diff --git a/substrate/client/cli/src/runner.rs b/substrate/client/cli/src/runner.rs
index a8b75f2665a..9db078046e3 100644
--- a/substrate/client/cli/src/runner.rs
+++ b/substrate/client/cli/src/runner.rs
@@ -287,18 +287,14 @@ mod tests {
 				wasm_method: Default::default(),
 				wasm_runtime_overrides: None,
 				execution_strategies: Default::default(),
-				rpc_http: None,
-				rpc_ws: None,
-				rpc_ipc: None,
-				rpc_ws_max_connections: None,
+				rpc_addr: None,
+				rpc_max_connections: Default::default(),
 				rpc_cors: None,
 				rpc_methods: Default::default(),
-				rpc_max_payload: None,
-				rpc_max_request_size: None,
-				rpc_max_response_size: None,
-				rpc_id_provider: None,
-				rpc_max_subs_per_conn: None,
-				ws_max_out_buffer_capacity: None,
+				rpc_max_request_size: Default::default(),
+				rpc_max_response_size: Default::default(),
+				rpc_id_provider: Default::default(),
+				rpc_max_subs_per_conn: Default::default(),
 				prometheus_config: None,
 				telemetry_endpoints: None,
 				default_heap_pages: None,
diff --git a/substrate/client/rpc-servers/src/lib.rs b/substrate/client/rpc-servers/src/lib.rs
index 62a9b4fc4a0..92b31937a0c 100644
--- a/substrate/client/rpc-servers/src/lib.rs
+++ b/substrate/client/rpc-servers/src/lib.rs
@@ -20,6 +20,9 @@
 
 #![warn(missing_docs)]
 
+pub mod middleware;
+
+use http::header::HeaderValue;
 use jsonrpsee::{
 	server::{
 		middleware::proxy_get_request::ProxyGetRequestLayer, AllowHosts, ServerBuilder,
@@ -28,119 +31,62 @@ use jsonrpsee::{
 	RpcModule,
 };
 use std::{error::Error as StdError, net::SocketAddr};
+use tower_http::cors::{AllowOrigin, CorsLayer};
 
 pub use crate::middleware::RpcMetrics;
-use http::header::HeaderValue;
 pub use jsonrpsee::core::{
 	id_providers::{RandomIntegerIdProvider, RandomStringIdProvider},
 	traits::IdProvider,
 };
-use tower_http::cors::{AllowOrigin, CorsLayer};
-
-const MEGABYTE: usize = 1024 * 1024;
-
-/// Maximal payload accepted by RPC servers.
-pub const RPC_MAX_PAYLOAD_DEFAULT: usize = 15 * MEGABYTE;
-
-/// Default maximum number of connections for WS RPC servers.
-const WS_MAX_CONNECTIONS: usize = 100;
 
-/// Default maximum number subscriptions per connection for WS RPC servers.
-const WS_MAX_SUBS_PER_CONN: usize = 1024;
+const MEGABYTE: u32 = 1024 * 1024;
 
-pub mod middleware;
-
-/// Type alias JSON-RPC server
+/// Type alias for the JSON-RPC server.
 pub type Server = ServerHandle;
 
-/// Server config.
-#[derive(Debug, Clone)]
-pub struct WsConfig {
+/// RPC server configuration.
+#[derive(Debug)]
+pub struct Config<'a, M: Send + Sync + 'static> {
+	/// Socket addresses.
+	pub addrs: [SocketAddr; 2],
+	/// CORS.
+	pub cors: Option<&'a Vec<String>>,
 	/// Maximum connections.
-	pub max_connections: Option<usize>,
+	pub max_connections: u32,
 	/// Maximum subscriptions per connection.
-	pub max_subs_per_conn: Option<usize>,
+	pub max_subs_per_conn: u32,
 	/// Maximum rpc request payload size.
-	pub max_payload_in_mb: Option<usize>,
+	pub max_payload_in_mb: u32,
 	/// Maximum rpc response payload size.
-	pub max_payload_out_mb: Option<usize>,
-}
-
-impl WsConfig {
-	// Deconstructs the config to get the finalized inner values.
-	//
-	// `Payload size` or `max subs per connection` bigger than u32::MAX will be truncated.
-	fn deconstruct(self) -> (u32, u32, u32, u32) {
-		let max_conns = self.max_connections.unwrap_or(WS_MAX_CONNECTIONS) as u32;
-		let max_payload_in_mb = payload_size_or_default(self.max_payload_in_mb) as u32;
-		let max_payload_out_mb = payload_size_or_default(self.max_payload_out_mb) as u32;
-		let max_subs_per_conn = self.max_subs_per_conn.unwrap_or(WS_MAX_SUBS_PER_CONN) as u32;
-
-		(max_payload_in_mb, max_payload_out_mb, max_conns, max_subs_per_conn)
-	}
-}
-
-/// Start HTTP server listening on given address.
-pub async fn start_http<M: Send + Sync + 'static>(
-	addrs: [SocketAddr; 2],
-	cors: Option<&Vec<String>>,
-	max_payload_in_mb: Option<usize>,
-	max_payload_out_mb: Option<usize>,
-	metrics: Option<RpcMetrics>,
-	rpc_api: RpcModule<M>,
-	rt: tokio::runtime::Handle,
-) -> Result<ServerHandle, Box<dyn StdError + Send + Sync>> {
-	let max_payload_in = payload_size_or_default(max_payload_in_mb) as u32;
-	let max_payload_out = payload_size_or_default(max_payload_out_mb) as u32;
-	let host_filter = hosts_filter(cors.is_some(), &addrs);
-
-	let middleware = tower::ServiceBuilder::new()
-		// Proxy `GET /health` requests to internal `system_health` method.
-		.layer(ProxyGetRequestLayer::new("/health", "system_health")?)
-		.layer(try_into_cors(cors)?);
-
-	let builder = ServerBuilder::new()
-		.max_request_body_size(max_payload_in)
-		.max_response_body_size(max_payload_out)
-		.set_host_filtering(host_filter)
-		.set_middleware(middleware)
-		.custom_tokio_runtime(rt)
-		.http_only();
-
-	let rpc_api = build_rpc_api(rpc_api);
-	let (handle, addr) = if let Some(metrics) = metrics {
-		let server = builder.set_logger(metrics).build(&addrs[..]).await?;
-		let addr = server.local_addr();
-		(server.start(rpc_api)?, addr)
-	} else {
-		let server = builder.build(&addrs[..]).await?;
-		let addr = server.local_addr();
-		(server.start(rpc_api)?, addr)
-	};
-
-	log::info!(
-		"Running JSON-RPC HTTP server: addr={}, allowed origins={}",
-		addr.map_or_else(|_| "unknown".to_string(), |a| a.to_string()),
-		format_cors(cors)
-	);
-
-	Ok(handle)
+	pub max_payload_out_mb: u32,
+	/// Metrics.
+	pub metrics: Option<RpcMetrics>,
+	/// RPC API.
+	pub rpc_api: RpcModule<M>,
+	/// Subscription ID provider.
+	pub id_provider: Option<Box<dyn IdProvider>>,
+	/// Tokio runtime handle.
+	pub tokio_handle: tokio::runtime::Handle,
 }
 
-/// Start a JSON-RPC server listening on given address that supports both HTTP and WS.
-pub async fn start<M: Send + Sync + 'static>(
-	addrs: [SocketAddr; 2],
-	cors: Option<&Vec<String>>,
-	ws_config: WsConfig,
-	metrics: Option<RpcMetrics>,
-	rpc_api: RpcModule<M>,
-	rt: tokio::runtime::Handle,
-	id_provider: Option<Box<dyn IdProvider>>,
+/// Start RPC server listening on given address.
+pub async fn start_server<M: Send + Sync + 'static>(
+	config: Config<'_, M>,
 ) -> Result<ServerHandle, Box<dyn StdError + Send + Sync>> {
-	let (max_payload_in, max_payload_out, max_connections, max_subs_per_conn) =
-		ws_config.deconstruct();
-
-	let host_filter = hosts_filter(cors.is_some(), &addrs);
+	let Config {
+		addrs,
+		cors,
+		max_payload_in_mb,
+		max_payload_out_mb,
+		max_connections,
+		max_subs_per_conn,
+		metrics,
+		id_provider,
+		tokio_handle,
+		rpc_api,
+	} = config;
+
+	let host_filter = hosts_filtering(cors.is_some(), &addrs);
 
 	let middleware = tower::ServiceBuilder::new()
 		// Proxy `GET /health` requests to internal `system_health` method.
@@ -148,14 +94,14 @@ pub async fn start<M: Send + Sync + 'static>(
 		.layer(try_into_cors(cors)?);
 
 	let mut builder = ServerBuilder::new()
-		.max_request_body_size(max_payload_in)
-		.max_response_body_size(max_payload_out)
+		.max_request_body_size(max_payload_in_mb.saturating_mul(MEGABYTE))
+		.max_response_body_size(max_payload_out_mb.saturating_mul(MEGABYTE))
 		.max_connections(max_connections)
 		.max_subscriptions_per_connection(max_subs_per_conn)
 		.ping_interval(std::time::Duration::from_secs(30))
 		.set_host_filtering(host_filter)
 		.set_middleware(middleware)
-		.custom_tokio_runtime(rt);
+		.custom_tokio_runtime(tokio_handle);
 
 	if let Some(provider) = id_provider {
 		builder = builder.set_id_provider(provider);
@@ -175,7 +121,7 @@ pub async fn start<M: Send + Sync + 'static>(
 	};
 
 	log::info!(
-		"Running JSON-RPC WS server: addr={}, allowed origins={}",
+		"Running JSON-RPC server: addr={}, allowed origins={}",
 		addr.map_or_else(|_| "unknown".to_string(), |a| a.to_string()),
 		format_cors(cors)
 	);
@@ -183,6 +129,20 @@ pub async fn start<M: Send + Sync + 'static>(
 	Ok(handle)
 }
 
+fn hosts_filtering(enabled: bool, addrs: &[SocketAddr]) -> AllowHosts {
+	if enabled {
+		// NOTE The listening addresses are whitelisted by default.
+		let mut hosts = Vec::with_capacity(addrs.len() * 2);
+		for addr in addrs {
+			hosts.push(format!("localhost:{}", addr.port()).into());
+			hosts.push(format!("127.0.0.1:{}", addr.port()).into());
+		}
+		AllowHosts::Only(hosts)
+	} else {
+		AllowHosts::Any
+	}
+}
+
 fn build_rpc_api<M: Send + Sync + 'static>(mut rpc_api: RpcModule<M>) -> RpcModule<M> {
 	let mut available_methods = rpc_api.method_names().collect::<Vec<_>>();
 	available_methods.sort();
@@ -198,24 +158,6 @@ fn build_rpc_api<M: Send + Sync + 'static>(mut rpc_api: RpcModule<M>) -> RpcModu
 	rpc_api
 }
 
-fn payload_size_or_default(size_mb: Option<usize>) -> usize {
-	size_mb.map_or(RPC_MAX_PAYLOAD_DEFAULT, |mb| mb.saturating_mul(MEGABYTE))
-}
-
-fn hosts_filter(enabled: bool, addrs: &[SocketAddr]) -> AllowHosts {
-	if enabled {
-		// NOTE The listening addresses are whitelisted by default.
-		let mut hosts = Vec::with_capacity(addrs.len() * 2);
-		for addr in addrs {
-			hosts.push(format!("localhost:{}", addr.port()).into());
-			hosts.push(format!("127.0.0.1:{}", addr.port()).into());
-		}
-		AllowHosts::Only(hosts)
-	} else {
-		AllowHosts::Any
-	}
-}
-
 fn try_into_cors(
 	maybe_cors: Option<&Vec<String>>,
 ) -> Result<CorsLayer, Box<dyn StdError + Send + Sync>> {
diff --git a/substrate/client/service/src/config.rs b/substrate/client/service/src/config.rs
index 6550fcdd8ef..40f8c6a4f04 100644
--- a/substrate/client/service/src/config.rs
+++ b/substrate/client/service/src/config.rs
@@ -83,34 +83,24 @@ pub struct Configuration {
 	pub wasm_runtime_overrides: Option<PathBuf>,
 	/// Execution strategies.
 	pub execution_strategies: ExecutionStrategies,
-	/// RPC over HTTP binding address. `None` if disabled.
-	pub rpc_http: Option<SocketAddr>,
-	/// RPC over Websockets binding address. `None` if disabled.
-	pub rpc_ws: Option<SocketAddr>,
-	/// RPC over IPC binding path. `None` if disabled.
-	pub rpc_ipc: Option<String>,
-	/// Maximum number of connections for WebSockets RPC server. `None` if default.
-	pub rpc_ws_max_connections: Option<usize>,
+	/// JSON-RPC server binding address.
+	pub rpc_addr: Option<SocketAddr>,
+	/// Maximum number of connections for JSON-RPC server.
+	pub rpc_max_connections: u32,
 	/// CORS settings for HTTP & WS servers. `None` if all origins are allowed.
 	pub rpc_cors: Option<Vec<String>>,
 	/// RPC methods to expose (by default only a safe subset or all of them).
 	pub rpc_methods: RpcMethods,
-	/// Maximum payload of rpc request/responses.
-	pub rpc_max_payload: Option<usize>,
 	/// Maximum payload of a rpc request
-	pub rpc_max_request_size: Option<usize>,
-	/// Maximum payload of a rpc request
-	pub rpc_max_response_size: Option<usize>,
+	pub rpc_max_request_size: u32,
+	/// Maximum payload of a rpc response.
+	pub rpc_max_response_size: u32,
 	/// Custom JSON-RPC subscription ID provider.
 	///
 	/// Default: [`crate::RandomStringSubscriptionId`].
 	pub rpc_id_provider: Option<Box<dyn crate::RpcSubscriptionIdProvider>>,
 	/// Maximum allowed subscriptions per rpc connection
-	///
-	/// Default: 1024.
-	pub rpc_max_subs_per_conn: Option<usize>,
-	/// Maximum size of the output buffer capacity for websocket connections.
-	pub ws_max_out_buffer_capacity: Option<usize>,
+	pub rpc_max_subs_per_conn: u32,
 	/// Prometheus endpoint configuration. `None` if disabled.
 	pub prometheus_config: Option<PrometheusConfig>,
 	/// Telemetry service URL. `None` if disabled.
diff --git a/substrate/client/service/src/lib.rs b/substrate/client/service/src/lib.rs
index c0c7c537c64..a5667717055 100644
--- a/substrate/client/service/src/lib.rs
+++ b/substrate/client/service/src/lib.rs
@@ -380,57 +380,37 @@ where
 		}
 	}
 
-	let (max_request_size, ws_max_response_size, http_max_response_size) =
-		legacy_cli_parsing(config);
-
-	let random_port = |mut addr: SocketAddr| {
+	// if binding the specified port failed then a random port is assigned by the OS.
+	let backup_port = |mut addr: SocketAddr| {
 		addr.set_port(0);
 		addr
 	};
 
-	let ws_addr = config
-		.rpc_ws
-		.unwrap_or_else(|| "127.0.0.1:9944".parse().expect("valid sockaddr; qed"));
-	let ws_addr2 = random_port(ws_addr);
-
-	let http_addr = config
-		.rpc_http
-		.unwrap_or_else(|| "127.0.0.1:9933".parse().expect("valid sockaddr; qed"));
-	let http_addr2 = random_port(http_addr);
-
+	let addr = config.rpc_addr.unwrap_or(([127, 0, 0, 1], 9944).into());
+	let backup_addr = backup_port(addr);
 	let metrics = sc_rpc_server::RpcMetrics::new(config.prometheus_registry())?;
 
-	let server_config = sc_rpc_server::WsConfig {
-		max_connections: config.rpc_ws_max_connections,
-		max_payload_in_mb: max_request_size,
-		max_payload_out_mb: ws_max_response_size,
+	let server_config = sc_rpc_server::Config {
+		addrs: [addr, backup_addr],
+		max_connections: config.rpc_max_connections,
+		max_payload_in_mb: config.rpc_max_request_size,
+		max_payload_out_mb: config.rpc_max_response_size,
 		max_subs_per_conn: config.rpc_max_subs_per_conn,
+		rpc_api: gen_rpc_module(deny_unsafe(addr, &config.rpc_methods))?,
+		metrics,
+		id_provider: rpc_id_provider,
+		cors: config.rpc_cors.as_ref(),
+		tokio_handle: config.tokio_handle.clone(),
 	};
 
-	let http_fut = sc_rpc_server::start_http(
-		[http_addr, http_addr2],
-		config.rpc_cors.as_ref(),
-		max_request_size,
-		http_max_response_size,
-		metrics.clone(),
-		gen_rpc_module(deny_unsafe(http_addr, &config.rpc_methods))?,
-		config.tokio_handle.clone(),
-	);
-
-	let ws_fut = sc_rpc_server::start(
-		[ws_addr, ws_addr2],
-		config.rpc_cors.as_ref(),
-		server_config.clone(),
-		metrics.clone(),
-		gen_rpc_module(deny_unsafe(ws_addr, &config.rpc_methods))?,
-		config.tokio_handle.clone(),
-		rpc_id_provider,
-	);
-
+	// TODO: https://github.com/paritytech/substrate/issues/13773
+	//
+	// `block_in_place` is a hack to allow callers to call `block_on` prior to
+	// calling `start_rpc_servers`.
 	match tokio::task::block_in_place(|| {
-		config.tokio_handle.block_on(futures::future::try_join(http_fut, ws_fut))
+		config.tokio_handle.block_on(sc_rpc_server::start_server(server_config))
 	}) {
-		Ok((http, ws)) => Ok(Box::new((waiting::Server(Some(http)), waiting::Server(Some(ws))))),
+		Ok(server) => Ok(Box::new(waiting::Server(Some(server)))),
 		Err(e) => Err(Error::Application(e)),
 	}
 }
@@ -534,51 +514,6 @@ where
 	}
 }
 
-fn legacy_cli_parsing(config: &Configuration) -> (Option<usize>, Option<usize>, Option<usize>) {
-	let ws_max_response_size = match (
-		config.ws_max_out_buffer_capacity,
-		config.rpc_max_response_size,
-	) {
-		(Some(legacy_max), max) => {
-			eprintln!("DEPRECATED: `--ws_max_out_buffer_capacity` has been removed; use `rpc-max-response-size or rpc-max-request-size` instead");
-			eprintln!("Setting WS `rpc-max-response-size` to `max(ws_max_out_buffer_capacity, rpc_max_response_size)`");
-			Some(std::cmp::max(legacy_max, max.unwrap_or(0)))
-		},
-		(None, Some(m)) => Some(m),
-		(None, None) => None,
-	};
-
-	let max_request_size = match (config.rpc_max_payload, config.rpc_max_request_size) {
-		(Some(legacy_max), max) => {
-			eprintln!("DEPRECATED: `--rpc_max_payload` has been removed use `rpc-max-response-size or rpc-max-request-size` instead");
-			eprintln!(
-				"Setting `rpc-max-response-size` to `max(rpc_max_payload, rpc_max_request_size)`"
-			);
-			Some(std::cmp::max(legacy_max, max.unwrap_or(0)))
-		},
-		(None, Some(max)) => Some(max),
-		(None, None) => None,
-	};
-
-	let http_max_response_size = match (config.rpc_max_payload, config.rpc_max_response_size) {
-		(Some(legacy_max), max) => {
-			eprintln!("DEPRECATED: `--rpc_max_payload` has been removed use `rpc-max-response-size or rpc-max-request-size` instead");
-			eprintln!(
-				"Setting HTTP `rpc-max-response-size` to `max(rpc_max_payload, rpc_max_response_size)`"
-			);
-			Some(std::cmp::max(legacy_max, max.unwrap_or(0)))
-		},
-		(None, Some(max)) => Some(max),
-		(None, None) => None,
-	};
-
-	if config.rpc_ipc.is_some() {
-		eprintln!("DEPRECATED: `--ipc-path` has no effect anymore IPC support has been removed");
-	}
-
-	(max_request_size, ws_max_response_size, http_max_response_size)
-}
-
 #[cfg(test)]
 mod tests {
 	use super::*;
diff --git a/substrate/client/service/test/src/lib.rs b/substrate/client/service/test/src/lib.rs
index 29e68261a10..0eb4489ad08 100644
--- a/substrate/client/service/test/src/lib.rs
+++ b/substrate/client/service/test/src/lib.rs
@@ -246,18 +246,14 @@ fn node_config<
 		wasm_method: Default::default(),
 		wasm_runtime_overrides: Default::default(),
 		execution_strategies: Default::default(),
-		rpc_http: None,
-		rpc_ipc: None,
-		rpc_ws: None,
-		rpc_ws_max_connections: None,
+		rpc_addr: Default::default(),
+		rpc_max_connections: Default::default(),
 		rpc_cors: None,
 		rpc_methods: Default::default(),
-		rpc_max_payload: None,
-		rpc_max_request_size: None,
-		rpc_max_response_size: None,
-		rpc_id_provider: None,
-		rpc_max_subs_per_conn: None,
-		ws_max_out_buffer_capacity: None,
+		rpc_max_request_size: Default::default(),
+		rpc_max_response_size: Default::default(),
+		rpc_id_provider: Default::default(),
+		rpc_max_subs_per_conn: Default::default(),
 		prometheus_config: None,
 		telemetry_endpoints: None,
 		default_heap_pages: None,
diff --git a/substrate/test-utils/cli/src/lib.rs b/substrate/test-utils/cli/src/lib.rs
index 1846f19090b..f225cc135a2 100644
--- a/substrate/test-utils/cli/src/lib.rs
+++ b/substrate/test-utils/cli/src/lib.rs
@@ -282,7 +282,7 @@ pub fn extract_info_from_output(read: impl Read + Send) -> (NodeInfo, String) {
 			data.push_str("\n");
 
 			// does the line contain our port (we expect this specific output from substrate).
-			let sock_addr = match line.split_once("Running JSON-RPC WS server: addr=") {
+			let sock_addr = match line.split_once("Running JSON-RPC server: addr=") {
 				None => return None,
 				Some((_, after)) => after.split_once(",").unwrap().0,
 			};
-- 
GitLab