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, conflicts_with_all = ["relay_chain_rpc_urls", "collator"])]
310 pub relay_chain_light_client: bool,
311
312 #[arg(long)]
318 pub experimental_max_pov_percentage: Option<u32>,
319
320 #[arg(long)]
324 pub no_dht_bootnode: bool,
325
326 #[arg(long)]
330 pub no_dht_bootnode_discovery: bool,
331}
332
333impl RunCmd {
334 pub fn normalize(&self) -> NormalizedRunCmd {
337 let mut new_base = self.base.clone();
338
339 new_base.validator = self.base.validator || self.collator;
340
341 NormalizedRunCmd { base: new_base }
342 }
343
344 pub fn collator_options(&self) -> CollatorOptions {
346 let relay_chain_mode =
347 match (self.relay_chain_light_client, !self.relay_chain_rpc_urls.is_empty()) {
348 (true, _) => RelayChainMode::LightClient,
349 (_, true) => RelayChainMode::ExternalRpc(self.relay_chain_rpc_urls.clone()),
350 _ => RelayChainMode::Embedded,
351 };
352
353 CollatorOptions {
354 relay_chain_mode,
355 embedded_dht_bootnode: !self.no_dht_bootnode,
356 dht_bootnode_discovery: !self.no_dht_bootnode_discovery,
357 }
358 }
359}
360
361#[derive(Clone, Debug)]
363pub enum RelayChainMode {
364 Embedded,
366 ExternalRpc(Vec<Url>),
368 LightClient,
370}
371
372#[derive(Clone, Debug)]
374pub struct CollatorOptions {
375 pub relay_chain_mode: RelayChainMode,
377 pub embedded_dht_bootnode: bool,
379 pub dht_bootnode_discovery: bool,
381}
382
383pub struct NormalizedRunCmd {
387 pub base: sc_cli::RunCmd,
389}
390
391impl sc_cli::CliConfiguration for NormalizedRunCmd {
392 fn shared_params(&self) -> &sc_cli::SharedParams {
393 self.base.shared_params()
394 }
395
396 fn import_params(&self) -> Option<&sc_cli::ImportParams> {
397 self.base.import_params()
398 }
399
400 fn network_params(&self) -> Option<&sc_cli::NetworkParams> {
401 self.base.network_params()
402 }
403
404 fn keystore_params(&self) -> Option<&sc_cli::KeystoreParams> {
405 self.base.keystore_params()
406 }
407
408 fn offchain_worker_params(&self) -> Option<&sc_cli::OffchainWorkerParams> {
409 self.base.offchain_worker_params()
410 }
411
412 fn node_name(&self) -> sc_cli::Result<String> {
413 self.base.node_name()
414 }
415
416 fn dev_key_seed(&self, is_dev: bool) -> sc_cli::Result<Option<String>> {
417 self.base.dev_key_seed(is_dev)
418 }
419
420 fn telemetry_endpoints(
421 &self,
422 chain_spec: &Box<dyn sc_cli::ChainSpec>,
423 ) -> sc_cli::Result<Option<TelemetryEndpoints>> {
424 self.base.telemetry_endpoints(chain_spec)
425 }
426
427 fn role(&self, is_dev: bool) -> sc_cli::Result<sc_cli::Role> {
428 self.base.role(is_dev)
429 }
430
431 fn force_authoring(&self) -> sc_cli::Result<bool> {
432 self.base.force_authoring()
433 }
434
435 fn prometheus_config(
436 &self,
437 default_listen_port: u16,
438 chain_spec: &Box<dyn sc_cli::ChainSpec>,
439 ) -> sc_cli::Result<Option<PrometheusConfig>> {
440 self.base.prometheus_config(default_listen_port, chain_spec)
441 }
442
443 fn disable_grandpa(&self) -> sc_cli::Result<bool> {
444 self.base.disable_grandpa()
445 }
446
447 fn rpc_max_connections(&self) -> sc_cli::Result<u32> {
448 self.base.rpc_max_connections()
449 }
450
451 fn rpc_cors(&self, is_dev: bool) -> sc_cli::Result<Option<Vec<String>>> {
452 self.base.rpc_cors(is_dev)
453 }
454
455 fn rpc_addr(&self, default_listen_port: u16) -> sc_cli::Result<Option<Vec<RpcEndpoint>>> {
456 self.base.rpc_addr(default_listen_port)
457 }
458
459 fn rpc_methods(&self) -> sc_cli::Result<sc_service::config::RpcMethods> {
460 self.base.rpc_methods()
461 }
462
463 fn rpc_rate_limit(&self) -> sc_cli::Result<Option<std::num::NonZeroU32>> {
464 Ok(self.base.rpc_params.rpc_rate_limit)
465 }
466
467 fn rpc_rate_limit_whitelisted_ips(&self) -> sc_cli::Result<Vec<sc_service::config::IpNetwork>> {
468 Ok(self.base.rpc_params.rpc_rate_limit_whitelisted_ips.clone())
469 }
470
471 fn rpc_rate_limit_trust_proxy_headers(&self) -> sc_cli::Result<bool> {
472 Ok(self.base.rpc_params.rpc_rate_limit_trust_proxy_headers)
473 }
474
475 fn rpc_max_request_size(&self) -> sc_cli::Result<u32> {
476 self.base.rpc_max_request_size()
477 }
478
479 fn rpc_max_response_size(&self) -> sc_cli::Result<u32> {
480 self.base.rpc_max_response_size()
481 }
482
483 fn rpc_max_subscriptions_per_connection(&self) -> sc_cli::Result<u32> {
484 self.base.rpc_max_subscriptions_per_connection()
485 }
486
487 fn rpc_buffer_capacity_per_connection(&self) -> sc_cli::Result<u32> {
488 Ok(self.base.rpc_params.rpc_message_buffer_capacity_per_connection)
489 }
490
491 fn rpc_batch_config(&self) -> sc_cli::Result<RpcBatchRequestConfig> {
492 self.base.rpc_batch_config()
493 }
494
495 fn transaction_pool(&self, is_dev: bool) -> sc_cli::Result<TransactionPoolOptions> {
496 self.base.transaction_pool(is_dev)
497 }
498
499 fn max_runtime_instances(&self) -> sc_cli::Result<Option<usize>> {
500 self.base.max_runtime_instances()
501 }
502
503 fn runtime_cache_size(&self) -> sc_cli::Result<u8> {
504 self.base.runtime_cache_size()
505 }
506
507 fn base_path(&self) -> sc_cli::Result<Option<BasePath>> {
508 self.base.base_path()
509 }
510}