diff --git a/Cargo.lock b/Cargo.lock index c99eb27779eb..86d455ba8898 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4325,6 +4325,7 @@ dependencies = [ "parity-scale-codec", "polkadot-primitives", "polkadot-runtime-common", + "polkadot-runtime-test-client", "rustc-hex", "serde", "serde_derive", @@ -4391,6 +4392,27 @@ dependencies = [ "trie-db", ] +[[package]] +name = "polkadot-runtime-test-client" +version = "2.0.0" +dependencies = [ + "futures 0.3.4", + "pallet-timestamp", + "parity-scale-codec", + "polkadot-primitives", + "polkadot-runtime", + "polkadot-runtime-common", + "sc-block-builder", + "sc-client-api", + "sc-consensus", + "sc-service", + "sp-api", + "sp-blockchain", + "sp-core", + "sp-runtime", + "substrate-test-client", +] + [[package]] name = "polkadot-service" version = "0.7.33" diff --git a/Cargo.toml b/Cargo.toml index d47c3cefd945..c0bdf2698e99 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,6 +30,7 @@ members = [ "primitives", "runtime/common", "runtime/polkadot", + "runtime/polkadot/client", "runtime/kusama", "runtime/westend", "runtime/test-runtime", diff --git a/runtime/polkadot/Cargo.toml b/runtime/polkadot/Cargo.toml index 70c4aac5631f..895f2a1c30f9 100644 --- a/runtime/polkadot/Cargo.toml +++ b/runtime/polkadot/Cargo.toml @@ -73,6 +73,7 @@ keyring = { package = "sp-keyring", git = "https://github.com/paritytech/substra sp-trie = { git = "https://github.com/paritytech/substrate", branch = "master" } trie-db = "0.20.0" serde_json = "1.0.41" +polkadot-runtime-test-client = { path = "./client" } [build-dependencies] wasm-builder-runner = { package = "substrate-wasm-builder-runner", version = "1.0.6" } diff --git a/runtime/polkadot/client/Cargo.toml b/runtime/polkadot/client/Cargo.toml new file mode 100644 index 000000000000..7f462af54b2d --- /dev/null +++ b/runtime/polkadot/client/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "polkadot-runtime-test-client" +version = "2.0.0" +authors = ["Parity Technologies "] +edition = "2018" +license = "GPL-3.0" + +[dependencies] +sc-block-builder = { git = "https://github.com/paritytech/substrate", branch = "master" } +sc-consensus = { git = "https://github.com/paritytech/substrate", branch = "master" } +sc-service = { git = "https://github.com/paritytech/substrate", branch = "master", features = ["test-helpers"], default-features = false } +substrate-test-client = { git = "https://github.com/paritytech/substrate", branch = "master" } +sp-core = { git = "https://github.com/paritytech/substrate", branch = "master" } +polkadot-runtime = { path = ".." } +polkadot-runtime-common = { path = "../../common" } +polkadot-primitives = { path = "../../../primitives" } +sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master" } +sp-api = { git = "https://github.com/paritytech/substrate", branch = "master" } +sp-blockchain = { git = "https://github.com/paritytech/substrate", branch = "master" } +codec = { package = "parity-scale-codec", version = "1.0.0" } +sc-client-api = { git = "https://github.com/paritytech/substrate", branch = "master" } +pallet-timestamp = { git = "https://github.com/paritytech/substrate", branch = "master" } +futures = "0.3.1" diff --git a/runtime/polkadot/client/src/lib.rs b/runtime/polkadot/client/src/lib.rs new file mode 100644 index 000000000000..a365d9a5510a --- /dev/null +++ b/runtime/polkadot/client/src/lib.rs @@ -0,0 +1,343 @@ +// Copyright 2020 Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot 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. + +// Polkadot 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 Polkadot. If not, see . + +//! Polkadot client testing utilities. + +#![warn(missing_docs)] + +use std::sync::Arc; +use std::collections::BTreeMap; +pub use substrate_test_client::*; +pub use polkadot_runtime as runtime; + +use sp_core::{sr25519, ChangesTrieConfiguration, map, twox_128}; +use sp_core::storage::{ChildInfo, Storage, StorageChild}; +use polkadot_runtime::genesismap::GenesisConfig; +use sp_runtime::traits::{Block as BlockT, Header as HeaderT, Hash as HashT, HashFor}; +use sc_consensus::LongestChain; +use sc_client_api::light::{RemoteCallRequest, RemoteBodyRequest}; +use sc_service::client::{ + light::{ + call_executor::GenesisCallExecutor, backend as light_backend, + new_light_blockchain, new_light_backend, + }, + genesis, Client as SubstrateClient, LocalCallExecutor +}; + +/// A prelude to import in tests. +pub mod prelude { + // Trait extensions + pub use super::{ClientExt, ClientBlockImportExt}; + // Client structs + pub use super::{ + TestClient, TestClientBuilder, Backend, LightBackend, + Executor, LightExecutor, LocalExecutor, NativeExecutor, WasmExecutionMethod, + }; + // Keyring + pub use super::{AccountKeyring, Sr25519Keyring}; +} + +sc_executor::native_executor_instance! { + pub LocalExecutor, + polkadot_runtime::api::dispatch, + polkadot_runtime::native_version, +} + +/// Test client database backend. +pub type Backend = substrate_test_client::Backend; + +/// Test client executor. +pub type Executor = LocalCallExecutor< + Backend, + NativeExecutor, +>; + +/// Test client light database backend. +pub type LightBackend = substrate_test_client::LightBackend; + +/// Test client light executor. +pub type LightExecutor = GenesisCallExecutor< + LightBackend, + LocalCallExecutor< + light_backend::Backend< + sc_client_db::light::LightStorage, + HashFor + >, + NativeExecutor + > +>; + +/// Parameters of test-client builder with test-runtime. +#[derive(Default)] +pub struct GenesisParameters { + changes_trie_config: Option, + extra_storage: Storage, +} + +impl GenesisParameters { + fn genesis_config(&self) -> GenesisConfig { + GenesisConfig::new( + self.changes_trie_config.clone(), + vec![ + sr25519::Public::from(Sr25519Keyring::Alice).into(), + sr25519::Public::from(Sr25519Keyring::Bob).into(), + sr25519::Public::from(Sr25519Keyring::Charlie).into(), + ], + 1000, + self.extra_storage.clone(), + ) + } +} + +fn additional_storage_with_genesis(genesis_block: &polkadot_runtime::Block) -> BTreeMap, Vec> { + map![ + twox_128(&b"latest"[..]).to_vec() => genesis_block.hash().as_fixed_bytes().to_vec() + ] +} + +impl substrate_test_client::GenesisInit for GenesisParameters { + fn genesis_storage(&self) -> Storage { + use codec::Encode; + + let mut storage = self.genesis_config().genesis_map(); + + let child_roots = storage.children_default.iter().map(|(sk, child_content)| { + let state_root = <<::Header as HeaderT>::Hashing as HashT>::trie_root( + child_content.data.clone().into_iter().collect() + ); + (sk.clone(), state_root.encode()) + }); + let state_root = <<::Header as HeaderT>::Hashing as HashT>::trie_root( + storage.top.clone().into_iter().chain(child_roots).collect() + ); + let block: runtime::Block = genesis::construct_genesis_block(state_root); + storage.top.extend(additional_storage_with_genesis(&block)); + + storage + } +} + +/// A `TestClient` with `test-runtime` builder. +pub type TestClientBuilder = substrate_test_client::TestClientBuilder< + polkadot_runtime::Block, + E, + B, + GenesisParameters, +>; + +/// Test client type with `LocalExecutor` and generic Backend. +pub type Client = SubstrateClient< + B, + LocalCallExecutor>, + polkadot_runtime::Block, + polkadot_runtime::RuntimeApi, +>; + +/// A test client with default backend. +pub type TestClient = Client; + +/// A `TestClientBuilder` with default backend and executor. +pub trait DefaultTestClientBuilderExt: Sized { + /// Create new `TestClientBuilder` + fn new() -> Self; +} + +impl DefaultTestClientBuilderExt for TestClientBuilder { + fn new() -> Self { + Self::with_default_backend() + } +} + +/// A `test-runtime` extensions to `TestClientBuilder`. +pub trait TestClientBuilderExt: Sized { + /// Returns a mutable reference to the genesis parameters. + fn genesis_init_mut(&mut self) -> &mut GenesisParameters; + + /// Set changes trie configuration for genesis. + fn changes_trie_config(mut self, config: Option) -> Self { + self.genesis_init_mut().changes_trie_config = config; + self + } + + /// Add an extra value into the genesis storage. + /// + /// # Panics + /// + /// Panics if the key is empty. + fn add_extra_child_storage>, K: Into>, V: Into>>( + mut self, + storage_key: SK, + child_info: ChildInfo, + key: K, + value: V, + ) -> Self { + let storage_key = storage_key.into(); + let key = key.into(); + assert!(!storage_key.is_empty()); + assert!(!key.is_empty()); + self.genesis_init_mut().extra_storage.children_default + .entry(storage_key) + .or_insert_with(|| StorageChild { + data: Default::default(), + child_info: child_info.to_owned(), + }).data.insert(key, value.into()); + self + } + + /// Add an extra child value into the genesis storage. + /// + /// # Panics + /// + /// Panics if the key is empty. + fn add_extra_storage>, V: Into>>(mut self, key: K, value: V) -> Self { + let key = key.into(); + assert!(!key.is_empty()); + self.genesis_init_mut().extra_storage.top.insert(key, value.into()); + self + } + + /// Build the test client. + fn build(self) -> Client { + self.build_with_longest_chain().0 + } + + /// Build the test client and longest chain selector. + fn build_with_longest_chain(self) -> (Client, LongestChain); + + /// Build the test client and the backend. + fn build_with_backend(self) -> (Client, Arc); +} + +impl TestClientBuilderExt for TestClientBuilder< + LocalCallExecutor>, + Backend +> { + fn genesis_init_mut(&mut self) -> &mut GenesisParameters { + Self::genesis_init_mut(self) + } + + fn build_with_longest_chain(self) -> (Client, LongestChain) { + self.build_with_native_executor(None) + } + + fn build_with_backend(self) -> (Client, Arc) { + let backend = self.backend(); + (self.build_with_native_executor(None).0, backend) + } +} + +/// Type of optional fetch callback. +type MaybeFetcherCallback = Option Result + Send + Sync>>; + +/// Implementation of light client fetcher used in tests. +#[derive(Default)] +pub struct LightFetcher { + call: MaybeFetcherCallback, Vec>, + body: MaybeFetcherCallback, Vec>, +} + +impl LightFetcher { + /// Sets remote call callback. + pub fn with_remote_call( + self, + call: MaybeFetcherCallback, Vec>, + ) -> Self { + LightFetcher { + call, + body: self.body, + } + } + + /// Sets remote body callback. + pub fn with_remote_body( + self, + body: MaybeFetcherCallback, Vec>, + ) -> Self { + LightFetcher { + call: self.call, + body, + } + } +} + +/// Creates new client instance used for tests. +pub fn new() -> Client { + TestClientBuilder::new().build() +} + +/// Creates new light client instance used for tests. +pub fn new_light() -> ( + SubstrateClient< + LightBackend, + LightExecutor, + polkadot_runtime::Block, + polkadot_runtime::RuntimeApi + >, + Arc, +) { + + let storage = sc_client_db::light::LightStorage::new_test(); + let blockchain =new_light_blockchain(storage); + let backend = new_light_backend(blockchain.clone()); + let executor = new_native_executor(); + let local_call_executor = LocalCallExecutor::new( + backend.clone(), + executor, + sp_core::tasks::executor(), + Default::default() + ); + let call_executor = LightExecutor::new( + backend.clone(), + local_call_executor, + ); + + ( + TestClientBuilder::with_backend(backend.clone()) + .build_with_executor(call_executor) + .0, + backend, + ) +} + +/// Creates new light client fetcher used for tests. +pub fn new_light_fetcher() -> LightFetcher { + LightFetcher::default() +} + +/// Create a new native executor. +pub fn new_native_executor() -> sc_executor::NativeExecutor { + sc_executor::NativeExecutor::new(sc_executor::WasmExecutionMethod::Interpreted, None, 8) +} + +/// Extrinsics that must be included in each block. +pub fn needed_extrinsics(heads: Vec) -> Vec { + use polkadot_runtime_common::parachains; + + vec![ + polkadot_runtime::UncheckedExtrinsic { + function: polkadot_runtime::Call::Parachains(parachains::Call::set_heads(heads)), + signature: None, + }, + polkadot_runtime::UncheckedExtrinsic { + function: polkadot_runtime::Call::Timestamp(pallet_timestamp::Call::set({ + std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH) + .expect("now always later than unix epoch; qed") + .as_millis() as u64 + })), + signature: None, + } + ] +} diff --git a/runtime/polkadot/src/genesismap.rs b/runtime/polkadot/src/genesismap.rs new file mode 100644 index 000000000000..4c378d675a6e --- /dev/null +++ b/runtime/polkadot/src/genesismap.rs @@ -0,0 +1,85 @@ +// 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 . + +//! Tool for creating the genesis block. + +use std::collections::BTreeMap; +use super::{AccountId, WASM_BINARY, constants::currency}; +use sp_core::ChangesTrieConfiguration; +use sp_core::storage::Storage; +use sp_runtime::BuildStorage; + +/// Configuration of a general Substrate test genesis block. +pub struct GenesisConfig { + changes_trie_config: Option, + balances: Vec<(AccountId, u128)>, + /// Additional storage key pairs that will be added to the genesis map. + extra_storage: Storage, +} + +impl GenesisConfig { + pub fn new( + changes_trie_config: Option, + endowed_accounts: Vec, + balance: u128, + extra_storage: Storage, + ) -> Self { + GenesisConfig { + changes_trie_config, + balances: endowed_accounts.into_iter().map(|a| (a, balance * currency::DOLLARS)).collect(), + extra_storage, + } + } + + pub fn genesis_map(&self) -> Storage { + // Assimilate the system genesis config. + let mut storage = Storage { + top: BTreeMap::new(), + children_default: self.extra_storage.children_default.clone(), + }; + + // Q: Which of these should be initialized? + let config = crate::GenesisConfig { + system: Some(system::GenesisConfig { + changes_trie_config: self.changes_trie_config.clone(), + code: WASM_BINARY.to_vec(), + }), + babe: None, + indices: None, + balances: Some(balances::GenesisConfig { + balances: self.balances.clone() + }), + staking: None, + session: None, + grandpa: None, + claims: None, + parachains: None, + registrar: None, + vesting: None, + authority_discovery: None, + collective_Instance1: None, + collective_Instance2: None, + elections_phragmen: None, + membership_Instance1: None, + democracy: None, + im_online: None, + sudo: None, + }; + config.assimilate_storage(&mut storage).expect("Adding `system::GensisConfig` to the genesis"); + + storage + } +} diff --git a/runtime/polkadot/src/lib.rs b/runtime/polkadot/src/lib.rs index a255063b4589..79e726f498a7 100644 --- a/runtime/polkadot/src/lib.rs +++ b/runtime/polkadot/src/lib.rs @@ -74,6 +74,8 @@ pub use parachains::Call as ParachainsCall; /// Constant values used within the runtime. pub mod constants; +#[cfg(feature = "std")] +pub mod genesismap; use constants::{time::*, currency::*, fee::*}; // Make the WASM binary available. @@ -771,6 +773,9 @@ pub type Executive = executive::Executive; +/// Type for extrinsics. +pub type Extrinsic = ::Extrinsic; + sp_api::impl_runtime_apis! { impl sp_api::Core for Runtime { fn version() -> RuntimeVersion { diff --git a/runtime/polkadot/tests/fees.rs b/runtime/polkadot/tests/fees.rs new file mode 100644 index 000000000000..9010d31c8d0c --- /dev/null +++ b/runtime/polkadot/tests/fees.rs @@ -0,0 +1,54 @@ +// Copyright 2017-2020 Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot 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. + +// Polkadot 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 Polkadot. If not, see . + +//! Tests for Polkadot's weight and fee mechanics + +use frame_support::weights::{GetDispatchInfo, constants::WEIGHT_PER_SECOND}; +use primitives::AccountId; +use polkadot_runtime_test_client::{self, prelude::*, runtime}; +use polkadot_runtime::Runtime; +use polkadot_runtime::constants::{currency::*, fee::*}; + +#[test] +// Sanity check to make sure that the weight value here is what we expect from Substrate. +fn sanity_check_weight_is_as_expected() { + assert_eq!(WEIGHT_PER_SECOND, 1_000_000_000_000) +} + +#[test] +fn weight_of_transfer_is_correct() { + let alice: AccountId = AccountKeyring::Alice.into(); + let bob: AccountId = AccountKeyring::Bob.into(); + + let expected_weight = 195_000_000; + + let weight = runtime::BalancesCall::transfer::(bob, 42 * DOLLARS).get_dispatch_info().weight; + assert_eq!(weight, expected_weight); +} + +#[test] +fn transfer_fees_are_correct() { + use sp_runtime::traits::Convert; + + let alice: AccountId = AccountKeyring::Alice.into(); + let bob: AccountId = AccountKeyring::Bob.into(); + + let expected_fee = 15_600_000; + + let weight = runtime::BalancesCall::transfer::(bob, 42 * DOLLARS).get_dispatch_info().weight; + let fee = WeightToFee::convert(weight); + assert_eq!(fee, expected_fee); +}