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.
294	/// This node connects to the remote nodes following the order they were specified in. If the
295	/// connection fails, it attempts to connect to the next endpoint in the list.
296	///
297	/// Note: This option doesn't stop the node from connecting to the relay chain network but
298	/// reduces bandwidth use.
299	#[arg(
300		long,
301		value_parser = validate_relay_chain_url,
302		num_args = 0..,
303		alias = "relay-chain-rpc-url"
304	)]
305	pub relay_chain_rpc_urls: Vec<Url>,
306
307	/// EXPERIMENTAL: This is meant to be used only if collator is overshooting the PoV size, and
308	/// building blocks that do not fit in the max_pov_size. It is a percentage of the max_pov_size
309	/// configuration of the relay-chain.
310	///
311	/// It will be removed once <https://github.com/paritytech/polkadot-sdk/issues/6020> is fixed.
312	#[arg(long)]
313	pub experimental_max_pov_percentage: Option<u32>,
314
315	/// Disable embedded DHT bootnode.
316	///
317	/// Do not advertise the node as a parachain bootnode on the relay chain DHT.
318	#[arg(long)]
319	pub no_dht_bootnode: bool,
320
321	/// Disable DHT bootnode discovery.
322	///
323	/// Disable discovery of the parachain bootnodes via the relay chain DHT.
324	#[arg(long)]
325	pub no_dht_bootnode_discovery: bool,
326}
327
328impl RunCmd {
329	/// Create a [`NormalizedRunCmd`] which merges the `collator` cli argument into `validator` to
330	/// have only one.
331	pub fn normalize(&self) -> NormalizedRunCmd {
332		let mut new_base = self.base.clone();
333
334		new_base.validator = self.base.validator || self.collator;
335
336		NormalizedRunCmd { base: new_base }
337	}
338
339	/// Create [`CollatorOptions`] representing options only relevant to parachain collator nodes
340	pub fn collator_options(&self) -> CollatorOptions {
341		let relay_chain_mode = if self.relay_chain_rpc_urls.is_empty() {
342			RelayChainMode::Embedded
343		} else {
344			RelayChainMode::ExternalRpc(self.relay_chain_rpc_urls.clone())
345		};
346
347		CollatorOptions {
348			relay_chain_mode,
349			embedded_dht_bootnode: !self.no_dht_bootnode,
350			dht_bootnode_discovery: !self.no_dht_bootnode_discovery,
351		}
352	}
353}
354
355/// Possible modes for the relay chain to operate in.
356#[derive(Clone, Debug)]
357pub enum RelayChainMode {
358	/// Spawn embedded relay chain node
359	Embedded,
360	/// Connect to remote relay chain node via websocket RPC
361	ExternalRpc(Vec<Url>),
362}
363
364/// Options only relevant for collator/parachain nodes
365#[derive(Clone, Debug)]
366pub struct CollatorOptions {
367	/// How this collator retrieves relay chain information
368	pub relay_chain_mode: RelayChainMode,
369	/// Enable embedded DHT bootnode.
370	pub embedded_dht_bootnode: bool,
371	/// Enable DHT bootnode discovery.
372	pub dht_bootnode_discovery: bool,
373}
374
375/// A non-redundant version of the `RunCmd` that sets the `validator` field when the
376/// original `RunCmd` had the `collator` field.
377/// This is how we make `--collator` imply `--validator`.
378pub struct NormalizedRunCmd {
379	/// The cumulus RunCmd inherents from sc_cli's
380	pub base: sc_cli::RunCmd,
381}
382
383impl sc_cli::CliConfiguration for NormalizedRunCmd {
384	fn shared_params(&self) -> &sc_cli::SharedParams {
385		self.base.shared_params()
386	}
387
388	fn import_params(&self) -> Option<&sc_cli::ImportParams> {
389		self.base.import_params()
390	}
391
392	fn network_params(&self) -> Option<&sc_cli::NetworkParams> {
393		self.base.network_params()
394	}
395
396	fn keystore_params(&self) -> Option<&sc_cli::KeystoreParams> {
397		self.base.keystore_params()
398	}
399
400	fn offchain_worker_params(&self) -> Option<&sc_cli::OffchainWorkerParams> {
401		self.base.offchain_worker_params()
402	}
403
404	fn node_name(&self) -> sc_cli::Result<String> {
405		self.base.node_name()
406	}
407
408	fn dev_key_seed(&self, is_dev: bool) -> sc_cli::Result<Option<String>> {
409		self.base.dev_key_seed(is_dev)
410	}
411
412	fn telemetry_endpoints(
413		&self,
414		chain_spec: &Box<dyn sc_cli::ChainSpec>,
415	) -> sc_cli::Result<Option<TelemetryEndpoints>> {
416		self.base.telemetry_endpoints(chain_spec)
417	}
418
419	fn role(&self, is_dev: bool) -> sc_cli::Result<sc_cli::Role> {
420		self.base.role(is_dev)
421	}
422
423	fn force_authoring(&self) -> sc_cli::Result<bool> {
424		self.base.force_authoring()
425	}
426
427	fn prometheus_config(
428		&self,
429		default_listen_port: u16,
430		chain_spec: &Box<dyn sc_cli::ChainSpec>,
431	) -> sc_cli::Result<Option<PrometheusConfig>> {
432		self.base.prometheus_config(default_listen_port, chain_spec)
433	}
434
435	fn disable_grandpa(&self) -> sc_cli::Result<bool> {
436		self.base.disable_grandpa()
437	}
438
439	fn rpc_max_connections(&self) -> sc_cli::Result<u32> {
440		self.base.rpc_max_connections()
441	}
442
443	fn rpc_cors(&self, is_dev: bool) -> sc_cli::Result<Option<Vec<String>>> {
444		self.base.rpc_cors(is_dev)
445	}
446
447	fn rpc_addr(&self, default_listen_port: u16) -> sc_cli::Result<Option<Vec<RpcEndpoint>>> {
448		self.base.rpc_addr(default_listen_port)
449	}
450
451	fn rpc_methods(&self) -> sc_cli::Result<sc_service::config::RpcMethods> {
452		self.base.rpc_methods()
453	}
454
455	fn rpc_rate_limit(&self) -> sc_cli::Result<Option<std::num::NonZeroU32>> {
456		Ok(self.base.rpc_params.rpc_rate_limit)
457	}
458
459	fn rpc_rate_limit_whitelisted_ips(&self) -> sc_cli::Result<Vec<sc_service::config::IpNetwork>> {
460		Ok(self.base.rpc_params.rpc_rate_limit_whitelisted_ips.clone())
461	}
462
463	fn rpc_rate_limit_trust_proxy_headers(&self) -> sc_cli::Result<bool> {
464		Ok(self.base.rpc_params.rpc_rate_limit_trust_proxy_headers)
465	}
466
467	fn rpc_max_request_size(&self) -> sc_cli::Result<u32> {
468		self.base.rpc_max_request_size()
469	}
470
471	fn rpc_max_response_size(&self) -> sc_cli::Result<u32> {
472		self.base.rpc_max_response_size()
473	}
474
475	fn rpc_max_subscriptions_per_connection(&self) -> sc_cli::Result<u32> {
476		self.base.rpc_max_subscriptions_per_connection()
477	}
478
479	fn rpc_buffer_capacity_per_connection(&self) -> sc_cli::Result<u32> {
480		Ok(self.base.rpc_params.rpc_message_buffer_capacity_per_connection)
481	}
482
483	fn rpc_batch_config(&self) -> sc_cli::Result<RpcBatchRequestConfig> {
484		self.base.rpc_batch_config()
485	}
486
487	fn transaction_pool(&self, is_dev: bool) -> sc_cli::Result<TransactionPoolOptions> {
488		self.base.transaction_pool(is_dev)
489	}
490
491	fn max_runtime_instances(&self) -> sc_cli::Result<Option<usize>> {
492		self.base.max_runtime_instances()
493	}
494
495	fn runtime_cache_size(&self) -> sc_cli::Result<u8> {
496		self.base.runtime_cache_size()
497	}
498
499	fn base_path(&self) -> sc_cli::Result<Option<BasePath>> {
500		self.base.base_path()
501	}
502}