Skip to main content

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