1use 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#[derive(Debug, Clone, Args)]
37pub struct RpcParams {
38 #[arg(long)]
47 pub rpc_external: bool,
48
49 #[arg(long)]
53 pub unsafe_rpc_external: bool,
54
55 #[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 #[arg(long)]
73 pub rpc_rate_limit: Option<NonZeroU32>,
74
75 #[arg(long, num_args = 1..)]
79 pub rpc_rate_limit_whitelisted_ips: Vec<IpNetwork>,
80
81 #[arg(long)]
89 pub rpc_rate_limit_trust_proxy_headers: bool,
90
91 #[arg(long, default_value_t = RPC_DEFAULT_MAX_REQUEST_SIZE_MB)]
93 pub rpc_max_request_size: u32,
94
95 #[arg(long, default_value_t = RPC_DEFAULT_MAX_RESPONSE_SIZE_MB)]
97 pub rpc_max_response_size: u32,
98
99 #[arg(long, default_value_t = RPC_DEFAULT_MAX_SUBS_PER_CONN)]
101 pub rpc_max_subscriptions_per_connection: u32,
102
103 #[arg(long, value_name = "PORT")]
105 pub rpc_port: Option<u16>,
106
107 #[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 #[arg(long, value_name = "COUNT", default_value_t = RPC_DEFAULT_MAX_CONNECTIONS)]
150 pub rpc_max_connections: u32,
151
152 #[arg(long, default_value_t = RPC_DEFAULT_MESSAGE_CAPACITY_PER_CONN)]
161 pub rpc_message_buffer_capacity_per_connection: u32,
162
163 #[arg(long, alias = "rpc_no_batch_requests", conflicts_with_all = &["rpc_max_batch_request_len"])]
165 pub rpc_disable_batch_requests: bool,
166
167 #[arg(long, conflicts_with_all = &["rpc_disable_batch_requests"], value_name = "LEN")]
169 pub rpc_max_batch_request_len: Option<u32>,
170
171 #[arg(long, value_name = "ORIGINS")]
178 pub rpc_cors: Option<Cors>,
179}
180
181impl RpcParams {
182 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 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 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 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#[derive(Debug, Clone)]
319pub struct RpcEndpoint {
320 pub listen_addr: SocketAddr,
322 pub batch_config: RpcBatchRequestConfig,
324 pub max_connections: u32,
326 pub max_payload_in_mb: u32,
328 pub max_payload_out_mb: u32,
330 pub max_subscriptions_per_connection: u32,
332 pub max_buffer_capacity_per_connection: u32,
334 pub rate_limit: Option<NonZeroU32>,
336 pub rate_limit_trust_proxy_headers: bool,
338 pub rate_limit_whitelisted_ips: Vec<IpNetwork>,
340 pub cors: Option<Vec<String>>,
342 pub rpc_methods: RpcMethods,
344 pub is_optional: bool,
348 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 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}