// This file is part of Cumulus. // Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. use codec::{Decode, Encode}; use core::time::Duration; use criterion::{criterion_group, criterion_main, BatchSize, Criterion}; use cumulus_primitives_core::{relay_chain::AccountId, PersistedValidationData, ValidationParams}; use cumulus_test_client::{ generate_extrinsic_with_pair, BlockBuilderAndSupportData, BuildParachainBlockData, Client, InitBlockBuilder, ParachainBlockData, TestClientBuilder, ValidationResult, }; use cumulus_test_relay_sproof_builder::RelayStateSproofBuilder; use cumulus_test_runtime::{Block, GluttonCall, Header, SudoCall}; use polkadot_primitives::HeadData; use sc_client_api::UsageProvider; use sc_consensus::{BlockImport, BlockImportParams, ForkChoiceStrategy, ImportResult, StateAction}; use sc_executor_common::wasm_runtime::WasmModule; use sp_api::ProvideRuntimeApi; use frame_system_rpc_runtime_api::AccountNonceApi; use sp_arithmetic::{ traits::{One, Zero}, FixedU64, }; use sp_consensus::BlockOrigin; use sp_keyring::Sr25519Keyring::Alice; use sp_runtime::traits::Header as HeaderT; use cumulus_test_service::bench_utils as utils; async fn import_block( mut client: &cumulus_test_client::Client, built: cumulus_test_runtime::Block, import_existing: bool, ) { let mut params = BlockImportParams::new(BlockOrigin::File, built.header.clone()); params.body = Some(built.extrinsics.clone()); params.state_action = StateAction::Execute; params.fork_choice = Some(ForkChoiceStrategy::LongestChain); params.import_existing = import_existing; let import_result = client.import_block(params).await; assert!(matches!(import_result, Ok(ImportResult::Imported(_)))); } fn benchmark_block_validation(c: &mut Criterion) { sp_tracing::try_init_simple(); let runtime = tokio::runtime::Runtime::new().expect("creating tokio runtime doesn't fail; qed"); let endowed_accounts = vec![AccountId::from(Alice.public())]; let mut test_client_builder = TestClientBuilder::with_default_backend(); let genesis_init = test_client_builder.genesis_init_mut(); *genesis_init = cumulus_test_client::GenesisParameters { endowed_accounts }; let client = test_client_builder.build_with_native_executor(None).0; let mut group = c.benchmark_group("Block validation"); group.sample_size(20); group.measurement_time(Duration::from_secs(120)); // In the first iteration we want to initialize the glutton pallet. let mut is_first = true; for (compute_ratio, storage_ratio) in &[(One::one(), Zero::zero()), (One::one(), One::one())] { let parachain_block = set_glutton_parameters(&client, is_first, compute_ratio, storage_ratio); is_first = false; runtime.block_on(import_block(&client, parachain_block.clone().into_block(), false)); // Build benchmark block let parent_hash = client.usage_info().chain.best_hash; let parent_header = client.header(parent_hash).expect("Just fetched this hash.").unwrap(); let validation_data = PersistedValidationData { relay_parent_number: 1, parent_head: parent_header.encode().into(), ..Default::default() }; let BlockBuilderAndSupportData { block_builder, .. } = client.init_block_builder(Some(validation_data), Default::default()); let parachain_block = block_builder.build_parachain_block(*parent_header.state_root()); let proof_size_in_kb = parachain_block.storage_proof().encode().len() as f64 / 1024f64; runtime.block_on(import_block(&client, parachain_block.clone().into_block(), false)); let runtime = utils::get_wasm_module(); let sproof_builder: RelayStateSproofBuilder = Default::default(); let (relay_parent_storage_root, _) = sproof_builder.clone().into_state_root_and_proof(); let encoded_params = ValidationParams { block_data: cumulus_test_client::BlockData(parachain_block.clone().encode()), parent_head: HeadData(parent_header.encode()), relay_parent_number: 1, relay_parent_storage_root, } .encode(); // This is not strictly necessary for this benchmark, but // let us make sure that the result of `validate_block` is what // we expect. verify_expected_result(&runtime, &encoded_params, parachain_block.into_block()); group.bench_function( format!( "(compute = {:?}, storage = {:?}, proof_size = {}kb) block validation", compute_ratio, storage_ratio, proof_size_in_kb ), |b| { b.iter_batched( || runtime.new_instance().unwrap(), |mut instance| { instance.call_export("validate_block", &encoded_params).unwrap(); }, BatchSize::SmallInput, ) }, ); } } fn verify_expected_result(runtime: &Box, encoded_params: &[u8], block: Block) { let res = runtime .new_instance() .unwrap() .call_export("validate_block", encoded_params) .expect("Call `validate_block`."); let validation_result = ValidationResult::decode(&mut &res[..]).expect("Decode `ValidationResult`."); let header = Header::decode(&mut &validation_result.head_data.0[..]).expect("Decodes `Header`."); assert_eq!(block.header, header); } fn set_glutton_parameters( client: &Client, initialize: bool, compute_ratio: &FixedU64, storage_ratio: &FixedU64, ) -> ParachainBlockData { let parent_hash = client.usage_info().chain.best_hash; let parent_header = client.header(parent_hash).expect("Just fetched this hash.").unwrap(); let mut last_nonce = client .runtime_api() .account_nonce(parent_hash, Alice.into()) .expect("Fetching account nonce works; qed"); let validation_data = PersistedValidationData { relay_parent_number: 1, parent_head: parent_header.encode().into(), ..Default::default() }; let mut extrinsics = vec![]; if initialize { extrinsics.push(generate_extrinsic_with_pair( client, Alice.into(), SudoCall::sudo { call: Box::new( GluttonCall::initialize_pallet { new_count: 5000, witness_count: None }.into(), ), }, Some(last_nonce), )); last_nonce += 1; } let set_compute = generate_extrinsic_with_pair( client, Alice.into(), SudoCall::sudo { call: Box::new(GluttonCall::set_compute { compute: *compute_ratio }.into()), }, Some(last_nonce), ); last_nonce += 1; extrinsics.push(set_compute); let set_storage = generate_extrinsic_with_pair( client, Alice.into(), SudoCall::sudo { call: Box::new(GluttonCall::set_storage { storage: *storage_ratio }.into()), }, Some(last_nonce), ); extrinsics.push(set_storage); let BlockBuilderAndSupportData { mut block_builder, .. } = client.init_block_builder(Some(validation_data), Default::default()); for extrinsic in extrinsics { block_builder.push(extrinsic).unwrap(); } block_builder.build_parachain_block(*parent_header.state_root()) } criterion_group!(benches, benchmark_block_validation); criterion_main!(benches);