referrerpolicy=no-referrer-when-downgrade

cumulus_client_cli/
lib.rs

1// Copyright (C) Parity Technologies (UK) Ltd.
2// This file is part of Cumulus.
3// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
4
5// Cumulus is free software: you can redistribute it and/or modify
6// it under the terms of the GNU General Public License as published by
7// the Free Software Foundation, either version 3 of the License, or
8// (at your option) any later version.
9
10// Cumulus is distributed in the hope that it will be useful,
11// but WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13// GNU General Public License for more details.
14
15// You should have received a copy of the GNU General Public License
16// along with Cumulus. If not, see <https://www.gnu.org/licenses/>.
17
18//! Cumulus CLI library.
19
20#![warn(missing_docs)]
21
22use std::{
23	fs,
24	io::{self, Write},
25	path::PathBuf,
26	sync::Arc,
27};
28
29use codec::Encode;
30use sc_chain_spec::ChainSpec;
31use sc_cli::RpcEndpoint;
32use sc_client_api::HeaderBackend;
33use sc_service::{
34	config::{PrometheusConfig, RpcBatchRequestConfig, TelemetryEndpoints},
35	BasePath, TransactionPoolOptions,
36};
37use sp_core::hexdisplay::HexDisplay;
38use sp_runtime::traits::{Block as BlockT, Zero};
39use url::Url;
40
41/// The `purge-chain` command used to remove the whole chain: the parachain and the relay chain.
42#[derive(Debug, clap::Parser)]
43#[group(skip)]
44pub struct PurgeChainCmd {
45	/// The base struct of the purge-chain command.
46	#[command(flatten)]
47	pub base: sc_cli::PurgeChainCmd,
48
49	/// Only delete the para chain database
50	#[arg(long, aliases = &["para"])]
51	pub parachain: bool,
52
53	/// Only delete the relay chain database
54	#[arg(long, aliases = &["relay"])]
55	pub relaychain: bool,
56}
57
58impl PurgeChainCmd {
59	/// Run the purge command
60	pub fn run(
61		&self,
62		para_config: sc_service::Configuration,
63		relay_config: sc_service::Configuration,
64	) -> sc_cli::Result<()> {
65		let databases = match (self.parachain, self.relaychain) {
66			(true, true) | (false, false) => {
67				vec![("parachain", para_config.database), ("relaychain", relay_config.database)]
68			},
69			(true, false) => vec![("parachain", para_config.database)],
70			(false, true) => vec![("relaychain", relay_config.database)],
71		};
72
73		let db_paths = databases
74			.iter()
75			.map(|(chain_label, database)| {
76				database.path().ok_or_else(|| {
77					sc_cli::Error::Input(format!(
78						"Cannot purge custom database implementation of: {}",
79						chain_label,
80					))
81				})
82			})
83			.collect::<sc_cli::Result<Vec<_>>>()?;
84
85		if !self.base.yes {
86			for db_path in &db_paths {
87				println!("{}", db_path.display());
88			}
89			print!("Are you sure to remove? [y/N]: ");
90			io::stdout().flush().expect("failed to flush stdout");
91
92			let mut input = String::new();
93			io::stdin().read_line(&mut input)?;
94			let input = input.trim();
95
96			match input.chars().next() {
97				Some('y') | Some('Y') => {},
98				_ => {
99					println!("Aborted");
100					return Ok(());
101				},
102			}
103		}
104
105		for db_path in &db_paths {
106			match fs::remove_dir_all(db_path) {
107				Ok(_) => {
108					println!("{:?} removed.", &db_path);
109				},
110				Err(ref err) if err.kind() == io::ErrorKind::NotFound => {
111					eprintln!("{:?} did not exist.", &db_path);
112				},
113				Err(err) => return Err(err.into()),
114			}
115		}
116
117		Ok(())
118	}
119}
120
121impl sc_cli::CliConfiguration for PurgeChainCmd {
122	fn shared_params(&self) -> &sc_cli::SharedParams {
123		&self.base.shared_params
124	}
125
126	fn database_params(&self) -> Option<&sc_cli::DatabaseParams> {
127		Some(&self.base.database_params)
128	}
129}
130
131/// Get the SCALE encoded genesis header of the parachain.
132pub fn get_raw_genesis_header<B, C>(client: Arc<C>) -> sc_cli::Result<Vec<u8>>
133where
134	B: BlockT,
135	C: HeaderBackend<B> + 'static,
136{
137	let genesis_hash =
138		client
139			.hash(Zero::zero())?
140			.ok_or(sc_cli::Error::Client(sp_blockchain::Error::Backend(
141				"Failed to lookup genesis block hash when exporting genesis head data.".into(),
142			)))?;
143	let genesis_header = client.header(genesis_hash)?.ok_or(sc_cli::Error::Client(
144		sp_blockchain::Error::Backend(
145			"Failed to lookup genesis header by hash when exporting genesis head data.".into(),
146		),
147	))?;
148
149	Ok(genesis_header.encode())
150}
151
152/// Command for exporting the genesis head data of the parachain
153#[derive(Debug, clap::Parser)]
154pub struct ExportGenesisHeadCommand {
155	/// Output file name or stdout if unspecified.
156	#[arg()]
157	pub output: Option<PathBuf>,
158
159	/// Write output in binary. Default is to write in hex.
160	#[arg(short, long)]
161	pub raw: bool,
162
163	#[allow(missing_docs)]
164	#[command(flatten)]
165	pub shared_params: sc_cli::SharedParams,
166}
167
168impl ExportGenesisHeadCommand {
169	/// Run the export-genesis-head command
170	pub fn run<B, C>(&self, client: Arc<C>) -> sc_cli::Result<()>
171	where
172		B: BlockT,
173		C: HeaderBackend<B> + 'static,
174	{
175		let raw_header = get_raw_genesis_header(client)?;
176		let output_buf = if self.raw {
177			raw_header
178		} else {
179			format!("0x{:?}", HexDisplay::from(&raw_header)).into_bytes()
180		};
181
182		if let Some(output) = &self.output {
183			fs::write(output, output_buf)?;
184		} else {
185			io::stdout().write_all(&output_buf)?;
186		}
187
188		Ok(())
189	}
190}
191
192impl sc_cli::CliConfiguration for ExportGenesisHeadCommand {
193	fn shared_params(&self) -> &sc_cli::SharedParams {
194		&self.shared_params
195	}
196
197	fn base_path(&self) -> sc_cli::Result<Option<BasePath>> {
198		// As we are just exporting the genesis wasm a tmp database is enough.
199		//
200		// As otherwise we may "pollute" the global base path.
201		Ok(Some(BasePath::new_temp_dir()?))
202	}
203}
204
205/// Command for exporting the genesis wasm file.
206#[derive(Debug, clap::Parser)]
207pub struct ExportGenesisWasmCommand {
208	/// Output file name or stdout if unspecified.
209	#[arg()]
210	pub output: Option<PathBuf>,
211
212	/// Write output in binary. Default is to write in hex.
213	#[arg(short, long)]
214	pub raw: bool,
215
216	#[allow(missing_docs)]
217	#[command(flatten)]
218	pub shared_params: sc_cli::SharedParams,
219}
220
221impl ExportGenesisWasmCommand {
222	/// Run the export-genesis-wasm command
223	pub fn run(&self, chain_spec: &dyn ChainSpec) -> sc_cli::Result<()> {
224		let raw_wasm_blob = extract_genesis_wasm(chain_spec)?;
225		let output_buf = if self.raw {
226			raw_wasm_blob
227		} else {
228			format!("0x{:?}", HexDisplay::from(&raw_wasm_blob)).into_bytes()
229		};
230
231		if let Some(output) = &self.output {
232			fs::write(output, output_buf)?;
233		} else {
234			io::stdout().write_all(&output_buf)?;
235		}
236
237		Ok(())
238	}
239}
240
241/// Extract the genesis code from a given ChainSpec.
242pub fn extract_genesis_wasm(chain_spec: &dyn ChainSpec) -> sc_cli::Result<Vec<u8>> {
243	let mut storage = chain_spec.build_storage()?;
244	storage
245		.top
246		.remove(sp_core::storage::well_known_keys::CODE)
247		.ok_or_else(|| "Could not find wasm file in genesis state!".into())
248}
249
250impl sc_cli::CliConfiguration for ExportGenesisWasmCommand {
251	fn shared_params(&self) -> &sc_cli::SharedParams {
252		&self.shared_params
253	}
254
255	fn base_path(&self) -> sc_cli::Result<Option<BasePath>> {
256		// As we are just exporting the genesis wasm a tmp database is enough.
257		//
258		// As otherwise we may "pollute" the global base path.
259		Ok(Some(BasePath::new_temp_dir()?))
260	}
261}
262
263fn validate_relay_chain_url(arg: &str) -> Result<Url, String> {
264	let url = Url::parse(arg).map_err(|e| e.to_string())?;
265
266	let scheme = url.scheme();
267	if scheme == "ws" || scheme == "wss" {
268		Ok(url)
269	} else {
270		Err(format!(
271			"'{}' URL scheme not supported. Only websocket RPC is currently supported",
272			url.scheme()
273		))
274	}
275}
276
277/// The `run` command used to run a node.
278#[derive(Debug, clap::Parser)]
279#[group(skip)]
280pub struct RunCmd {
281	/// The cumulus RunCmd inherents from sc_cli's
282	#[command(flatten)]
283	pub base: sc_cli::RunCmd,
284
285	/// Run node as collator.
286	///
287	/// Note that this is the same as running with `--validator`.
288	#[arg(long, conflicts_with = "validator")]
289	pub collator: bool,
290
291	/// Creates a less resource-hungry node that retrieves relay chain data from an RPC endpoint.
292	///
293	/// The provided URLs should point to RPC endpoints of the relay chain and are used in order of
294	/// preference. The node connects to the first endpoint it can reach and, if that connection
295	/// fails, to the next one in the list. While on a later endpoint, it periodically checks the
296	/// earlier ones and switches back to the first that is reachable and has caught up.
297	///
298	/// Note: This option doesn't stop the node from connecting to the relay chain network but
299	/// reduces bandwidth use.
300	#[arg(
301		long,
302		value_parser = validate_relay_chain_url,
303		num_args = 0..,
304		alias = "relay-chain-rpc-url"
305	)]
306	pub relay_chain_rpc_urls: Vec<Url>,
307
308	/// EXPERIMENTAL: This is meant to be used only if collator is overshooting the PoV size, and
309	/// building blocks that do not fit in the max_pov_size. It is a percentage of the max_pov_size
310	/// configuration of the relay-chain.
311	///
312	/// It will be removed once <https://github.com/paritytech/polkadot-sdk/issues/6020> is fixed.
313	#[arg(long)]
314	pub experimental_max_pov_percentage: Option<u32>,
315
316	/// Disable embedded DHT bootnode.
317	///
318	/// Do not advertise the node as a parachain bootnode on the relay chain DHT.
319	#[arg(long)]
320	pub no_dht_bootnode: bool,
321
322	/// Disable DHT bootnode discovery.
323	///
324	/// Disable discovery of the parachain bootnodes via the relay chain DHT.
325	#[arg(long)]
326	pub no_dht_bootnode_discovery: bool,
327}
328
329impl RunCmd {
330	/// Create a [`NormalizedRunCmd`] which merges the `collator` cli argument into `validator` to
331	/// have only one.
332	pub fn normalize(&self) -> NormalizedRunCmd {
333		let mut new_base = self.base.clone();
334
335		new_base.validator = self.base.validator || self.collator;
336
337		NormalizedRunCmd { base: new_base }
338	}
339
340	/// Create [`CollatorOptions`] representing options only relevant to parachain collator nodes
341	pub fn collator_options(&self) -> CollatorOptions {
342		let relay_chain_mode = if self.relay_chain_rpc_urls.is_empty() {
343			RelayChainMode::Embedded
344		} else {
345			RelayChainMode::ExternalRpc(self.relay_chain_rpc_urls.clone())
346		};
347
348		CollatorOptions {
349			relay_chain_mode,
350			embedded_dht_bootnode: !self.no_dht_bootnode,
351			dht_bootnode_discovery: !self.no_dht_bootnode_discovery,
352		}
353	}
354}
355
356/// Possible modes for the relay chain to operate in.
357#[derive(Clone, Debug)]
358pub enum RelayChainMode {
359	/// Spawn embedded relay chain node
360	Embedded,
361	/// Connect to remote relay chain node via websocket RPC
362	ExternalRpc(Vec<Url>),
363}
364
365/// Options only relevant for collator/parachain nodes
366#[derive(Clone, Debug)]
367pub struct CollatorOptions {
368	/// How this collator retrieves relay chain information
369	pub relay_chain_mode: RelayChainMode,
370	/// Enable embedded DHT bootnode.
371	pub embedded_dht_bootnode: bool,
372	/// Enable DHT bootnode discovery.
373	pub dht_bootnode_discovery: bool,
374}
375
376/// A non-redundant version of the `RunCmd` that sets the `validator` field when the
377/// original `RunCmd` had the `collator` field.
378/// This is how we make `--collator` imply `--validator`.
379pub struct NormalizedRunCmd {
380	/// The cumulus RunCmd inherents from sc_cli's
381	pub base: sc_cli::RunCmd,
382}
383
384impl sc_cli::CliConfiguration for NormalizedRunCmd {
385	fn shared_params(&self) -> &sc_cli::SharedParams {
386		self.base.shared_params()
387	}
388
389	fn import_params(&self) -> Option<&sc_cli::ImportParams> {
390		self.base.import_params()
391	}
392
393	fn network_params(&self) -> Option<&sc_cli::NetworkParams> {
394		self.base.network_params()
395	}
396
397	fn keystore_params(&self) -> Option<&sc_cli::KeystoreParams> {
398		self.base.keystore_params()
399	}
400
401	fn offchain_worker_params(&self) -> Option<&sc_cli::OffchainWorkerParams> {
402		self.base.offchain_worker_params()
403	}
404
405	fn node_name(&self) -> sc_cli::Result<String> {
406		self.base.node_name()
407	}
408
409	fn dev_key_seed(&self, is_dev: bool) -> sc_cli::Result<Option<String>> {
410		self.base.dev_key_seed(is_dev)
411	}
412
413	fn telemetry_endpoints(
414		&self,
415		chain_spec: &Box<dyn sc_cli::ChainSpec>,
416	) -> sc_cli::Result<Option<TelemetryEndpoints>> {
417		self.base.telemetry_endpoints(chain_spec)
418	}
419
420	fn role(&self, is_dev: bool) -> sc_cli::Result<sc_cli::Role> {
421		self.base.role(is_dev)
422	}
423
424	fn force_authoring(&self) -> sc_cli::Result<bool> {
425		self.base.force_authoring()
426	}
427
428	fn prometheus_config(
429		&self,
430		default_listen_port: u16,
431		chain_spec: &Box<dyn sc_cli::ChainSpec>,
432	) -> sc_cli::Result<Option<PrometheusConfig>> {
433		self.base.prometheus_config(default_listen_port, chain_spec)
434	}
435
436	fn disable_grandpa(&self) -> sc_cli::Result<bool> {
437		self.base.disable_grandpa()
438	}
439
440	fn rpc_max_connections(&self) -> sc_cli::Result<u32> {
441		self.base.rpc_max_connections()
442	}
443
444	fn rpc_cors(&self, is_dev: bool) -> sc_cli::Result<Option<Vec<String>>> {
445		self.base.rpc_cors(is_dev)
446	}
447
448	fn rpc_addr(&self, default_listen_port: u16) -> sc_cli::Result<Option<Vec<RpcEndpoint>>> {
449		self.base.rpc_addr(default_listen_port)
450	}
451
452	fn rpc_methods(&self) -> sc_cli::Result<sc_service::config::RpcMethods> {
453		self.base.rpc_methods()
454	}
455
456	fn rpc_rate_limit(&self) -> sc_cli::Result<Option<std::num::NonZeroU32>> {
457		Ok(self.base.rpc_params.rpc_rate_limit)
458	}
459
460	fn rpc_rate_limit_whitelisted_ips(&self) -> sc_cli::Result<Vec<sc_service::config::IpNetwork>> {
461		Ok(self.base.rpc_params.rpc_rate_limit_whitelisted_ips.clone())
462	}
463
464	fn rpc_rate_limit_trust_proxy_headers(&self) -> sc_cli::Result<bool> {
465		Ok(self.base.rpc_params.rpc_rate_limit_trust_proxy_headers)
466	}
467
468	fn rpc_max_request_size(&self) -> sc_cli::Result<u32> {
469		self.base.rpc_max_request_size()
470	}
471
472	fn rpc_max_response_size(&self) -> sc_cli::Result<u32> {
473		self.base.rpc_max_response_size()
474	}
475
476	fn rpc_max_subscriptions_per_connection(&self) -> sc_cli::Result<u32> {
477		self.base.rpc_max_subscriptions_per_connection()
478	}
479
480	fn rpc_buffer_capacity_per_connection(&self) -> sc_cli::Result<u32> {
481		Ok(self.base.rpc_params.rpc_message_buffer_capacity_per_connection)
482	}
483
484	fn rpc_batch_config(&self) -> sc_cli::Result<RpcBatchRequestConfig> {
485		self.base.rpc_batch_config()
486	}
487
488	fn transaction_pool(&self, is_dev: bool) -> sc_cli::Result<TransactionPoolOptions> {
489		self.base.transaction_pool(is_dev)
490	}
491
492	fn max_runtime_instances(&self) -> sc_cli::Result<Option<usize>> {
493		self.base.max_runtime_instances()
494	}
495
496	fn runtime_cache_size(&self) -> sc_cli::Result<u8> {
497		self.base.runtime_cache_size()
498	}
499
500	fn base_path(&self) -> sc_cli::Result<Option<BasePath>> {
501		self.base.base_path()
502	}
503}