Skip to main content

frame_benchmarking_cli/overhead/
command.rs

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