referrerpolicy=no-referrer-when-downgrade

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