polkadot_cli/
command.rs

1// Copyright (C) Parity Technologies (UK) Ltd.
2// This file is part of Polkadot.
3
4// Polkadot is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// Polkadot is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with Polkadot.  If not, see <http://www.gnu.org/licenses/>.
16
17use crate::cli::{Cli, Subcommand, NODE_VERSION};
18use frame_benchmarking_cli::{
19	BenchmarkCmd, ExtrinsicFactory, SubstrateRemarkBuilder, SUBSTRATE_REFERENCE_HARDWARE,
20};
21use futures::future::TryFutureExt;
22use log::{info, warn};
23use polkadot_service::{
24	self,
25	benchmarking::{benchmark_inherent_data, TransferKeepAliveBuilder},
26	HeaderBackend, IdentifyVariant,
27};
28#[cfg(feature = "pyroscope")]
29use pyroscope_pprofrs::{pprof_backend, PprofConfig};
30use sc_cli::SubstrateCli;
31use sc_network_types::PeerId;
32use sp_core::crypto::Ss58AddressFormatRegistry;
33use sp_keyring::Sr25519Keyring;
34
35pub use crate::error::Error;
36#[cfg(feature = "pyroscope")]
37use std::net::ToSocketAddrs;
38use std::{collections::HashSet, time::Duration};
39
40type Result<T> = std::result::Result<T, Error>;
41
42fn get_exec_name() -> Option<String> {
43	std::env::current_exe()
44		.ok()
45		.and_then(|pb| pb.file_name().map(|s| s.to_os_string()))
46		.and_then(|s| s.into_string().ok())
47}
48
49fn get_invulnerable_ah_collators(
50	chain_spec: &Box<dyn polkadot_service::ChainSpec>,
51) -> HashSet<PeerId> {
52	// A default set of invulnerable asset hub collators
53	const KUSAMA: [&str; 11] = [
54		"12D3KooWHNEENyCc4R3iDLLFaJiynUp9eDZp7TtS1G6DCp459vVK",
55		"12D3KooWAVqLdQEjSezy7CPEgMLMSTuyfSBdbxPGkmik5x2aL8u4",
56		"12D3KooWBxMiVQdYa5MaQjSWAu3YsfKdrs7vgX9cPk4cCwFVAXEu",
57		"12D3KooWGbRmQ9FjwkzTVTSxfUh854wxc3LUD5agjzcucDarZrNn",
58		"12D3KooWHwXftCGdp73t4BUxW3c9UKjYTvjc7tHsrinT5M8AUmXo",
59		"12D3KooWCTSAq83D99RcT64rrV5X3sGZxc9JQ8nVtd6GbZEKnDqC",
60		"12D3KooWF63ZxKtZMYs5247WQA8fcTiGJb2osXykc31cmjwNLwem",
61		"12D3KooWGowDwrXAh9cxkbPHPHuwMouFHrMcJhCVXcFS2B8vc5Ry",
62		"12D3KooWRhoxXsZypnp1Tady6XSRqXfxu7Bj6hGk8aj6FJ1iU6pt",
63		"12D3KooWJUs11H7S3Hv9BVh72w3yVmHoYTXaoBUg1KQyYk4hL2bB",
64		"12D3KooWAeLjabo2foz6gAQvLRfwF2d3WnpUGDjhg8V5AQUnv5AZ",
65	];
66
67	const POLKADOT: [&str; 7] = [
68		"12D3KooWEyGg3oUwYfaLWM5AJ2pvXCUxBuXNapX1tQXLsbDmMV6z",
69		"12D3KooWD9dTKLW65NFFLVjqgaXNzb3zKXBfwRS5iovxV6XaoVX6",
70		"12D3KooWPJfGGisRMkiD5yhySZggEhyMSwELb34P2bEuAmUh9RYy",
71		"12D3KooWQB9RBoJEByMtXtD8aC1WR1DJQb3QMXRcsQmNxrghsQLv",
72		"12D3KooWFhBYG98e53DQB7W2JKBL9xWrP83ANkAjzvp4enEJAt3k",
73		"12D3KooWG3GrM6XKMM4gp3cvemdwUvu96ziYoJmqmetLZBXE8bSa",
74		"12D3KooWMRyTLrCEPcAQD6c4EnudL3vVzg9zji3whvsMYPUYevpq",
75	];
76
77	let invulnerables = if chain_spec.is_kusama() {
78		KUSAMA.to_vec()
79	} else if chain_spec.is_polkadot() {
80		POLKADOT.to_vec()
81	} else {
82		vec![]
83	};
84
85	invulnerables
86			.iter()
87			.filter_map(|invuln_str| {
88				invuln_str
89					.parse::<PeerId>()
90					.map_err(|e| {
91						warn!("Failed to parse AssetHub invulnerable peer from the default list. This should never happen. {:?}", e)
92					})
93					.ok()
94			})
95			.collect()
96}
97
98impl SubstrateCli for Cli {
99	fn impl_name() -> String {
100		"Parity Polkadot".into()
101	}
102
103	fn impl_version() -> String {
104		let commit_hash = env!("SUBSTRATE_CLI_COMMIT_HASH");
105		format!("{}-{commit_hash}", NODE_VERSION)
106	}
107
108	fn description() -> String {
109		env!("CARGO_PKG_DESCRIPTION").into()
110	}
111
112	fn author() -> String {
113		env!("CARGO_PKG_AUTHORS").into()
114	}
115
116	fn support_url() -> String {
117		"https://github.com/paritytech/polkadot-sdk/issues/new".into()
118	}
119
120	fn copyright_start_year() -> i32 {
121		2017
122	}
123
124	fn executable_name() -> String {
125		"polkadot".into()
126	}
127
128	fn load_spec(&self, id: &str) -> std::result::Result<Box<dyn sc_service::ChainSpec>, String> {
129		let id = if id == "" {
130			let n = get_exec_name().unwrap_or_default();
131			["polkadot", "kusama", "westend", "rococo", "versi"]
132				.iter()
133				.cloned()
134				.find(|&chain| n.starts_with(chain))
135				.unwrap_or("polkadot")
136		} else {
137			id
138		};
139		Ok(match id {
140			"kusama" => Box::new(polkadot_service::chain_spec::kusama_config()?),
141			name if name.starts_with("kusama-") && !name.ends_with(".json") =>
142				Err(format!("`{name}` is not supported anymore as the kusama native runtime no longer part of the node."))?,
143			"polkadot" => Box::new(polkadot_service::chain_spec::polkadot_config()?),
144			name if name.starts_with("polkadot-") && !name.ends_with(".json") =>
145				Err(format!("`{name}` is not supported anymore as the polkadot native runtime no longer part of the node."))?,
146			"paseo" => Box::new(polkadot_service::chain_spec::paseo_config()?),
147			"rococo" => Box::new(polkadot_service::chain_spec::rococo_config()?),
148			#[cfg(feature = "rococo-native")]
149			"dev" | "rococo-dev" => Box::new(polkadot_service::chain_spec::rococo_development_config()?),
150			#[cfg(feature = "rococo-native")]
151			"rococo-local" => Box::new(polkadot_service::chain_spec::rococo_local_testnet_config()?),
152			#[cfg(feature = "rococo-native")]
153			"rococo-staging" => Box::new(polkadot_service::chain_spec::rococo_staging_testnet_config()?),
154			#[cfg(not(feature = "rococo-native"))]
155			name if name.starts_with("rococo-") && !name.ends_with(".json") || name == "dev" =>
156				Err(format!("`{}` only supported with `rococo-native` feature enabled.", name))?,
157			"westend" => Box::new(polkadot_service::chain_spec::westend_config()?),
158			#[cfg(feature = "westend-native")]
159			"westend-dev" => Box::new(polkadot_service::chain_spec::westend_development_config()?),
160			#[cfg(feature = "westend-native")]
161			"westend-local" => Box::new(polkadot_service::chain_spec::westend_local_testnet_config()?),
162			#[cfg(feature = "westend-native")]
163			"westend-staging" => Box::new(polkadot_service::chain_spec::westend_staging_testnet_config()?),
164			#[cfg(feature = "rococo-native")]
165			"versi-dev" => Box::new(polkadot_service::chain_spec::versi_development_config()?),
166			#[cfg(feature = "rococo-native")]
167			"versi-local" => Box::new(polkadot_service::chain_spec::versi_local_testnet_config()?),
168			#[cfg(feature = "rococo-native")]
169			"versi-staging" => Box::new(polkadot_service::chain_spec::versi_staging_testnet_config()?),
170			#[cfg(not(feature = "rococo-native"))]
171			name if name.starts_with("versi-") =>
172				Err(format!("`{}` only supported with `rococo-native` feature enabled.", name))?,
173			path => {
174				let path = std::path::PathBuf::from(path);
175
176				let chain_spec = Box::new(polkadot_service::GenericChainSpec::from_json_file(path.clone())?)
177					as Box<dyn polkadot_service::ChainSpec>;
178
179				// When `force_*` is given or the file name starts with the name of one of the known
180				// chains, we use the chain spec for the specific chain.
181				if self.run.force_rococo ||
182					chain_spec.is_rococo() ||
183					chain_spec.is_versi()
184				{
185					Box::new(polkadot_service::RococoChainSpec::from_json_file(path)?)
186				} else if self.run.force_kusama || chain_spec.is_kusama() {
187					Box::new(polkadot_service::GenericChainSpec::from_json_file(path)?)
188				} else if self.run.force_westend || chain_spec.is_westend() {
189					Box::new(polkadot_service::WestendChainSpec::from_json_file(path)?)
190				} else {
191					chain_spec
192				}
193			},
194		})
195	}
196}
197
198fn set_default_ss58_version(spec: &Box<dyn polkadot_service::ChainSpec>) {
199	let ss58_version = if spec.is_kusama() {
200		Ss58AddressFormatRegistry::KusamaAccount
201	} else if spec.is_westend() {
202		Ss58AddressFormatRegistry::SubstrateAccount
203	} else {
204		Ss58AddressFormatRegistry::PolkadotAccount
205	}
206	.into();
207
208	sp_core::crypto::set_default_ss58_version(ss58_version);
209}
210
211/// Launch a node, accepting arguments just like a regular node,
212/// accepts an alternative overseer generator, to adjust behavior
213/// for integration tests as needed.
214/// `malus_finality_delay` restrict finality votes of this node
215/// to be at most `best_block - malus_finality_delay` height.
216#[cfg(feature = "malus")]
217pub fn run_node(
218	run: Cli,
219	overseer_gen: impl polkadot_service::OverseerGen,
220	malus_finality_delay: Option<u32>,
221) -> Result<()> {
222	run_node_inner(run, overseer_gen, malus_finality_delay, |_logger_builder, _config| {})
223}
224
225fn run_node_inner<F>(
226	cli: Cli,
227	overseer_gen: impl polkadot_service::OverseerGen,
228	maybe_malus_finality_delay: Option<u32>,
229	logger_hook: F,
230) -> Result<()>
231where
232	F: FnOnce(&mut sc_cli::LoggerBuilder, &sc_service::Configuration),
233{
234	let runner = cli
235		.create_runner_with_logger_hook::<_, _, F>(&cli.run.base, logger_hook)
236		.map_err(Error::from)?;
237	let chain_spec = &runner.config().chain_spec;
238
239	// By default, enable BEEFY on all networks, unless explicitly disabled through CLI.
240	let enable_beefy = !cli.run.no_beefy;
241
242	set_default_ss58_version(chain_spec);
243
244	if chain_spec.is_kusama() {
245		info!("----------------------------");
246		info!("This chain is not in any way");
247		info!("      endorsed by the       ");
248		info!("     KUSAMA FOUNDATION      ");
249		info!("----------------------------");
250	}
251
252	let node_version =
253		if cli.run.disable_worker_version_check { None } else { Some(NODE_VERSION.to_string()) };
254
255	let secure_validator_mode = cli.run.base.validator && !cli.run.insecure_validator;
256
257	// Parse collator protocol hold off value and get the list of the invlunerable collators.
258	let collator_protocol_hold_off = cli.run.collator_protocol_hold_off.map(Duration::from_millis);
259	let invulnerable_ah_collators = get_invulnerable_ah_collators(&chain_spec);
260
261	runner.run_node_until_exit(move |config| async move {
262		let hwbench = (!cli.run.no_hardware_benchmarks)
263			.then(|| {
264				config.database.path().map(|database_path| {
265					let _ = std::fs::create_dir_all(&database_path);
266					sc_sysinfo::gather_hwbench(Some(database_path), &SUBSTRATE_REFERENCE_HARDWARE)
267				})
268			})
269			.flatten();
270
271		let database_source = config.database.clone();
272		let task_manager = polkadot_service::build_full(
273			config,
274			polkadot_service::NewFullParams {
275				is_parachain_node: polkadot_service::IsParachainNode::No,
276				enable_beefy,
277				force_authoring_backoff: cli.run.force_authoring_backoff,
278				telemetry_worker_handle: None,
279				node_version,
280				secure_validator_mode,
281				workers_path: cli.run.workers_path,
282				workers_names: None,
283				overseer_gen,
284				overseer_message_channel_capacity_override: cli
285					.run
286					.overseer_channel_capacity_override,
287				malus_finality_delay: maybe_malus_finality_delay,
288				hwbench,
289				execute_workers_max_num: cli.run.execute_workers_max_num,
290				prepare_workers_hard_max_num: cli.run.prepare_workers_hard_max_num,
291				prepare_workers_soft_max_num: cli.run.prepare_workers_soft_max_num,
292				keep_finalized_for: cli.run.keep_finalized_for,
293				invulnerable_ah_collators,
294				collator_protocol_hold_off,
295			},
296		)
297		.map(|full| full.task_manager)?;
298
299		if let Some(path) = database_source.path() {
300			sc_storage_monitor::StorageMonitorService::try_spawn(
301				cli.storage_monitor,
302				path.to_path_buf(),
303				&task_manager.spawn_essential_handle(),
304			)?;
305		}
306
307		Ok(task_manager)
308	})
309}
310
311/// Parses polkadot specific CLI arguments and run the service.
312pub fn run() -> Result<()> {
313	let cli: Cli = Cli::from_args();
314
315	#[cfg(feature = "pyroscope")]
316	let mut pyroscope_agent_maybe = if let Some(ref agent_addr) = cli.run.pyroscope_server {
317		let address = agent_addr
318			.to_socket_addrs()
319			.map_err(Error::AddressResolutionFailure)?
320			.next()
321			.ok_or_else(|| Error::AddressResolutionMissing)?;
322		// The pyroscope agent requires a `http://` prefix, so we just do that.
323		let agent = pyroscope::PyroscopeAgent::builder(
324			"http://".to_owned() + address.to_string().as_str(),
325			"polkadot".to_owned(),
326		)
327		.backend(pprof_backend(PprofConfig::new().sample_rate(113)))
328		.build()?;
329		Some(agent.start()?)
330	} else {
331		None
332	};
333
334	#[cfg(not(feature = "pyroscope"))]
335	if cli.run.pyroscope_server.is_some() {
336		return Err(Error::PyroscopeNotCompiledIn)
337	}
338
339	match &cli.subcommand {
340		None => run_node_inner(
341			cli,
342			polkadot_service::ValidatorOverseerGen,
343			None,
344			polkadot_node_metrics::logger_hook(),
345		),
346		#[allow(deprecated)]
347		Some(Subcommand::BuildSpec(cmd)) => {
348			let runner = cli.create_runner(cmd)?;
349			Ok(runner.sync_run(|config| cmd.run(config.chain_spec, config.network))?)
350		},
351		Some(Subcommand::ExportChainSpec(cmd)) => {
352			// Directly load the embedded chain spec using the CLI’s load_spec method.
353			let spec = cli.load_spec(&cmd.chain)?;
354			cmd.run(spec).map_err(Into::into)
355		},
356		Some(Subcommand::CheckBlock(cmd)) => {
357			let runner = cli.create_runner(cmd).map_err(Error::SubstrateCli)?;
358			let chain_spec = &runner.config().chain_spec;
359
360			set_default_ss58_version(chain_spec);
361
362			runner.async_run(|mut config| {
363				let (client, _, import_queue, task_manager) =
364					polkadot_service::new_chain_ops(&mut config)?;
365				Ok((cmd.run(client, import_queue).map_err(Error::SubstrateCli), task_manager))
366			})
367		},
368		Some(Subcommand::ExportBlocks(cmd)) => {
369			let runner = cli.create_runner(cmd)?;
370			let chain_spec = &runner.config().chain_spec;
371
372			set_default_ss58_version(chain_spec);
373
374			Ok(runner.async_run(|mut config| {
375				let (client, _, _, task_manager) =
376					polkadot_service::new_chain_ops(&mut config).map_err(Error::PolkadotService)?;
377				Ok((cmd.run(client, config.database).map_err(Error::SubstrateCli), task_manager))
378			})?)
379		},
380		Some(Subcommand::ExportState(cmd)) => {
381			let runner = cli.create_runner(cmd)?;
382			let chain_spec = &runner.config().chain_spec;
383
384			set_default_ss58_version(chain_spec);
385
386			Ok(runner.async_run(|mut config| {
387				let (client, _, _, task_manager) = polkadot_service::new_chain_ops(&mut config)?;
388				Ok((cmd.run(client, config.chain_spec).map_err(Error::SubstrateCli), task_manager))
389			})?)
390		},
391		Some(Subcommand::ImportBlocks(cmd)) => {
392			let runner = cli.create_runner(cmd)?;
393			let chain_spec = &runner.config().chain_spec;
394
395			set_default_ss58_version(chain_spec);
396
397			Ok(runner.async_run(|mut config| {
398				let (client, _, import_queue, task_manager) =
399					polkadot_service::new_chain_ops(&mut config)?;
400				Ok((cmd.run(client, import_queue).map_err(Error::SubstrateCli), task_manager))
401			})?)
402		},
403		Some(Subcommand::PurgeChain(cmd)) => {
404			let runner = cli.create_runner(cmd)?;
405			Ok(runner.sync_run(|config| cmd.run(config.database))?)
406		},
407		Some(Subcommand::Revert(cmd)) => {
408			let runner = cli.create_runner(cmd)?;
409			let chain_spec = &runner.config().chain_spec;
410
411			set_default_ss58_version(chain_spec);
412
413			Ok(runner.async_run(|mut config| {
414				let (client, backend, _, task_manager) =
415					polkadot_service::new_chain_ops(&mut config)?;
416				let task_handle = task_manager.spawn_handle();
417				let aux_revert = Box::new(|client, backend, blocks| {
418					polkadot_service::revert_backend(client, backend, blocks, config, task_handle)
419						.map_err(|err| {
420							match err {
421								polkadot_service::Error::Blockchain(err) => err.into(),
422								// Generic application-specific error.
423								err => sc_cli::Error::Application(err.into()),
424							}
425						})
426				});
427				Ok((
428					cmd.run(client, backend, Some(aux_revert)).map_err(Error::SubstrateCli),
429					task_manager,
430				))
431			})?)
432		},
433		Some(Subcommand::Benchmark(cmd)) => {
434			let runner = cli.create_runner(cmd)?;
435			let chain_spec = &runner.config().chain_spec;
436
437			match cmd {
438				#[cfg(not(feature = "runtime-benchmarks"))]
439				BenchmarkCmd::Storage(_) =>
440					return Err(sc_cli::Error::Input(
441						"Compile with --features=runtime-benchmarks \
442						to enable storage benchmarks."
443							.into(),
444					)
445					.into()),
446				#[cfg(feature = "runtime-benchmarks")]
447				BenchmarkCmd::Storage(cmd) => runner.sync_run(|mut config| {
448					let (client, backend, _, _) = polkadot_service::new_chain_ops(&mut config)?;
449					let db = backend.expose_db();
450					let storage = backend.expose_storage();
451					let shared_trie_cache = backend.expose_shared_trie_cache();
452
453					cmd.run(config, client.clone(), db, storage, shared_trie_cache).map_err(Error::SubstrateCli)
454				}),
455				BenchmarkCmd::Block(cmd) => runner.sync_run(|mut config| {
456					let (client, _, _, _) = polkadot_service::new_chain_ops(&mut config)?;
457
458					cmd.run(client.clone()).map_err(Error::SubstrateCli)
459				}),
460				BenchmarkCmd::Overhead(cmd) => runner.sync_run(|config| {
461					if cmd.params.runtime.is_some() {
462						return Err(sc_cli::Error::Input(
463							"Polkadot binary does not support `--runtime` flag for `benchmark overhead`. Please provide a chain spec or use the `frame-omni-bencher`."
464								.into(),
465						)
466						.into())
467					}
468
469					cmd.run_with_default_builder_and_spec::<polkadot_service::Block, ()>(
470						Some(config.chain_spec),
471					)
472					.map_err(Error::SubstrateCli)
473				}),
474				BenchmarkCmd::Extrinsic(cmd) => runner.sync_run(|mut config| {
475					let (client, _, _, _) = polkadot_service::new_chain_ops(&mut config)?;
476					let header = client.header(client.info().genesis_hash).unwrap().unwrap();
477					let inherent_data = benchmark_inherent_data(header)
478						.map_err(|e| format!("generating inherent data: {:?}", e))?;
479
480					let remark_builder = SubstrateRemarkBuilder::new_from_client(client.clone())?;
481
482					let tka_builder = TransferKeepAliveBuilder::new(
483						client.clone(),
484						Sr25519Keyring::Alice.to_account_id(),
485						config.chain_spec.identify_chain(),
486					);
487
488					let ext_factory =
489						ExtrinsicFactory(vec![Box::new(remark_builder), Box::new(tka_builder)]);
490
491					cmd.run(client.clone(), inherent_data, Vec::new(), &ext_factory)
492						.map_err(Error::SubstrateCli)
493				}),
494				BenchmarkCmd::Pallet(cmd) => {
495					set_default_ss58_version(chain_spec);
496
497					if cfg!(feature = "runtime-benchmarks") {
498						runner.sync_run(|config| {
499							cmd.run_with_spec::<sp_runtime::traits::HashingFor<polkadot_service::Block>, ()>(
500								Some(config.chain_spec),
501							)
502							.map_err(|e| Error::SubstrateCli(e))
503						})
504					} else {
505						Err(sc_cli::Error::Input(
506							"Benchmarking wasn't enabled when building the node. \
507				You can enable it with `--features runtime-benchmarks`."
508								.into(),
509						)
510						.into())
511					}
512				},
513				BenchmarkCmd::Machine(cmd) => runner.sync_run(|config| {
514					cmd.run(&config, SUBSTRATE_REFERENCE_HARDWARE.clone())
515						.map_err(Error::SubstrateCli)
516				}),
517				// NOTE: this allows the Polkadot client to leniently implement
518				// new benchmark commands.
519				#[allow(unreachable_patterns)]
520				_ => Err(Error::CommandNotImplemented),
521			}
522		},
523		Some(Subcommand::Key(cmd)) => Ok(cmd.run(&cli)?),
524		Some(Subcommand::ChainInfo(cmd)) => {
525			let runner = cli.create_runner(cmd)?;
526			Ok(runner.sync_run(|config| cmd.run::<polkadot_service::Block>(&config))?)
527		},
528	}?;
529
530	#[cfg(feature = "pyroscope")]
531	if let Some(pyroscope_agent) = pyroscope_agent_maybe.take() {
532		let agent = pyroscope_agent.stop()?;
533		agent.shutdown();
534	}
535	Ok(())
536}