1#![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#[derive(Debug, clap::Parser)]
43#[group(skip)]
44pub struct PurgeChainCmd {
45 #[command(flatten)]
47 pub base: sc_cli::PurgeChainCmd,
48
49 #[arg(long, aliases = &["para"])]
51 pub parachain: bool,
52
53 #[arg(long, aliases = &["relay"])]
55 pub relaychain: bool,
56}
57
58impl PurgeChainCmd {
59 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
131pub 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#[derive(Debug, clap::Parser)]
154pub struct ExportGenesisHeadCommand {
155 #[arg()]
157 pub output: Option<PathBuf>,
158
159 #[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 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 Ok(Some(BasePath::new_temp_dir()?))
202 }
203}
204
205#[derive(Debug, clap::Parser)]
207pub struct ExportGenesisWasmCommand {
208 #[arg()]
210 pub output: Option<PathBuf>,
211
212 #[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 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
241pub 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 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#[derive(Debug, clap::Parser)]
279#[group(skip)]
280pub struct RunCmd {
281 #[command(flatten)]
283 pub base: sc_cli::RunCmd,
284
285 #[arg(long, conflicts_with = "validator")]
289 pub collator: bool,
290
291 #[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 #[arg(long)]
313 pub experimental_max_pov_percentage: Option<u32>,
314
315 #[arg(long)]
319 pub no_dht_bootnode: bool,
320
321 #[arg(long)]
325 pub no_dht_bootnode_discovery: bool,
326}
327
328impl RunCmd {
329 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 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#[derive(Clone, Debug)]
357pub enum RelayChainMode {
358 Embedded,
360 ExternalRpc(Vec<Url>),
362}
363
364#[derive(Clone, Debug)]
366pub struct CollatorOptions {
367 pub relay_chain_mode: RelayChainMode,
369 pub embedded_dht_bootnode: bool,
371 pub dht_bootnode_discovery: bool,
373}
374
375pub struct NormalizedRunCmd {
379 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}