1use crate::{
22 extrinsic::{
23 bench::{Benchmark, BenchmarkParams as ExtrinsicBenchmarkParams},
24 ExtrinsicBuilder,
25 },
26 overhead::{
27 command::ChainType::{Parachain, Relaychain, Unknown},
28 fake_runtime_api,
29 remark_builder::SubstrateRemarkBuilder,
30 template::TemplateData,
31 },
32 shared::{
33 genesis_state,
34 genesis_state::{GenesisStateHandler, SpecGenesisSource},
35 HostInfoParams, WeightParams,
36 },
37};
38use clap::{error::ErrorKind, Args, CommandFactory, Parser};
39use codec::{Decode, Encode};
40use cumulus_client_parachain_inherent::MockValidationDataInherentDataProvider;
41use fake_runtime_api::RuntimeApi as FakeRuntimeApi;
42use frame_support::Deserialize;
43use genesis_state::WARN_SPEC_GENESIS_CTOR;
44use log::info;
45use polkadot_parachain_primitives::primitives::Id as ParaId;
46use sc_block_builder::BlockBuilderApi;
47use sc_chain_spec::{ChainSpec, ChainSpecExtension, GenesisBlockBuilder};
48use sc_cli::{CliConfiguration, Database, ImportParams, Result, SharedParams};
49use sc_client_api::{execution_extensions::ExecutionExtensions, UsageProvider};
50use sc_client_db::{BlocksPruning, DatabaseSettings};
51use sc_executor::WasmExecutor;
52use sc_runtime_utilities::fetch_latest_metadata_from_code_blob;
53use sc_service::{new_client, new_db_backend, BasePath, ClientConfig, TFullClient, TaskManager};
54use serde::Serialize;
55use serde_json::{json, Value};
56use sp_api::{ApiExt, CallApiAt, Core, ProvideRuntimeApi};
57use sp_blockchain::HeaderBackend;
58use sp_core::H256;
59use sp_inherents::{InherentData, InherentDataProvider};
60use sp_runtime::{
61 generic,
62 traits::{BlakeTwo256, Block as BlockT},
63 DigestItem, OpaqueExtrinsic,
64};
65use sp_storage::Storage;
66use sp_wasm_interface::HostFunctions;
67use std::{
68 fmt::{Debug, Display, Formatter},
69 fs,
70 path::PathBuf,
71 sync::Arc,
72};
73use subxt::{client::RuntimeVersion, ext::futures, Metadata};
74
75const DEFAULT_PARA_ID: u32 = 100;
76const LOG_TARGET: &'static str = "polkadot_sdk_frame::benchmark::overhead";
77
78#[derive(Debug, Parser)]
80pub struct OverheadCmd {
81 #[allow(missing_docs)]
82 #[clap(flatten)]
83 pub shared_params: SharedParams,
84
85 #[allow(missing_docs)]
86 #[clap(flatten)]
87 pub import_params: ImportParams,
88
89 #[allow(missing_docs)]
90 #[clap(flatten)]
91 pub params: OverheadParams,
92}
93
94#[derive(Debug, Default, Serialize, Clone, PartialEq, Args)]
96pub struct OverheadParams {
97 #[allow(missing_docs)]
98 #[clap(flatten)]
99 pub weight: WeightParams,
100
101 #[allow(missing_docs)]
102 #[clap(flatten)]
103 pub bench: ExtrinsicBenchmarkParams,
104
105 #[allow(missing_docs)]
106 #[clap(flatten)]
107 pub hostinfo: HostInfoParams,
108
109 #[arg(long, value_name = "PATH")]
113 pub header: Option<PathBuf>,
114
115 #[arg(long)]
119 pub enable_trie_cache: bool,
120
121 #[arg(
123 long,
124 value_name = "PATH",
125 conflicts_with = "chain",
126 required_if_eq("genesis_builder", "runtime")
127 )]
128 pub runtime: Option<PathBuf>,
129
130 #[arg(long, default_value = sp_genesis_builder::DEV_RUNTIME_PRESET)]
135 pub genesis_builder_preset: String,
136
137 #[arg(long, value_enum, alias = "genesis-builder-policy")]
143 pub genesis_builder: Option<GenesisBuilderPolicy>,
144
145 #[arg(long)]
148 pub para_id: Option<u32>,
149}
150
151#[derive(clap::ValueEnum, Debug, Eq, PartialEq, Clone, Copy, Serialize)]
153#[clap(rename_all = "kebab-case")]
154pub enum GenesisBuilderPolicy {
155 Runtime,
158 SpecRuntime,
160 #[value(alias = "spec")]
162 SpecGenesis,
163}
164
165#[derive(Serialize, Clone, PartialEq, Copy)]
167pub(crate) enum BenchmarkType {
168 Extrinsic,
170 Block,
172}
173
174pub type ParachainHostFunctions = (
176 cumulus_primitives_proof_size_hostfunction::storage_proof_size::HostFunctions,
177 sp_io::SubstrateHostFunctions,
178);
179
180pub type BlockNumber = u32;
181
182pub type Header = generic::Header<BlockNumber, BlakeTwo256>;
184
185pub type OpaqueBlock = generic::Block<Header, OpaqueExtrinsic>;
187
188type OverheadClient<Block, HF> = TFullClient<Block, FakeRuntimeApi, WasmExecutor<HF>>;
190
191fn create_inherent_data<Client: UsageProvider<Block> + HeaderBackend<Block>, Block: BlockT>(
198 client: &Arc<Client>,
199 chain_type: &ChainType,
200) -> InherentData {
201 let genesis = client.usage_info().chain.best_hash;
202 let header = client.header(genesis).unwrap().unwrap();
203
204 let mut inherent_data = InherentData::new();
205
206 if let Parachain(para_id) = chain_type {
208 let parachain_validation_data_provider = MockValidationDataInherentDataProvider::<()> {
209 para_id: ParaId::from(*para_id),
210 current_para_block_head: Some(header.encode().into()),
211 relay_offset: 1,
212 ..Default::default()
213 };
214 let _ = futures::executor::block_on(
215 parachain_validation_data_provider.provide_inherent_data(&mut inherent_data),
216 );
217 }
218
219 let para_inherent = polkadot_primitives::InherentData {
221 bitfields: Vec::new(),
222 backed_candidates: Vec::new(),
223 disputes: Vec::new(),
224 parent_header: header,
225 };
226
227 let timestamp = sp_timestamp::InherentDataProvider::new(std::time::Duration::default().into());
229
230 let _ = futures::executor::block_on(timestamp.provide_inherent_data(&mut inherent_data));
231 let _ =
232 inherent_data.put_data(polkadot_primitives::PARACHAINS_INHERENT_IDENTIFIER, ¶_inherent);
233
234 inherent_data
235}
236
237fn identify_chain(metadata: &Metadata, para_id: Option<u32>) -> ChainType {
242 let parachain_info_exists = metadata.pallet_by_name("ParachainInfo").is_some();
243 let parachain_system_exists = metadata.pallet_by_name("ParachainSystem").is_some();
244 let para_inherent_exists = metadata.pallet_by_name("ParaInherent").is_some();
245
246 log::debug!("{} ParachainSystem", if parachain_system_exists { "✅" } else { "❌" });
247 log::debug!("{} ParachainInfo", if parachain_info_exists { "✅" } else { "❌" });
248 log::debug!("{} ParaInherent", if para_inherent_exists { "✅" } else { "❌" });
249
250 let chain_type = if parachain_system_exists && parachain_info_exists {
251 Parachain(para_id.unwrap_or(DEFAULT_PARA_ID))
252 } else if para_inherent_exists {
253 Relaychain
254 } else {
255 Unknown
256 };
257
258 log::info!(target: LOG_TARGET, "Identified Chain type from metadata: {}", chain_type);
259
260 chain_type
261}
262
263#[derive(Deserialize, Serialize, Clone, ChainSpecExtension)]
264pub struct ParachainExtension {
265 pub para_id: Option<u32>,
267}
268
269impl OverheadCmd {
270 fn state_handler_from_cli<HF: HostFunctions>(
271 &self,
272 chain_spec_from_api: Option<Box<dyn ChainSpec>>,
273 ) -> Result<(GenesisStateHandler, Option<u32>)> {
274 let genesis_builder_to_source = || match self.params.genesis_builder {
275 Some(GenesisBuilderPolicy::Runtime) | Some(GenesisBuilderPolicy::SpecRuntime) =>
276 SpecGenesisSource::Runtime(self.params.genesis_builder_preset.clone()),
277 Some(GenesisBuilderPolicy::SpecGenesis) | None => {
278 log::warn!(target: LOG_TARGET, "{WARN_SPEC_GENESIS_CTOR}");
279 SpecGenesisSource::SpecJson
280 },
281 };
282
283 if let Some(chain_spec) = chain_spec_from_api {
285 log::debug!(target: LOG_TARGET, "Initializing state handler with chain-spec from API: {:?}", chain_spec);
286
287 let source = genesis_builder_to_source();
288 return Ok((GenesisStateHandler::ChainSpec(chain_spec, source), self.params.para_id))
289 };
290
291 if let Some(chain_spec_path) = &self.shared_params.chain {
293 log::debug!(target: LOG_TARGET,
294 "Initializing state handler with chain-spec from path: {:?}",
295 chain_spec_path
296 );
297 let (chain_spec, para_id_from_chain_spec) =
298 genesis_state::chain_spec_from_path::<HF>(chain_spec_path.to_string().into())?;
299
300 let source = genesis_builder_to_source();
301
302 return Ok((
303 GenesisStateHandler::ChainSpec(chain_spec, source),
304 self.params.para_id.or(para_id_from_chain_spec),
305 ))
306 };
307
308 if let Some(runtime_path) = &self.params.runtime {
311 log::debug!(target: LOG_TARGET, "Initializing state handler with runtime from path: {:?}", runtime_path);
312
313 let runtime_blob = fs::read(runtime_path)?;
314 return Ok((
315 GenesisStateHandler::Runtime(
316 runtime_blob,
317 Some(self.params.genesis_builder_preset.clone()),
318 ),
319 self.params.para_id,
320 ));
321 };
322
323 Err("Neither a runtime nor a chain-spec were specified".to_string().into())
324 }
325
326 fn check_args(
327 &self,
328 chain_spec: &Option<Box<dyn ChainSpec>>,
329 ) -> std::result::Result<(), (ErrorKind, String)> {
330 if chain_spec.is_none() &&
331 self.params.runtime.is_none() &&
332 self.shared_params.chain.is_none()
333 {
334 return Err((
335 ErrorKind::MissingRequiredArgument,
336 "Provide either a runtime via `--runtime` or a chain spec via `--chain`"
337 .to_string(),
338 ));
339 }
340
341 match self.params.genesis_builder {
342 Some(GenesisBuilderPolicy::SpecGenesis | GenesisBuilderPolicy::SpecRuntime) =>
343 if chain_spec.is_none() && self.shared_params.chain.is_none() {
344 return Err((
345 ErrorKind::MissingRequiredArgument,
346 "Provide a chain spec via `--chain`.".to_string(),
347 ));
348 },
349 _ => {},
350 };
351 Ok(())
352 }
353
354 pub fn run_with_default_builder_and_spec<Block, ExtraHF>(
359 &self,
360 chain_spec: Option<Box<dyn ChainSpec>>,
361 ) -> Result<()>
362 where
363 Block: BlockT<Extrinsic = OpaqueExtrinsic, Hash = H256>,
364 ExtraHF: HostFunctions,
365 {
366 self.run_with_extrinsic_builder_and_spec::<Block, ExtraHF>(
367 Box::new(|metadata, hash, version| {
368 let genesis = subxt::utils::H256::from(hash.to_fixed_bytes());
369 Box::new(SubstrateRemarkBuilder::new(metadata, genesis, version)) as Box<_>
370 }),
371 chain_spec,
372 )
373 }
374
375 pub fn run_with_extrinsic_builder_and_spec<Block, ExtraHF>(
381 &self,
382 ext_builder_provider: Box<
383 dyn FnOnce(Metadata, Block::Hash, RuntimeVersion) -> Box<dyn ExtrinsicBuilder>,
384 >,
385 chain_spec: Option<Box<dyn ChainSpec>>,
386 ) -> Result<()>
387 where
388 Block: BlockT<Extrinsic = OpaqueExtrinsic>,
389 ExtraHF: HostFunctions,
390 {
391 if let Err((error_kind, msg)) = self.check_args(&chain_spec) {
392 let mut cmd = OverheadCmd::command();
393 cmd.error(error_kind, msg).exit();
394 };
395
396 let (state_handler, para_id) =
397 self.state_handler_from_cli::<(ParachainHostFunctions, ExtraHF)>(chain_spec)?;
398
399 let executor = WasmExecutor::<(ParachainHostFunctions, ExtraHF)>::builder()
400 .with_allow_missing_host_functions(true)
401 .build();
402
403 let opaque_metadata =
404 fetch_latest_metadata_from_code_blob(&executor, state_handler.get_code_bytes()?)
405 .map_err(|_| {
406 <&str as Into<sc_cli::Error>>::into("Unable to fetch latest stable metadata")
407 })?;
408 let metadata = subxt::Metadata::decode(&mut (*opaque_metadata).as_slice())?;
409
410 let chain_type = identify_chain(&metadata, para_id);
412
413 let genesis_patcher = match chain_type {
416 Parachain(para_id) =>
417 Some(Box::new(move |value| patch_genesis(value, Some(para_id))) as Box<_>),
418 _ => None,
419 };
420
421 let client = self.build_client_components::<Block, (ParachainHostFunctions, ExtraHF)>(
422 state_handler.build_storage::<(ParachainHostFunctions, ExtraHF)>(genesis_patcher)?,
423 executor,
424 &chain_type,
425 )?;
426
427 let inherent_data = create_inherent_data(&client, &chain_type);
428
429 let (ext_builder, runtime_name) = {
430 let genesis = client.usage_info().chain.best_hash;
431 let version = client.runtime_api().version(genesis).unwrap();
432 let runtime_name = version.spec_name;
433 let runtime_version = RuntimeVersion {
434 spec_version: version.spec_version,
435 transaction_version: version.transaction_version,
436 };
437
438 (ext_builder_provider(metadata, genesis, runtime_version), runtime_name)
439 };
440
441 self.run(
442 runtime_name.to_string(),
443 client,
444 inherent_data,
445 Default::default(),
446 &*ext_builder,
447 chain_type.requires_proof_recording(),
448 )
449 }
450
451 pub fn run_with_extrinsic_builder<Block, ExtraHF>(
453 &self,
454 ext_builder_provider: Box<
455 dyn FnOnce(Metadata, Block::Hash, RuntimeVersion) -> Box<dyn ExtrinsicBuilder>,
456 >,
457 ) -> Result<()>
458 where
459 Block: BlockT<Extrinsic = OpaqueExtrinsic>,
460 ExtraHF: HostFunctions,
461 {
462 self.run_with_extrinsic_builder_and_spec::<Block, ExtraHF>(ext_builder_provider, None)
463 }
464
465 fn build_client_components<Block, HF>(
466 &self,
467 genesis_storage: Storage,
468 executor: WasmExecutor<HF>,
469 chain_type: &ChainType,
470 ) -> Result<Arc<OverheadClient<Block, HF>>>
471 where
472 Block: BlockT,
473 HF: HostFunctions,
474 {
475 let extensions = ExecutionExtensions::new(None, Arc::new(executor.clone()));
476
477 let base_path = match &self.shared_params.base_path {
478 None => BasePath::new_temp_dir()?,
479 Some(path) => BasePath::from(path.clone()),
480 };
481
482 let database_source = self.database_config(
483 &base_path.path().to_path_buf(),
484 self.database_cache_size()?.unwrap_or(1024),
485 self.database()?.unwrap_or(Database::Auto),
486 )?;
487
488 let backend = new_db_backend(DatabaseSettings {
489 trie_cache_maximum_size: self.trie_cache_maximum_size()?,
490 state_pruning: None,
491 blocks_pruning: BlocksPruning::KeepAll,
492 source: database_source,
493 metrics_registry: None,
494 })?;
495
496 let genesis_block_builder = GenesisBlockBuilder::new_with_storage(
497 genesis_storage,
498 true,
499 backend.clone(),
500 executor.clone(),
501 )?;
502
503 let tokio_runtime = sc_cli::build_runtime()?;
504 let task_manager = TaskManager::new(tokio_runtime.handle().clone(), None)
505 .map_err(|_| "Unable to build task manager")?;
506
507 let client: Arc<OverheadClient<Block, HF>> = Arc::new(new_client(
508 backend.clone(),
509 executor,
510 genesis_block_builder,
511 Default::default(),
512 Default::default(),
513 extensions,
514 Box::new(task_manager.spawn_handle()),
515 None,
516 None,
517 ClientConfig {
518 offchain_worker_enabled: false,
519 offchain_indexing_api: false,
520 wasm_runtime_overrides: None,
521 no_genesis: false,
522 wasm_runtime_substitutes: Default::default(),
523 enable_import_proof_recording: chain_type.requires_proof_recording(),
524 },
525 )?);
526
527 Ok(client)
528 }
529
530 pub fn run<Block, C>(
535 &self,
536 chain_name: String,
537 client: Arc<C>,
538 inherent_data: sp_inherents::InherentData,
539 digest_items: Vec<DigestItem>,
540 ext_builder: &dyn ExtrinsicBuilder,
541 should_record_proof: bool,
542 ) -> Result<()>
543 where
544 Block: BlockT<Extrinsic = OpaqueExtrinsic>,
545 C: ProvideRuntimeApi<Block>
546 + CallApiAt<Block>
547 + UsageProvider<Block>
548 + sp_blockchain::HeaderBackend<Block>,
549 C::Api: ApiExt<Block> + BlockBuilderApi<Block>,
550 {
551 if ext_builder.pallet() != "system" || ext_builder.extrinsic() != "remark" {
552 return Err(format!("The extrinsic builder is required to build `System::Remark` extrinsics but builds `{}` extrinsics instead", ext_builder.name()).into());
553 }
554
555 let bench = Benchmark::new(
556 client,
557 self.params.bench.clone(),
558 inherent_data,
559 digest_items,
560 should_record_proof,
561 );
562
563 {
565 let (stats, proof_size) = bench.bench_block()?;
566 info!(target: LOG_TARGET, "Per-block execution overhead [ns]:\n{:?}", stats);
567 let template = TemplateData::new(
568 BenchmarkType::Block,
569 &chain_name,
570 &self.params,
571 &stats,
572 proof_size,
573 )?;
574 template.write(&self.params.weight.weight_path)?;
575 }
576 {
578 let (stats, proof_size) = bench.bench_extrinsic(ext_builder)?;
579 info!(target: LOG_TARGET, "Per-extrinsic execution overhead [ns]:\n{:?}", stats);
580 let template = TemplateData::new(
581 BenchmarkType::Extrinsic,
582 &chain_name,
583 &self.params,
584 &stats,
585 proof_size,
586 )?;
587 template.write(&self.params.weight.weight_path)?;
588 }
589
590 Ok(())
591 }
592}
593
594impl BenchmarkType {
595 pub(crate) fn short_name(&self) -> &'static str {
597 match self {
598 Self::Extrinsic => "extrinsic",
599 Self::Block => "block",
600 }
601 }
602
603 pub(crate) fn long_name(&self) -> &'static str {
605 match self {
606 Self::Extrinsic => "ExtrinsicBase",
607 Self::Block => "BlockExecution",
608 }
609 }
610}
611
612#[derive(Clone, PartialEq, Debug)]
613enum ChainType {
614 Parachain(u32),
615 Relaychain,
616 Unknown,
617}
618
619impl Display for ChainType {
620 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
621 match self {
622 ChainType::Parachain(id) => write!(f, "Parachain(paraid = {})", id),
623 ChainType::Relaychain => write!(f, "Relaychain"),
624 ChainType::Unknown => write!(f, "Unknown"),
625 }
626 }
627}
628
629impl ChainType {
630 fn requires_proof_recording(&self) -> bool {
631 match self {
632 Parachain(_) => true,
633 Relaychain => false,
634 Unknown => false,
635 }
636 }
637}
638
639fn patch_genesis(mut input_value: Value, para_id: Option<u32>) -> Value {
642 if let Some(para_id) = para_id {
646 sc_chain_spec::json_patch::merge(
647 &mut input_value,
648 json!({
649 "parachainInfo": {
650 "parachainId": para_id,
651 }
652 }),
653 );
654 log::debug!(target: LOG_TARGET, "Genesis Config Json");
655 log::debug!(target: LOG_TARGET, "{}", input_value);
656 }
657 input_value
658}
659
660impl CliConfiguration for OverheadCmd {
662 fn shared_params(&self) -> &SharedParams {
663 &self.shared_params
664 }
665
666 fn import_params(&self) -> Option<&ImportParams> {
667 Some(&self.import_params)
668 }
669
670 fn base_path(&self) -> Result<Option<BasePath>> {
671 Ok(Some(BasePath::new_temp_dir()?))
672 }
673
674 fn trie_cache_maximum_size(&self) -> Result<Option<usize>> {
675 if self.params.enable_trie_cache {
676 Ok(self.import_params().map(|x| x.trie_cache_maximum_size()).unwrap_or_default())
677 } else {
678 Ok(None)
679 }
680 }
681}
682
683#[cfg(test)]
684mod tests {
685 use crate::{
686 overhead::command::{identify_chain, ChainType, ParachainHostFunctions, DEFAULT_PARA_ID},
687 OverheadCmd,
688 };
689 use clap::Parser;
690 use codec::Decode;
691 use sc_executor::WasmExecutor;
692
693 #[test]
694 fn test_chain_type_relaychain() {
695 let executor: WasmExecutor<ParachainHostFunctions> = WasmExecutor::builder().build();
696 let code_bytes = westend_runtime::WASM_BINARY
697 .expect("To run this test, build the wasm binary of westend-runtime")
698 .to_vec();
699 let opaque_metadata =
700 super::fetch_latest_metadata_from_code_blob(&executor, code_bytes.into()).unwrap();
701 let metadata = subxt::Metadata::decode(&mut (*opaque_metadata).as_slice()).unwrap();
702 let chain_type = identify_chain(&metadata, None);
703 assert_eq!(chain_type, ChainType::Relaychain);
704 assert_eq!(chain_type.requires_proof_recording(), false);
705 }
706
707 #[test]
708 fn test_chain_type_parachain() {
709 let executor: WasmExecutor<ParachainHostFunctions> = WasmExecutor::builder().build();
710 let code_bytes = cumulus_test_runtime::WASM_BINARY
711 .expect("To run this test, build the wasm binary of cumulus-test-runtime")
712 .to_vec();
713 let opaque_metadata =
714 super::fetch_latest_metadata_from_code_blob(&executor, code_bytes.into()).unwrap();
715 let metadata = subxt::Metadata::decode(&mut (*opaque_metadata).as_slice()).unwrap();
716 let chain_type = identify_chain(&metadata, Some(100));
717 assert_eq!(chain_type, ChainType::Parachain(100));
718 assert!(chain_type.requires_proof_recording());
719 assert_eq!(identify_chain(&metadata, None), ChainType::Parachain(DEFAULT_PARA_ID));
720 }
721
722 #[test]
723 fn test_chain_type_custom() {
724 let executor: WasmExecutor<ParachainHostFunctions> = WasmExecutor::builder().build();
725 let code_bytes = substrate_test_runtime::WASM_BINARY
726 .expect("To run this test, build the wasm binary of substrate-test-runtime")
727 .to_vec();
728 let opaque_metadata =
729 super::fetch_latest_metadata_from_code_blob(&executor, code_bytes.into()).unwrap();
730 let metadata = subxt::Metadata::decode(&mut (*opaque_metadata).as_slice()).unwrap();
731 let chain_type = identify_chain(&metadata, None);
732 assert_eq!(chain_type, ChainType::Unknown);
733 assert_eq!(chain_type.requires_proof_recording(), false);
734 }
735
736 fn cli_succeed(args: &[&str]) -> Result<(), clap::Error> {
737 let cmd = OverheadCmd::try_parse_from(args)?;
738 assert!(cmd.check_args(&None).is_ok());
739 Ok(())
740 }
741
742 fn cli_fail(args: &[&str]) {
743 let cmd = OverheadCmd::try_parse_from(args);
744 if let Ok(cmd) = cmd {
745 assert!(cmd.check_args(&None).is_err());
746 }
747 }
748
749 #[test]
750 fn test_cli_conflicts() -> Result<(), clap::Error> {
751 cli_succeed(&["test", "--runtime", "path/to/runtime", "--genesis-builder", "runtime"])?;
753 cli_succeed(&["test", "--runtime", "path/to/runtime"])?;
754 cli_succeed(&[
755 "test",
756 "--runtime",
757 "path/to/runtime",
758 "--genesis-builder-preset",
759 "preset",
760 ])?;
761 cli_fail(&["test", "--runtime", "path/to/spec", "--genesis-builder", "spec"]);
762 cli_fail(&["test", "--runtime", "path/to/spec", "--genesis-builder", "spec-genesis"]);
763 cli_fail(&["test", "--runtime", "path/to/spec", "--genesis-builder", "spec-runtime"]);
764
765 cli_succeed(&["test", "--chain", "path/to/spec"])?;
767 cli_succeed(&["test", "--chain", "path/to/spec", "--genesis-builder", "spec"])?;
768 cli_succeed(&["test", "--chain", "path/to/spec", "--genesis-builder", "spec-genesis"])?;
769 cli_succeed(&["test", "--chain", "path/to/spec", "--genesis-builder", "spec-runtime"])?;
770 cli_fail(&["test", "--chain", "path/to/spec", "--genesis-builder", "none"]);
771 cli_fail(&["test", "--chain", "path/to/spec", "--genesis-builder", "runtime"]);
772 cli_fail(&[
773 "test",
774 "--chain",
775 "path/to/spec",
776 "--genesis-builder",
777 "runtime",
778 "--genesis-builder-preset",
779 "preset",
780 ]);
781 Ok(())
782 }
783}