Skip to main content

sc_rpc_server/
lib.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
5
6// This program is free software: you can redistribute it and/or modify
7// it under the terms of the GNU General Public License as published by
8// the Free Software Foundation, either version 3 of the License, or
9// (at your option) any later version.
10
11// This program is distributed in the hope that it will be useful,
12// but WITHOUT ANY WARRANTY; without even the implied warranty of
13// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14// GNU General Public License for more details.
15
16// You should have received a copy of the GNU General Public License
17// along with this program. If not, see <https://www.gnu.org/licenses/>.
18
19//! Substrate RPC servers.
20
21#![warn(missing_docs)]
22
23pub mod middleware;
24pub mod utils;
25
26use std::{error::Error as StdError, net::SocketAddr, sync::Arc, time::Duration};
27
28use futures::future::BoxFuture;
29use jsonrpsee::{
30	core::BoxError,
31	server::{
32		serve_with_graceful_shutdown, stop_channel, ws, PingConfig, ServerHandle, StopHandle,
33	},
34	Methods, RpcModule,
35};
36use tower::Service;
37use utils::{
38	build_rpc_api, deny_unsafe, format_listen_addrs, get_proxy_ip, ListenAddrError, RpcSettings,
39};
40
41pub use ip_network::IpNetwork;
42pub use jsonrpsee::{
43	core::id_providers::{RandomIntegerIdProvider, RandomStringIdProvider},
44	server::{middleware::rpc::RpcServiceBuilder, BatchRequestConfig},
45};
46pub use middleware::{Metrics, MiddlewareLayer, NodeHealthProxyLayer, RpcMetrics};
47pub use utils::{RpcEndpoint, RpcMethods};
48
49const MEGABYTE: u32 = 1024 * 1024;
50
51/// Creates a dedicated tokio runtime for RPC operations.
52///
53/// This runtime isolates RPC blocking operations from the rest of the node
54/// by limiting the number of blocking threads to `max_connections`.
55pub fn create_rpc_runtime(max_connections: u32) -> std::io::Result<tokio::runtime::Runtime> {
56	tokio::runtime::Builder::new_multi_thread()
57		.thread_name("rpc")
58		.enable_all()
59		.max_blocking_threads((max_connections as usize).max(1))
60		.on_thread_start(|| {
61			sc_utils::metrics::TOKIO_THREADS_ALIVE.inc();
62			sc_utils::metrics::TOKIO_THREADS_TOTAL.inc();
63			middleware::RPC_THREADS_ALIVE.inc();
64			middleware::RPC_THREADS_TOTAL.inc();
65		})
66		.on_thread_stop(|| {
67			sc_utils::metrics::TOKIO_THREADS_ALIVE.dec();
68			middleware::RPC_THREADS_ALIVE.dec();
69		})
70		.build()
71}
72
73/// Spawn handle for RPC tasks that uses the dedicated RPC runtime.
74///
75/// This ensures all RPC-related task spawning (including rpc-spec-v2 APIs like
76/// chainHead and transactionWatch) runs on the isolated RPC runtime rather than
77/// the main node runtime.
78#[derive(Clone)]
79pub struct RpcSpawnHandle {
80	handle: tokio::runtime::Handle,
81}
82
83impl RpcSpawnHandle {
84	/// Create a new RpcSpawnHandle from a tokio runtime handle.
85	pub fn new(handle: tokio::runtime::Handle) -> Self {
86		Self { handle }
87	}
88}
89
90impl sp_core::traits::SpawnNamed for RpcSpawnHandle {
91	fn spawn_blocking(
92		&self,
93		_name: &'static str,
94		_group: Option<&'static str>,
95		future: BoxFuture<'static, ()>,
96	) {
97		let handle = self.handle.clone();
98		self.handle.spawn_blocking(move || {
99			handle.block_on(future);
100		});
101	}
102
103	fn spawn(
104		&self,
105		_name: &'static str,
106		_group: Option<&'static str>,
107		future: BoxFuture<'static, ()>,
108	) {
109		self.handle.spawn(future);
110	}
111}
112
113/// Type to encapsulate the server handle and listening address.
114pub struct Server {
115	/// Handle to the rpc server
116	handle: ServerHandle,
117	/// Listening address of the server
118	listen_addrs: Vec<SocketAddr>,
119	/// Dedicated RPC runtime (kept alive for the lifetime of the server)
120	rpc_runtime: Option<tokio::runtime::Runtime>,
121}
122
123impl Server {
124	/// Creates a new Server.
125	pub fn new(
126		handle: ServerHandle,
127		listen_addrs: Vec<SocketAddr>,
128		rpc_runtime: tokio::runtime::Runtime,
129	) -> Server {
130		Server { handle, listen_addrs, rpc_runtime: Some(rpc_runtime) }
131	}
132
133	/// Returns the `jsonrpsee::server::ServerHandle` for this Server. Can be used to stop the
134	/// server.
135	pub fn handle(&self) -> &ServerHandle {
136		&self.handle
137	}
138
139	/// The listen address for the running RPC service.
140	pub fn listen_addrs(&self) -> &[SocketAddr] {
141		&self.listen_addrs
142	}
143
144	/// Returns the spawn handle for tasks on the dedicated RPC runtime.
145	pub fn spawn_handle(&self) -> Arc<dyn sp_core::traits::SpawnNamed> {
146		Arc::new(RpcSpawnHandle::new(
147			self.rpc_runtime
148				.as_ref()
149				.expect("rpc_runtime is only taken in Drop; qed")
150				.handle()
151				.clone(),
152		))
153	}
154}
155
156impl Drop for Server {
157	fn drop(&mut self) {
158		// This doesn't not wait for the server to be stopped but fires the signal.
159		let _ = self.handle.stop();
160
161		// Use `shutdown_background()` to avoid blocking, which would panic if
162		// we are being dropped from within an async context.
163		if let Some(runtime) = self.rpc_runtime.take() {
164			runtime.shutdown_background();
165		}
166	}
167}
168
169/// Trait for providing subscription IDs that can be cloned.
170pub trait SubscriptionIdProvider:
171	jsonrpsee::core::traits::IdProvider + dyn_clone::DynClone
172{
173}
174
175dyn_clone::clone_trait_object!(SubscriptionIdProvider);
176
177/// RPC server configuration.
178pub struct Config<M: Send + Sync + 'static> {
179	/// RPC interfaces to start.
180	pub endpoints: Vec<RpcEndpoint>,
181	/// Metrics.
182	pub metrics: Option<RpcMetrics>,
183	/// RPC API module.
184	pub rpc_api: RpcModule<M>,
185	/// Subscription ID provider.
186	pub id_provider: Option<Box<dyn SubscriptionIdProvider>>,
187	/// RPC logger capacity (default: 1024).
188	pub request_logger_limit: u32,
189	/// Dedicated RPC runtime.
190	pub rpc_runtime: tokio::runtime::Runtime,
191}
192
193#[derive(Debug, Clone)]
194struct PerConnection {
195	methods: Methods,
196	stop_handle: StopHandle,
197	metrics: Option<RpcMetrics>,
198	tokio_handle: tokio::runtime::Handle,
199}
200
201/// Start RPC server listening on given address.
202pub async fn start_server<M>(config: Config<M>) -> Result<Server, Box<dyn StdError + Send + Sync>>
203where
204	M: Send + Sync,
205{
206	let Config { endpoints, metrics, rpc_api, id_provider, request_logger_limit, rpc_runtime } =
207		config;
208
209	let rpc_handle = rpc_runtime.handle().clone();
210
211	let (stop_handle, server_handle) = stop_channel();
212	let rpc_api = build_rpc_api(rpc_api);
213	// Bound the metrics `method` label to the registered methods so its
214	// cardinality stays finite (unknown names collapse to `"unknown"`).
215	let metrics = metrics.map(|m| {
216		let known: Vec<&'static str> = rpc_api.method_names().collect();
217		m.with_known_methods(known)
218	});
219	let cfg = PerConnection {
220		methods: rpc_api.into(),
221		metrics,
222		tokio_handle: rpc_handle.clone(),
223		stop_handle,
224	};
225
226	let mut local_addrs = Vec::new();
227
228	for endpoint in endpoints {
229		let allowed_to_fail = endpoint.is_optional;
230		let local_addr = endpoint.listen_addr;
231
232		let mut listener = match endpoint.bind().await {
233			Ok(l) => l,
234			Err(e) if allowed_to_fail => {
235				log::debug!(target: "rpc", "JSON-RPC server failed to bind optional address: {:?}, error: {:?}", local_addr, e);
236				continue;
237			},
238			Err(e) => return Err(e),
239		};
240		let local_addr = listener.local_addr();
241		local_addrs.push(local_addr);
242		let cfg = cfg.clone();
243
244		let RpcSettings {
245			batch_config,
246			max_connections,
247			max_payload_in_mb,
248			max_payload_out_mb,
249			max_buffer_capacity_per_connection,
250			max_subscriptions_per_connection,
251			rpc_methods,
252			rate_limit_trust_proxy_headers,
253			rate_limit_whitelisted_ips,
254			host_filter,
255			cors,
256			rate_limit,
257		} = listener.rpc_settings();
258
259		let http_middleware = tower::ServiceBuilder::new()
260			.option_layer(host_filter)
261			// Proxy `GET /health, /health/readiness` requests to the internal
262			// `system_health` method.
263			.layer(NodeHealthProxyLayer::default())
264			.layer(cors);
265
266		let mut builder = jsonrpsee::server::Server::builder()
267			.max_request_body_size(max_payload_in_mb.saturating_mul(MEGABYTE))
268			.max_response_body_size(max_payload_out_mb.saturating_mul(MEGABYTE))
269			.max_connections(max_connections)
270			.max_subscriptions_per_connection(max_subscriptions_per_connection)
271			.enable_ws_ping(
272				PingConfig::new()
273					.ping_interval(Duration::from_secs(30))
274					.inactive_limit(Duration::from_secs(60))
275					.max_failures(3),
276			)
277			.set_http_middleware(http_middleware)
278			.set_message_buffer_capacity(max_buffer_capacity_per_connection)
279			.set_batch_request_config(batch_config)
280			.custom_tokio_runtime(rpc_handle.clone());
281
282		if let Some(provider) = id_provider.clone() {
283			builder = builder.set_id_provider(provider);
284		} else {
285			builder = builder.set_id_provider(RandomStringIdProvider::new(16));
286		};
287
288		let service_builder = builder.to_service_builder();
289		let deny_unsafe = deny_unsafe(&local_addr, &rpc_methods);
290
291		rpc_handle.spawn(async move {
292			loop {
293				let (sock, remote_addr) = tokio::select! {
294					res = listener.accept() => {
295						match res {
296							Ok(s) => s,
297							Err(e) => {
298								log::debug!(target: "rpc", "Failed to accept connection: {:?}", e);
299								continue;
300							}
301						}
302					}
303					_ = cfg.stop_handle.clone().shutdown() => break,
304				};
305
306				let ip = remote_addr.ip();
307				let cfg2 = cfg.clone();
308				let service_builder2 = service_builder.clone();
309				let rate_limit_whitelisted_ips2 = rate_limit_whitelisted_ips.clone();
310
311				let svc =
312					tower::service_fn(move |mut req: http::Request<hyper::body::Incoming>| {
313						req.extensions_mut().insert(deny_unsafe);
314
315						let PerConnection { methods, metrics, tokio_handle, stop_handle } =
316							cfg2.clone();
317						let service_builder = service_builder2.clone();
318
319						let proxy_ip =
320							if rate_limit_trust_proxy_headers { get_proxy_ip(&req) } else { None };
321
322						let rate_limit_cfg = if rate_limit_whitelisted_ips2
323							.iter()
324							.any(|ips| ips.contains(proxy_ip.unwrap_or(ip)))
325						{
326							log::debug!(target: "rpc", "ip={ip}, proxy_ip={:?} is trusted, disabling rate-limit", proxy_ip);
327							None
328						} else {
329							if !rate_limit_whitelisted_ips2.is_empty() {
330								log::debug!(target: "rpc", "ip={ip}, proxy_ip={:?} is not trusted, rate-limit enabled", proxy_ip);
331							}
332							rate_limit
333						};
334
335						let is_websocket = ws::is_upgrade_request(&req);
336						let transport_label = if is_websocket { "ws" } else { "http" };
337
338						let middleware_layer = match (metrics, rate_limit_cfg) {
339							(None, None) => None,
340							(Some(metrics), None) => Some(
341								MiddlewareLayer::new()
342									.with_metrics(Metrics::new(metrics, transport_label)),
343							),
344							(None, Some(rate_limit)) =>
345								Some(MiddlewareLayer::new().with_rate_limit_per_minute(rate_limit)),
346							(Some(metrics), Some(rate_limit)) => Some(
347								MiddlewareLayer::new()
348									.with_metrics(Metrics::new(metrics, transport_label))
349									.with_rate_limit_per_minute(rate_limit),
350							),
351						};
352
353						let rpc_middleware = RpcServiceBuilder::new()
354							.rpc_logger(request_logger_limit)
355							.option_layer(middleware_layer.clone());
356						let mut svc = service_builder
357							.set_rpc_middleware(rpc_middleware)
358							.build(methods, stop_handle);
359
360						async move {
361							if is_websocket {
362								let on_disconnect = svc.on_session_closed();
363
364								// Spawn a task to handle when the connection is closed.
365								tokio_handle.spawn(async move {
366									let now = std::time::Instant::now();
367									middleware_layer.as_ref().map(|m| m.ws_connect());
368									on_disconnect.await;
369									middleware_layer.as_ref().map(|m| m.ws_disconnect(now));
370								});
371							}
372
373							// https://github.com/rust-lang/rust/issues/102211 the error type can't be inferred
374							// to be `Box<dyn std::error::Error + Send + Sync>` so we need to
375							// convert it to a concrete type as workaround.
376							svc.call(req).await.map_err(|e| BoxError::from(e))
377						}
378					});
379
380				cfg.tokio_handle.spawn(serve_with_graceful_shutdown(
381					sock,
382					svc,
383					cfg.stop_handle.clone().shutdown(),
384				));
385			}
386		});
387	}
388
389	if local_addrs.is_empty() {
390		return Err(Box::new(ListenAddrError));
391	}
392
393	// The previous logging format was before
394	// `Running JSON-RPC server: addr=127.0.0.1:9944, allowed origins=["*"]`
395	//
396	// The new format is `Running JSON-RPC server: addr=<addr1, addr2, .. addr_n>`
397	// with the exception that for a single address it will be `Running JSON-RPC server: addr=addr,`
398	// with a trailing comma.
399	//
400	// This is to make it work with old scripts/utils that parse the logs.
401	log::info!("Running JSON-RPC server: addr={}", format_listen_addrs(&local_addrs));
402
403	Ok(Server::new(server_handle, local_addrs, rpc_runtime))
404}