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