Skip to main content

pezframe_benchmarking_cli/overhead/
command.rs

1// This file is part of Bizinikiwi.
2
3// Copyright (C) Parity Technologies (UK) Ltd. and Dijital Kurdistan Tech Institute
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18//! Contains the [`OverheadCmd`] as entry point for the CLI to execute
19//! the *overhead* benchmarks.
20
21use crate::{
22	extrinsic::{
23		bench::{Benchmark, BenchmarkParams as ExtrinsicBenchmarkParams},
24		ExtrinsicBuilder,
25	},
26	overhead::{
27		command::ChainType::{Relaychain, Teyrchain, Unknown},
28		fake_runtime_api,
29		remark_builder::BizinikiwiRemarkBuilder,
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;
40#[cfg(feature = "teyrchain-benchmarks")]
41use codec::Encode;
42use fake_runtime_api::RuntimeApi as FakeRuntimeApi;
43use genesis_state::WARN_SPEC_GENESIS_CTOR;
44use log::info;
45// MockValidationDataInherentDataProvider is feature-gated because it requires pezcumulus-test-relay-sproof-builder
46#[cfg(feature = "teyrchain-benchmarks")]
47use pezcumulus_client_teyrchain_inherent::MockValidationDataInherentDataProvider;
48use pezframe_support::Deserialize;
49use pezkuwi_subxt::{client::RuntimeVersion, ext::futures, Metadata};
50#[cfg(feature = "teyrchain-benchmarks")]
51use pezkuwi_teyrchain_primitives::primitives::Id as ParaId;
52use pezsc_block_builder::BlockBuilderApi;
53use pezsc_chain_spec::{ChainSpec, ChainSpecExtension, GenesisBlockBuilder};
54use pezsc_cli::{CliConfiguration, Database, ImportParams, Result, SharedParams};
55use pezsc_client_api::{execution_extensions::ExecutionExtensions, UsageProvider};
56use pezsc_client_db::{BlocksPruning, DatabaseSettings};
57use pezsc_executor::WasmExecutor;
58use pezsc_runtime_utilities::fetch_latest_metadata_from_code_blob;
59use pezsc_service::{new_client, new_db_backend, BasePath, ClientConfig, TFullClient, TaskManager};
60use pezsp_api::{ApiExt, CallApiAt, Core, ProvideRuntimeApi};
61use pezsp_blockchain::HeaderBackend;
62use pezsp_core::H256;
63use pezsp_inherents::{InherentData, InherentDataProvider};
64use pezsp_runtime::{
65	generic,
66	traits::{BlakeTwo256, Block as BlockT},
67	DigestItem, OpaqueExtrinsic,
68};
69use pezsp_storage::Storage;
70use pezsp_wasm_interface::HostFunctions;
71use serde::Serialize;
72use serde_json::{json, Value};
73use std::{
74	fmt::{Debug, Display, Formatter},
75	fs,
76	path::PathBuf,
77	sync::Arc,
78};
79
80const DEFAULT_PARA_ID: u32 = 100;
81const LOG_TARGET: &'static str = "pezframe::benchmark::overhead";
82
83/// Benchmark the execution overhead per-block and per-extrinsic.
84#[derive(Debug, Parser)]
85pub struct OverheadCmd {
86	#[allow(missing_docs)]
87	#[clap(flatten)]
88	pub shared_params: SharedParams,
89
90	#[allow(missing_docs)]
91	#[clap(flatten)]
92	pub import_params: ImportParams,
93
94	#[allow(missing_docs)]
95	#[clap(flatten)]
96	pub params: OverheadParams,
97}
98
99/// Configures the benchmark, the post-processing and weight generation.
100#[derive(Debug, Default, Serialize, Clone, PartialEq, Args)]
101pub struct OverheadParams {
102	#[allow(missing_docs)]
103	#[clap(flatten)]
104	pub weight: WeightParams,
105
106	#[allow(missing_docs)]
107	#[clap(flatten)]
108	pub bench: ExtrinsicBenchmarkParams,
109
110	#[allow(missing_docs)]
111	#[clap(flatten)]
112	pub hostinfo: HostInfoParams,
113
114	/// Add a header to the generated weight output file.
115	///
116	/// Good for adding LICENSE headers.
117	#[arg(long, value_name = "PATH")]
118	pub header: Option<PathBuf>,
119
120	/// Enable the Trie cache.
121	///
122	/// This should only be used for performance analysis and not for final results.
123	#[arg(long)]
124	pub enable_trie_cache: bool,
125
126	/// Optional runtime blob to use instead of the one from the genesis config.
127	#[arg(
128		long,
129		value_name = "PATH",
130		conflicts_with = "chain",
131		required_if_eq("genesis_builder", "runtime")
132	)]
133	pub runtime: Option<PathBuf>,
134
135	/// The preset that we expect to find in the GenesisBuilder runtime API.
136	///
137	/// This can be useful when a runtime has a dedicated benchmarking preset instead of using the
138	/// default one.
139	#[arg(long, default_value = pezsp_genesis_builder::DEV_RUNTIME_PRESET)]
140	pub genesis_builder_preset: String,
141
142	/// How to construct the genesis state.
143	///
144	/// Can be used together with `--chain` to determine whether the
145	/// genesis state should be initialized with the values from the
146	/// provided chain spec or a runtime-provided genesis preset.
147	#[arg(long, value_enum, alias = "genesis-builder-policy")]
148	pub genesis_builder: Option<GenesisBuilderPolicy>,
149
150	/// Teyrchain Id to use for teyrchains. If not specified, the benchmark code will choose
151	/// a para-id and patch the state accordingly.
152	#[arg(long)]
153	pub para_id: Option<u32>,
154}
155
156/// How the genesis state for benchmarking should be built.
157#[derive(clap::ValueEnum, Debug, Eq, PartialEq, Clone, Copy, Serialize)]
158#[clap(rename_all = "kebab-case")]
159pub enum GenesisBuilderPolicy {
160	/// Let the runtime build the genesis state through its `BuildGenesisConfig` runtime API.
161	/// This will use the `development` preset by default.
162	Runtime,
163	/// Use the runtime from the Spec file to build the genesis state.
164	SpecRuntime,
165	/// Use the spec file to build the genesis state. This fails when there is no spec.
166	#[value(alias = "spec")]
167	SpecGenesis,
168}
169
170/// Type of a benchmark.
171#[derive(Serialize, Clone, PartialEq, Copy)]
172pub(crate) enum BenchmarkType {
173	/// Measure the per-extrinsic execution overhead.
174	Extrinsic,
175	/// Measure the per-block execution overhead.
176	Block,
177}
178
179/// Hostfunctions that are typically used by teyrchains.
180pub type TeyrchainHostFunctions = (
181	pezcumulus_primitives_proof_size_hostfunction::storage_proof_size::HostFunctions,
182	pezsp_io::BizinikiwiHostFunctions,
183);
184
185pub type BlockNumber = u32;
186
187/// Typical block header.
188pub type Header = generic::Header<BlockNumber, BlakeTwo256>;
189
190/// Typical block type using `OpaqueExtrinsic`.
191pub type OpaqueBlock = generic::Block<Header, OpaqueExtrinsic>;
192
193/// Client type used throughout the benchmarking code.
194type OverheadClient<Block, HF> = TFullClient<Block, FakeRuntimeApi, WasmExecutor<HF>>;
195
196/// Creates inherent data for a given teyrchain ID.
197///
198/// This function constructs the inherent data required for block execution,
199/// including the relay chain state and validation data. Not all of these
200/// inherents are required for every chain. The runtime will pick the ones
201/// it requires based on their identifier.
202fn create_inherent_data<Client: UsageProvider<Block> + HeaderBackend<Block>, Block: BlockT>(
203	client: &Arc<Client>,
204	chain_type: &ChainType,
205) -> InherentData {
206	let genesis = client.usage_info().chain.best_hash;
207	let header = client.header(genesis).unwrap().unwrap();
208
209	let mut inherent_data = InherentData::new();
210
211	// Para inherent can only makes sense when we are handling a teyrchain.
212	// This requires the teyrchain-benchmarks feature which depends on pezcumulus-test-relay-sproof-builder
213	#[cfg(feature = "teyrchain-benchmarks")]
214	if let Teyrchain(para_id) = chain_type {
215		let teyrchain_validation_data_provider = MockValidationDataInherentDataProvider::<()> {
216			para_id: ParaId::from(*para_id),
217			current_para_block_head: Some(header.encode().into()),
218			relay_offset: 0,
219			..Default::default()
220		};
221		let _ = futures::executor::block_on(
222			teyrchain_validation_data_provider.provide_inherent_data(&mut inherent_data),
223		);
224	}
225	#[cfg(not(feature = "teyrchain-benchmarks"))]
226	if let Teyrchain(_) = chain_type {
227		log::warn!("Teyrchain benchmark inherents not available. Enable the 'teyrchain-benchmarks' feature.");
228	}
229
230	// Teyrchain inherent that is used on relay chains to perform teyrchain validation.
231	let para_inherent = pezkuwi_primitives::InherentData {
232		bitfields: Vec::new(),
233		backed_candidates: Vec::new(),
234		disputes: Vec::new(),
235		parent_header: header,
236	};
237
238	// Timestamp inherent that is very common in bizinikiwi chains.
239	let timestamp =
240		pezsp_timestamp::InherentDataProvider::new(std::time::Duration::default().into());
241
242	let _ = futures::executor::block_on(timestamp.provide_inherent_data(&mut inherent_data));
243	let _ =
244		inherent_data.put_data(pezkuwi_primitives::TEYRCHAINS_INHERENT_IDENTIFIER, &para_inherent);
245
246	inherent_data
247}
248
249/// Identifies what kind of chain we are dealing with.
250///
251/// Chains containing the `TeyrchainSystem` and `TeyrchainInfo` pezpallet are considered teyrchains.
252/// Chains containing the `ParaInherent` pezpallet are considered relay chains.
253fn identify_chain(metadata: &Metadata, para_id: Option<u32>) -> ChainType {
254	let teyrchain_info_exists = metadata.pallet_by_name("TeyrchainInfo").is_some();
255	let teyrchain_system_exists = metadata.pallet_by_name("TeyrchainSystem").is_some();
256	let para_inherent_exists = metadata.pallet_by_name("ParaInherent").is_some();
257
258	log::debug!("{} TeyrchainSystem", if teyrchain_system_exists { "✅" } else { "❌" });
259	log::debug!("{} TeyrchainInfo", if teyrchain_info_exists { "✅" } else { "❌" });
260	log::debug!("{} ParaInherent", if para_inherent_exists { "✅" } else { "❌" });
261
262	let chain_type = if teyrchain_system_exists && teyrchain_info_exists {
263		Teyrchain(para_id.unwrap_or(DEFAULT_PARA_ID))
264	} else if para_inherent_exists {
265		Relaychain
266	} else {
267		Unknown
268	};
269
270	log::info!(target: LOG_TARGET, "Identified Chain type from metadata: {}", chain_type);
271
272	chain_type
273}
274
275#[derive(Deserialize, Serialize, Clone, ChainSpecExtension)]
276pub struct TeyrchainExtension {
277	/// The id of the Teyrchain.
278	pub para_id: Option<u32>,
279}
280
281impl OverheadCmd {
282	fn state_handler_from_cli<HF: HostFunctions>(
283		&self,
284		chain_spec_from_api: Option<Box<dyn ChainSpec>>,
285	) -> Result<(GenesisStateHandler, Option<u32>)> {
286		let genesis_builder_to_source = || match self.params.genesis_builder {
287			Some(GenesisBuilderPolicy::Runtime) | Some(GenesisBuilderPolicy::SpecRuntime) => {
288				SpecGenesisSource::Runtime(self.params.genesis_builder_preset.clone())
289			},
290			Some(GenesisBuilderPolicy::SpecGenesis) | None => {
291				log::warn!(target: LOG_TARGET, "{WARN_SPEC_GENESIS_CTOR}");
292				SpecGenesisSource::SpecJson
293			},
294		};
295
296		// First handle chain-spec passed in via API parameter.
297		if let Some(chain_spec) = chain_spec_from_api {
298			log::debug!(target: LOG_TARGET, "Initializing state handler with chain-spec from API: {:?}", chain_spec);
299
300			let source = genesis_builder_to_source();
301			return Ok((GenesisStateHandler::ChainSpec(chain_spec, source), self.params.para_id));
302		};
303
304		// Handle chain-spec passed in via CLI.
305		if let Some(chain_spec_path) = &self.shared_params.chain {
306			log::debug!(target: LOG_TARGET,
307				"Initializing state handler with chain-spec from path: {:?}",
308				chain_spec_path
309			);
310			let (chain_spec, para_id_from_chain_spec) =
311				genesis_state::chain_spec_from_path::<HF>(chain_spec_path.to_string().into())?;
312
313			let source = genesis_builder_to_source();
314
315			return Ok((
316				GenesisStateHandler::ChainSpec(chain_spec, source),
317				self.params.para_id.or(para_id_from_chain_spec),
318			));
319		};
320
321		// Check for runtimes. In general, we make sure that `--runtime` and `--chain` are
322		// incompatible on the CLI level.
323		if let Some(runtime_path) = &self.params.runtime {
324			log::debug!(target: LOG_TARGET, "Initializing state handler with runtime from path: {:?}", runtime_path);
325
326			let runtime_blob = fs::read(runtime_path)?;
327			return Ok((
328				GenesisStateHandler::Runtime(
329					runtime_blob,
330					Some(self.params.genesis_builder_preset.clone()),
331				),
332				self.params.para_id,
333			));
334		};
335
336		Err("Neither a runtime nor a chain-spec were specified".to_string().into())
337	}
338
339	fn check_args(
340		&self,
341		chain_spec: &Option<Box<dyn ChainSpec>>,
342	) -> std::result::Result<(), (ErrorKind, String)> {
343		if chain_spec.is_none()
344			&& self.params.runtime.is_none()
345			&& self.shared_params.chain.is_none()
346		{
347			return Err((
348				ErrorKind::MissingRequiredArgument,
349				"Provide either a runtime via `--runtime` or a chain spec via `--chain`"
350					.to_string(),
351			));
352		}
353
354		match self.params.genesis_builder {
355			Some(GenesisBuilderPolicy::SpecGenesis | GenesisBuilderPolicy::SpecRuntime) => {
356				if chain_spec.is_none() && self.shared_params.chain.is_none() {
357					return Err((
358						ErrorKind::MissingRequiredArgument,
359						"Provide a chain spec via `--chain`.".to_string(),
360					));
361				}
362			},
363			_ => {},
364		};
365		Ok(())
366	}
367
368	/// Run the overhead benchmark with the default extrinsic builder.
369	///
370	/// This will use [BizinikiwiRemarkBuilder] to build the extrinsic. It is
371	/// designed to match common configurations found in bizinikiwi chains.
372	pub fn run_with_default_builder_and_spec<Block, ExtraHF>(
373		&self,
374		chain_spec: Option<Box<dyn ChainSpec>>,
375	) -> Result<()>
376	where
377		Block: BlockT<Extrinsic = OpaqueExtrinsic, Hash = H256>,
378		ExtraHF: HostFunctions,
379	{
380		self.run_with_extrinsic_builder_and_spec::<Block, ExtraHF>(
381			Box::new(|metadata, hash, version| {
382				let genesis = pezkuwi_subxt::utils::H256::from(hash.to_fixed_bytes());
383				Box::new(BizinikiwiRemarkBuilder::new(metadata, genesis, version)) as Box<_>
384			}),
385			chain_spec,
386		)
387	}
388
389	/// Run the benchmark overhead command.
390	///
391	/// The provided [ExtrinsicBuilder] will be used to build extrinsics for
392	/// block-building. It is expected that the provided implementation builds
393	/// a `System::remark` extrinsic.
394	pub fn run_with_extrinsic_builder_and_spec<Block, ExtraHF>(
395		&self,
396		ext_builder_provider: Box<
397			dyn FnOnce(Metadata, Block::Hash, RuntimeVersion) -> Box<dyn ExtrinsicBuilder>,
398		>,
399		chain_spec: Option<Box<dyn ChainSpec>>,
400	) -> Result<()>
401	where
402		Block: BlockT<Extrinsic = OpaqueExtrinsic>,
403		ExtraHF: HostFunctions,
404	{
405		if let Err((error_kind, msg)) = self.check_args(&chain_spec) {
406			let mut cmd = OverheadCmd::command();
407			cmd.error(error_kind, msg).exit();
408		};
409
410		let (state_handler, para_id) =
411			self.state_handler_from_cli::<(TeyrchainHostFunctions, ExtraHF)>(chain_spec)?;
412
413		let executor = WasmExecutor::<(TeyrchainHostFunctions, ExtraHF)>::builder()
414			.with_allow_missing_host_functions(true)
415			.build();
416
417		let opaque_metadata =
418			fetch_latest_metadata_from_code_blob(&executor, state_handler.get_code_bytes()?)
419				.map_err(|_| {
420					<&str as Into<pezsc_cli::Error>>::into("Unable to fetch latest stable metadata")
421				})?;
422		let metadata = pezkuwi_subxt::Metadata::decode(&mut (*opaque_metadata).as_slice())?;
423
424		// At this point we know what kind of chain we are dealing with.
425		let chain_type = identify_chain(&metadata, para_id);
426
427		// If we are dealing  with a teyrchain, make sure that the para id in genesis will
428		// match what we expect.
429		let genesis_patcher = match chain_type {
430			Teyrchain(para_id) => {
431				Some(Box::new(move |value| patch_genesis(value, Some(para_id))) as Box<_>)
432			},
433			_ => None,
434		};
435
436		let client = self.build_client_components::<Block, (TeyrchainHostFunctions, ExtraHF)>(
437			state_handler.build_storage::<(TeyrchainHostFunctions, ExtraHF)>(genesis_patcher)?,
438			executor,
439			&chain_type,
440		)?;
441
442		let inherent_data = create_inherent_data(&client, &chain_type);
443
444		let (ext_builder, runtime_name) = {
445			let genesis = client.usage_info().chain.best_hash;
446			let version = client.runtime_api().version(genesis).unwrap();
447			let runtime_name = version.spec_name;
448			let runtime_version = RuntimeVersion {
449				spec_version: version.spec_version,
450				transaction_version: version.transaction_version,
451			};
452
453			(ext_builder_provider(metadata, genesis, runtime_version), runtime_name)
454		};
455
456		self.run(
457			runtime_name.to_string(),
458			client,
459			inherent_data,
460			Default::default(),
461			&*ext_builder,
462			chain_type.requires_proof_recording(),
463		)
464	}
465
466	/// Run the benchmark overhead command.
467	pub fn run_with_extrinsic_builder<Block, ExtraHF>(
468		&self,
469		ext_builder_provider: Box<
470			dyn FnOnce(Metadata, Block::Hash, RuntimeVersion) -> Box<dyn ExtrinsicBuilder>,
471		>,
472	) -> Result<()>
473	where
474		Block: BlockT<Extrinsic = OpaqueExtrinsic>,
475		ExtraHF: HostFunctions,
476	{
477		self.run_with_extrinsic_builder_and_spec::<Block, ExtraHF>(ext_builder_provider, None)
478	}
479
480	fn build_client_components<Block, HF>(
481		&self,
482		genesis_storage: Storage,
483		executor: WasmExecutor<HF>,
484		chain_type: &ChainType,
485	) -> Result<Arc<OverheadClient<Block, HF>>>
486	where
487		Block: BlockT,
488		HF: HostFunctions,
489	{
490		let extensions = ExecutionExtensions::new(None, Arc::new(executor.clone()));
491
492		let base_path = match &self.shared_params.base_path {
493			None => BasePath::new_temp_dir()?,
494			Some(path) => BasePath::from(path.clone()),
495		};
496
497		let database_source = self.database_config(
498			&base_path.path().to_path_buf(),
499			self.database_cache_size()?.unwrap_or(1024),
500			self.database()?.unwrap_or(Database::Auto),
501		)?;
502
503		let backend = new_db_backend(DatabaseSettings {
504			trie_cache_maximum_size: self.trie_cache_maximum_size()?,
505			state_pruning: None,
506			blocks_pruning: BlocksPruning::KeepAll,
507			source: database_source,
508			metrics_registry: None,
509		})?;
510
511		let genesis_block_builder = GenesisBlockBuilder::new_with_storage(
512			genesis_storage,
513			true,
514			backend.clone(),
515			executor.clone(),
516		)?;
517
518		let tokio_runtime = pezsc_cli::build_runtime()?;
519		let task_manager = TaskManager::new(tokio_runtime.handle().clone(), None)
520			.map_err(|_| "Unable to build task manager")?;
521
522		let client: Arc<OverheadClient<Block, HF>> = Arc::new(new_client(
523			backend.clone(),
524			executor,
525			genesis_block_builder,
526			Default::default(),
527			Default::default(),
528			extensions,
529			Box::new(task_manager.spawn_handle()),
530			None,
531			None,
532			ClientConfig {
533				offchain_worker_enabled: false,
534				offchain_indexing_api: false,
535				wasm_runtime_overrides: None,
536				no_genesis: false,
537				wasm_runtime_substitutes: Default::default(),
538				enable_import_proof_recording: chain_type.requires_proof_recording(),
539			},
540		)?);
541
542		Ok(client)
543	}
544
545	/// Measure the per-block and per-extrinsic execution overhead.
546	///
547	/// Writes the results to console and into two instances of the
548	/// `weights.hbs` template, one for each benchmark.
549	pub fn run<Block, C>(
550		&self,
551		chain_name: String,
552		client: Arc<C>,
553		inherent_data: pezsp_inherents::InherentData,
554		digest_items: Vec<DigestItem>,
555		ext_builder: &dyn ExtrinsicBuilder,
556		should_record_proof: bool,
557	) -> Result<()>
558	where
559		Block: BlockT<Extrinsic = OpaqueExtrinsic>,
560		C: ProvideRuntimeApi<Block>
561			+ CallApiAt<Block>
562			+ UsageProvider<Block>
563			+ pezsp_blockchain::HeaderBackend<Block>,
564		C::Api: ApiExt<Block> + BlockBuilderApi<Block>,
565	{
566		if ext_builder.pezpallet() != "system" || ext_builder.extrinsic() != "remark" {
567			return Err(format!("The extrinsic builder is required to build `System::Remark` extrinsics but builds `{}` extrinsics instead", ext_builder.name()).into());
568		}
569
570		let bench = Benchmark::new(
571			client,
572			self.params.bench.clone(),
573			inherent_data,
574			digest_items,
575			should_record_proof,
576		);
577
578		// per-block execution overhead
579		{
580			let (stats, proof_size) = bench.bench_block()?;
581			info!(target: LOG_TARGET, "Per-block execution overhead [ns]:\n{:?}", stats);
582			let template = TemplateData::new(
583				BenchmarkType::Block,
584				&chain_name,
585				&self.params,
586				&stats,
587				proof_size,
588			)?;
589			template.write(&self.params.weight.weight_path)?;
590		}
591		// per-extrinsic execution overhead
592		{
593			let (stats, proof_size) = bench.bench_extrinsic(ext_builder)?;
594			info!(target: LOG_TARGET, "Per-extrinsic execution overhead [ns]:\n{:?}", stats);
595			let template = TemplateData::new(
596				BenchmarkType::Extrinsic,
597				&chain_name,
598				&self.params,
599				&stats,
600				proof_size,
601			)?;
602			template.write(&self.params.weight.weight_path)?;
603		}
604
605		Ok(())
606	}
607}
608
609impl BenchmarkType {
610	/// Short name of the benchmark type.
611	pub(crate) fn short_name(&self) -> &'static str {
612		match self {
613			Self::Extrinsic => "extrinsic",
614			Self::Block => "block",
615		}
616	}
617
618	/// Long name of the benchmark type.
619	pub(crate) fn long_name(&self) -> &'static str {
620		match self {
621			Self::Extrinsic => "ExtrinsicBase",
622			Self::Block => "BlockExecution",
623		}
624	}
625}
626
627#[derive(Clone, PartialEq, Debug)]
628enum ChainType {
629	Teyrchain(u32),
630	Relaychain,
631	Unknown,
632}
633
634impl Display for ChainType {
635	fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
636		match self {
637			ChainType::Teyrchain(id) => write!(f, "Teyrchain(paraid = {})", id),
638			ChainType::Relaychain => write!(f, "Relaychain"),
639			ChainType::Unknown => write!(f, "Unknown"),
640		}
641	}
642}
643
644impl ChainType {
645	fn requires_proof_recording(&self) -> bool {
646		match self {
647			Teyrchain(_) => true,
648			Relaychain => false,
649			Unknown => false,
650		}
651	}
652}
653
654/// Patch the teyrchain id into the genesis config. This is necessary since the inherents
655/// also contain a teyrchain id and they need to match.
656fn patch_genesis(mut input_value: Value, para_id: Option<u32>) -> Value {
657	// If we identified a teyrchain we should patch a teyrchain id into the genesis config.
658	// This ensures compatibility with the inherents that we provide to successfully build a
659	// block.
660	if let Some(para_id) = para_id {
661		pezsc_chain_spec::json_patch::merge(
662			&mut input_value,
663			json!({
664				"teyrchainInfo": {
665					"teyrchainId": para_id,
666				}
667			}),
668		);
669		log::debug!(target: LOG_TARGET, "Genesis Config Json");
670		log::debug!(target: LOG_TARGET, "{}", input_value);
671	}
672	input_value
673}
674
675// Boilerplate
676impl CliConfiguration for OverheadCmd {
677	fn shared_params(&self) -> &SharedParams {
678		&self.shared_params
679	}
680
681	fn import_params(&self) -> Option<&ImportParams> {
682		Some(&self.import_params)
683	}
684
685	fn base_path(&self) -> Result<Option<BasePath>> {
686		Ok(Some(BasePath::new_temp_dir()?))
687	}
688
689	fn trie_cache_maximum_size(&self) -> Result<Option<usize>> {
690		if self.params.enable_trie_cache {
691			Ok(self.import_params().map(|x| x.trie_cache_maximum_size()).unwrap_or_default())
692		} else {
693			Ok(None)
694		}
695	}
696}
697
698#[cfg(test)]
699mod tests {
700	use crate::{
701		overhead::command::{identify_chain, ChainType, TeyrchainHostFunctions, DEFAULT_PARA_ID},
702		OverheadCmd,
703	};
704	use clap::Parser;
705	use codec::Decode;
706	use pezsc_executor::WasmExecutor;
707
708	#[test]
709	fn test_chain_type_relaychain() {
710		let executor: WasmExecutor<TeyrchainHostFunctions> = WasmExecutor::builder().build();
711		let code_bytes = zagros_runtime::WASM_BINARY
712			.expect("To run this test, build the wasm binary of zagros-runtime")
713			.to_vec();
714		let opaque_metadata =
715			super::fetch_latest_metadata_from_code_blob(&executor, code_bytes.into()).unwrap();
716		let metadata = pezkuwi_subxt::Metadata::decode(&mut (*opaque_metadata).as_slice()).unwrap();
717		let chain_type = identify_chain(&metadata, None);
718		assert_eq!(chain_type, ChainType::Relaychain);
719		assert_eq!(chain_type.requires_proof_recording(), false);
720	}
721
722	#[test]
723	fn test_chain_type_teyrchain() {
724		let executor: WasmExecutor<TeyrchainHostFunctions> = WasmExecutor::builder().build();
725		let code_bytes = pezcumulus_test_runtime::WASM_BINARY
726			.expect("To run this test, build the wasm binary of pezcumulus-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 = pezkuwi_subxt::Metadata::decode(&mut (*opaque_metadata).as_slice()).unwrap();
731		let chain_type = identify_chain(&metadata, Some(100));
732		assert_eq!(chain_type, ChainType::Teyrchain(100));
733		assert!(chain_type.requires_proof_recording());
734		assert_eq!(identify_chain(&metadata, None), ChainType::Teyrchain(DEFAULT_PARA_ID));
735	}
736
737	#[test]
738	fn test_chain_type_custom() {
739		let executor: WasmExecutor<TeyrchainHostFunctions> = WasmExecutor::builder().build();
740		let code_bytes = bizinikiwi_test_runtime::WASM_BINARY
741			.expect("To run this test, build the wasm binary of bizinikiwi-test-runtime")
742			.to_vec();
743		let opaque_metadata =
744			super::fetch_latest_metadata_from_code_blob(&executor, code_bytes.into()).unwrap();
745		let metadata = pezkuwi_subxt::Metadata::decode(&mut (*opaque_metadata).as_slice()).unwrap();
746		let chain_type = identify_chain(&metadata, None);
747		assert_eq!(chain_type, ChainType::Unknown);
748		assert_eq!(chain_type.requires_proof_recording(), false);
749	}
750
751	fn cli_succeed(args: &[&str]) -> Result<(), clap::Error> {
752		let cmd = OverheadCmd::try_parse_from(args)?;
753		assert!(cmd.check_args(&None).is_ok());
754		Ok(())
755	}
756
757	fn cli_fail(args: &[&str]) {
758		let cmd = OverheadCmd::try_parse_from(args);
759		if let Ok(cmd) = cmd {
760			assert!(cmd.check_args(&None).is_err());
761		}
762	}
763
764	#[test]
765	fn test_cli_conflicts() -> Result<(), clap::Error> {
766		// Runtime tests
767		cli_succeed(&["test", "--runtime", "path/to/runtime", "--genesis-builder", "runtime"])?;
768		cli_succeed(&["test", "--runtime", "path/to/runtime"])?;
769		cli_succeed(&[
770			"test",
771			"--runtime",
772			"path/to/runtime",
773			"--genesis-builder-preset",
774			"preset",
775		])?;
776		cli_fail(&["test", "--runtime", "path/to/spec", "--genesis-builder", "spec"]);
777		cli_fail(&["test", "--runtime", "path/to/spec", "--genesis-builder", "spec-genesis"]);
778		cli_fail(&["test", "--runtime", "path/to/spec", "--genesis-builder", "spec-runtime"]);
779
780		// Spec tests
781		cli_succeed(&["test", "--chain", "path/to/spec"])?;
782		cli_succeed(&["test", "--chain", "path/to/spec", "--genesis-builder", "spec"])?;
783		cli_succeed(&["test", "--chain", "path/to/spec", "--genesis-builder", "spec-genesis"])?;
784		cli_succeed(&["test", "--chain", "path/to/spec", "--genesis-builder", "spec-runtime"])?;
785		cli_fail(&["test", "--chain", "path/to/spec", "--genesis-builder", "none"]);
786		cli_fail(&["test", "--chain", "path/to/spec", "--genesis-builder", "runtime"]);
787		cli_fail(&[
788			"test",
789			"--chain",
790			"path/to/spec",
791			"--genesis-builder",
792			"runtime",
793			"--genesis-builder-preset",
794			"preset",
795		]);
796		Ok(())
797	}
798}