1use std::{
26 collections::BTreeMap,
27 path::{Path, PathBuf},
28 sync::Arc,
29};
30
31use crate::{
32 client::{Backend, Client},
33 keyring::*,
34};
35use codec::{Decode, Encode};
36use futures::executor;
37use kitchensink_runtime::{
38 constants::currency::DOLLARS, AccountId, BalancesCall, CheckedExtrinsic, MinimumPeriod,
39 RuntimeCall, Signature, SystemCall, UncheckedExtrinsic,
40};
41use node_primitives::Block;
42use sc_block_builder::BlockBuilderBuilder;
43use sc_client_api::{execution_extensions::ExecutionExtensions, UsageProvider};
44use sc_client_db::PruningMode;
45use sc_consensus::{BlockImport, BlockImportParams, ForkChoiceStrategy, ImportResult, ImportedAux};
46use sc_executor::{WasmExecutionMethod, WasmtimeInstantiationStrategy};
47use sp_api::ProvideRuntimeApi;
48use sp_block_builder::BlockBuilder;
49use sp_consensus::BlockOrigin;
50use sp_core::{
51 crypto::get_public_from_string_or_panic, ed25519, sr25519, traits::SpawnNamed, Pair,
52};
53use sp_crypto_hashing::blake2_256;
54use sp_inherents::InherentData;
55use sp_runtime::{
56 generic::{self, ExtrinsicFormat, Preamble},
57 traits::{Block as BlockT, IdentifyAccount, Verify},
58 OpaqueExtrinsic,
59};
60
61#[derive(Clone)]
69pub struct BenchKeyring {
70 accounts: BTreeMap<AccountId, BenchPair>,
71}
72
73#[derive(Clone)]
74enum BenchPair {
75 Sr25519(sr25519::Pair),
76 Ed25519(ed25519::Pair),
77}
78
79impl BenchPair {
80 fn sign(&self, payload: &[u8]) -> Signature {
81 match self {
82 Self::Sr25519(pair) => pair.sign(payload).into(),
83 Self::Ed25519(pair) => pair.sign(payload).into(),
84 }
85 }
86}
87
88pub fn drop_system_cache() {
92 #[cfg(target_os = "windows")]
93 {
94 log::warn!(
95 target: "bench-logistics",
96 "Clearing system cache on windows is not supported. Benchmark might totally be wrong.",
97 );
98 return
99 }
100
101 std::process::Command::new("sync")
102 .output()
103 .expect("Failed to execute system cache clear");
104
105 #[cfg(target_os = "linux")]
106 {
107 log::trace!(target: "bench-logistics", "Clearing system cache...");
108 std::process::Command::new("echo")
109 .args(&["3", ">", "/proc/sys/vm/drop_caches", "2>", "/dev/null"])
110 .output()
111 .expect("Failed to execute system cache clear");
112
113 let temp = tempfile::tempdir().expect("Failed to spawn tempdir");
114 let temp_file_path = format!("of={}/buf", temp.path().to_string_lossy());
115
116 std::process::Command::new("dd")
118 .args(&["if=/dev/urandom", &temp_file_path, "bs=64M", "count=32"])
119 .output()
120 .expect("Failed to execute dd for cache clear");
121
122 std::process::Command::new("rm")
124 .arg(&temp_file_path)
125 .output()
126 .expect("Failed to remove temp file");
127
128 std::process::Command::new("sync")
129 .output()
130 .expect("Failed to execute system cache clear");
131
132 log::trace!(target: "bench-logistics", "Clearing system cache done!");
133 }
134
135 #[cfg(target_os = "macos")]
136 {
137 log::trace!(target: "bench-logistics", "Clearing system cache...");
138 if let Err(err) = std::process::Command::new("purge").output() {
139 log::error!("purge error {:?}: ", err);
140 panic!("Could not clear system cache. Run under sudo?");
141 }
142 log::trace!(target: "bench-logistics", "Clearing system cache done!");
143 }
144}
145
146pub struct BenchDb {
151 keyring: BenchKeyring,
152 directory_guard: Guard,
153 database_type: DatabaseType,
154}
155
156impl Clone for BenchDb {
157 fn clone(&self) -> Self {
158 let keyring = self.keyring.clone();
159 let database_type = self.database_type;
160 let dir = tempfile::tempdir().expect("temp dir creation failed");
161
162 let seed_dir = self.directory_guard.0.path();
163
164 log::trace!(
165 target: "bench-logistics",
166 "Copying seed db from {} to {}",
167 seed_dir.to_string_lossy(),
168 dir.path().to_string_lossy(),
169 );
170 let seed_db_files = std::fs::read_dir(seed_dir)
171 .expect("failed to list file in seed dir")
172 .map(|f_result| f_result.expect("failed to read file in seed db").path())
173 .collect::<Vec<PathBuf>>();
174 fs_extra::copy_items(&seed_db_files, dir.path(), &fs_extra::dir::CopyOptions::new())
175 .expect("Copy of seed database is ok");
176
177 drop_system_cache();
183
184 BenchDb { keyring, directory_guard: Guard(dir), database_type }
185 }
186}
187
188#[derive(Debug, PartialEq, Clone, Copy)]
190pub enum BlockType {
191 RandomTransfersKeepAlive,
193 RandomTransfersReaping,
195 Noop,
197}
198
199impl BlockType {
200 pub fn to_content(self, size: Option<usize>) -> BlockContent {
202 BlockContent { block_type: self, size }
203 }
204}
205
206#[derive(Clone, Debug)]
208pub struct BlockContent {
209 block_type: BlockType,
210 size: Option<usize>,
211}
212
213#[derive(Debug, PartialEq, Clone, Copy)]
215pub enum DatabaseType {
216 RocksDb,
218 ParityDb,
220}
221
222impl DatabaseType {
223 fn into_settings(self, path: PathBuf) -> sc_client_db::DatabaseSource {
224 match self {
225 Self::RocksDb => sc_client_db::DatabaseSource::RocksDb { path, cache_size: 512 },
226 Self::ParityDb => sc_client_db::DatabaseSource::ParityDb { path },
227 }
228 }
229}
230
231#[derive(Debug, Clone)]
235pub struct TaskExecutor {
236 pool: executor::ThreadPool,
237}
238
239impl TaskExecutor {
240 fn new() -> Self {
241 Self { pool: executor::ThreadPool::new().expect("Failed to create task executor") }
242 }
243}
244
245impl SpawnNamed for TaskExecutor {
246 fn spawn(
247 &self,
248 _: &'static str,
249 _: Option<&'static str>,
250 future: futures::future::BoxFuture<'static, ()>,
251 ) {
252 self.pool.spawn_ok(future);
253 }
254
255 fn spawn_blocking(
256 &self,
257 _: &'static str,
258 _: Option<&'static str>,
259 future: futures::future::BoxFuture<'static, ()>,
260 ) {
261 self.pool.spawn_ok(future);
262 }
263}
264
265pub struct BlockContentIterator<'a> {
267 iteration: usize,
268 content: BlockContent,
269 runtime_version: sc_executor::RuntimeVersion,
270 genesis_hash: node_primitives::Hash,
271 keyring: &'a BenchKeyring,
272}
273
274impl<'a> BlockContentIterator<'a> {
275 fn new(content: BlockContent, keyring: &'a BenchKeyring, client: &Client) -> Self {
276 let genesis_hash = client.chain_info().genesis_hash;
277 let runtime_version = client
278 .runtime_version_at(genesis_hash)
279 .expect("There should be runtime version at 0");
280
281 BlockContentIterator { iteration: 0, content, keyring, runtime_version, genesis_hash }
282 }
283}
284
285impl<'a> Iterator for BlockContentIterator<'a> {
286 type Item = OpaqueExtrinsic;
287
288 fn next(&mut self) -> Option<Self::Item> {
289 if self.content.size.map(|size| size <= self.iteration).unwrap_or(false) {
290 return None
291 }
292
293 let sender = self.keyring.at(self.iteration);
294 let receiver = get_public_from_string_or_panic::<sr25519::Public>(&format!(
295 "random-user//{}",
296 self.iteration
297 ))
298 .into();
299
300 let signed = self.keyring.sign(
301 CheckedExtrinsic {
302 format: ExtrinsicFormat::Signed(
303 sender,
304 tx_ext(0, kitchensink_runtime::ExistentialDeposit::get() + 1),
305 ),
306 function: match self.content.block_type {
307 BlockType::RandomTransfersKeepAlive =>
308 RuntimeCall::Balances(BalancesCall::transfer_keep_alive {
309 dest: sp_runtime::MultiAddress::Id(receiver),
310 value: kitchensink_runtime::ExistentialDeposit::get() + 1,
311 }),
312 BlockType::RandomTransfersReaping => {
313 RuntimeCall::Balances(BalancesCall::transfer_allow_death {
314 dest: sp_runtime::MultiAddress::Id(receiver),
315 value: 100 * DOLLARS -
318 (kitchensink_runtime::ExistentialDeposit::get() - 1),
319 })
320 },
321 BlockType::Noop =>
322 RuntimeCall::System(SystemCall::remark { remark: Vec::new() }),
323 },
324 },
325 self.runtime_version.spec_version,
326 self.runtime_version.transaction_version,
327 self.genesis_hash.into(),
328 );
329
330 let encoded = Encode::encode(&signed);
331
332 let opaque = OpaqueExtrinsic::decode(&mut &encoded[..]).expect("Failed to decode opaque");
333
334 self.iteration += 1;
335
336 Some(opaque)
337 }
338}
339
340impl BenchDb {
341 pub fn with_key_types(
346 database_type: DatabaseType,
347 keyring_length: usize,
348 key_types: KeyTypes,
349 ) -> Self {
350 let keyring = BenchKeyring::new(keyring_length, key_types);
351
352 let dir = tempfile::tempdir().expect("temp dir creation failed");
353 log::trace!(
354 target: "bench-logistics",
355 "Created seed db at {}",
356 dir.path().to_string_lossy(),
357 );
358 let (_client, _backend, _task_executor) =
359 Self::bench_client(database_type, dir.path(), &keyring);
360 let directory_guard = Guard(dir);
361
362 BenchDb { keyring, directory_guard, database_type }
363 }
364
365 pub fn new(database_type: DatabaseType, keyring_length: usize) -> Self {
374 Self::with_key_types(database_type, keyring_length, KeyTypes::Sr25519)
375 }
376
377 fn bench_client(
383 database_type: DatabaseType,
384 dir: &std::path::Path,
385 keyring: &BenchKeyring,
386 ) -> (Client, std::sync::Arc<Backend>, TaskExecutor) {
387 let db_config = sc_client_db::DatabaseSettings {
388 trie_cache_maximum_size: Some(16 * 1024 * 1024),
389 state_pruning: Some(PruningMode::ArchiveAll),
390 source: database_type.into_settings(dir.into()),
391 blocks_pruning: sc_client_db::BlocksPruning::KeepAll,
392 metrics_registry: None,
393 };
394 let task_executor = TaskExecutor::new();
395
396 let backend = sc_service::new_db_backend(db_config).expect("Should not fail");
397 let executor = sc_executor::WasmExecutor::builder()
398 .with_execution_method(WasmExecutionMethod::Compiled {
399 instantiation_strategy: WasmtimeInstantiationStrategy::PoolingCopyOnWrite,
400 })
401 .build();
402
403 let client_config = sc_service::ClientConfig::default();
404 let genesis_block_builder = sc_service::GenesisBlockBuilder::new(
405 keyring.as_storage_builder(),
406 !client_config.no_genesis,
407 backend.clone(),
408 executor.clone(),
409 )
410 .expect("Failed to create genesis block builder");
411
412 let client = sc_service::new_client(
413 backend.clone(),
414 executor.clone(),
415 genesis_block_builder,
416 None,
417 None,
418 ExecutionExtensions::new(None, Arc::new(executor)),
419 Box::new(task_executor.clone()),
420 None,
421 None,
422 client_config,
423 )
424 .expect("Should not fail");
425
426 (client, backend, task_executor)
427 }
428
429 pub fn generate_inherents(&mut self, client: &Client) -> Vec<OpaqueExtrinsic> {
433 let mut inherent_data = InherentData::new();
434 let timestamp = 1 * MinimumPeriod::get();
435
436 inherent_data
437 .put_data(sp_timestamp::INHERENT_IDENTIFIER, ×tamp)
438 .expect("Put timestamp failed");
439
440 client
441 .runtime_api()
442 .inherent_extrinsics(client.chain_info().genesis_hash, inherent_data)
443 .expect("Get inherents failed")
444 }
445
446 pub fn block_content(&self, content: BlockContent, client: &Client) -> BlockContentIterator {
448 BlockContentIterator::new(content, &self.keyring, client)
449 }
450
451 pub fn client(&mut self) -> Client {
453 let (client, _backend, _task_executor) =
454 Self::bench_client(self.database_type, self.directory_guard.path(), &self.keyring);
455
456 client
457 }
458
459 pub fn generate_block(&mut self, content: BlockContent) -> Block {
461 let client = self.client();
462 let chain = client.usage_info().chain;
463
464 let mut block = BlockBuilderBuilder::new(&client)
465 .on_parent_block(chain.best_hash)
466 .with_parent_block_number(chain.best_number)
467 .build()
468 .expect("Failed to create block builder.");
469
470 for extrinsic in self.generate_inherents(&client) {
471 block.push(extrinsic).expect("Push inherent failed");
472 }
473
474 let start = std::time::Instant::now();
475 for opaque in self.block_content(content, &client) {
476 match block.push(opaque) {
477 Err(sp_blockchain::Error::ApplyExtrinsicFailed(
478 sp_blockchain::ApplyExtrinsicFailed::Validity(e),
479 )) if e.exhausted_resources() => break,
480 Err(err) => panic!("Error pushing transaction: {:?}", err),
481 Ok(_) => {},
482 }
483 }
484
485 let block = block.build().expect("Block build failed").block;
486
487 log::info!(
488 target: "bench-logistics",
489 "Block construction: {:#?} ({} tx)",
490 start.elapsed(), block.extrinsics.len()
491 );
492
493 block
494 }
495
496 pub fn path(&self) -> &Path {
498 self.directory_guard.path()
499 }
500
501 pub fn create_context(&self) -> BenchContext {
503 let BenchDb { directory_guard, keyring, database_type } = self.clone();
504 let (client, backend, task_executor) =
505 Self::bench_client(database_type, directory_guard.path(), &keyring);
506
507 BenchContext {
508 client: Arc::new(client),
509 db_guard: directory_guard,
510 backend,
511 spawn_handle: Box::new(task_executor),
512 }
513 }
514}
515
516pub enum KeyTypes {
518 Sr25519,
520 Ed25519,
522}
523
524impl BenchKeyring {
525 pub fn new(length: usize, key_types: KeyTypes) -> Self {
529 let mut accounts = BTreeMap::new();
530
531 for n in 0..length {
532 let seed = format!("//endowed-user/{}", n);
533 let (account_id, pair) = match key_types {
534 KeyTypes::Sr25519 => {
535 let pair =
536 sr25519::Pair::from_string(&seed, None).expect("failed to generate pair");
537 let account_id = AccountPublic::from(pair.public()).into_account();
538 (account_id, BenchPair::Sr25519(pair))
539 },
540 KeyTypes::Ed25519 => {
541 let pair = ed25519::Pair::from_seed(&blake2_256(seed.as_bytes()));
542 let account_id = AccountPublic::from(pair.public()).into_account();
543 (account_id, BenchPair::Ed25519(pair))
544 },
545 };
546 accounts.insert(account_id, pair);
547 }
548
549 Self { accounts }
550 }
551
552 pub fn collect_account_ids(&self) -> Vec<AccountId> {
554 self.accounts.keys().cloned().collect()
555 }
556
557 pub fn at(&self, index: usize) -> AccountId {
559 self.accounts.keys().nth(index).expect("Failed to get account").clone()
560 }
561
562 pub fn sign(
564 &self,
565 xt: CheckedExtrinsic,
566 spec_version: u32,
567 tx_version: u32,
568 genesis_hash: [u8; 32],
569 ) -> UncheckedExtrinsic {
570 match xt.format {
571 ExtrinsicFormat::Signed(signed, tx_ext) => {
572 let payload = (
573 xt.function,
574 tx_ext.clone(),
575 spec_version,
576 tx_version,
577 genesis_hash,
578 genesis_hash,
579 None::<()>,
581 );
582 let key = self.accounts.get(&signed).expect("Account id not found in keyring");
583 let signature = payload.using_encoded(|b| {
584 if b.len() > 256 {
585 key.sign(&blake2_256(b))
586 } else {
587 key.sign(b)
588 }
589 });
590 generic::UncheckedExtrinsic::new_signed(
591 payload.0,
592 sp_runtime::MultiAddress::Id(signed),
593 signature,
594 tx_ext,
595 )
596 .into()
597 },
598 ExtrinsicFormat::Bare => generic::UncheckedExtrinsic::new_bare(xt.function).into(),
599 ExtrinsicFormat::General(ext_version, tx_ext) =>
600 generic::UncheckedExtrinsic::from_parts(
601 xt.function,
602 Preamble::General(ext_version, tx_ext),
603 )
604 .into(),
605 }
606 }
607
608 pub fn as_storage_builder(&self) -> &dyn sp_runtime::BuildStorage {
611 self
612 }
613}
614
615impl sp_runtime::BuildStorage for BenchKeyring {
616 fn assimilate_storage(&self, storage: &mut sp_core::storage::Storage) -> Result<(), String> {
617 storage.top.insert(
618 sp_core::storage::well_known_keys::CODE.to_vec(),
619 kitchensink_runtime::wasm_binary_unwrap().into(),
620 );
621 crate::genesis::config_endowed(self.collect_account_ids()).assimilate_storage(storage)
622 }
623}
624
625struct Guard(tempfile::TempDir);
626
627impl Guard {
628 fn path(&self) -> &Path {
629 self.0.path()
630 }
631}
632
633pub struct BenchContext {
635 pub client: Arc<Client>,
637 pub backend: Arc<Backend>,
639 pub spawn_handle: Box<dyn SpawnNamed>,
641
642 db_guard: Guard,
643}
644
645type AccountPublic = <Signature as Verify>::Signer;
646
647impl BenchContext {
648 pub fn import_block(&mut self, block: Block) {
650 let mut import_params =
651 BlockImportParams::new(BlockOrigin::NetworkBroadcast, block.header.clone());
652 import_params.body = Some(block.extrinsics().to_vec());
653 import_params.fork_choice = Some(ForkChoiceStrategy::LongestChain);
654
655 assert_eq!(self.client.chain_info().best_number, 0);
656
657 assert_eq!(
658 futures::executor::block_on(self.client.import_block(import_params))
659 .expect("Failed to import block"),
660 ImportResult::Imported(ImportedAux {
661 header_only: false,
662 clear_justification_requests: false,
663 needs_justification: false,
664 bad_justification: false,
665 is_new_best: true,
666 })
667 );
668
669 assert_eq!(self.client.chain_info().best_number, 1);
670 }
671
672 pub fn path(&self) -> &Path {
674 self.db_guard.path()
675 }
676}