referrerpolicy=no-referrer-when-downgrade

sc_cli/params/
rpc_params.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
19use crate::{
20	arg_enums::{Cors, RpcMethods},
21	params::{IpNetwork, RpcBatchRequestConfig},
22	RPC_DEFAULT_MAX_CONNECTIONS, RPC_DEFAULT_MAX_REQUEST_SIZE_MB, RPC_DEFAULT_MAX_RESPONSE_SIZE_MB,
23	RPC_DEFAULT_MAX_SUBS_PER_CONN, RPC_DEFAULT_MESSAGE_CAPACITY_PER_CONN,
24};
25use clap::Args;
26use std::{
27	net::{Ipv4Addr, Ipv6Addr, SocketAddr},
28	num::NonZeroU32,
29};
30
31const RPC_LISTEN_ADDR: &str = "listen-addr";
32const RPC_CORS: &str = "cors";
33const RPC_MAX_CONNS: &str = "max-connections";
34const RPC_MAX_REQUEST_SIZE: &str = "max-request-size";
35const RPC_MAX_RESPONSE_SIZE: &str = "max-response-size";
36const RPC_MAX_SUBS_PER_CONN: &str = "max-subscriptions-per-connection";
37const RPC_MAX_BUF_CAP_PER_CONN: &str = "max-buffer-capacity-per-connection";
38const RPC_RATE_LIMIT: &str = "rate-limit";
39const RPC_RATE_LIMIT_TRUST_PROXY_HEADERS: &str = "rate-limit-trust-proxy-headers";
40const RPC_RATE_LIMIT_WHITELISTED_IPS: &str = "rate-limit-whitelisted-ips";
41const RPC_RETRY_RANDOM_PORT: &str = "retry-random-port";
42const RPC_METHODS: &str = "methods";
43const RPC_OPTIONAL: &str = "optional";
44const RPC_DISABLE_BATCH: &str = "disable-batch-requests";
45const RPC_BATCH_LIMIT: &str = "max-batch-request-len";
46
47/// Parameters of RPC.
48#[derive(Debug, Clone, Args)]
49pub struct RpcParams {
50	/// Listen to all RPC interfaces (default: local).
51	///
52	/// Not all RPC methods are safe to be exposed publicly.
53	///
54	/// Use an RPC proxy server to filter out dangerous methods. More details:
55	/// <https://docs.substrate.io/build/remote-procedure-calls/#public-rpc-interfaces>.
56	///
57	/// Use `--unsafe-rpc-external` to suppress the warning if you understand the risks.
58	#[arg(long)]
59	pub rpc_external: bool,
60
61	/// Listen to all RPC interfaces.
62	///
63	/// Same as `--rpc-external`.
64	#[arg(long)]
65	pub unsafe_rpc_external: bool,
66
67	/// RPC methods to expose.
68	#[arg(
69		long,
70		value_name = "METHOD SET",
71		value_enum,
72		ignore_case = true,
73		default_value_t = RpcMethods::Auto,
74		verbatim_doc_comment
75	)]
76	pub rpc_methods: RpcMethods,
77
78	/// RPC rate limiting (calls/minute) for each connection.
79	///
80	/// This is disabled by default.
81	///
82	/// For example `--rpc-rate-limit 10` will maximum allow
83	/// 10 calls per minute per connection.
84	#[arg(long)]
85	pub rpc_rate_limit: Option<NonZeroU32>,
86
87	/// Disable RPC rate limiting for certain ip addresses.
88	///
89	/// Each IP address must be in CIDR notation such as `1.2.3.4/24`.
90	#[arg(long, num_args = 1..)]
91	pub rpc_rate_limit_whitelisted_ips: Vec<IpNetwork>,
92
93	/// Trust proxy headers for disable rate limiting.
94	///
95	/// By default the rpc server will not trust headers such `X-Real-IP`, `X-Forwarded-For` and
96	/// `Forwarded` and this option will make the rpc server to trust these headers.
97	///
98	/// For instance this may be secure if the rpc server is behind a reverse proxy and that the
99	/// proxy always sets these headers.
100	#[arg(long)]
101	pub rpc_rate_limit_trust_proxy_headers: bool,
102
103	/// Set the maximum RPC request payload size for both HTTP and WS in megabytes.
104	#[arg(long, default_value_t = RPC_DEFAULT_MAX_REQUEST_SIZE_MB)]
105	pub rpc_max_request_size: u32,
106
107	/// Set the maximum RPC response payload size for both HTTP and WS in megabytes.
108	#[arg(long, default_value_t = RPC_DEFAULT_MAX_RESPONSE_SIZE_MB)]
109	pub rpc_max_response_size: u32,
110
111	/// Set the maximum concurrent subscriptions per connection.
112	#[arg(long, default_value_t = RPC_DEFAULT_MAX_SUBS_PER_CONN)]
113	pub rpc_max_subscriptions_per_connection: u32,
114
115	/// Specify JSON-RPC server TCP port.
116	#[arg(long, value_name = "PORT")]
117	pub rpc_port: Option<u16>,
118
119	/// EXPERIMENTAL: Specify the JSON-RPC server interface and this option which can be enabled
120	/// several times if you want expose several RPC interfaces with different configurations.
121	///
122	/// The format for this option is:
123	/// `--experimental-rpc-endpoint" listen-addr=<ip:port>,<key=value>,..."` where each option is
124	/// separated by a comma and `listen-addr` is the only required param.
125	///
126	/// The following options are available:
127	///  • listen-addr: The socket address (ip:port) to listen on. Be careful to not expose the
128	///    server to the public internet unless you know what you're doing. (required)
129	///  • disable-batch-requests: Disable batch requests (optional)
130	///  • max-connections: The maximum number of concurrent connections that the server will
131	///    accept (optional)
132	///  • max-request-size: The maximum size of a request body in megabytes (optional)
133	///  • max-response-size: The maximum size of a response body in megabytes (optional)
134	///  • max-subscriptions-per-connection: The maximum number of subscriptions per connection
135	///    (optional)
136	///  • max-buffer-capacity-per-connection: The maximum buffer capacity per connection
137	///    (optional)
138	///  • max-batch-request-len: The maximum number of requests in a batch (optional)
139	///  • cors: The CORS allowed origins, this can enabled more than once (optional)
140	///  • methods: Which RPC methods to allow, valid values are "safe", "unsafe" and "auto"
141	///    (optional)
142	///  • optional: If the listen address is optional i.e the interface is not required to be
143	///    available For example this may be useful if some platforms doesn't support ipv6
144	///    (optional)
145	///  • rate-limit: The rate limit in calls per minute for each connection (optional)
146	///  • rate-limit-trust-proxy-headers: Trust proxy headers for disable rate limiting (optional)
147	///  • rate-limit-whitelisted-ips: Disable rate limiting for certain ip addresses, this can be
148	/// enabled more than once (optional)  • retry-random-port: If the port is already in use,
149	/// retry with a random port (optional)
150	///
151	/// Use with care, this flag is unstable and subject to change.
152	#[arg(
153		long,
154		num_args = 1..,
155		verbatim_doc_comment,
156		conflicts_with_all = &["rpc_external", "unsafe_rpc_external", "rpc_port", "rpc_cors", "rpc_rate_limit_trust_proxy_headers", "rpc_rate_limit", "rpc_rate_limit_whitelisted_ips", "rpc_message_buffer_capacity_per_connection", "rpc_disable_batch_requests", "rpc_max_subscriptions_per_connection", "rpc_max_request_size", "rpc_max_response_size"]
157	)]
158	pub experimental_rpc_endpoint: Vec<RpcEndpoint>,
159
160	/// Maximum number of RPC server connections.
161	#[arg(long, value_name = "COUNT", default_value_t = RPC_DEFAULT_MAX_CONNECTIONS)]
162	pub rpc_max_connections: u32,
163
164	/// The number of messages the RPC server is allowed to keep in memory.
165	///
166	/// If the buffer becomes full then the server will not process
167	/// new messages until the connected client start reading the
168	/// underlying messages.
169	///
170	/// This applies per connection which includes both
171	/// JSON-RPC methods calls and subscriptions.
172	#[arg(long, default_value_t = RPC_DEFAULT_MESSAGE_CAPACITY_PER_CONN)]
173	pub rpc_message_buffer_capacity_per_connection: u32,
174
175	/// Disable RPC batch requests
176	#[arg(long, alias = "rpc_no_batch_requests", conflicts_with_all = &["rpc_max_batch_request_len"])]
177	pub rpc_disable_batch_requests: bool,
178
179	/// Limit the max length per RPC batch request
180	#[arg(long, conflicts_with_all = &["rpc_disable_batch_requests"], value_name = "LEN")]
181	pub rpc_max_batch_request_len: Option<u32>,
182
183	/// Specify browser *origins* allowed to access the HTTP & WS RPC servers.
184	///
185	/// A comma-separated list of origins (protocol://domain or special `null`
186	/// value). Value of `all` will disable origin validation. Default is to
187	/// allow localhost and <https://polkadot.js.org> origins. When running in
188	/// `--dev` mode the default is to allow all origins.
189	#[arg(long, value_name = "ORIGINS")]
190	pub rpc_cors: Option<Cors>,
191}
192
193impl RpcParams {
194	/// Returns the RPC CORS configuration.
195	pub fn rpc_cors(&self, is_dev: bool) -> crate::Result<Option<Vec<String>>> {
196		Ok(self
197			.rpc_cors
198			.clone()
199			.unwrap_or_else(|| {
200				if is_dev {
201					log::warn!("Running in --dev mode, RPC CORS has been disabled.");
202					Cors::All
203				} else {
204					Cors::List(vec![
205						"http://localhost:*".into(),
206						"http://127.0.0.1:*".into(),
207						"https://localhost:*".into(),
208						"https://127.0.0.1:*".into(),
209						"https://polkadot.js.org".into(),
210					])
211				}
212			})
213			.into())
214	}
215
216	/// Returns the RPC endpoints.
217	pub fn rpc_addr(
218		&self,
219		is_dev: bool,
220		is_validator: bool,
221		default_listen_port: u16,
222	) -> crate::Result<Option<Vec<RpcEndpoint>>> {
223		if !self.experimental_rpc_endpoint.is_empty() {
224			for endpoint in &self.experimental_rpc_endpoint {
225				// Technically, `0.0.0.0` isn't a public IP address, but it's a way to listen on
226				// all interfaces. Thus, we consider it as a public endpoint and warn about it.
227				if endpoint.rpc_methods == RpcMethods::Unsafe && endpoint.is_global() ||
228					endpoint.listen_addr.ip().is_unspecified()
229				{
230					eprintln!(
231						"It isn't safe to expose RPC publicly without a proxy server that filters \
232						 available set of RPC methods."
233					);
234				}
235			}
236
237			return Ok(Some(self.experimental_rpc_endpoint.clone()));
238		}
239
240		let (ipv4, ipv6) = rpc_interface(
241			self.rpc_external,
242			self.unsafe_rpc_external,
243			self.rpc_methods,
244			is_validator,
245		)?;
246
247		let cors = self.rpc_cors(is_dev)?;
248		let port = self.rpc_port.unwrap_or(default_listen_port);
249
250		Ok(Some(vec![
251			RpcEndpoint {
252				batch_config: self.rpc_batch_config()?,
253				max_connections: self.rpc_max_connections,
254				listen_addr: SocketAddr::new(std::net::IpAddr::V4(ipv4), port),
255				rpc_methods: self.rpc_methods,
256				rate_limit: self.rpc_rate_limit,
257				rate_limit_trust_proxy_headers: self.rpc_rate_limit_trust_proxy_headers,
258				rate_limit_whitelisted_ips: self.rpc_rate_limit_whitelisted_ips.clone(),
259				max_payload_in_mb: self.rpc_max_request_size,
260				max_payload_out_mb: self.rpc_max_response_size,
261				max_subscriptions_per_connection: self.rpc_max_subscriptions_per_connection,
262				max_buffer_capacity_per_connection: self.rpc_message_buffer_capacity_per_connection,
263				cors: cors.clone(),
264				retry_random_port: true,
265				is_optional: false,
266			},
267			RpcEndpoint {
268				batch_config: self.rpc_batch_config()?,
269				max_connections: self.rpc_max_connections,
270				listen_addr: SocketAddr::new(std::net::IpAddr::V6(ipv6), port),
271				rpc_methods: self.rpc_methods,
272				rate_limit: self.rpc_rate_limit,
273				rate_limit_trust_proxy_headers: self.rpc_rate_limit_trust_proxy_headers,
274				rate_limit_whitelisted_ips: self.rpc_rate_limit_whitelisted_ips.clone(),
275				max_payload_in_mb: self.rpc_max_request_size,
276				max_payload_out_mb: self.rpc_max_response_size,
277				max_subscriptions_per_connection: self.rpc_max_subscriptions_per_connection,
278				max_buffer_capacity_per_connection: self.rpc_message_buffer_capacity_per_connection,
279				cors: cors.clone(),
280				retry_random_port: true,
281				is_optional: true,
282			},
283		]))
284	}
285
286	/// Returns the configuration for batch RPC requests.
287	pub fn rpc_batch_config(&self) -> crate::Result<RpcBatchRequestConfig> {
288		let cfg = if self.rpc_disable_batch_requests {
289			RpcBatchRequestConfig::Disabled
290		} else if let Some(l) = self.rpc_max_batch_request_len {
291			RpcBatchRequestConfig::Limit(l)
292		} else {
293			RpcBatchRequestConfig::Unlimited
294		};
295
296		Ok(cfg)
297	}
298}
299
300fn rpc_interface(
301	is_external: bool,
302	is_unsafe_external: bool,
303	rpc_methods: RpcMethods,
304	is_validator: bool,
305) -> crate::Result<(Ipv4Addr, Ipv6Addr)> {
306	if is_external && is_validator && rpc_methods != RpcMethods::Unsafe {
307		return Err(crate::Error::Input(
308			"--rpc-external option shouldn't be used if the node is running as \
309			 a validator. Use `--unsafe-rpc-external` or `--rpc-methods=unsafe` if you understand \
310			 the risks. See the options description for more information."
311				.to_owned(),
312		));
313	}
314
315	if is_external || is_unsafe_external {
316		if rpc_methods == RpcMethods::Unsafe {
317			eprintln!(
318				"It isn't safe to expose RPC publicly without a proxy server that filters \
319				 available set of RPC methods."
320			);
321		}
322
323		Ok((Ipv4Addr::UNSPECIFIED, Ipv6Addr::UNSPECIFIED))
324	} else {
325		Ok((Ipv4Addr::LOCALHOST, Ipv6Addr::LOCALHOST))
326	}
327}
328
329/// Represent a single RPC endpoint with its configuration.
330#[derive(Debug, Clone)]
331pub struct RpcEndpoint {
332	/// Listen address.
333	pub listen_addr: SocketAddr,
334	/// Batch request configuration.
335	pub batch_config: RpcBatchRequestConfig,
336	/// Maximum number of connections.
337	pub max_connections: u32,
338	/// Maximum inbound payload size in MB.
339	pub max_payload_in_mb: u32,
340	/// Maximum outbound payload size in MB.
341	pub max_payload_out_mb: u32,
342	/// Maximum number of subscriptions per connection.
343	pub max_subscriptions_per_connection: u32,
344	/// Maximum buffer capacity per connection.
345	pub max_buffer_capacity_per_connection: u32,
346	/// Rate limit per minute.
347	pub rate_limit: Option<NonZeroU32>,
348	/// Whether to trust proxy headers for rate limiting.
349	pub rate_limit_trust_proxy_headers: bool,
350	/// Whitelisted IPs for rate limiting.
351	pub rate_limit_whitelisted_ips: Vec<IpNetwork>,
352	/// CORS.
353	pub cors: Option<Vec<String>>,
354	/// RPC methods to expose.
355	pub rpc_methods: RpcMethods,
356	/// Whether it's an optional listening address i.e, it's ignored if it fails to bind.
357	/// For example substrate tries to bind both ipv4 and ipv6 addresses but some platforms
358	/// may not support ipv6.
359	pub is_optional: bool,
360	/// Whether to retry with a random port if the provided port is already in use.
361	pub retry_random_port: bool,
362}
363
364impl std::str::FromStr for RpcEndpoint {
365	type Err = String;
366
367	fn from_str(s: &str) -> Result<Self, Self::Err> {
368		let mut listen_addr = None;
369		let mut max_connections = None;
370		let mut max_payload_in_mb = None;
371		let mut max_payload_out_mb = None;
372		let mut max_subscriptions_per_connection = None;
373		let mut max_buffer_capacity_per_connection = None;
374		let mut cors: Option<Vec<String>> = None;
375		let mut rpc_methods = None;
376		let mut is_optional = None;
377		let mut disable_batch_requests = None;
378		let mut max_batch_request_len = None;
379		let mut rate_limit = None;
380		let mut rate_limit_trust_proxy_headers = None;
381		let mut rate_limit_whitelisted_ips = Vec::new();
382		let mut retry_random_port = None;
383
384		for input in s.split(',') {
385			let (key, val) = input.trim().split_once('=').ok_or_else(|| invalid_input(input))?;
386			let key = key.trim();
387			let val = val.trim();
388
389			match key {
390				RPC_LISTEN_ADDR => {
391					if listen_addr.is_some() {
392						return Err(only_once_err(RPC_LISTEN_ADDR));
393					}
394					let val: SocketAddr =
395						val.parse().map_err(|_| invalid_value(RPC_LISTEN_ADDR, &val))?;
396					listen_addr = Some(val);
397				},
398				RPC_CORS => {
399					if val.is_empty() {
400						return Err(invalid_value(RPC_CORS, &val));
401					}
402
403					if let Some(cors) = cors.as_mut() {
404						cors.push(val.to_string());
405					} else {
406						cors = Some(vec![val.to_string()]);
407					}
408				},
409				RPC_MAX_CONNS => {
410					if max_connections.is_some() {
411						return Err(only_once_err(RPC_MAX_CONNS));
412					}
413
414					let val = val.parse().map_err(|_| invalid_value(RPC_MAX_CONNS, &val))?;
415					max_connections = Some(val);
416				},
417				RPC_MAX_REQUEST_SIZE => {
418					if max_payload_in_mb.is_some() {
419						return Err(only_once_err(RPC_MAX_REQUEST_SIZE));
420					}
421
422					let val =
423						val.parse().map_err(|_| invalid_value(RPC_MAX_RESPONSE_SIZE, &val))?;
424					max_payload_in_mb = Some(val);
425				},
426				RPC_MAX_RESPONSE_SIZE => {
427					if max_payload_out_mb.is_some() {
428						return Err(only_once_err(RPC_MAX_RESPONSE_SIZE));
429					}
430
431					let val =
432						val.parse().map_err(|_| invalid_value(RPC_MAX_RESPONSE_SIZE, &val))?;
433					max_payload_out_mb = Some(val);
434				},
435				RPC_MAX_SUBS_PER_CONN => {
436					if max_subscriptions_per_connection.is_some() {
437						return Err(only_once_err(RPC_MAX_SUBS_PER_CONN));
438					}
439
440					let val =
441						val.parse().map_err(|_| invalid_value(RPC_MAX_SUBS_PER_CONN, &val))?;
442					max_subscriptions_per_connection = Some(val);
443				},
444				RPC_MAX_BUF_CAP_PER_CONN => {
445					if max_buffer_capacity_per_connection.is_some() {
446						return Err(only_once_err(RPC_MAX_BUF_CAP_PER_CONN));
447					}
448
449					let val =
450						val.parse().map_err(|_| invalid_value(RPC_MAX_BUF_CAP_PER_CONN, &val))?;
451					max_buffer_capacity_per_connection = Some(val);
452				},
453				RPC_RATE_LIMIT => {
454					if rate_limit.is_some() {
455						return Err(only_once_err("rate-limit"));
456					}
457
458					let val = val.parse().map_err(|_| invalid_value(RPC_RATE_LIMIT, &val))?;
459					rate_limit = Some(val);
460				},
461				RPC_RATE_LIMIT_TRUST_PROXY_HEADERS => {
462					if rate_limit_trust_proxy_headers.is_some() {
463						return Err(only_once_err(RPC_RATE_LIMIT_TRUST_PROXY_HEADERS));
464					}
465
466					let val = val
467						.parse()
468						.map_err(|_| invalid_value(RPC_RATE_LIMIT_TRUST_PROXY_HEADERS, &val))?;
469					rate_limit_trust_proxy_headers = Some(val);
470				},
471				RPC_RATE_LIMIT_WHITELISTED_IPS => {
472					let ip: IpNetwork = val
473						.parse()
474						.map_err(|_| invalid_value(RPC_RATE_LIMIT_WHITELISTED_IPS, &val))?;
475					rate_limit_whitelisted_ips.push(ip);
476				},
477				RPC_RETRY_RANDOM_PORT => {
478					if retry_random_port.is_some() {
479						return Err(only_once_err(RPC_RETRY_RANDOM_PORT));
480					}
481					let val =
482						val.parse().map_err(|_| invalid_value(RPC_RETRY_RANDOM_PORT, &val))?;
483					retry_random_port = Some(val);
484				},
485				RPC_METHODS => {
486					if rpc_methods.is_some() {
487						return Err(only_once_err("methods"));
488					}
489					let val = val.parse().map_err(|_| invalid_value(RPC_METHODS, &val))?;
490					rpc_methods = Some(val);
491				},
492				RPC_OPTIONAL => {
493					if is_optional.is_some() {
494						return Err(only_once_err(RPC_OPTIONAL));
495					}
496
497					let val = val.parse().map_err(|_| invalid_value(RPC_OPTIONAL, &val))?;
498					is_optional = Some(val);
499				},
500				RPC_DISABLE_BATCH => {
501					if disable_batch_requests.is_some() {
502						return Err(only_once_err(RPC_DISABLE_BATCH));
503					}
504
505					let val = val.parse().map_err(|_| invalid_value(RPC_DISABLE_BATCH, &val))?;
506					disable_batch_requests = Some(val);
507				},
508				RPC_BATCH_LIMIT => {
509					if max_batch_request_len.is_some() {
510						return Err(only_once_err(RPC_BATCH_LIMIT));
511					}
512
513					let val = val.parse().map_err(|_| invalid_value(RPC_BATCH_LIMIT, &val))?;
514					max_batch_request_len = Some(val);
515				},
516				_ => return Err(invalid_key(key)),
517			}
518		}
519
520		let listen_addr = listen_addr.ok_or("`listen-addr` must be specified exactly once")?;
521
522		let batch_config = match (disable_batch_requests, max_batch_request_len) {
523			(Some(true), Some(_)) => {
524				return Err(format!("`{RPC_BATCH_LIMIT}` and `{RPC_DISABLE_BATCH}` are mutually exclusive and can't be used together"));
525			},
526			(Some(false), None) => RpcBatchRequestConfig::Disabled,
527			(None, Some(len)) => RpcBatchRequestConfig::Limit(len),
528			_ => RpcBatchRequestConfig::Unlimited,
529		};
530
531		Ok(Self {
532			listen_addr,
533			batch_config,
534			max_connections: max_connections.unwrap_or(RPC_DEFAULT_MAX_CONNECTIONS),
535			max_payload_in_mb: max_payload_in_mb.unwrap_or(RPC_DEFAULT_MAX_REQUEST_SIZE_MB),
536			max_payload_out_mb: max_payload_out_mb.unwrap_or(RPC_DEFAULT_MAX_RESPONSE_SIZE_MB),
537			cors,
538			max_buffer_capacity_per_connection: max_buffer_capacity_per_connection
539				.unwrap_or(RPC_DEFAULT_MESSAGE_CAPACITY_PER_CONN),
540			max_subscriptions_per_connection: max_subscriptions_per_connection
541				.unwrap_or(RPC_DEFAULT_MAX_SUBS_PER_CONN),
542			rpc_methods: rpc_methods.unwrap_or(RpcMethods::Auto),
543			rate_limit,
544			rate_limit_trust_proxy_headers: rate_limit_trust_proxy_headers.unwrap_or(false),
545			rate_limit_whitelisted_ips,
546			is_optional: is_optional.unwrap_or(false),
547			retry_random_port: retry_random_port.unwrap_or(false),
548		})
549	}
550}
551
552impl Into<sc_service::config::RpcEndpoint> for RpcEndpoint {
553	fn into(self) -> sc_service::config::RpcEndpoint {
554		sc_service::config::RpcEndpoint {
555			batch_config: self.batch_config,
556			listen_addr: self.listen_addr,
557			max_buffer_capacity_per_connection: self.max_buffer_capacity_per_connection,
558			max_connections: self.max_connections,
559			max_payload_in_mb: self.max_payload_in_mb,
560			max_payload_out_mb: self.max_payload_out_mb,
561			max_subscriptions_per_connection: self.max_subscriptions_per_connection,
562			rpc_methods: self.rpc_methods.into(),
563			rate_limit: self.rate_limit,
564			rate_limit_trust_proxy_headers: self.rate_limit_trust_proxy_headers,
565			rate_limit_whitelisted_ips: self.rate_limit_whitelisted_ips,
566			cors: self.cors,
567			retry_random_port: self.retry_random_port,
568			is_optional: self.is_optional,
569		}
570	}
571}
572
573impl RpcEndpoint {
574	/// Returns whether the endpoint is globally exposed.
575	pub fn is_global(&self) -> bool {
576		let ip = IpNetwork::from(self.listen_addr.ip());
577		ip.is_global()
578	}
579}
580
581fn only_once_err(reason: &str) -> String {
582	format!("`{reason}` is only allowed be specified once")
583}
584
585fn invalid_input(input: &str) -> String {
586	format!("`{input}`, expects: `key=value`")
587}
588
589fn invalid_value(key: &str, value: &str) -> String {
590	format!("value=`{value}` key=`{key}`")
591}
592
593fn invalid_key(key: &str) -> String {
594	format!("unknown key=`{key}`, see `--help` for available options")
595}
596
597#[cfg(test)]
598mod tests {
599	use super::*;
600	use std::{num::NonZeroU32, str::FromStr};
601
602	#[test]
603	fn parse_rpc_endpoint_works() {
604		assert!(RpcEndpoint::from_str("listen-addr=127.0.0.1:9944").is_ok());
605		assert!(RpcEndpoint::from_str("listen-addr=[::1]:9944").is_ok());
606		assert!(RpcEndpoint::from_str("listen-addr=127.0.0.1:9944,methods=auto").is_ok());
607		assert!(RpcEndpoint::from_str("listen-addr=[::1]:9944,methods=auto").is_ok());
608		assert!(RpcEndpoint::from_str(
609			"listen-addr=127.0.0.1:9944,methods=auto,cors=*,optional=true"
610		)
611		.is_ok());
612
613		assert!(RpcEndpoint::from_str("listen-addrs=127.0.0.1:9944,foo=*").is_err());
614		assert!(RpcEndpoint::from_str("listen-addrs=127.0.0.1:9944,cors=").is_err());
615	}
616
617	#[test]
618	fn parse_rpc_endpoint_all() {
619		let endpoint = RpcEndpoint::from_str(
620			"listen-addr=127.0.0.1:9944,methods=unsafe,cors=*,optional=true,retry-random-port=true,rate-limit=99,\
621			max-batch-request-len=100,rate-limit-trust-proxy-headers=true,max-connections=33,max-request-size=4,\
622			max-response-size=3,max-subscriptions-per-connection=7,max-buffer-capacity-per-connection=8,\
623			rate-limit-whitelisted-ips=192.168.1.0/24,rate-limit-whitelisted-ips=ff01::0/32"
624		).unwrap();
625		assert_eq!(endpoint.listen_addr, ([127, 0, 0, 1], 9944).into());
626		assert_eq!(endpoint.rpc_methods, RpcMethods::Unsafe);
627		assert_eq!(endpoint.cors, Some(vec!["*".to_string()]));
628		assert_eq!(endpoint.is_optional, true);
629		assert_eq!(endpoint.retry_random_port, true);
630		assert_eq!(endpoint.rate_limit, Some(NonZeroU32::new(99).unwrap()));
631		assert!(matches!(endpoint.batch_config, RpcBatchRequestConfig::Limit(l) if l == 100));
632		assert_eq!(endpoint.rate_limit_trust_proxy_headers, true);
633		assert_eq!(
634			endpoint.rate_limit_whitelisted_ips,
635			vec![
636				IpNetwork::V4("192.168.1.0/24".parse().unwrap()),
637				IpNetwork::V6("ff01::0/32".parse().unwrap())
638			]
639		);
640		assert_eq!(endpoint.max_connections, 33);
641		assert_eq!(endpoint.max_payload_in_mb, 4);
642		assert_eq!(endpoint.max_payload_out_mb, 3);
643		assert_eq!(endpoint.max_subscriptions_per_connection, 7);
644		assert_eq!(endpoint.max_buffer_capacity_per_connection, 8);
645	}
646
647	#[test]
648	fn parse_rpc_endpoint_multiple_cors() {
649		let addr = RpcEndpoint::from_str(
650			"listen-addr=127.0.0.1:9944,methods=auto,cors=https://polkadot.js.org,cors=*,cors=localhost:*",
651		)
652		.unwrap();
653
654		assert_eq!(
655			addr.cors,
656			Some(vec![
657				"https://polkadot.js.org".to_string(),
658				"*".to_string(),
659				"localhost:*".to_string()
660			])
661		);
662	}
663
664	#[test]
665	fn parse_rpc_endpoint_whitespaces() {
666		let addr = RpcEndpoint::from_str(
667			"   listen-addr = 127.0.0.1:9944,       methods    =   auto,  optional    =     true   ",
668		)
669		.unwrap();
670		assert_eq!(addr.rpc_methods, RpcMethods::Auto);
671		assert_eq!(addr.is_optional, true);
672	}
673
674	#[test]
675	fn parse_rpc_endpoint_batch_options_mutually_exclusive() {
676		assert!(RpcEndpoint::from_str(
677			"listen-addr = 127.0.0.1:9944,disable-batch-requests=true,max-batch-request-len=100",
678		)
679		.is_err());
680	}
681}