Skip to main content

soil_cli/params/
rpc_params.rs

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