diff --git a/Cargo.lock b/Cargo.lock index d13c45afa22f7..b59da69bfe3c0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5654,6 +5654,7 @@ dependencies = [ "parity-util-mem", "parking_lot 0.10.0", "quickcheck", + "rand 0.7.3", "sc-client", "sc-client-api", "sc-executor", diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 76241eb5e7712..c9528a3c3e6db 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -30,12 +30,14 @@ use sp_core::u32_trait::{_1, _2, _3, _4}; use node_primitives::{AccountId, AccountIndex, Balance, BlockNumber, Hash, Index, Moment, Signature}; use sp_api::impl_runtime_apis; use sp_runtime::{ - Permill, Perbill, Percent, ApplyExtrinsicResult, impl_opaque_keys, generic, create_runtime_str + Permill, Perbill, Percent, ApplyExtrinsicResult, impl_opaque_keys, generic, create_runtime_str, + BenchmarkResults, }; use sp_runtime::curve::PiecewiseLinear; use sp_runtime::transaction_validity::TransactionValidity; use sp_runtime::traits::{ - self, BlakeTwo256, Block as BlockT, StaticLookup, SaturatedConversion, ConvertInto, OpaqueKeys, + self, BlakeTwo256, Block as BlockT, StaticLookup, SaturatedConversion, + ConvertInto, OpaqueKeys, Benchmarking, }; use sp_version::RuntimeVersion; #[cfg(any(feature = "std", test))] @@ -804,6 +806,25 @@ impl_runtime_apis! { SessionKeys::decode_into_raw_public_keys(&encoded) } } + + impl crate::Benchmark for Runtime { + fn dispatch_benchmark(module: Vec, extrinsic: Vec, steps: u32, repeat: u32) + -> Option> + { + match module.as_slice() { + b"pallet-identity" | b"identity" => Identity::run_benchmark(extrinsic, steps, repeat).ok(), + _ => return None, + } + } + } +} + +sp_api::decl_runtime_apis! { + pub trait Benchmark + { + fn dispatch_benchmark(module: Vec, extrinsic: Vec, steps: u32, repeat: u32) + -> Option>; + } } #[cfg(test)] diff --git a/client/cli/src/lib.rs b/client/cli/src/lib.rs index 54a98a6ad3951..5649232c370dd 100644 --- a/client/cli/src/lib.rs +++ b/client/cli/src/lib.rs @@ -58,6 +58,7 @@ use params::{ pub use params::{ SharedParams, ImportParams, ExecutionStrategy, Subcommand, RunCmd, BuildSpecCmd, ExportBlocksCmd, ImportBlocksCmd, CheckBlockCmd, PurgeChainCmd, RevertCmd, + BenchmarkCmd, }; pub use traits::GetSharedParams; use app_dirs::{AppInfo, AppDataType}; diff --git a/client/cli/src/params.rs b/client/cli/src/params.rs index a99d887d25fa5..0881247b07d06 100644 --- a/client/cli/src/params.rs +++ b/client/cli/src/params.rs @@ -845,6 +845,49 @@ pub struct PurgeChainCmd { pub shared_params: SharedParams, } +/// The `benchmark` command used to benchmark FRAME Pallets. +#[derive(Debug, StructOpt, Clone)] +pub struct BenchmarkCmd { + /// Select a FRAME Pallet to benchmark. + #[structopt(short, long)] + pub pallet: String, + + /// Select an extrinsic to benchmark. + #[structopt(short, long)] + pub extrinsic: String, + + /// Select how many samples we should take across the variable components. + #[structopt(short, long, default_value = "1")] + pub steps: u32, + + /// Select how many repetitions of this benchmark should run. + #[structopt(short, long, default_value = "1")] + pub repeat: u32, + + #[allow(missing_docs)] + #[structopt(flatten)] + pub shared_params: SharedParams, + + /// The execution strategy that should be used for benchmarks + #[structopt( + long = "execution", + value_name = "STRATEGY", + possible_values = &ExecutionStrategy::variants(), + case_insensitive = true, + )] + pub execution: Option, + + /// Method for executing Wasm runtime code. + #[structopt( + long = "wasm-execution", + value_name = "METHOD", + possible_values = &WasmExecutionMethod::enabled_variants(), + case_insensitive = true, + default_value = "Interpreted" + )] + pub wasm_method: WasmExecutionMethod, +} + /// All core commands that are provided by default. /// /// The core commands are split into multiple subcommands and `Run` is the default subcommand. From @@ -869,6 +912,9 @@ pub enum Subcommand { /// Remove the whole chain data. PurgeChain(PurgeChainCmd), + + /// Run runtime benchmarks. + Benchmark(BenchmarkCmd), } impl Subcommand { @@ -883,6 +929,7 @@ impl Subcommand { CheckBlock(params) => ¶ms.shared_params, Revert(params) => ¶ms.shared_params, PurgeChain(params) => ¶ms.shared_params, + Benchmark(params) => ¶ms.shared_params, } } @@ -909,6 +956,7 @@ impl Subcommand { Subcommand::ImportBlocks(cmd) => cmd.run(config, builder), Subcommand::CheckBlock(cmd) => cmd.run(config, builder), Subcommand::PurgeChain(cmd) => cmd.run(config), + Subcommand::Benchmark(cmd) => cmd.run(config, builder), Subcommand::Revert(cmd) => cmd.run(config, builder), } } @@ -1186,3 +1234,32 @@ impl RevertCmd { Ok(()) } } + +impl BenchmarkCmd { + /// Runs the command and benchmarks the chain. + pub fn run( + self, + config: Configuration, + _builder: B, + ) -> error::Result<()> + where + B: FnOnce(Configuration) -> Result, + G: RuntimeGenesis, + E: ChainSpecExtension, + BC: ServiceBuilderCommand + Unpin, + BB: sp_runtime::traits::Block + Debug, + <<::Header as HeaderT>::Number as std::str::FromStr>::Err: std::fmt::Debug, + ::Hash: std::str::FromStr, + { + let spec = config.chain_spec.expect("chain_spec is always Some"); + let execution_strategy = self.execution.unwrap_or(ExecutionStrategy::Native).into(); + let wasm_method = self.wasm_method.into(); + let pallet = self.pallet; + let extrinsic = self.extrinsic; + let steps = self.steps; + let repeat = self.repeat; + sc_service::chain_ops::benchmark_runtime::(spec, execution_strategy, wasm_method, pallet, extrinsic, steps, repeat)?; + Ok(()) + } +} + diff --git a/client/db/Cargo.toml b/client/db/Cargo.toml index d35a554d3f29e..ef418d43efc18 100644 --- a/client/db/Cargo.toml +++ b/client/db/Cargo.toml @@ -8,6 +8,7 @@ license = "GPL-3.0" [dependencies] parking_lot = "0.10.0" log = "0.4.8" +rand = "0.7" kvdb = "0.4.0" kvdb-rocksdb = { version = "0.5", optional = true } kvdb-memorydb = "0.4.0" diff --git a/client/db/src/bench.rs b/client/db/src/bench.rs new file mode 100644 index 0000000000000..9858a5c148bfa --- /dev/null +++ b/client/db/src/bench.rs @@ -0,0 +1,286 @@ +// Copyright 2020 Parity Technologies (UK) Ltd. +// This file is part of Substrate. + +// Substrate is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Substrate is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Substrate. If not, see . + +//! State backend that's useful for benchmarking + +use std::sync::Arc; +use std::path::PathBuf; +use std::cell::{Cell, RefCell}; +use rand::Rng; + +use hash_db::{Prefix, Hasher}; +use sp_trie::{MemoryDB, prefixed_key}; +use sp_core::storage::ChildInfo; +use sp_runtime::traits::{Block as BlockT, HasherFor}; +use sp_runtime::Storage; +use sp_state_machine::{DBValue, backend::Backend as StateBackend}; +use kvdb::{KeyValueDB, DBTransaction}; +use kvdb_rocksdb::{Database, DatabaseConfig}; + +type DbState = sp_state_machine::TrieBackend< + Arc>>, HasherFor +>; + +struct StorageDb { + db: Arc, + _block: std::marker::PhantomData, +} + +impl sp_state_machine::Storage> for StorageDb { + fn get(&self, key: &Block::Hash, prefix: Prefix) -> Result, String> { + let key = prefixed_key::>(key, prefix); + self.db.get(0, &key) + .map_err(|e| format!("Database backend error: {:?}", e)) + } +} + +/// State that manages the backend database reference. Allows runtime to control the database. +pub struct BenchmarkingState { + path: PathBuf, + root: Cell, + state: RefCell>>, + db: Cell>>, + genesis: as StateBackend>>::Transaction, +} + +impl BenchmarkingState { + /// Create a new instance that creates a database in a temporary dir. + pub fn new(genesis: Storage) -> Result { + let temp_dir = PathBuf::from(std::env::temp_dir()); + let name: String = rand::thread_rng().sample_iter(&rand::distributions::Alphanumeric).take(10).collect(); + let path = temp_dir.join(&name); + + let mut root = B::Hash::default(); + let mut mdb = MemoryDB::>::default(); + sp_state_machine::TrieDBMut::>::new(&mut mdb, &mut root); + + std::fs::create_dir(&path).map_err(|_| String::from("Error creating temp dir"))?; + let mut state = BenchmarkingState { + state: RefCell::new(None), + db: Cell::new(None), + path, + root: Cell::new(root), + genesis: Default::default(), + }; + + state.reopen()?; + let child_delta = genesis.children.into_iter().map(|(storage_key, child_content)| ( + storage_key, + child_content.data.into_iter().map(|(k, v)| (k, Some(v))), + child_content.child_info + )); + let (root, transaction) = state.state.borrow_mut().as_mut().unwrap().full_storage_root( + genesis.top.into_iter().map(|(k, v)| (k, Some(v))), + child_delta, + ); + state.genesis = transaction.clone(); + state.commit(root, transaction)?; + Ok(state) + } + + fn reopen(&self) -> Result<(), String> { + *self.state.borrow_mut() = None; + self.db.set(None); + let db_config = DatabaseConfig::with_columns(1); + let path = self.path.to_str() + .ok_or_else(|| String::from("Invalid database path"))?; + let db = Arc::new(Database::open(&db_config, &path).map_err(|e| format!("Error opening database: {:?}", e))?); + self.db.set(Some(db.clone())); + let storage_db = Arc::new(StorageDb:: { db, _block: Default::default() }); + *self.state.borrow_mut() = Some(DbState::::new(storage_db, self.root.get())); + Ok(()) + } + + fn kill(&self) -> Result<(), String> { + self.db.set(None); + *self.state.borrow_mut() = None; + let mut root = B::Hash::default(); + let mut mdb = MemoryDB::>::default(); + sp_state_machine::TrieDBMut::>::new(&mut mdb, &mut root); + self.root.set(root); + + std::fs::remove_dir_all(&self.path).map_err(|_| "Error removing database dir".into()) + } +} + +impl Drop for BenchmarkingState { + fn drop(&mut self) { + self.kill().ok(); + } +} + +fn state_err() -> String { + "State is not open".into() +} + +impl StateBackend> for BenchmarkingState { + type Error = as StateBackend>>::Error; + type Transaction = as StateBackend>>::Transaction; + type TrieBackendStorage = as StateBackend>>::TrieBackendStorage; + + fn storage(&self, key: &[u8]) -> Result>, Self::Error> { + self.state.borrow().as_ref().ok_or_else(state_err)?.storage(key) + } + + fn storage_hash(&self, key: &[u8]) -> Result, Self::Error> { + self.state.borrow().as_ref().ok_or_else(state_err)?.storage_hash(key) + } + + fn child_storage( + &self, + storage_key: &[u8], + child_info: ChildInfo, + key: &[u8], + ) -> Result>, Self::Error> { + self.state.borrow().as_ref().ok_or_else(state_err)?.child_storage(storage_key, child_info, key) + } + + fn exists_storage(&self, key: &[u8]) -> Result { + self.state.borrow().as_ref().ok_or_else(state_err)?.exists_storage(key) + } + + fn exists_child_storage( + &self, + storage_key: &[u8], + child_info: ChildInfo, + key: &[u8], + ) -> Result { + self.state.borrow().as_ref().ok_or_else(state_err)?.exists_child_storage(storage_key, child_info, key) + } + + fn next_storage_key(&self, key: &[u8]) -> Result>, Self::Error> { + self.state.borrow().as_ref().ok_or_else(state_err)?.next_storage_key(key) + } + + fn next_child_storage_key( + &self, + storage_key: &[u8], + child_info: ChildInfo, + key: &[u8], + ) -> Result>, Self::Error> { + self.state.borrow().as_ref().ok_or_else(state_err)?.next_child_storage_key(storage_key, child_info, key) + } + + fn for_keys_with_prefix(&self, prefix: &[u8], f: F) { + if let Some(ref state) = *self.state.borrow() { + state.for_keys_with_prefix(prefix, f) + } + } + + fn for_key_values_with_prefix(&self, prefix: &[u8], f: F) { + if let Some(ref state) = *self.state.borrow() { + state.for_key_values_with_prefix(prefix, f) + } + } + + fn for_keys_in_child_storage( + &self, + storage_key: &[u8], + child_info: ChildInfo, + f: F, + ) { + if let Some(ref state) = *self.state.borrow() { + state.for_keys_in_child_storage(storage_key, child_info, f) + } + } + + fn for_child_keys_with_prefix( + &self, + storage_key: &[u8], + child_info: ChildInfo, + prefix: &[u8], + f: F, + ) { + if let Some(ref state) = *self.state.borrow() { + state.for_child_keys_with_prefix(storage_key, child_info, prefix, f) + } + } + + fn storage_root(&self, delta: I) -> (B::Hash, Self::Transaction) where + I: IntoIterator, Option>)> + { + self.state.borrow().as_ref().map_or(Default::default(), |s| s.storage_root(delta)) + } + + fn child_storage_root( + &self, + storage_key: &[u8], + child_info: ChildInfo, + delta: I, + ) -> (B::Hash, bool, Self::Transaction) where + I: IntoIterator, Option>)>, + { + self.state.borrow().as_ref().map_or(Default::default(), |s| s.child_storage_root(storage_key, child_info, delta)) + } + + fn pairs(&self) -> Vec<(Vec, Vec)> { + self.state.borrow().as_ref().map_or(Default::default(), |s| s.pairs()) + } + + fn keys(&self, prefix: &[u8]) -> Vec> { + self.state.borrow().as_ref().map_or(Default::default(), |s| s.keys(prefix)) + } + + fn child_keys( + &self, + storage_key: &[u8], + child_info: ChildInfo, + prefix: &[u8], + ) -> Vec> { + self.state.borrow().as_ref().map_or(Default::default(), |s| s.child_keys(storage_key, child_info, prefix)) + } + + fn as_trie_backend(&mut self) + -> Option<&sp_state_machine::TrieBackend>> + { + None + } + + fn commit(&self, storage_root: as Hasher>::Out, mut transaction: Self::Transaction) + -> Result<(), Self::Error> + { + if let Some(db) = self.db.take() { + let mut db_transaction = DBTransaction::new(); + + for (key, (val, rc)) in transaction.drain() { + if rc > 0 { + db_transaction.put(0, &key, &val); + } else if rc < 0 { + db_transaction.delete(0, &key); + } + } + db.write(db_transaction).map_err(|_| String::from("Error committing transaction"))?; + self.root.set(storage_root); + } else { + return Err("Trying to commit to a closed db".into()) + } + self.reopen() + } + + fn wipe(&self) -> Result<(), Self::Error> { + self.kill()?; + self.reopen()?; + self.commit(self.root.get(), self.genesis.clone())?; + Ok(()) + } +} + +impl std::fmt::Debug for BenchmarkingState { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "DB at {:?}", self.path) + } +} + diff --git a/client/db/src/lib.rs b/client/db/src/lib.rs index 36cf7dacf4660..20998a57f9c29 100644 --- a/client/db/src/lib.rs +++ b/client/db/src/lib.rs @@ -29,6 +29,9 @@ pub mod light; pub mod offchain; +#[cfg(any(feature = "kvdb-rocksdb", test))] +pub mod bench; + mod children; mod cache; mod changes_tries_storage; @@ -80,6 +83,9 @@ use crate::stats::StateUsageStats; use log::{trace, debug, warn}; pub use sc_state_db::PruningMode; +#[cfg(any(feature = "kvdb-rocksdb", test))] +pub use bench::BenchmarkingState; + #[cfg(feature = "test-helpers")] use sc_client::in_mem::Backend as InMemoryBackend; diff --git a/client/executor/src/integration_tests/mod.rs b/client/executor/src/integration_tests/mod.rs index 3f3d9f69e13dc..2ac67215a82f0 100644 --- a/client/executor/src/integration_tests/mod.rs +++ b/client/executor/src/integration_tests/mod.rs @@ -88,7 +88,7 @@ fn call_not_existing_function(wasm_method: WasmExecutionMethod) { #[cfg(feature = "wasmtime")] WasmExecutionMethod::Compiled => assert_eq!( &format!("{:?}", e), - "Other(\"call to undefined external function with index 68\")" + "Other(\"call to undefined external function with index 71\")" ), } } @@ -117,7 +117,7 @@ fn call_yet_another_not_existing_function(wasm_method: WasmExecutionMethod) { #[cfg(feature = "wasmtime")] WasmExecutionMethod::Compiled => assert_eq!( &format!("{:?}", e), - "Other(\"call to undefined external function with index 69\")" + "Other(\"call to undefined external function with index 72\")" ), } } diff --git a/client/service/src/builder.rs b/client/service/src/builder.rs index 2c10804675d3b..71749ac308f31 100644 --- a/client/service/src/builder.rs +++ b/client/service/src/builder.rs @@ -673,6 +673,8 @@ impl(spec: ChainSpec, raw: bool) -> error::Result ( + spec: ChainSpec, + strategy: ExecutionStrategy, + wasm_method: WasmExecutionMethod, + pallet: String, + extrinsic: String, + steps: u32, + repeat: u32, +) -> error::Result<()> where + TBl: BlockT, + TExecDisp: NativeExecutionDispatch + 'static, + G: RuntimeGenesis, + E: Extension, +{ + let genesis_storage = spec.build_storage()?; + let mut changes = Default::default(); + let state = BenchmarkingState::::new(genesis_storage)?; + let executor = NativeExecutor::::new( + wasm_method, + None, // heap pages + ); + let result = StateMachine::<_, _, NumberFor, _>::new( + &state, + None, + &mut changes, + &executor, + "Benchmark_dispatch_benchmark", + &(&pallet, &extrinsic, steps, repeat).encode(), + Default::default(), + ).execute(strategy).map_err(|e| format!("Error executing runtime benchmark: {:?}", e))?; + let results = > as Decode>::decode(&mut &result[..]).unwrap_or(None); + if let Some(results) = results { + // Print benchmark metadata + println!("Pallet: {:?}, Extrinsic: {:?}, Steps: {:?}, Repeat: {:?}", pallet, extrinsic, steps, repeat); + // Print the table header + results[0].0.iter().for_each(|param| print!("{:?},", param.0)); + print!("time\n"); + // Print the values + results.iter().for_each(|result| { + let parameters = &result.0; + parameters.iter().for_each(|param| print!("{:?},", param.1)); + print!("{:?}\n", result.1); + }); + info!("Done."); + } else { + info!("No Results."); + } + Ok(()) +} + + impl< TBl, TRtApi, TGen, TCSExt, TBackend, - TExec, TFchr, TSc, TImpQu, TFprb, TFpp, TNetP, + TExecDisp, TFchr, TSc, TImpQu, TFprb, TFpp, TNetP, TExPool, TRpc, Backend > ServiceBuilderCommand for ServiceBuilder< - TBl, TRtApi, TGen, TCSExt, Client, + TBl, TRtApi, TGen, TCSExt, + Client>, TBl, TRtApi>, TFchr, TSc, TImpQu, TFprb, TFpp, TNetP, TExPool, TRpc, Backend > where TBl: BlockT, TBackend: 'static + sc_client_api::backend::Backend + Send, - TExec: 'static + sc_client::CallExecutor + Send + Sync + Clone, + TExecDisp: 'static + NativeExecutionDispatch, TImpQu: 'static + ImportQueue, TRtApi: 'static + Send + Sync, { type Block = TBl; + type NativeDispatch = TExecDisp; fn import_blocks( self, diff --git a/client/service/src/lib.rs b/client/service/src/lib.rs index 577f36572acbb..920cdc5c4c9f4 100644 --- a/client/service/src/lib.rs +++ b/client/service/src/lib.rs @@ -65,6 +65,7 @@ pub use sp_transaction_pool::{TransactionPool, InPoolTransaction, error::IntoPoo pub use sc_transaction_pool::txpool::Options as TransactionPoolOptions; pub use sc_client::FinalityNotifications; pub use sc_rpc::Metadata as RpcMetadata; +pub use sc_executor::NativeExecutionDispatch; #[doc(hidden)] pub use std::{ops::Deref, result::Result, sync::Arc}; #[doc(hidden)] diff --git a/client/src/lib.rs b/client/src/lib.rs index 4caabfa201fe6..d97246d478c2b 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -104,4 +104,4 @@ pub use crate::{ }, leaves::LeafSet, }; -pub use sp_state_machine::{ExecutionStrategy, StorageProof}; +pub use sp_state_machine::{ExecutionStrategy, StorageProof, StateMachine}; diff --git a/frame/identity/src/benchmarking.rs b/frame/identity/src/benchmarking.rs new file mode 100644 index 0000000000000..0a4a143ea9dd1 --- /dev/null +++ b/frame/identity/src/benchmarking.rs @@ -0,0 +1,630 @@ +// Copyright 2020 Parity Technologies (UK) Ltd. +// This file is part of Substrate. + +// Substrate is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Substrate is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Substrate. If not, see . + +//! Identity pallet benchmarking. + +use super::*; + +use frame_system::RawOrigin; +use sp_io::hashing::blake2_256; +use sp_runtime::{BenchmarkResults, BenchmarkParameter}; +use sp_runtime::traits::{Bounded, Benchmarking, BenchmarkingSetup, Dispatchable}; + +use crate::Module as Identity; + +// The maximum number of identity registrars we will test. +const MAX_REGISTRARS: u32 = 50; + +// Support Functions +fn account(name: &'static str, index: u32) -> T::AccountId { + let entropy = (name, index).using_encoded(blake2_256); + T::AccountId::decode(&mut &entropy[..]).unwrap_or_default() +} + +// Adds `r` registrars to the Identity Pallet. These registrars will have set fees and fields. +fn add_registrars(r: u32) -> Result<(), &'static str> { + for i in 0..r { + let _ = T::Currency::make_free_balance_be(&account::("registrar", i), BalanceOf::::max_value()); + Identity::::add_registrar(RawOrigin::Root.into(), account::("registrar", i))?; + Identity::::set_fee(RawOrigin::Signed(account::("registrar", i)).into(), i.into(), 10.into())?; + let fields = IdentityFields( + IdentityField::Display | IdentityField::Legal | IdentityField::Web | IdentityField::Riot + | IdentityField::Email | IdentityField::PgpFingerprint | IdentityField::Image | IdentityField::Twitter + ); + Identity::::set_fields(RawOrigin::Signed(account::("registrar", i)).into(), i.into(), fields)?; + } + + assert_eq!(Registrars::::get().len(), r as usize); + Ok(()) +} + +// Adds `s` sub-accounts to the identity of `who`. Each wil have 32 bytes of raw data added to it. +// This additionally returns the vector of sub-accounts to it can be modified if needed. +fn add_sub_accounts(who: T::AccountId, s: u32) -> Result, &'static str> { + let mut subs = Vec::new(); + let who_origin = RawOrigin::Signed(who.clone()); + let data = Data::Raw(vec![0; 32]); + + for i in 0..s { + let sub_account = account::("sub", i); + subs.push((sub_account, data.clone())); + } + Identity::::set_subs(who_origin.into(), subs.clone())?; + + return Ok(subs) +} + +// This creates an `IdentityInfo` object with `num_fields` extra fields. +// All data is pre-populated with some arbitrary bytes. +fn create_identity_info(num_fields: u32) -> IdentityInfo { + let data = Data::Raw(vec![0; 32]); + + let info = IdentityInfo { + additional: vec![(data.clone(), data.clone()); num_fields as usize], + display: data.clone(), + legal: data.clone(), + web: data.clone(), + riot: data.clone(), + email: data.clone(), + pgp_fingerprint: Some([0; 20]), + image: data.clone(), + twitter: data.clone(), + }; + + return info +} + +// Benchmark `add_registrar` extrinsic. +struct AddRegistrar; +impl BenchmarkingSetup, RawOrigin> for AddRegistrar { + fn components(&self) -> Vec<(BenchmarkParameter, u32, u32)> { + vec![ + // Registrar Count + (BenchmarkParameter::R, 1, MAX_REGISTRARS), + ] + } + + fn instance(&self, components: &[(BenchmarkParameter, u32)]) + -> Result<(crate::Call, RawOrigin), &'static str> + { + // Add r registrars + let r = components.iter().find(|&c| c.0 == BenchmarkParameter::R).unwrap().1; + benchmarking::add_registrars::(r)?; + + // Return the `add_registrar` r + 1 call + Ok((crate::Call::::add_registrar(account::("registrar", r + 1)), RawOrigin::Root)) + } +} + +// Benchmark `set_identity` extrinsic. +struct SetIdentity; +impl BenchmarkingSetup, RawOrigin> for SetIdentity { + fn components(&self) -> Vec<(BenchmarkParameter, u32, u32)> { + vec![ + // Registrar Count + (BenchmarkParameter::R, 1, MAX_REGISTRARS), + // Additional Field Count + (BenchmarkParameter::X, 1, T::MaxAdditionalFields::get()) + ] + } + + fn instance(&self, components: &[(BenchmarkParameter, u32)]) + -> Result<(crate::Call, RawOrigin), &'static str> + { + // Add r registrars + let r = components.iter().find(|&c| c.0 == BenchmarkParameter::R).unwrap().1; + benchmarking::add_registrars::(r)?; + + // The target user + let caller = account::("caller", r); + let caller_lookup: ::Source = T::Lookup::unlookup(caller.clone()); + let caller_origin: ::Origin = RawOrigin::Signed(caller.clone()).into(); + let _ = T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); + + // Add an initial identity + let initial_info = benchmarking::create_identity_info::(1); + Identity::::set_identity(caller_origin.clone(), initial_info)?; + + // User requests judgement from all the registrars, and they approve + for i in 0..r { + Identity::::request_judgement(caller_origin.clone(), i, 10.into())?; + Identity::::provide_judgement( + RawOrigin::Signed(account::("registrar", i)).into(), + i, + caller_lookup.clone(), + Judgement::Reasonable + )?; + } + + // Create identity info with x additional fields + let x = components.iter().find(|&c| c.0 == BenchmarkParameter::X).unwrap().1; + // 32 byte data that we reuse below + let info = benchmarking::create_identity_info::(x); + + // Return the `set_identity` call + Ok((crate::Call::::set_identity(info), RawOrigin::Signed(caller))) + } +} + +// Benchmark `set_subs` extrinsic. +struct SetSubs; +impl BenchmarkingSetup, RawOrigin> for SetSubs { + fn components(&self) -> Vec<(BenchmarkParameter, u32, u32)> { + vec![ + // Subs Count + (BenchmarkParameter::S, 1, T::MaxSubAccounts::get()), + ] + } + + fn instance(&self, components: &[(BenchmarkParameter, u32)]) + -> Result<(crate::Call, RawOrigin), &'static str> + { + // Generic data to be used. + let data = Data::Raw(vec![0; 32]); + + // The target user + let caller = account::("caller", 0); + let caller_origin: ::Origin = RawOrigin::Signed(caller.clone()).into(); + let _ = T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); + + // Create their main identity + let info = benchmarking::create_identity_info::(1); + Identity::::set_identity(caller_origin.clone(), info)?; + + // Give them s many sub accounts + let s = components.iter().find(|&c| c.0 == BenchmarkParameter::S).unwrap().1; + let mut subs = add_sub_accounts::(caller.clone(), s)?; + + // Create an s+1 sub account to add + subs.push((account::("sub", s+1), data)); + + // Return the `set_subs` call + Ok((crate::Call::::set_subs(subs), RawOrigin::Signed(caller))) + } +} + +// Benchmark `clear_identity` extrinsic. +struct ClearIdentity; +impl BenchmarkingSetup, RawOrigin> for ClearIdentity { + fn components(&self) -> Vec<(BenchmarkParameter, u32, u32)> { + vec![ + // Registrar Count + (BenchmarkParameter::R, 1, MAX_REGISTRARS), + // Subs Count + (BenchmarkParameter::S, 1, T::MaxSubAccounts::get()), + // Additional Field Count + (BenchmarkParameter::X, 1, T::MaxAdditionalFields::get()), + ] + } + + fn instance(&self, components: &[(BenchmarkParameter, u32)]) + -> Result<(crate::Call, RawOrigin), &'static str> + { + // The target user + let caller = account::("caller", 0); + let caller_origin: ::Origin = RawOrigin::Signed(caller.clone()).into(); + let caller_lookup: ::Source = T::Lookup::unlookup(caller.clone()); + let _ = T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); + + // Register r registrars + let r = components.iter().find(|&c| c.0 == BenchmarkParameter::R).unwrap().1; + benchmarking::add_registrars::(r)?; + + // Create their main identity with x additional fields + let x = components.iter().find(|&c| c.0 == BenchmarkParameter::X).unwrap().1; + let info = benchmarking::create_identity_info::(x); + Identity::::set_identity(caller_origin.clone(), info)?; + + // Give them s many sub accounts + let s = components.iter().find(|&c| c.0 == BenchmarkParameter::S).unwrap().1; + let _ = benchmarking::add_sub_accounts::(caller.clone(), s)?; + + // User requests judgement from all the registrars, and they approve + for i in 0..r { + Identity::::request_judgement(caller_origin.clone(), i, 10.into())?; + Identity::::provide_judgement( + RawOrigin::Signed(account::("registrar", i)).into(), + i, + caller_lookup.clone(), + Judgement::Reasonable + )?; + } + + // Return the `clear_identity` call + Ok((crate::Call::::clear_identity(), RawOrigin::Signed(caller))) + } +} + +// Benchmark `request_judgement` extrinsic. +struct RequestJudgement; +impl BenchmarkingSetup, RawOrigin> for RequestJudgement { + fn components(&self) -> Vec<(BenchmarkParameter, u32, u32)> { + vec![ + // Registrar Count + (BenchmarkParameter::R, 1, MAX_REGISTRARS), + // Additional Field Count + (BenchmarkParameter::X, 1, T::MaxAdditionalFields::get()), + ] + } + + fn instance(&self, components: &[(BenchmarkParameter, u32)]) + -> Result<(crate::Call, RawOrigin), &'static str> + { + // The target user + let caller = account::("caller", 0); + let caller_origin: ::Origin = RawOrigin::Signed(caller.clone()).into(); + let _ = T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); + + // Register r registrars + let r = components.iter().find(|&c| c.0 == BenchmarkParameter::R).unwrap().1; + benchmarking::add_registrars::(r)?; + + // Create their main identity with x additional fields + let x = components.iter().find(|&c| c.0 == BenchmarkParameter::X).unwrap().1; + let info = benchmarking::create_identity_info::(x); + Identity::::set_identity(caller_origin.clone(), info)?; + + // Return the `request_judgement` call + Ok((crate::Call::::request_judgement(r-1, 10.into()), RawOrigin::Signed(caller))) + } +} + +// Benchmark `cancel_request` extrinsic. +struct CancelRequest; +impl BenchmarkingSetup, RawOrigin> for CancelRequest { + fn components(&self) -> Vec<(BenchmarkParameter, u32, u32)> { + vec![ + // Registrar Count + (BenchmarkParameter::R, 1, MAX_REGISTRARS), + // Additional Field Count + (BenchmarkParameter::X, 1, T::MaxAdditionalFields::get()), + ] + } + + fn instance(&self, components: &[(BenchmarkParameter, u32)]) + -> Result<(crate::Call, RawOrigin), &'static str> + { + // The target user + let caller = account::("caller", 0); + let caller_origin: ::Origin = RawOrigin::Signed(caller.clone()).into(); + let _ = T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); + + // Register r registrars + let r = components.iter().find(|&c| c.0 == BenchmarkParameter::R).unwrap().1; + benchmarking::add_registrars::(r)?; + + // Create their main identity with x additional fields + let x = components.iter().find(|&c| c.0 == BenchmarkParameter::X).unwrap().1; + let info = benchmarking::create_identity_info::(x); + Identity::::set_identity(caller_origin.clone(), info)?; + + // Request judgement + Identity::::request_judgement(caller_origin.clone(), r-1, 10.into())?; + + Ok((crate::Call::::cancel_request(r-1), RawOrigin::Signed(caller))) + } +} + +// Benchmark `set_fee` extrinsic. +struct SetFee; +impl BenchmarkingSetup, RawOrigin> for SetFee { + fn components(&self) -> Vec<(BenchmarkParameter, u32, u32)> { + vec![ + // Registrar Count + (BenchmarkParameter::R, 1, MAX_REGISTRARS), + ] + } + + fn instance(&self, components: &[(BenchmarkParameter, u32)]) + -> Result<(crate::Call, RawOrigin), &'static str> + { + // The target user + let caller = account::("caller", 0); + let _ = T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); + + // Register r registrars + let r = components.iter().find(|&c| c.0 == BenchmarkParameter::R).unwrap().1; + benchmarking::add_registrars::(r)?; + + // Add caller as registrar + Identity::::add_registrar(RawOrigin::Root.into(), caller.clone())?; + + // Return `set_fee` call + Ok((crate::Call::::set_fee(r, 10.into()), RawOrigin::Signed(caller))) + } +} + +// Benchmark `set_account_id` extrinsic. +struct SetAccountId; +impl BenchmarkingSetup, RawOrigin> for SetAccountId { + fn components(&self) -> Vec<(BenchmarkParameter, u32, u32)> { + vec![ + // Registrar Count + (BenchmarkParameter::R, 1, MAX_REGISTRARS), + ] + } + + fn instance(&self, components: &[(BenchmarkParameter, u32)]) + -> Result<(crate::Call, RawOrigin), &'static str> + { + // The target user + let caller = account::("caller", 0); + let _ = T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); + + // Register r registrars + let r = components.iter().find(|&c| c.0 == BenchmarkParameter::R).unwrap().1; + benchmarking::add_registrars::(r)?; + + // Add caller as registrar + Identity::::add_registrar(RawOrigin::Root.into(), caller.clone())?; + + // Return `set_account_id` call + Ok((crate::Call::::set_account_id(r, account::("new", 0)), RawOrigin::Signed(caller))) + } +} + +// Benchmark `set_fields` extrinsic. +struct SetFields; +impl BenchmarkingSetup, RawOrigin> for SetFields { + fn components(&self) -> Vec<(BenchmarkParameter, u32, u32)> { + vec![ + // Registrar Count + (BenchmarkParameter::R, 1, MAX_REGISTRARS), + ] + } + + fn instance(&self, components: &[(BenchmarkParameter, u32)]) + -> Result<(crate::Call, RawOrigin), &'static str> + { + // The target user + let caller = account::("caller", 0); + let _ = T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); + + // Register r registrars + let r = components.iter().find(|&c| c.0 == BenchmarkParameter::R).unwrap().1; + benchmarking::add_registrars::(r)?; + + // Add caller as registrar + Identity::::add_registrar(RawOrigin::Root.into(), caller.clone())?; + + let fields = IdentityFields( + IdentityField::Display | IdentityField::Legal | IdentityField::Web | IdentityField::Riot + | IdentityField::Email | IdentityField::PgpFingerprint | IdentityField::Image | IdentityField::Twitter + ); + + // Return `set_account_id` call + Ok((crate::Call::::set_fields(r, fields), RawOrigin::Signed(caller))) + } +} + +// Benchmark `provide_judgement` extrinsic. +struct ProvideJudgement; +impl BenchmarkingSetup, RawOrigin> for ProvideJudgement { + + fn components(&self) -> Vec<(BenchmarkParameter, u32, u32)> { + vec![ + // Registrar Count + (BenchmarkParameter::R, 1, MAX_REGISTRARS), + // Additional Field Count + (BenchmarkParameter::X, 1, T::MaxAdditionalFields::get()), + ] + } + + fn instance(&self, components: &[(BenchmarkParameter, u32)]) + -> Result<(crate::Call, RawOrigin), &'static str> + { + // Add r registrars + let r = components.iter().find(|&c| c.0 == BenchmarkParameter::R).unwrap().1; + benchmarking::add_registrars::(r)?; + + // The user + let user = account::("user", r); + let user_origin: ::Origin = RawOrigin::Signed(user.clone()).into(); + let user_lookup: ::Source = T::Lookup::unlookup(user.clone()); + let _ = T::Currency::make_free_balance_be(&user, BalanceOf::::max_value()); + + // Create their main identity with x additional fields + let x = components.iter().find(|&c| c.0 == BenchmarkParameter::X).unwrap().1; + let info = benchmarking::create_identity_info::(x); + Identity::::set_identity(user_origin.clone(), info)?; + + // The caller registrar + let caller = account::("caller", r); + let _ = T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); + + // Add caller as registrar + Identity::::add_registrar(RawOrigin::Root.into(), caller.clone())?; + + // User requests judgement from caller registrar + Identity::::request_judgement(user_origin.clone(), r, 10.into())?; + + // Return `provide_judgement` call + Ok((crate::Call::::provide_judgement( + r, + user_lookup.clone(), + Judgement::Reasonable + ), RawOrigin::Signed(caller))) + } +} + +// Benchmark `kill_identity` extrinsic. +struct KillIdentity; +impl BenchmarkingSetup, RawOrigin> for KillIdentity { + + fn components(&self) -> Vec<(BenchmarkParameter, u32, u32)> { + vec![ + // Registrar Count + (BenchmarkParameter::R, 1, MAX_REGISTRARS), + // Subs Count + (BenchmarkParameter::S, 1, T::MaxSubAccounts::get()), + // Additional Field Count + (BenchmarkParameter::X, 1, T::MaxAdditionalFields::get()), + ] + } + + fn instance(&self, components: &[(BenchmarkParameter, u32)]) + -> Result<(crate::Call, RawOrigin), &'static str> + { + // The target user + let caller = account::("caller", 0); + let caller_origin: ::Origin = RawOrigin::Signed(caller.clone()).into(); + let caller_lookup: ::Source = T::Lookup::unlookup(caller.clone()); + let _ = T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); + + // Register r registrars + let r = components.iter().find(|&c| c.0 == BenchmarkParameter::R).unwrap().1; + benchmarking::add_registrars::(r)?; + + // Create their main identity with x additional fields + let x = components.iter().find(|&c| c.0 == BenchmarkParameter::X).unwrap().1; + let info = benchmarking::create_identity_info::(x); + Identity::::set_identity(caller_origin.clone(), info)?; + + // Give them s many sub accounts + let s = components.iter().find(|&c| c.0 == BenchmarkParameter::S).unwrap().1; + let _ = benchmarking::add_sub_accounts::(caller.clone(), s)?; + + // User requests judgement from all the registrars, and they approve + for i in 0..r { + Identity::::request_judgement(caller_origin.clone(), i, 10.into())?; + Identity::::provide_judgement( + RawOrigin::Signed(account::("registrar", i)).into(), + i, + caller_lookup.clone(), + Judgement::Reasonable + )?; + } + + // Return the `kill_identity` call + Ok((crate::Call::::kill_identity(caller_lookup), RawOrigin::Root)) + } +} + +// The list of available benchmarks for this pallet. +enum SelectedBenchmark { + AddRegistrar, + SetIdentity, + SetSubs, + ClearIdentity, + RequestJudgement, + CancelRequest, + SetFee, + SetAccountId, + SetFields, + ProvideJudgement, + KillIdentity, +} + +// Allow us to select a benchmark from the list of available benchmarks. + +impl BenchmarkingSetup, RawOrigin> for SelectedBenchmark { + fn components(&self) -> Vec<(BenchmarkParameter, u32, u32)> { + match self { + Self::AddRegistrar => , RawOrigin>>::components(&AddRegistrar), + Self::SetIdentity => , RawOrigin>>::components(&SetIdentity), + Self::SetSubs => , RawOrigin>>::components(&SetSubs), + Self::ClearIdentity => , RawOrigin>>::components(&ClearIdentity), + Self::RequestJudgement => , RawOrigin>>::components(&RequestJudgement), + Self::CancelRequest => , RawOrigin>>::components(&CancelRequest), + Self::SetFee => , RawOrigin>>::components(&SetFee), + Self::SetAccountId => , RawOrigin>>::components(&SetAccountId), + Self::SetFields => , RawOrigin>>::components(&SetFields), + Self::ProvideJudgement => , RawOrigin>>::components(&ProvideJudgement), + Self::KillIdentity => , RawOrigin>>::components(&KillIdentity), + } + } + + fn instance(&self, components: &[(BenchmarkParameter, u32)]) + -> Result<(crate::Call, RawOrigin), &'static str> + { + match self { + Self::AddRegistrar => , RawOrigin>>::instance(&AddRegistrar, components), + Self::SetIdentity => , RawOrigin>>::instance(&SetIdentity, components), + Self::SetSubs => , RawOrigin>>::instance(&SetSubs, components), + Self::ClearIdentity => , RawOrigin>>::instance(&ClearIdentity, components), + Self::RequestJudgement => , RawOrigin>>::instance(&RequestJudgement, components), + Self::CancelRequest => , RawOrigin>>::instance(&CancelRequest, components), + Self::SetFee => , RawOrigin>>::instance(&SetFee, components), + Self::SetAccountId => , RawOrigin>>::instance(&SetAccountId, components), + Self::SetFields => , RawOrigin>>::instance(&SetFields, components), + Self::ProvideJudgement => , RawOrigin>>::instance(&ProvideJudgement, components), + Self::KillIdentity => , RawOrigin>>::instance(&KillIdentity, components), + } + } +} + +impl Benchmarking for Module { + fn run_benchmark(extrinsic: Vec, steps: u32, repeat: u32) -> Result, &'static str> { + // Map the input to the selected benchmark. + let selected_benchmark = match extrinsic.as_slice() { + b"add_registrar" => SelectedBenchmark::AddRegistrar, + b"set_identity" => SelectedBenchmark::SetIdentity, + b"set_subs" => SelectedBenchmark::SetSubs, + b"clear_identity" => SelectedBenchmark::ClearIdentity, + b"request_judgement" => SelectedBenchmark::RequestJudgement, + b"cancel_request" => SelectedBenchmark::CancelRequest, + b"set_fee" => SelectedBenchmark::SetFee, + b"set_account_id" => SelectedBenchmark::SetAccountId, + b"set_fields" => SelectedBenchmark::SetFields, + b"provide_judgement" => SelectedBenchmark::ProvideJudgement, + b"kill_identity" => SelectedBenchmark::KillIdentity, + _ => return Err("Could not find extrinsic."), + }; + + // Warm up the DB + sp_io::benchmarking::commit_db(); + sp_io::benchmarking::wipe_db(); + + // first one is set_identity. + let components = , RawOrigin>>::components(&selected_benchmark); + // results go here + let mut results: Vec = Vec::new(); + // Select the component we will be benchmarking. Each component will be benchmarked. + for (name, low, high) in components.iter() { + // Create up to `STEPS` steps for that component between high and low. + let step_size = ((high - low) / steps).max(1); + let num_of_steps = (high - low) / step_size; + for s in 0..num_of_steps { + // This is the value we will be testing for component `name` + let component_value = low + step_size * s; + + // Select the mid value for all the other components. + let c: Vec<(BenchmarkParameter, u32)> = components.iter() + .map(|(n, l, h)| + (*n, if n == name { component_value } else { (h - l) / 2 + l }) + ).collect(); + + // Run the benchmark `repeat` times. + for _ in 0..repeat { + // Set up the externalities environment for the setup we want to benchmark. + let (call, caller) = , RawOrigin>>::instance(&selected_benchmark, &c)?; + // Commit the externalities to the database, flushing the DB cache. + // This will enable worst case scenario for reading from the database. + sp_io::benchmarking::commit_db(); + // Run the benchmark. + let start = sp_io::benchmarking::current_time(); + call.dispatch(caller.into())?; + let finish = sp_io::benchmarking::current_time(); + let elapsed = finish - start; + results.push((c.clone(), elapsed)); + // Wipe the DB back to the genesis state. + sp_io::benchmarking::wipe_db(); + } + } + } + return Ok(results); + } +} diff --git a/frame/identity/src/lib.rs b/frame/identity/src/lib.rs index 80a200e808f45..c6fc0062be153 100644 --- a/frame/identity/src/lib.rs +++ b/frame/identity/src/lib.rs @@ -78,6 +78,8 @@ use frame_support::{ }; use frame_system::{self as system, ensure_signed, ensure_root}; +pub mod benchmarking; + type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; type NegativeImbalanceOf = <::Currency as Currency<::AccountId>>::NegativeImbalance; @@ -880,7 +882,7 @@ mod tests { use sp_runtime::traits::BadOrigin; use frame_support::{ assert_ok, assert_noop, impl_outer_origin, parameter_types, weights::Weight, - ord_parameter_types + ord_parameter_types, }; use sp_core::H256; use frame_system::EnsureSignedBy; diff --git a/primitives/externalities/src/lib.rs b/primitives/externalities/src/lib.rs index 350b65d190840..75193a4b9c392 100644 --- a/primitives/externalities/src/lib.rs +++ b/primitives/externalities/src/lib.rs @@ -202,6 +202,14 @@ pub trait Externalities: ExtensionStore { /// /// Returns the SCALE encoded hash. fn storage_changes_root(&mut self, parent: &[u8]) -> Result>, ()>; + + fn wipe(&mut self) { + unimplemented!() + } + + fn commit(&mut self) { + unimplemented!() + } } /// Extension for the [`Externalities`] trait. diff --git a/primitives/io/src/lib.rs b/primitives/io/src/lib.rs index 1b531725fefc8..ea45b580ce27c 100644 --- a/primitives/io/src/lib.rs +++ b/primitives/io/src/lib.rs @@ -771,6 +771,30 @@ pub trait Logging { } } +/// Interface that provides functions for benchmarking the runtime. +#[runtime_interface] +pub trait Benchmarking { + /// Get the number of nanoseconds passed since the UNIX epoch + /// + /// WARNING! This is a non-deterministic call. Do not use this within + /// consensus critical logic. + fn current_time() -> u128 { + std::time::SystemTime::now().duration_since(std::time::SystemTime::UNIX_EPOCH) + .expect("Unix time doesn't go backwards; qed") + .as_nanos() + } + + /// Reset the trie database to the genesis state. + fn wipe_db(&mut self) { + self.wipe() + } + + /// Commit pending storage changes to the trie database and clear the database cache. + fn commit_db(&mut self) { + self.commit() + } +} + /// Wasm-only interface that provides functions for interacting with the sandbox. #[runtime_interface(wasm_only)] pub trait Sandbox { @@ -923,6 +947,7 @@ pub type SubstrateHostFunctions = ( logging::HostFunctions, sandbox::HostFunctions, crate::trie::HostFunctions, + benchmarking::HostFunctions, ); #[cfg(test)] diff --git a/primitives/runtime/src/lib.rs b/primitives/runtime/src/lib.rs index 9494c53cc612f..f525f91ff6a50 100644 --- a/primitives/runtime/src/lib.rs +++ b/primitives/runtime/src/lib.rs @@ -685,6 +685,18 @@ pub fn print(print: impl traits::Printable) { print.print(); } +/// An alphabet of possible parameters to use for benchmarking. +#[derive(Encode, Decode, Clone, Copy, PartialEq, Debug)] +#[allow(missing_docs)] +pub enum BenchmarkParameter { + A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, +} + +/// Results from running benchmarks on a FRAME pallet. +/// Contains duration of the function call in nanoseconds along with the benchmark parameters +/// used for that benchmark result. +pub type BenchmarkResults = (Vec<(BenchmarkParameter, u32)>, u128); + #[cfg(test)] mod tests { use super::*; diff --git a/primitives/runtime/src/traits.rs b/primitives/runtime/src/traits.rs index f07f4dd7ac3bf..e95fc073eb445 100644 --- a/primitives/runtime/src/traits.rs +++ b/primitives/runtime/src/traits.rs @@ -24,6 +24,7 @@ use std::fmt::Display; #[cfg(feature = "std")] use serde::{Serialize, Deserialize, de::DeserializeOwned}; use sp_core::{self, Hasher, Blake2Hasher, TypeId, RuntimeDebug}; +use crate::BenchmarkParameter; use crate::codec::{Codec, Encode, Decode}; use crate::transaction_validity::{ ValidTransaction, TransactionValidity, TransactionValidityError, UnknownTransaction, @@ -1328,6 +1329,26 @@ pub trait BlockIdTo { ) -> Result>, Self::Error>; } +/// The pallet benchmarking trait. +pub trait Benchmarking { + /// Run the benchmarks for this pallet. + /// + /// Parameters + /// - `extrinsic`: The name of extrinsic function you want to benchmark encoded as bytes. + /// - `steps`: The number of sample points you want to take across the range of parameters. + /// - `repeat`: The number of times you want to repeat a benchmark. + fn run_benchmark(extrinsic: Vec, steps: u32, repeat: u32) -> Result, &'static str>; +} + +/// The required setup for creating a benchmark. +pub trait BenchmarkingSetup { + /// Return the components and their ranges which should be tested in this benchmark. + fn components(&self) -> Vec<(BenchmarkParameter, u32, u32)>; + + /// Set up the storage, and prepare a call and caller to test in a single run of the benchmark. + fn instance(&self, components: &[(BenchmarkParameter, u32)]) -> Result<(Call, RawOrigin), &'static str>; +} + #[cfg(test)] mod tests { use super::*; diff --git a/primitives/state-machine/src/backend.rs b/primitives/state-machine/src/backend.rs index 4ef9b970ae21d..396ef6575af07 100644 --- a/primitives/state-machine/src/backend.rs +++ b/primitives/state-machine/src/backend.rs @@ -213,6 +213,16 @@ pub trait Backend: std::fmt::Debug { fn usage_info(&self) -> UsageInfo { UsageInfo::empty() } + + /// Wipe the state database. + fn wipe(&self) -> Result<(), Self::Error> { + unimplemented!() + } + + /// Commit given transaction to storage. + fn commit(&self, _storage_root: H::Out, _transaction: Self::Transaction) -> Result<(), Self::Error> { + unimplemented!() + } } impl<'a, T: Backend, H: Hasher> Backend for &'a T { diff --git a/primitives/state-machine/src/ext.rs b/primitives/state-machine/src/ext.rs index 38a2e70262d85..53156cb18619c 100644 --- a/primitives/state-machine/src/ext.rs +++ b/primitives/state-machine/src/ext.rs @@ -583,6 +583,25 @@ where root.map(|r| r.map(|o| o.encode())) } + + fn wipe(&mut self) { + self.overlay.discard_prospective(); + self.overlay.drain_storage_changes(&self.backend, None, Default::default(), self.storage_transaction_cache) + .expect(EXT_NOT_ALLOWED_TO_FAIL); + self.storage_transaction_cache.reset(); + self.backend.wipe().expect(EXT_NOT_ALLOWED_TO_FAIL) + } + + fn commit(&mut self) { + self.overlay.commit_prospective(); + let changes = self.overlay.drain_storage_changes(&self.backend, None, Default::default(), self.storage_transaction_cache) + .expect(EXT_NOT_ALLOWED_TO_FAIL); + self.backend.commit( + changes.transaction_storage_root, + changes.transaction, + ).expect(EXT_NOT_ALLOWED_TO_FAIL); + self.storage_transaction_cache.reset(); + } } impl<'a, H, B, N> sp_externalities::ExtensionStore for Ext<'a, H, N, B> diff --git a/primitives/state-machine/src/overlayed_changes.rs b/primitives/state-machine/src/overlayed_changes.rs index ed6f30a4f596b..37187e163fe1c 100644 --- a/primitives/state-machine/src/overlayed_changes.rs +++ b/primitives/state-machine/src/overlayed_changes.rs @@ -428,15 +428,18 @@ impl OverlayedChanges { /// /// Panics: /// Will panic if there are any uncommitted prospective changes. - pub fn into_committed(self) -> ( + fn drain_committed(&mut self) -> ( impl Iterator)>, impl Iterator)>, OwnedChildInfo))>, - ){ + ) { assert!(self.prospective.is_empty()); ( - self.committed.top.into_iter().map(|(k, v)| (k, v.value)), - self.committed.children.into_iter() - .map(|(sk, (v, ci))| (sk, (v.into_iter().map(|(k, v)| (k, v.value)), ci))) + std::mem::replace(&mut self.committed.top, Default::default()) + .into_iter() + .map(|(k, v)| (k, v.value)), + std::mem::replace(&mut self.committed.children, Default::default()) + .into_iter() + .map(|(sk, (v, ci))| (sk, (v.into_iter().map(|(k, v)| (k, v.value)), ci))), ) } @@ -444,11 +447,22 @@ impl OverlayedChanges { pub fn into_storage_changes< B: Backend, H: Hasher, N: BlockNumber >( - self, + mut self, backend: &B, changes_trie_state: Option<&ChangesTrieState>, parent_hash: H::Out, mut cache: StorageTransactionCache, + ) -> Result, String> where H::Out: Ord + Encode + 'static { + self.drain_storage_changes(backend, changes_trie_state, parent_hash, &mut cache) + } + + /// Drain all changes into a [`StorageChanges`] instance. Leave empty overlay in place. + pub fn drain_storage_changes, H: Hasher, N: BlockNumber>( + &mut self, + backend: &B, + changes_trie_state: Option<&ChangesTrieState>, + parent_hash: H::Out, + mut cache: &mut StorageTransactionCache, ) -> Result, String> where H::Out: Ord + Encode + 'static { // If the transaction does not exist, we generate it. if cache.transaction.is_none() { @@ -474,7 +488,7 @@ impl OverlayedChanges { .take() .expect("Changes trie transaction was generated by `changes_trie_root`; qed"); - let (main_storage_changes, child_storage_changes) = self.into_committed(); + let (main_storage_changes, child_storage_changes) = self.drain_committed(); Ok(StorageChanges { main_storage_changes: main_storage_changes.collect(),