sc_cli/commands/
run_cmd.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	error::{Error, Result},
21	params::{
22		ImportParams, KeystoreParams, NetworkParams, OffchainWorkerParams, RpcEndpoint,
23		SharedParams, TransactionPoolParams,
24	},
25	CliConfiguration, PrometheusParams, RpcParams, RuntimeParams, TelemetryParams,
26};
27use clap::Parser;
28use regex::Regex;
29use sc_service::{
30	config::{
31		BasePath, IpNetwork, PrometheusConfig, RpcBatchRequestConfig, TransactionPoolOptions,
32	},
33	ChainSpec, Role,
34};
35use sc_telemetry::TelemetryEndpoints;
36use std::num::NonZeroU32;
37
38/// The `run` command used to run a node.
39#[derive(Debug, Clone, Parser)]
40pub struct RunCmd {
41	/// Enable validator mode.
42	///
43	/// The node will be started with the authority role and actively
44	/// participate in any consensus task that it can (e.g. depending on
45	/// availability of local keys).
46	#[arg(long)]
47	pub validator: bool,
48
49	/// Disable GRANDPA.
50	///
51	/// Disables voter when running in validator mode, otherwise disable the GRANDPA
52	/// observer.
53	#[arg(long)]
54	pub no_grandpa: bool,
55
56	/// The human-readable name for this node.
57	///
58	/// It's used as network node name.
59	#[arg(long, value_name = "NAME")]
60	pub name: Option<String>,
61
62	#[allow(missing_docs)]
63	#[clap(flatten)]
64	pub rpc_params: RpcParams,
65
66	#[allow(missing_docs)]
67	#[clap(flatten)]
68	pub telemetry_params: TelemetryParams,
69
70	#[allow(missing_docs)]
71	#[clap(flatten)]
72	pub prometheus_params: PrometheusParams,
73
74	#[allow(missing_docs)]
75	#[clap(flatten)]
76	pub runtime_params: RuntimeParams,
77
78	#[allow(missing_docs)]
79	#[clap(flatten)]
80	pub offchain_worker_params: OffchainWorkerParams,
81
82	#[allow(missing_docs)]
83	#[clap(flatten)]
84	pub shared_params: SharedParams,
85
86	#[allow(missing_docs)]
87	#[clap(flatten)]
88	pub import_params: ImportParams,
89
90	#[allow(missing_docs)]
91	#[clap(flatten)]
92	pub network_params: NetworkParams,
93
94	#[allow(missing_docs)]
95	#[clap(flatten)]
96	pub pool_config: TransactionPoolParams,
97
98	#[allow(missing_docs)]
99	#[clap(flatten)]
100	pub keystore_params: KeystoreParams,
101
102	/// Shortcut for `--name Alice --validator`.
103	///
104	/// Session keys for `Alice` are added to keystore.
105	#[arg(long, conflicts_with_all = &["bob", "charlie", "dave", "eve", "ferdie", "one", "two"])]
106	pub alice: bool,
107
108	/// Shortcut for `--name Bob --validator`.
109	///
110	/// Session keys for `Bob` are added to keystore.
111	#[arg(long, conflicts_with_all = &["alice", "charlie", "dave", "eve", "ferdie", "one", "two"])]
112	pub bob: bool,
113
114	/// Shortcut for `--name Charlie --validator`.
115	///
116	/// Session keys for `Charlie` are added to keystore.
117	#[arg(long, conflicts_with_all = &["alice", "bob", "dave", "eve", "ferdie", "one", "two"])]
118	pub charlie: bool,
119
120	/// Shortcut for `--name Dave --validator`.
121	///
122	/// Session keys for `Dave` are added to keystore.
123	#[arg(long, conflicts_with_all = &["alice", "bob", "charlie", "eve", "ferdie", "one", "two"])]
124	pub dave: bool,
125
126	/// Shortcut for `--name Eve --validator`.
127	///
128	/// Session keys for `Eve` are added to keystore.
129	#[arg(long, conflicts_with_all = &["alice", "bob", "charlie", "dave", "ferdie", "one", "two"])]
130	pub eve: bool,
131
132	/// Shortcut for `--name Ferdie --validator`.
133	///
134	/// Session keys for `Ferdie` are added to keystore.
135	#[arg(long, conflicts_with_all = &["alice", "bob", "charlie", "dave", "eve", "one", "two"])]
136	pub ferdie: bool,
137
138	/// Shortcut for `--name One --validator`.
139	///
140	/// Session keys for `One` are added to keystore.
141	#[arg(long, conflicts_with_all = &["alice", "bob", "charlie", "dave", "eve", "ferdie", "two"])]
142	pub one: bool,
143
144	/// Shortcut for `--name Two --validator`.
145	///
146	/// Session keys for `Two` are added to keystore.
147	#[arg(long, conflicts_with_all = &["alice", "bob", "charlie", "dave", "eve", "ferdie", "one"])]
148	pub two: bool,
149
150	/// Enable authoring even when offline.
151	#[arg(long)]
152	pub force_authoring: bool,
153
154	/// Run a temporary node.
155	///
156	/// A temporary directory will be created to store the configuration and will be deleted
157	/// at the end of the process.
158	///
159	/// Note: the directory is random per process execution. This directory is used as base path
160	/// which includes: database, node key and keystore.
161	///
162	/// When `--dev` is given and no explicit `--base-path`, this option is implied.
163	#[arg(long, conflicts_with = "base_path")]
164	pub tmp: bool,
165}
166
167impl RunCmd {
168	/// Get the `Sr25519Keyring` matching one of the flag.
169	pub fn get_keyring(&self) -> Option<sp_keyring::Sr25519Keyring> {
170		use sp_keyring::Sr25519Keyring::*;
171
172		if self.alice {
173			Some(Alice)
174		} else if self.bob {
175			Some(Bob)
176		} else if self.charlie {
177			Some(Charlie)
178		} else if self.dave {
179			Some(Dave)
180		} else if self.eve {
181			Some(Eve)
182		} else if self.ferdie {
183			Some(Ferdie)
184		} else if self.one {
185			Some(One)
186		} else if self.two {
187			Some(Two)
188		} else {
189			None
190		}
191	}
192}
193
194impl CliConfiguration for RunCmd {
195	fn shared_params(&self) -> &SharedParams {
196		&self.shared_params
197	}
198
199	fn import_params(&self) -> Option<&ImportParams> {
200		Some(&self.import_params)
201	}
202
203	fn network_params(&self) -> Option<&NetworkParams> {
204		let network_params = &self.network_params;
205		let is_authority = self.role(self.is_dev().ok()?).ok()?.is_authority();
206		if is_authority && network_params.public_addr.is_empty() {
207			eprintln!(
208				"WARNING: No public address specified, validator node may not be reachable.
209				Consider setting `--public-addr` to the public IP address of this node.
210				This will become a hard requirement in future versions."
211			);
212		}
213
214		Some(network_params)
215	}
216
217	fn keystore_params(&self) -> Option<&KeystoreParams> {
218		Some(&self.keystore_params)
219	}
220
221	fn offchain_worker_params(&self) -> Option<&OffchainWorkerParams> {
222		Some(&self.offchain_worker_params)
223	}
224
225	fn node_name(&self) -> Result<String> {
226		let name: String = match (self.name.as_ref(), self.get_keyring()) {
227			(Some(name), _) => name.to_string(),
228			(_, Some(keyring)) => keyring.to_string(),
229			(None, None) => crate::generate_node_name(),
230		};
231
232		is_node_name_valid(&name).map_err(|msg| {
233			Error::Input(format!(
234				"Invalid node name '{}'. Reason: {}. If unsure, use none.",
235				name, msg
236			))
237		})?;
238
239		Ok(name)
240	}
241
242	fn dev_key_seed(&self, is_dev: bool) -> Result<Option<String>> {
243		Ok(self.get_keyring().map(|a| format!("//{}", a)).or_else(|| {
244			if is_dev {
245				Some("//Alice".into())
246			} else {
247				None
248			}
249		}))
250	}
251
252	fn telemetry_endpoints(
253		&self,
254		chain_spec: &Box<dyn ChainSpec>,
255	) -> Result<Option<TelemetryEndpoints>> {
256		let params = &self.telemetry_params;
257		Ok(if params.no_telemetry {
258			None
259		} else if !params.telemetry_endpoints.is_empty() {
260			Some(
261				TelemetryEndpoints::new(params.telemetry_endpoints.clone())
262					.map_err(|e| e.to_string())?,
263			)
264		} else {
265			chain_spec.telemetry_endpoints().clone()
266		})
267	}
268
269	fn role(&self, is_dev: bool) -> Result<Role> {
270		let keyring = self.get_keyring();
271		let is_authority = self.validator || is_dev || keyring.is_some();
272
273		Ok(if is_authority { Role::Authority } else { Role::Full })
274	}
275
276	fn force_authoring(&self) -> Result<bool> {
277		// Imply forced authoring on --dev
278		Ok(self.shared_params.dev || self.force_authoring)
279	}
280
281	fn prometheus_config(
282		&self,
283		default_listen_port: u16,
284		chain_spec: &Box<dyn ChainSpec>,
285	) -> Result<Option<PrometheusConfig>> {
286		Ok(self
287			.prometheus_params
288			.prometheus_config(default_listen_port, chain_spec.id().to_string()))
289	}
290
291	fn disable_grandpa(&self) -> Result<bool> {
292		Ok(self.no_grandpa)
293	}
294
295	fn rpc_max_connections(&self) -> Result<u32> {
296		Ok(self.rpc_params.rpc_max_connections)
297	}
298
299	fn rpc_cors(&self, is_dev: bool) -> Result<Option<Vec<String>>> {
300		self.rpc_params.rpc_cors(is_dev)
301	}
302
303	fn rpc_addr(&self, default_listen_port: u16) -> Result<Option<Vec<RpcEndpoint>>> {
304		self.rpc_params.rpc_addr(self.is_dev()?, self.validator, default_listen_port)
305	}
306
307	fn rpc_methods(&self) -> Result<sc_service::config::RpcMethods> {
308		Ok(self.rpc_params.rpc_methods.into())
309	}
310
311	fn rpc_max_request_size(&self) -> Result<u32> {
312		Ok(self.rpc_params.rpc_max_request_size)
313	}
314
315	fn rpc_max_response_size(&self) -> Result<u32> {
316		Ok(self.rpc_params.rpc_max_response_size)
317	}
318
319	fn rpc_max_subscriptions_per_connection(&self) -> Result<u32> {
320		Ok(self.rpc_params.rpc_max_subscriptions_per_connection)
321	}
322
323	fn rpc_buffer_capacity_per_connection(&self) -> Result<u32> {
324		Ok(self.rpc_params.rpc_message_buffer_capacity_per_connection)
325	}
326
327	fn rpc_batch_config(&self) -> Result<RpcBatchRequestConfig> {
328		self.rpc_params.rpc_batch_config()
329	}
330
331	fn rpc_rate_limit(&self) -> Result<Option<NonZeroU32>> {
332		Ok(self.rpc_params.rpc_rate_limit)
333	}
334
335	fn rpc_rate_limit_whitelisted_ips(&self) -> Result<Vec<IpNetwork>> {
336		Ok(self.rpc_params.rpc_rate_limit_whitelisted_ips.clone())
337	}
338
339	fn rpc_rate_limit_trust_proxy_headers(&self) -> Result<bool> {
340		Ok(self.rpc_params.rpc_rate_limit_trust_proxy_headers)
341	}
342
343	fn transaction_pool(&self, is_dev: bool) -> Result<TransactionPoolOptions> {
344		Ok(self.pool_config.transaction_pool(is_dev))
345	}
346
347	fn max_runtime_instances(&self) -> Result<Option<usize>> {
348		Ok(Some(self.runtime_params.max_runtime_instances))
349	}
350
351	fn runtime_cache_size(&self) -> Result<u8> {
352		Ok(self.runtime_params.runtime_cache_size)
353	}
354
355	fn base_path(&self) -> Result<Option<BasePath>> {
356		Ok(if self.tmp {
357			Some(BasePath::new_temp_dir()?)
358		} else {
359			match self.shared_params().base_path()? {
360				Some(r) => Some(r),
361				// If `dev` is enabled, we use the temp base path.
362				None if self.shared_params().is_dev() => Some(BasePath::new_temp_dir()?),
363				None => None,
364			}
365		})
366	}
367}
368
369/// Check whether a node name is considered as valid.
370pub fn is_node_name_valid(_name: &str) -> std::result::Result<(), &str> {
371	let name = _name.to_string();
372
373	if name.is_empty() {
374		return Err("Node name cannot be empty");
375	}
376
377	if name.chars().count() >= crate::NODE_NAME_MAX_LENGTH {
378		return Err("Node name too long");
379	}
380
381	let invalid_chars = r"[\\.@]";
382	let re = Regex::new(invalid_chars).unwrap();
383	if re.is_match(&name) {
384		return Err("Node name should not contain invalid chars such as '.' and '@'");
385	}
386
387	let invalid_patterns = r"^https?:";
388	let re = Regex::new(invalid_patterns).unwrap();
389	if re.is_match(&name) {
390		return Err("Node name should not contain urls");
391	}
392
393	Ok(())
394}
395
396#[cfg(test)]
397mod tests {
398	use super::*;
399
400	#[test]
401	fn tests_node_name_good() {
402		assert!(is_node_name_valid("short name").is_ok());
403		assert!(is_node_name_valid("www").is_ok());
404		assert!(is_node_name_valid("aawww").is_ok());
405		assert!(is_node_name_valid("wwwaa").is_ok());
406		assert!(is_node_name_valid("www aa").is_ok());
407	}
408
409	#[test]
410	fn tests_node_name_bad() {
411		assert!(is_node_name_valid("").is_err());
412		assert!(is_node_name_valid(
413			"very very long names are really not very cool for the ui at all, really they're not"
414		)
415		.is_err());
416		assert!(is_node_name_valid("Dots.not.Ok").is_err());
417		// NOTE: the urls below don't include a domain otherwise
418		// they'd get filtered for including a `.`
419		assert!(is_node_name_valid("http://visitme").is_err());
420		assert!(is_node_name_valid("http:/visitme").is_err());
421		assert!(is_node_name_valid("http:visitme").is_err());
422		assert!(is_node_name_valid("https://visitme").is_err());
423		assert!(is_node_name_valid("https:/visitme").is_err());
424		assert!(is_node_name_valid("https:visitme").is_err());
425		assert!(is_node_name_valid("www.visit.me").is_err());
426		assert!(is_node_name_valid("www.visit").is_err());
427		assert!(is_node_name_valid("hello\\world").is_err());
428		assert!(is_node_name_valid("visit.www").is_err());
429		assert!(is_node_name_valid("email@domain").is_err());
430	}
431}