1const DEFAULT_DEV_BLOCK_TIME_MS: u64 = 3000;
21
22use crate::{
23 chain_spec::DiskChainSpecLoader,
24 common::{
25 chain_spec::{Extensions, LoadSpec},
26 NodeExtraArgs,
27 },
28};
29use chain_spec_builder::ChainSpecBuilder;
30use clap::{Command, CommandFactory, FromArgMatches, ValueEnum};
31use sc_chain_spec::ChainSpec;
32use sc_cli::{
33 CliConfiguration, DefaultConfigurationValues, ImportParams, KeystoreParams, NetworkParams,
34 RpcEndpoint, SharedParams, SubstrateCli,
35};
36use sc_service::{config::PrometheusConfig, BasePath};
37use sc_storage_monitor::StorageMonitorParams;
38use std::{
39 fmt::{Display, Formatter},
40 marker::PhantomData,
41 path::PathBuf,
42};
43pub trait CliConfig {
49 fn impl_version() -> String;
51
52 fn description(executable_name: String) -> String {
54 format!(
55 "The command-line arguments provided first will be passed to the parachain node, \n\
56 and the arguments provided after -- will be passed to the relay chain node. \n\
57 \n\
58 Example: \n\
59 \n\
60 {} [parachain-args] -- [relay-chain-args]",
61 executable_name
62 )
63 }
64
65 fn author() -> String;
67
68 fn support_url() -> String;
70
71 fn copyright_start_year() -> u16;
73}
74
75#[derive(Debug, clap::Subcommand)]
77pub enum Subcommand {
78 #[command(subcommand)]
80 Key(sc_cli::KeySubcommand),
81
82 #[deprecated(
94 note = "build-spec will be removed after 1/06/2025. Use chain-spec-builder instead"
95 )]
96 BuildSpec(sc_cli::BuildSpecCmd),
97
98 CheckBlock(sc_cli::CheckBlockCmd),
100
101 ExportBlocks(sc_cli::ExportBlocksCmd),
103
104 ExportState(sc_cli::ExportStateCmd),
106
107 ImportBlocks(sc_cli::ImportBlocksCmd),
109
110 Revert(sc_cli::RevertCmd),
112
113 ChainSpecBuilder(ChainSpecBuilder),
125
126 PurgeChain(cumulus_client_cli::PurgeChainCmd),
128 #[command(alias = "export-genesis-state")]
130 ExportGenesisHead(cumulus_client_cli::ExportGenesisHeadCommand),
131
132 ExportGenesisWasm(cumulus_client_cli::ExportGenesisWasmCommand),
134
135 #[command(subcommand)]
138 Benchmark(frame_benchmarking_cli::BenchmarkCmd),
139}
140
141#[derive(clap::Parser)]
143#[command(
144 propagate_version = true,
145 args_conflicts_with_subcommands = true,
146 subcommand_negates_reqs = true
147)]
148pub struct Cli<Config: CliConfig> {
149 #[arg(skip)]
150 pub(crate) chain_spec_loader: Option<Box<dyn LoadSpec>>,
151
152 #[command(subcommand)]
154 pub subcommand: Option<Subcommand>,
155
156 #[command(flatten)]
158 pub run: cumulus_client_cli::RunCmd,
159
160 #[command(flatten)]
162 pub storage_monitor: StorageMonitorParams,
163
164 #[arg(long, conflicts_with = "instant_seal")]
175 pub dev_block_time: Option<u64>,
176
177 #[arg(long, conflicts_with = "dev_block_time")]
185 pub instant_seal: bool,
186
187 #[arg(long, conflicts_with = "authoring")]
192 pub experimental_use_slot_based: bool,
193
194 #[arg(long, default_value_t = AuthoringPolicy::Lookahead)]
196 pub authoring: AuthoringPolicy,
197
198 #[arg(long)]
206 pub no_hardware_benchmarks: bool,
207
208 #[arg(long)]
213 pub export_pov_to_path: Option<PathBuf>,
214
215 #[arg(raw = true)]
217 pub relay_chain_args: Vec<String>,
218
219 #[arg(long)]
224 pub enable_statement_store: bool,
225
226 #[arg(long, default_value_t = sc_statement_store::DEFAULT_NETWORK_WORKERS)]
230 pub statement_network_workers: usize,
231
232 #[arg(long, default_value_t = sc_statement_store::DEFAULT_RATE_LIMIT)]
239 pub statement_rate_limit: u32,
240
241 #[arg(long, default_value_t = sc_statement_store::DEFAULT_MAX_TOTAL_STATEMENTS)]
247 pub statement_store_max_total_statements: usize,
248
249 #[arg(long, default_value_t = sc_statement_store::DEFAULT_MAX_TOTAL_SIZE)]
255 pub statement_store_max_total_size: usize,
256
257 #[arg(long, default_value_t = sc_statement_store::DEFAULT_PURGE_AFTER_SEC)]
263 pub statement_store_purge_after_sec: u64,
264
265 #[arg(long = "statement-affinity-topic", value_name = "TOPIC", hide = true)]
275 pub statement_affinity_topics: Vec<sc_statement_store::Topic>,
276
277 #[arg(long = "statement-affinity-topics-file", value_name = "PATH", hide = true)]
284 pub statement_affinity_topics_file: Option<sc_statement_store::AffinityTopicsFile>,
285
286 #[arg(
292 long,
293 value_name = "K",
294 default_value_t = sc_statement_store::DEFAULT_REPLICATION_FACTOR,
295 hide = true
296 )]
297 pub statement_replication_factor: std::num::NonZeroUsize,
298
299 #[arg(
305 long,
306 value_name = "N",
307 default_value_t = sc_statement_store::DEFAULT_GOSSIP_TARGET,
308 hide = true
309 )]
310 pub statement_gossip_target: std::num::NonZeroUsize,
311
312 #[arg(
318 long,
319 value_name = "RATE",
320 default_value_t = sc_statement_store::DEFAULT_BLOOM_FALSE_POS_RATE,
321 hide = true
322 )]
323 pub statement_bloom_false_positive_rate: f64,
324
325 #[arg(long, value_name = "SEED", hide = true)]
332 pub statement_bloom_seed: Option<u128>,
333
334 #[arg(long, value_name = "BYTES", hide = true)]
341 pub statement_store_max_dht_affinity_size: Option<usize>,
342
343 #[arg(long, value_name = "BYTES", hide = true)]
350 pub statement_store_max_transient_size: Option<usize>,
351
352 #[arg(long, value_name = "N", default_value_t = 32)]
354 pub collator_reserved_slots: usize,
355
356 #[command(flatten)]
358 pub hop: sc_hop::HopParams,
359
360 #[arg(skip)]
361 pub(crate) _phantom: PhantomData<Config>,
362}
363
364#[derive(Debug, Clone, Copy)]
366pub(crate) enum DevSealMode {
367 InstantSeal,
369 ManualSeal(u64),
372}
373
374#[derive(PartialEq, Debug, ValueEnum, Clone, Copy)]
376pub enum AuthoringPolicy {
377 Lookahead,
380 SlotBased,
384}
385
386impl Display for AuthoringPolicy {
387 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
388 match self {
389 AuthoringPolicy::Lookahead => write!(f, "lookahead"),
390 AuthoringPolicy::SlotBased => write!(f, "slot-based"),
391 }
392 }
393}
394
395impl<Config: CliConfig> Cli<Config> {
396 pub(crate) fn node_extra_args(&self) -> NodeExtraArgs {
397 NodeExtraArgs {
398 authoring_policy: self
399 .experimental_use_slot_based
400 .then(|| AuthoringPolicy::SlotBased)
401 .unwrap_or(self.authoring),
402 export_pov: self.export_pov_to_path.clone(),
403 max_pov_percentage: self.run.experimental_max_pov_percentage,
404 statement_store_config: self.enable_statement_store.then_some(
405 sc_statement_store::Config {
406 max_total_statements: self.statement_store_max_total_statements,
407 max_total_size: self.statement_store_max_total_size,
408 purge_after_sec: self.statement_store_purge_after_sec,
409 network_workers: self.statement_network_workers,
410 rate_limit: self.statement_rate_limit,
411 v2dht: sc_network_statement::v2dht_enabled().then(|| {
412 sc_statement_store::V2DhtConfig {
413 affinity_topics: self.statement_affinity_topics.clone(),
414 affinity_topics_file: self.statement_affinity_topics_file.clone(),
415 bloom_false_pos_rate: self.statement_bloom_false_positive_rate,
416 bloom_seed: self.statement_bloom_seed,
417 replication_factor: self.statement_replication_factor,
418 gossip_target: self.statement_gossip_target,
419 dht_affinity_max_size: self.statement_store_max_dht_affinity_size,
420 transient_max_size: self.statement_store_max_transient_size,
421 }
422 }),
423 },
424 ),
425 storage_monitor: self.storage_monitor.clone(),
426 collator_reserved_slots: self.collator_reserved_slots,
427 hop: self.hop.enabled.then(|| self.hop.clone()),
428 }
429 }
430
431 pub(crate) fn dev_mode(&self) -> Option<DevSealMode> {
433 if self.instant_seal {
434 Some(DevSealMode::InstantSeal)
435 } else if let Some(dev_block_time) = self.dev_block_time {
436 Some(DevSealMode::ManualSeal(dev_block_time))
437 } else if self.run.base.is_dev().unwrap_or(false) {
438 Some(DevSealMode::ManualSeal(DEFAULT_DEV_BLOCK_TIME_MS))
439 } else {
440 None
441 }
442 }
443}
444
445impl<Config: CliConfig> SubstrateCli for Cli<Config> {
446 fn impl_name() -> String {
447 Self::executable_name()
448 }
449
450 fn impl_version() -> String {
451 Config::impl_version()
452 }
453
454 fn description() -> String {
455 Config::description(Self::executable_name())
456 }
457
458 fn author() -> String {
459 Config::author()
460 }
461
462 fn support_url() -> String {
463 Config::support_url()
464 }
465
466 fn copyright_start_year() -> i32 {
467 Config::copyright_start_year() as i32
468 }
469
470 fn load_spec(&self, id: &str) -> Result<Box<dyn ChainSpec>, String> {
471 match &self.chain_spec_loader {
472 Some(chain_spec_loader) => chain_spec_loader.load_spec(id),
473 None => DiskChainSpecLoader.load_spec(id),
474 }
475 }
476}
477
478#[derive(Debug)]
480pub struct RelayChainCli<Config: CliConfig> {
481 pub base: polkadot_cli::RunCmd,
483
484 pub chain_id: Option<String>,
486
487 pub base_path: Option<PathBuf>,
489
490 _phantom: PhantomData<Config>,
491}
492
493impl<Config: CliConfig> RelayChainCli<Config> {
494 fn polkadot_cmd() -> Command {
495 let help_template = color_print::cformat!(
496 "The arguments that are passed to the relay chain node. \n\
497 \n\
498 <bold><underline>RELAY_CHAIN_ARGS:</></> \n\
499 {{options}}",
500 );
501
502 polkadot_cli::RunCmd::command()
503 .no_binary_name(true)
504 .help_template(help_template)
505 }
506
507 pub fn new<'a>(
509 para_config: &sc_service::Configuration,
510 relay_chain_args: impl Iterator<Item = &'a String>,
511 ) -> Self {
512 let polkadot_cmd = Self::polkadot_cmd();
513 let matches = polkadot_cmd.get_matches_from(relay_chain_args);
514 let base = FromArgMatches::from_arg_matches(&matches).unwrap_or_else(|e| e.exit());
515
516 let extension = Extensions::try_get(&*para_config.chain_spec);
517 let chain_id = extension.map(|e| e.relay_chain());
518
519 let base_path = para_config.base_path.path().join("polkadot");
520 Self { base, chain_id, base_path: Some(base_path), _phantom: Default::default() }
521 }
522}
523
524impl<Config: CliConfig> SubstrateCli for RelayChainCli<Config> {
525 fn impl_name() -> String {
526 Cli::<Config>::impl_name()
527 }
528
529 fn impl_version() -> String {
530 Cli::<Config>::impl_version()
531 }
532
533 fn description() -> String {
534 Cli::<Config>::description()
535 }
536
537 fn author() -> String {
538 Cli::<Config>::author()
539 }
540
541 fn support_url() -> String {
542 Cli::<Config>::support_url()
543 }
544
545 fn copyright_start_year() -> i32 {
546 Cli::<Config>::copyright_start_year()
547 }
548
549 fn load_spec(&self, id: &str) -> std::result::Result<Box<dyn ChainSpec>, String> {
550 polkadot_cli::Cli::from_iter([Self::executable_name()].iter()).load_spec(id)
551 }
552}
553
554impl<Config: CliConfig> DefaultConfigurationValues for RelayChainCli<Config> {
555 fn p2p_listen_port() -> u16 {
556 30334
557 }
558
559 fn rpc_listen_port() -> u16 {
560 9945
561 }
562
563 fn prometheus_listen_port() -> u16 {
564 9616
565 }
566}
567
568impl<Config: CliConfig> CliConfiguration<Self> for RelayChainCli<Config> {
569 fn shared_params(&self) -> &SharedParams {
570 self.base.base.shared_params()
571 }
572
573 fn import_params(&self) -> Option<&ImportParams> {
574 self.base.base.import_params()
575 }
576
577 fn network_params(&self) -> Option<&NetworkParams> {
578 self.base.base.network_params()
579 }
580
581 fn keystore_params(&self) -> Option<&KeystoreParams> {
582 self.base.base.keystore_params()
583 }
584
585 fn base_path(&self) -> sc_cli::Result<Option<BasePath>> {
586 Ok(self
587 .shared_params()
588 .base_path()?
589 .or_else(|| self.base_path.clone().map(Into::into)))
590 }
591
592 fn rpc_addr(&self, default_listen_port: u16) -> sc_cli::Result<Option<Vec<RpcEndpoint>>> {
593 self.base.base.rpc_addr(default_listen_port)
594 }
595
596 fn prometheus_config(
597 &self,
598 default_listen_port: u16,
599 chain_spec: &Box<dyn ChainSpec>,
600 ) -> sc_cli::Result<Option<PrometheusConfig>> {
601 self.base.base.prometheus_config(default_listen_port, chain_spec)
602 }
603
604 fn init<F>(
605 &self,
606 _support_url: &String,
607 _impl_version: &String,
608 _logger_hook: F,
609 ) -> sc_cli::Result<()>
610 where
611 F: FnOnce(&mut sc_cli::LoggerBuilder),
612 {
613 unreachable!("PolkadotCli is never initialized; qed");
614 }
615
616 fn chain_id(&self, is_dev: bool) -> sc_cli::Result<String> {
617 let chain_id = self.base.base.chain_id(is_dev)?;
618
619 Ok(if chain_id.is_empty() { self.chain_id.clone().unwrap_or_default() } else { chain_id })
620 }
621
622 fn role(&self, is_dev: bool) -> sc_cli::Result<sc_service::Role> {
623 self.base.base.role(is_dev)
624 }
625
626 fn transaction_pool(
627 &self,
628 is_dev: bool,
629 ) -> sc_cli::Result<sc_service::config::TransactionPoolOptions> {
630 self.base.base.transaction_pool(is_dev)
631 }
632
633 fn trie_cache_maximum_size(&self) -> sc_cli::Result<Option<usize>> {
634 self.base.base.trie_cache_maximum_size()
635 }
636
637 fn rpc_methods(&self) -> sc_cli::Result<sc_service::config::RpcMethods> {
638 self.base.base.rpc_methods()
639 }
640
641 fn rpc_max_connections(&self) -> sc_cli::Result<u32> {
642 self.base.base.rpc_max_connections()
643 }
644
645 fn rpc_cors(&self, is_dev: bool) -> sc_cli::Result<Option<Vec<String>>> {
646 self.base.base.rpc_cors(is_dev)
647 }
648
649 fn default_heap_pages(&self) -> sc_cli::Result<Option<u64>> {
650 self.base.base.default_heap_pages()
651 }
652
653 fn force_authoring(&self) -> sc_cli::Result<bool> {
654 self.base.base.force_authoring()
655 }
656
657 fn disable_grandpa(&self) -> sc_cli::Result<bool> {
658 self.base.base.disable_grandpa()
659 }
660
661 fn max_runtime_instances(&self) -> sc_cli::Result<Option<usize>> {
662 self.base.base.max_runtime_instances()
663 }
664
665 fn announce_block(&self) -> sc_cli::Result<bool> {
666 self.base.base.announce_block()
667 }
668
669 fn telemetry_endpoints(
670 &self,
671 chain_spec: &Box<dyn ChainSpec>,
672 ) -> sc_cli::Result<Option<sc_telemetry::TelemetryEndpoints>> {
673 self.base.base.telemetry_endpoints(chain_spec)
674 }
675
676 fn node_name(&self) -> sc_cli::Result<String> {
677 self.base.base.node_name()
678 }
679}
680
681#[cfg(test)]
682mod tests {
683 use super::*;
684 use clap::{CommandFactory, FromArgMatches};
685
686 struct TestCliConfig;
687 impl CliConfig for TestCliConfig {
688 fn impl_version() -> String {
689 "0.0.0".into()
690 }
691 fn author() -> String {
692 "test".into()
693 }
694 fn support_url() -> String {
695 "https://example.invalid".into()
696 }
697 fn copyright_start_year() -> u16 {
698 2025
699 }
700 }
701
702 #[test]
703 fn statement_affinity_topics_accumulate_repeated_flags() {
704 let topic_a = "11".repeat(32);
705 let topic_b = "22".repeat(32);
706 let matches = Cli::<TestCliConfig>::command().version("0.0.0").get_matches_from([
709 "polkadot-omni-node",
710 "--statement-affinity-topic",
711 &format!("0x{topic_a}"),
712 "--statement-affinity-topic",
713 &format!("0x{topic_b}"),
714 ]);
715 let cli = Cli::<TestCliConfig>::from_arg_matches(&matches).expect("args parse");
716 assert_eq!(
717 cli.statement_affinity_topics,
718 vec![sc_statement_store::Topic([0x11; 32]), sc_statement_store::Topic([0x22; 32])]
719 );
720 }
721
722 #[test]
723 fn statement_bloom_flags_parse() {
724 let matches = Cli::<TestCliConfig>::command().version("0.0.0").get_matches_from([
725 "polkadot-omni-node",
726 "--statement-bloom-false-positive-rate",
727 "0.001",
728 "--statement-bloom-seed",
729 "12345678901234567890123456789012345678",
730 ]);
731 let cli = Cli::<TestCliConfig>::from_arg_matches(&matches).expect("args parse");
732 assert_eq!(cli.statement_bloom_false_positive_rate, 0.001);
733 assert_eq!(cli.statement_bloom_seed, Some(12345678901234567890123456789012345678));
734
735 let matches = Cli::<TestCliConfig>::command()
736 .version("0.0.0")
737 .get_matches_from(["polkadot-omni-node"]);
738 let cli = Cli::<TestCliConfig>::from_arg_matches(&matches).expect("args parse");
739 assert_eq!(
740 cli.statement_bloom_false_positive_rate,
741 sc_statement_store::DEFAULT_BLOOM_FALSE_POS_RATE
742 );
743 assert_eq!(cli.statement_bloom_seed, None);
744 }
745}