From 5f782a4c5836f9bd8f279e8dd8d3a0d03a88a64f Mon Sep 17 00:00:00 2001
From: PG Herveou <pgherveou@gmail.com>
Date: Wed, 30 Oct 2024 23:49:25 +0100
Subject: [PATCH] [pallet-revive] Add metrics to eth-rpc (#6288)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Add metrics for eth-rpc

---------

Co-authored-by: GitHub Action <action@github.com>
Co-authored-by: Alexander Theißen <alex.theissen@me.com>
---
 Cargo.lock                                    | 129 +-----------
 prdoc/pr_6288.prdoc                           |   7 +
 substrate/client/cli/src/signals.rs           |  15 ++
 substrate/frame/revive/rpc/Cargo.toml         |   9 +-
 substrate/frame/revive/rpc/Dockerfile         |  11 +-
 .../revive/rpc/examples/js/src/script.ts      |   2 +-
 substrate/frame/revive/rpc/src/cli.rs         | 195 ++++++++++--------
 substrate/frame/revive/rpc/src/client.rs      |  48 ++---
 substrate/frame/revive/rpc/src/main.rs        |  19 +-
 substrate/frame/revive/rpc/src/tests.rs       |  34 ++-
 10 files changed, 194 insertions(+), 275 deletions(-)
 create mode 100644 prdoc/pr_6288.prdoc

diff --git a/Cargo.lock b/Cargo.lock
index 166344f0e5f..a6c2bc1fd31 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -119,21 +119,6 @@ dependencies = [
  "memchr",
 ]
 
-[[package]]
-name = "alloc-no-stdlib"
-version = "2.0.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3"
-
-[[package]]
-name = "alloc-stdlib"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece"
-dependencies = [
- "alloc-no-stdlib",
-]
-
 [[package]]
 name = "allocator-api2"
 version = "0.2.16"
@@ -1165,22 +1150,6 @@ dependencies = [
  "pin-project-lite",
 ]
 
-[[package]]
-name = "async-compression"
-version = "0.4.11"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cd066d0b4ef8ecb03a55319dc13aa6910616d0f44008a045bb1835af830abff5"
-dependencies = [
- "brotli",
- "flate2",
- "futures-core",
- "memchr",
- "pin-project-lite",
- "tokio",
- "zstd 0.13.0",
- "zstd-safe 7.0.0",
-]
-
 [[package]]
 name = "async-executor"
 version = "1.5.1"
@@ -2613,27 +2582,6 @@ dependencies = [
  "tuplex",
 ]
 
-[[package]]
-name = "brotli"
-version = "6.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "74f7971dbd9326d58187408ab83117d8ac1bb9c17b085fdacd1cf2f598719b6b"
-dependencies = [
- "alloc-no-stdlib",
- "alloc-stdlib",
- "brotli-decompressor",
-]
-
-[[package]]
-name = "brotli-decompressor"
-version = "4.0.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9a45bd2e4095a8b518033b128020dd4a55aab1c0a381ba4404a472630f4bc362"
-dependencies = [
- "alloc-no-stdlib",
- "alloc-stdlib",
-]
-
 [[package]]
 name = "bs58"
 version = "0.5.1"
@@ -7507,12 +7455,6 @@ version = "0.3.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "add0ab9360ddbd88cfeb3bd9574a1d85cfdfa14db10b3e21d3700dbc4328758f"
 
-[[package]]
-name = "http-range-header"
-version = "0.4.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "08a397c49fec283e3d6211adbe480be95aae5f304cfb923e9970e08956d5168a"
-
 [[package]]
 name = "httparse"
 version = "1.8.0"
@@ -7993,16 +7935,6 @@ version = "2.8.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "28b29a3cd74f0f4598934efe3aeba42bae0eb4680554128851ebbecb02af14e6"
 
-[[package]]
-name = "iri-string"
-version = "0.7.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dc0f0a572e8ffe56e2ff4f769f32ffe919282c3916799f8b68688b6030063bea"
-dependencies = [
- "memchr",
- "serde",
-]
-
 [[package]]
 name = "is-terminal"
 version = "0.4.9"
@@ -9786,16 +9718,6 @@ version = "0.3.17"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
 
-[[package]]
-name = "mime_guess"
-version = "2.0.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e"
-dependencies = [
- "mime",
- "unicase",
-]
-
 [[package]]
 name = "minimal-lexical"
 version = "0.2.1"
@@ -12585,14 +12507,15 @@ dependencies = [
  "futures",
  "hex",
  "hex-literal",
- "hyper 1.3.1",
  "jsonrpsee 0.24.3",
  "log",
  "pallet-revive",
  "pallet-revive-fixtures",
  "parity-scale-codec",
  "rlp 0.6.1",
+ "sc-cli",
  "sc-rpc",
+ "sc-service",
  "scale-info",
  "secp256k1",
  "serde_json",
@@ -12601,13 +12524,11 @@ dependencies = [
  "sp-runtime 31.0.1",
  "sp-weights 27.0.0",
  "substrate-cli-test-utils",
+ "substrate-prometheus-endpoint",
  "subxt",
  "subxt-signer",
  "thiserror",
  "tokio",
- "tower",
- "tower-http 0.5.2",
- "tracing-subscriber 0.3.18",
 ]
 
 [[package]]
@@ -25309,7 +25230,7 @@ dependencies = [
  "futures-util",
  "http 0.2.9",
  "http-body 0.4.5",
- "http-range-header 0.3.1",
+ "http-range-header",
  "mime",
  "pin-project-lite",
  "tower-layer",
@@ -25323,29 +25244,14 @@ version = "0.5.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1e9cd434a998747dd2c4276bc96ee2e0c7a2eadf3cae88e52be55a05fa9053f5"
 dependencies = [
- "async-compression",
- "base64 0.21.7",
  "bitflags 2.6.0",
  "bytes",
- "futures-core",
- "futures-util",
  "http 1.1.0",
  "http-body 1.0.0",
  "http-body-util",
- "http-range-header 0.4.1",
- "httpdate",
- "iri-string",
- "mime",
- "mime_guess",
- "percent-encoding",
  "pin-project-lite",
- "tokio",
- "tokio-util",
- "tower",
  "tower-layer",
  "tower-service",
- "tracing",
- "uuid",
 ]
 
 [[package]]
@@ -25762,15 +25668,6 @@ version = "0.1.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94"
 
-[[package]]
-name = "unicase"
-version = "2.7.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89"
-dependencies = [
- "version_check",
-]
-
 [[package]]
 name = "unicode-bidi"
 version = "0.3.13"
@@ -27612,15 +27509,6 @@ dependencies = [
  "zstd-safe 6.0.6",
 ]
 
-[[package]]
-name = "zstd"
-version = "0.13.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bffb3309596d527cfcba7dfc6ed6052f1d39dfbd7c867aa2e865e4a449c10110"
-dependencies = [
- "zstd-safe 7.0.0",
-]
-
 [[package]]
 name = "zstd-safe"
 version = "5.0.2+zstd.1.5.2"
@@ -27641,15 +27529,6 @@ dependencies = [
  "zstd-sys",
 ]
 
-[[package]]
-name = "zstd-safe"
-version = "7.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "43747c7422e2924c11144d5229878b98180ef8b06cca4ab5af37afc8a8d8ea3e"
-dependencies = [
- "zstd-sys",
-]
-
 [[package]]
 name = "zstd-sys"
 version = "2.0.8+zstd.1.5.5"
diff --git a/prdoc/pr_6288.prdoc b/prdoc/pr_6288.prdoc
new file mode 100644
index 00000000000..8c1ed920efc
--- /dev/null
+++ b/prdoc/pr_6288.prdoc
@@ -0,0 +1,7 @@
+title: '[pallet-revive] Add metrics to eth-rpc'
+doc:
+- audience: Runtime Dev
+  description: Add metrics for eth-rpc
+crates:
+- name: pallet-revive-eth-rpc
+  bump: minor
diff --git a/substrate/client/cli/src/signals.rs b/substrate/client/cli/src/signals.rs
index 4b6a6f957a7..64cae03de7a 100644
--- a/substrate/client/cli/src/signals.rs
+++ b/substrate/client/cli/src/signals.rs
@@ -89,4 +89,19 @@ impl Signals {
 
 		Ok(())
 	}
+
+	/// Execute the future task and returns it's value if it completes before the signal.
+	pub async fn try_until_signal<F, T>(self, func: F) -> Result<T, ()>
+	where
+		F: Future<Output = T> + future::FusedFuture,
+	{
+		let signals = self.future().fuse();
+
+		pin_mut!(func, signals);
+
+		select! {
+			s = signals => Err(s),
+			res = func => Ok(res),
+		}
+	}
 }
diff --git a/substrate/frame/revive/rpc/Cargo.toml b/substrate/frame/revive/rpc/Cargo.toml
index 3c05bdeef47..ef7a7c1b28e 100644
--- a/substrate/frame/revive/rpc/Cargo.toml
+++ b/substrate/frame/revive/rpc/Cargo.toml
@@ -51,16 +51,14 @@ subxt = { workspace = true, default-features = true, features = [
 tokio = { workspace = true, features = ["full"] }
 codec = { workspace = true, features = ["derive"] }
 log.workspace = true
-tracing-subscriber.workspace = true
 pallet-revive = { workspace = true, default-features = true }
 sp-core = { workspace = true, default-features = true }
 sp-weights = { workspace = true, default-features = true }
 sp-runtime = { workspace = true, default-features = true }
 sc-rpc = { workspace = true, default-features = true }
-
-tower.workspace = true
-tower-http = { workspace = true, features = ["full"] }
-hyper.workspace = true
+sc-cli = { workspace = true, default-features = true }
+sc-service = { workspace = true, default-features = true }
+prometheus-endpoint = { workspace = true, default-features = true }
 
 rlp = { workspace = true, optional = true }
 subxt-signer = { workspace = true, optional = true, features = [
@@ -73,7 +71,6 @@ secp256k1 = { workspace = true, optional = true, features = ["recovery"] }
 env_logger = { workspace = true }
 
 [features]
-dev = []
 example = ["hex", "hex-literal", "rlp", "secp256k1", "subxt-signer"]
 riscv = ["pallet-revive/riscv"]
 
diff --git a/substrate/frame/revive/rpc/Dockerfile b/substrate/frame/revive/rpc/Dockerfile
index 3ad476651d8..981d5c19a15 100644
--- a/substrate/frame/revive/rpc/Dockerfile
+++ b/substrate/frame/revive/rpc/Dockerfile
@@ -2,7 +2,8 @@ FROM rust AS builder
 
 RUN apt-get update && \
 		DEBIAN_FRONTEND=noninteractive apt-get install -y \
-		protobuf-compiler
+		protobuf-compiler \
+		clang libclang-dev
 
 WORKDIR /polkadot
 COPY . /polkadot
@@ -19,5 +20,11 @@ RUN useradd -m -u 1001 -U -s /bin/sh -d /polkadot polkadot && \
 	/usr/local/bin/eth-rpc --help
 
 USER polkadot
-EXPOSE 8545
+
+# 8545 is the default port for the RPC server
+# 9616 is the default port for the prometheus metrics
+EXPOSE 8545 9616
 ENTRYPOINT ["/usr/local/bin/eth-rpc"]
+
+# We call the help by default
+CMD ["--help"]
diff --git a/substrate/frame/revive/rpc/examples/js/src/script.ts b/substrate/frame/revive/rpc/examples/js/src/script.ts
index 96414e34b7e..999312f0fd5 100644
--- a/substrate/frame/revive/rpc/examples/js/src/script.ts
+++ b/substrate/frame/revive/rpc/examples/js/src/script.ts
@@ -18,7 +18,7 @@ function str_to_bytes(str: string): Uint8Array {
 async function deploy() {
   console.log(`Deploying Contract...`);
 
-  const bytecode = readFileSync("rpc_demo.polkavm");
+  const bytecode = readFileSync("../rpc_demo.polkavm");
   const contractFactory = new ContractFactory(
     [
       "constructor(bytes memory _data)",
diff --git a/substrate/frame/revive/rpc/src/cli.rs b/substrate/frame/revive/rpc/src/cli.rs
index 019eb624b99..b95fa78bf3e 100644
--- a/substrate/frame/revive/rpc/src/cli.rs
+++ b/substrate/frame/revive/rpc/src/cli.rs
@@ -15,117 +15,138 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 //! The Ethereum JSON-RPC server.
-use crate::{client::Client, EthRpcClient, EthRpcServer, EthRpcServerImpl, LOG_TARGET};
+use crate::{client::Client, EthRpcServer, EthRpcServerImpl};
 use clap::Parser;
-use hyper::Method;
-use jsonrpsee::{
-	http_client::HttpClientBuilder,
-	server::{RpcModule, Server},
+use futures::{pin_mut, FutureExt};
+use jsonrpsee::server::RpcModule;
+use sc_cli::{PrometheusParams, RpcParams, SharedParams, Signals};
+use sc_service::{
+	config::{PrometheusConfig, RpcConfiguration},
+	start_rpc_servers, TaskManager,
 };
-use std::net::SocketAddr;
-use tower_http::cors::{Any, CorsLayer};
+
+// Default port if --prometheus-port is not specified
+const DEFAULT_PROMETHEUS_PORT: u16 = 9616;
+
+// Default port if --rpc-port is not specified
+const DEFAULT_RPC_PORT: u16 = 8545;
 
 // Parsed command instructions from the command line
-#[derive(Parser)]
+#[derive(Parser, Debug)]
 #[clap(author, about, version)]
 pub struct CliCommand {
-	/// The server address to bind to
-	#[clap(long, default_value = "8545")]
-	pub rpc_port: String,
-
 	/// The node url to connect to
 	#[clap(long, default_value = "ws://127.0.0.1:9944")]
 	pub node_rpc_url: String,
-}
 
-/// Run the JSON-RPC server.
-pub async fn run(cmd: CliCommand) -> anyhow::Result<()> {
-	let CliCommand { rpc_port, node_rpc_url } = cmd;
-	let client = Client::from_url(&node_rpc_url).await?;
-	let mut updates = client.updates.clone();
+	#[allow(missing_docs)]
+	#[clap(flatten)]
+	pub shared_params: SharedParams,
 
-	let server_addr = run_server(client, &format!("127.0.0.1:{rpc_port}")).await?;
-	log::info!("Running JSON-RPC server: addr={server_addr}");
+	#[allow(missing_docs)]
+	#[clap(flatten)]
+	pub rpc_params: RpcParams,
 
-	let url = format!("http://{}", server_addr);
-	let client = HttpClientBuilder::default().build(url)?;
+	#[allow(missing_docs)]
+	#[clap(flatten)]
+	pub prometheus_params: PrometheusParams,
+}
+
+/// Initialize the logger
+#[cfg(not(test))]
+fn init_logger(params: &SharedParams) -> anyhow::Result<()> {
+	let mut logger = sc_cli::LoggerBuilder::new(params.log_filters().join(","));
+	logger
+		.with_log_reloading(params.enable_log_reloading)
+		.with_detailed_output(params.detailed_log_output);
+
+	if let Some(tracing_targets) = &params.tracing_targets {
+		let tracing_receiver = params.tracing_receiver.into();
+		logger.with_profiling(tracing_receiver, tracing_targets);
+	}
 
-	let block_number = client.block_number().await?;
-	log::info!(target: LOG_TARGET, "Client initialized - Current 📦 block: #{block_number:?}");
+	if params.disable_log_color {
+		logger.with_colors(false);
+	}
 
-	// keep running server until ctrl-c or client subscription fails
-	let _ = updates.wait_for(|_| false).await;
+	logger.init()?;
 	Ok(())
 }
 
-#[cfg(feature = "dev")]
-mod dev {
-	use crate::LOG_TARGET;
-	use futures::{future::BoxFuture, FutureExt};
-	use jsonrpsee::{server::middleware::rpc::RpcServiceT, types::Request, MethodResponse};
-
-	/// Dev Logger middleware, that logs the method and params of the request, along with the
-	/// success of the response.
-	#[derive(Clone)]
-	pub struct DevLogger<S>(pub S);
-
-	impl<'a, S> RpcServiceT<'a> for DevLogger<S>
-	where
-		S: RpcServiceT<'a> + Send + Sync + Clone + 'static,
-	{
-		type Future = BoxFuture<'a, MethodResponse>;
-
-		fn call(&self, req: Request<'a>) -> Self::Future {
-			let service = self.0.clone();
-			let method = req.method.clone();
-			let params = req.params.clone().unwrap_or_default();
-
-			async move {
-				log::info!(target: LOG_TARGET, "Method: {method} params: {params}");
-				let resp = service.call(req).await;
-				if resp.is_success() {
-					log::info!(target: LOG_TARGET, "✅ rpc: {method}");
-				} else {
-					log::info!(target: LOG_TARGET, "❌ rpc: {method} {}", resp.as_result());
-				}
-				resp
-			}
-			.boxed()
+/// Start the JSON-RPC server using the given command line arguments.
+pub fn run(cmd: CliCommand) -> anyhow::Result<()> {
+	let CliCommand { rpc_params, prometheus_params, node_rpc_url, shared_params, .. } = cmd;
+
+	#[cfg(not(test))]
+	init_logger(&shared_params)?;
+	let is_dev = shared_params.dev;
+	let rpc_addrs: Option<Vec<sc_service::config::RpcEndpoint>> = rpc_params
+		.rpc_addr(is_dev, false, 8545)?
+		.map(|addrs| addrs.into_iter().map(Into::into).collect());
+
+	let rpc_config = RpcConfiguration {
+		addr: rpc_addrs,
+		methods: rpc_params.rpc_methods.into(),
+		max_connections: rpc_params.rpc_max_connections,
+		cors: rpc_params.rpc_cors(is_dev)?,
+		max_request_size: rpc_params.rpc_max_request_size,
+		max_response_size: rpc_params.rpc_max_response_size,
+		id_provider: None,
+		max_subs_per_conn: rpc_params.rpc_max_subscriptions_per_connection,
+		port: rpc_params.rpc_port.unwrap_or(DEFAULT_RPC_PORT),
+		message_buffer_capacity: rpc_params.rpc_message_buffer_capacity_per_connection,
+		batch_config: rpc_params.rpc_batch_config()?,
+		rate_limit: rpc_params.rpc_rate_limit,
+		rate_limit_whitelisted_ips: rpc_params.rpc_rate_limit_whitelisted_ips,
+		rate_limit_trust_proxy_headers: rpc_params.rpc_rate_limit_trust_proxy_headers,
+	};
+
+	let prometheus_config =
+		prometheus_params.prometheus_config(DEFAULT_PROMETHEUS_PORT, "eth-rpc".into());
+	let prometheus_registry = prometheus_config.as_ref().map(|config| &config.registry);
+
+	let tokio_runtime = sc_cli::build_runtime()?;
+	let tokio_handle = tokio_runtime.handle();
+	let signals = tokio_runtime.block_on(async { Signals::capture() })?;
+	let mut task_manager = TaskManager::new(tokio_handle.clone(), prometheus_registry)?;
+	let spawn_handle = task_manager.spawn_handle();
+
+	let gen_rpc_module = || {
+		let signals = tokio_runtime.block_on(async { Signals::capture() })?;
+		let fut = Client::from_url(&node_rpc_url, &spawn_handle).fuse();
+		pin_mut!(fut);
+
+		match tokio_handle.block_on(signals.try_until_signal(fut)) {
+			Ok(Ok(client)) => rpc_module(is_dev, client),
+			Ok(Err(err)) => Err(sc_service::Error::Application(err.into())),
+			Err(_) => Err(sc_service::Error::Application("Client connection interrupted".into())),
 		}
+	};
+
+	// Prometheus metrics.
+	if let Some(PrometheusConfig { port, registry }) = prometheus_config.clone() {
+		spawn_handle.spawn(
+			"prometheus-endpoint",
+			None,
+			prometheus_endpoint::init_prometheus(port, registry).map(drop),
+		);
 	}
-}
-
-/// Starts the rpc server and returns the server address.
-async fn run_server(client: Client, url: &str) -> anyhow::Result<SocketAddr> {
-	let cors = CorsLayer::new()
-		.allow_methods([Method::POST])
-		.allow_origin(Any)
-		.allow_headers([hyper::header::CONTENT_TYPE]);
-	let cors_middleware = tower::ServiceBuilder::new().layer(cors);
 
-	let builder = Server::builder().set_http_middleware(cors_middleware);
+	let rpc_server_handle =
+		start_rpc_servers(&rpc_config, prometheus_registry, tokio_handle, gen_rpc_module, None)?;
 
-	#[cfg(feature = "dev")]
-	let builder = builder
-		.set_rpc_middleware(jsonrpsee::server::RpcServiceBuilder::new().layer_fn(dev::DevLogger));
-
-	let server = builder.build(url.parse::<SocketAddr>()?).await?;
-	let addr = server.local_addr()?;
+	task_manager.keep_alive(rpc_server_handle);
+	tokio_runtime.block_on(signals.run_until_signal(task_manager.future().fuse()))?;
+	Ok(())
+}
 
+/// Create the JSON-RPC module.
+fn rpc_module(is_dev: bool, client: Client) -> Result<RpcModule<()>, sc_service::Error> {
 	let eth_api = EthRpcServerImpl::new(client)
-		.with_accounts(if cfg!(feature = "dev") {
-			use pallet_revive::evm::Account;
-			vec![Account::default()]
-		} else {
-			vec![]
-		})
+		.with_accounts(if is_dev { vec![crate::Account::default()] } else { vec![] })
 		.into_rpc();
 
 	let mut module = RpcModule::new(());
-	module.merge(eth_api)?;
-
-	let handle = server.start(module);
-	tokio::spawn(handle.stopped());
-
-	Ok(addr)
+	module.merge(eth_api).map_err(|e| sc_service::Error::Application(e.into()))?;
+	Ok(module)
 }
diff --git a/substrate/frame/revive/rpc/src/client.rs b/substrate/frame/revive/rpc/src/client.rs
index c707f298512..64f7f2a6161 100644
--- a/substrate/frame/revive/rpc/src/client.rs
+++ b/substrate/frame/revive/rpc/src/client.rs
@@ -35,6 +35,7 @@ use pallet_revive::{
 	},
 	EthContractResult,
 };
+use sc_service::SpawnTaskHandle;
 use sp_runtime::traits::{BlakeTwo256, Hash};
 use sp_weights::Weight;
 use std::{
@@ -57,10 +58,7 @@ use subxt::{
 };
 use subxt_client::transaction_payment::events::TransactionFeePaid;
 use thiserror::Error;
-use tokio::{
-	sync::{watch::Sender, RwLock},
-	task::JoinSet,
-};
+use tokio::sync::{watch::Sender, RwLock};
 
 use crate::subxt_client::{self, system::events::ExtrinsicSuccess, SrcChainConfig};
 
@@ -201,8 +199,6 @@ impl<const N: usize> BlockCache<N> {
 pub struct Client {
 	/// The inner state of the client.
 	inner: Arc<ClientInner>,
-	// JoinSet to manage spawned tasks.
-	join_set: JoinSet<Result<(), ClientError>>,
 	/// A watch channel to signal cache updates.
 	pub updates: tokio::sync::watch::Receiver<()>,
 }
@@ -308,13 +304,6 @@ impl ClientInner {
 	}
 }
 
-/// Drop all the tasks spawned by the client on drop.
-impl Drop for Client {
-	fn drop(&mut self) {
-		self.join_set.abort_all()
-	}
-}
-
 /// Fetch the chain ID from the substrate chain.
 async fn chain_id(api: &OnlineClient<SrcChainConfig>) -> Result<u64, ClientError> {
 	let query = subxt_client::constants().revive().chain_id();
@@ -355,18 +344,18 @@ async fn extract_block_timestamp(block: &SubstrateBlock) -> Option<u64> {
 impl Client {
 	/// Create a new client instance.
 	/// The client will subscribe to new blocks and maintain a cache of [`CACHE_SIZE`] blocks.
-	pub async fn from_url(url: &str) -> Result<Self, ClientError> {
+	pub async fn from_url(url: &str, spawn_handle: &SpawnTaskHandle) -> Result<Self, ClientError> {
 		log::info!(target: LOG_TARGET, "Connecting to node at: {url} ...");
 		let inner: Arc<ClientInner> = Arc::new(ClientInner::from_url(url).await?);
 		log::info!(target: LOG_TARGET, "Connected to node at: {url}");
 
 		let (tx, mut updates) = tokio::sync::watch::channel(());
-		let mut join_set = JoinSet::new();
-		join_set.spawn(Self::subscribe_blocks(inner.clone(), tx));
-		join_set.spawn(Self::subscribe_reconnect(inner.clone()));
+
+		spawn_handle.spawn("subscribe-blocks", None, Self::subscribe_blocks(inner.clone(), tx));
+		spawn_handle.spawn("subscribe-reconnect", None, Self::subscribe_reconnect(inner.clone()));
 
 		updates.changed().await.expect("tx is not dropped");
-		Ok(Self { inner, join_set, updates })
+		Ok(Self { inner, updates })
 	}
 
 	/// Expose the storage API.
@@ -422,7 +411,7 @@ impl Client {
 	}
 
 	/// Subscribe and log reconnection events.
-	async fn subscribe_reconnect(inner: Arc<ClientInner>) -> Result<(), ClientError> {
+	async fn subscribe_reconnect(inner: Arc<ClientInner>) {
 		let rpc = inner.as_ref().rpc_client.clone();
 		loop {
 			let reconnected = rpc.reconnect_initiated().await;
@@ -434,12 +423,15 @@ impl Client {
 	}
 
 	/// Subscribe to new blocks and update the cache.
-	async fn subscribe_blocks(inner: Arc<ClientInner>, tx: Sender<()>) -> Result<(), ClientError> {
+	async fn subscribe_blocks(inner: Arc<ClientInner>, tx: Sender<()>) {
 		log::info!(target: LOG_TARGET, "Subscribing to new blocks");
-		let mut block_stream =
-			inner.as_ref().api.blocks().subscribe_best().await.inspect_err(|err| {
-				log::error!("Failed to subscribe to blocks: {err:?}");
-			})?;
+		let mut block_stream = match inner.as_ref().api.blocks().subscribe_best().await {
+			Ok(s) => s,
+			Err(err) => {
+				log::error!(target: LOG_TARGET, "Failed to subscribe to blocks: {err:?}");
+				return
+			},
+		};
 
 		while let Some(block) = block_stream.next().await {
 			let block = match block {
@@ -447,13 +439,14 @@ impl Client {
 				Err(err) => {
 					if err.is_disconnected_will_reconnect() {
 						log::warn!(
+							target: LOG_TARGET,
 							"The RPC connection was lost and we may have missed a few blocks"
 						);
 						continue;
 					}
 
-					log::error!("Failed to fetch block: {err:?}");
-					return Err(err.into());
+					log::error!(target: LOG_TARGET, "Failed to fetch block: {err:?}");
+					return
 				},
 			};
 
@@ -464,7 +457,7 @@ impl Client {
 				.receipt_infos(&block)
 				.await
 				.inspect_err(|err| {
-					log::error!("Failed to get receipts: {err:?}");
+					log::error!(target: LOG_TARGET, "Failed to get receipts: {err:?}");
 				})
 				.unwrap_or_default();
 
@@ -491,7 +484,6 @@ impl Client {
 		}
 
 		log::info!(target: LOG_TARGET, "Block subscription ended");
-		Ok(())
 	}
 }
 
diff --git a/substrate/frame/revive/rpc/src/main.rs b/substrate/frame/revive/rpc/src/main.rs
index b1306ad096b..3376b9b10be 100644
--- a/substrate/frame/revive/rpc/src/main.rs
+++ b/substrate/frame/revive/rpc/src/main.rs
@@ -17,23 +17,8 @@
 //! The Ethereum JSON-RPC server.
 use clap::Parser;
 use pallet_revive_eth_rpc::cli;
-use tracing_subscriber::{util::SubscriberInitExt, EnvFilter, FmtSubscriber};
 
-/// Initialize tracing
-fn init_tracing() {
-	let env_filter =
-		EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("eth_rpc=trace"));
-
-	FmtSubscriber::builder()
-		.with_env_filter(env_filter)
-		.finish()
-		.try_init()
-		.expect("failed to initialize tracing");
-}
-
-#[tokio::main]
-async fn main() -> anyhow::Result<()> {
-	init_tracing();
+fn main() -> anyhow::Result<()> {
 	let cmd = cli::CliCommand::parse();
-	cli::run(cmd).await
+	cli::run(cmd)
 }
diff --git a/substrate/frame/revive/rpc/src/tests.rs b/substrate/frame/revive/rpc/src/tests.rs
index 8b618469f25..f745bea6a5f 100644
--- a/substrate/frame/revive/rpc/src/tests.rs
+++ b/substrate/frame/revive/rpc/src/tests.rs
@@ -19,10 +19,11 @@
 // We require the `riscv` feature to get access to the compiled fixtures.
 #![cfg(feature = "riscv")]
 use crate::{
-	cli,
+	cli::{self, CliCommand},
 	example::{send_transaction, wait_for_receipt},
 	EthRpcClient,
 };
+use clap::Parser;
 use jsonrpsee::ws_client::{WsClient, WsClientBuilder};
 use pallet_revive::{
 	create1,
@@ -50,19 +51,34 @@ async fn ws_client_with_retry(url: &str) -> WsClient {
 #[tokio::test]
 async fn test_jsonrpsee_server() -> anyhow::Result<()> {
 	// Start the node.
-	let _ = thread::spawn(move || match start_node_inline(vec!["--dev", "--rpc-port=45789"]) {
+	let _ = thread::spawn(move || {
+		match start_node_inline(vec![
+			"--dev",
+			"--rpc-port=45789",
+			"--no-telemetry",
+			"--no-prometheus",
+		]) {
+			Ok(_) => {},
+			Err(e) => {
+				panic!("Node exited with error: {}", e);
+			},
+		}
+	});
+
+	// Start the rpc server.
+	let args = CliCommand::parse_from([
+		"--dev",
+		"--rpc-port=45788",
+		"--node-rpc-url=ws://localhost:45789",
+		"--no-prometheus",
+	]);
+	let _ = thread::spawn(move || match cli::run(args) {
 		Ok(_) => {},
 		Err(e) => {
-			panic!("Node exited with error: {}", e);
+			panic!("eth-rpc exited with error: {}", e);
 		},
 	});
 
-	// Start the rpc server.
-	tokio::spawn(cli::run(cli::CliCommand {
-		rpc_port: "45788".to_string(),
-		node_rpc_url: "ws://localhost:45789".to_string(),
-	}));
-
 	let client = ws_client_with_retry("ws://localhost:45788").await;
 	let account = Account::default();
 
-- 
GitLab