From a706d994cb275fa4715152bd51c74f9faef090db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20M=C3=BCller?= Date: Tue, 28 May 2019 17:01:43 +0200 Subject: [PATCH] Transaction factory (#2481) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix typos * Add transaction factory `cargo run -- purge-chain -y --chain dev && cargo run -- --dev --transaction-factory 10` * Fix comment and remove build deps * Move crate to test-utils * Switch from flag to subcommand `cargo run -- factory --dev --num 5` * Decouple factory from node specifics * Introduce different manufacturing modes * Remove unrelated changes * Update Cargo.lock * Use SelectChain to fetch best block * Improve expect proof * Panic if factory executed with unsupported chain spec * Link ToDo comments to follow-up ticket * Address comments and improve style * Remove unused dependencies * Fix indent level * Replace naked unwrap * Update node/cli/src/factory_impl.rs * Fix typo * Use inherent_extrinsics instead of timestamp * Generalize factory and remove saturated conversions * Format imports * Make it clearer that database needs to be empty * Ensure factory settings * Apply suggestions from code review Co-Authored-By: Bastian Köcher * Update test-utils/transaction-factory/src/lib.rs Co-Authored-By: Bastian Köcher * Fix match guard syntax * Simplify import, remove empty line * Update node/cli/Cargo.toml * Update lockfile --- substrate/Cargo.lock | 20 ++ substrate/core/cli/src/lib.rs | 9 +- substrate/core/cli/src/traits.rs | 2 +- substrate/core/consensus/common/src/lib.rs | 2 +- substrate/core/inherents/src/lib.rs | 2 +- .../core/sr-primitives/src/generic/mod.rs | 2 +- substrate/node/cli/Cargo.toml | 7 + substrate/node/cli/src/factory_impl.rs | 259 ++++++++++++++++++ substrate/node/cli/src/lib.rs | 103 ++++++- .../test-utils/transaction-factory/Cargo.toml | 22 ++ .../transaction-factory/src/complex_mode.rs | 156 +++++++++++ .../test-utils/transaction-factory/src/lib.rs | 179 ++++++++++++ .../transaction-factory/src/modes.rs | 41 +++ .../transaction-factory/src/simple_modes.rs | 106 +++++++ 14 files changed, 898 insertions(+), 12 deletions(-) create mode 100644 substrate/node/cli/src/factory_impl.rs create mode 100644 substrate/test-utils/transaction-factory/Cargo.toml create mode 100644 substrate/test-utils/transaction-factory/src/complex_mode.rs create mode 100644 substrate/test-utils/transaction-factory/src/lib.rs create mode 100644 substrate/test-utils/transaction-factory/src/modes.rs create mode 100644 substrate/test-utils/transaction-factory/src/simple_modes.rs diff --git a/substrate/Cargo.lock b/substrate/Cargo.lock index 5d32c8882b..ac6f721d81 100644 --- a/substrate/Cargo.lock +++ b/substrate/Cargo.lock @@ -2020,8 +2020,12 @@ dependencies = [ "node-primitives 2.0.0", "node-runtime 2.0.0", "parity-codec 3.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", "sr-io 2.0.0", "sr-primitives 2.0.0", + "srml-finality-tracker 2.0.0", + "srml-indices 2.0.0", + "srml-timestamp 2.0.0", "structopt 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)", "substrate-basic-authorship 2.0.0", "substrate-cli 2.0.0", @@ -2029,6 +2033,7 @@ dependencies = [ "substrate-consensus-aura 2.0.0", "substrate-finality-grandpa 2.0.0", "substrate-inherents 2.0.0", + "substrate-keyring 2.0.0", "substrate-keystore 2.0.0", "substrate-network 2.0.0", "substrate-primitives 2.0.0", @@ -2037,6 +2042,7 @@ dependencies = [ "substrate-telemetry 2.0.0", "substrate-transaction-pool 2.0.0", "tokio 0.1.19 (registry+https://github.com/rust-lang/crates.io-index)", + "transaction-factory 0.0.1", ] [[package]] @@ -4918,6 +4924,20 @@ name = "traitobject" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "transaction-factory" +version = "0.0.1" +dependencies = [ + "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "parity-codec 3.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "sr-primitives 2.0.0", + "substrate-cli 2.0.0", + "substrate-client 2.0.0", + "substrate-consensus-common 2.0.0", + "substrate-primitives 2.0.0", + "substrate-service 2.0.0", +] + [[package]] name = "trie-bench" version = "0.12.2" diff --git a/substrate/core/cli/src/lib.rs b/substrate/core/cli/src/lib.rs index c8df07ad1f..101151ea0d 100644 --- a/substrate/core/cli/src/lib.rs +++ b/substrate/core/cli/src/lib.rs @@ -49,10 +49,10 @@ use structopt::{StructOpt, clap::AppSettings}; pub use structopt::clap::App; use params::{ RunCmd, PurgeChainCmd, RevertCmd, ImportBlocksCmd, ExportBlocksCmd, BuildSpecCmd, - NetworkConfigurationParams, SharedParams, MergeParameters, TransactionPoolParams, + NetworkConfigurationParams, MergeParameters, TransactionPoolParams, NodeKeyParams, NodeKeyType }; -pub use params::{NoCustom, CoreParams}; +pub use params::{NoCustom, CoreParams, SharedParams}; pub use traits::{GetLogFilter, AugmentClap}; use app_dirs::{AppInfo, AppDataType}; use log::info; @@ -178,7 +178,7 @@ fn is_node_name_valid(_name: &str) -> Result<(), &str> { /// /// `CC` is a custom subcommand. This needs to be an `enum`! If no custom subcommand is required, /// `NoCustom` can be used as type here. -/// `RP` is are custom parameters for the run command. This needs to be a `struct`! The custom +/// `RP` are custom parameters for the run command. This needs to be a `struct`! The custom /// parameters are visible to the user as if they were normal run command parameters. If no custom /// parameters are required, `NoCustom` can be used as type here. pub fn parse_and_execute<'a, F, CC, RP, S, RS, E, I, T>( @@ -582,7 +582,8 @@ where Ok(()) } -fn create_config_with_db_path( +/// Creates a configuration including the database path. +pub fn create_config_with_db_path( spec_factory: S, cli: &SharedParams, version: &VersionInfo, ) -> error::Result> where diff --git a/substrate/core/cli/src/traits.rs b/substrate/core/cli/src/traits.rs index ddb389e454..0f8d247e49 100644 --- a/substrate/core/cli/src/traits.rs +++ b/substrate/core/cli/src/traits.rs @@ -16,7 +16,7 @@ use structopt::{StructOpt, clap::App}; -/// Something that can augment a clapp app with further parameters. +/// Something that can augment a clap app with further parameters. /// `derive(StructOpt)` is implementing this function by default, so a macro `impl_augment_clap!` /// is provided to simplify the implementation of this trait. pub trait AugmentClap: StructOpt { diff --git a/substrate/core/consensus/common/src/lib.rs b/substrate/core/consensus/common/src/lib.rs index d28864a0cc..2d6c53d164 100644 --- a/substrate/core/consensus/common/src/lib.rs +++ b/substrate/core/consensus/common/src/lib.rs @@ -39,7 +39,7 @@ pub use inherents::InherentData; pub mod offline_tracker; pub mod error; -mod block_import; +pub mod block_import; mod select_chain; pub mod import_queue; pub mod evaluation; diff --git a/substrate/core/inherents/src/lib.rs b/substrate/core/inherents/src/lib.rs index 87fa39fe06..62b6f655cf 100644 --- a/substrate/core/inherents/src/lib.rs +++ b/substrate/core/inherents/src/lib.rs @@ -69,7 +69,7 @@ impl InherentData { /// /// # Return /// - /// Returns `Ok(())` if the data could be inserted an no data for an inherent with the same + /// Returns `Ok(())` if the data could be inserted and no data for an inherent with the same /// identifier existed, otherwise an error is returned. /// /// Inherent identifiers need to be unique, otherwise decoding of these values will not work! diff --git a/substrate/core/sr-primitives/src/generic/mod.rs b/substrate/core/sr-primitives/src/generic/mod.rs index b0f86f959f..3d7682407e 100644 --- a/substrate/core/sr-primitives/src/generic/mod.rs +++ b/substrate/core/sr-primitives/src/generic/mod.rs @@ -32,7 +32,7 @@ mod tests; pub use self::unchecked_extrinsic::UncheckedExtrinsic; pub use self::unchecked_mortal_extrinsic::UncheckedMortalExtrinsic; pub use self::unchecked_mortal_compact_extrinsic::UncheckedMortalCompactExtrinsic; -pub use self::era::Era; +pub use self::era::{Era, Phase}; pub use self::checked_extrinsic::CheckedExtrinsic; pub use self::header::Header; pub use self::block::{Block, SignedBlock, BlockId}; diff --git a/substrate/node/cli/Cargo.toml b/substrate/node/cli/Cargo.toml index 38cf49c35f..3c46f48f3f 100644 --- a/substrate/node/cli/Cargo.toml +++ b/substrate/node/cli/Cargo.toml @@ -30,6 +30,13 @@ sr-primitives = { path = "../../core/sr-primitives" } node-executor = { path = "../executor" } substrate-keystore = { path = "../../core/keystore" } substrate-telemetry = { package = "substrate-telemetry", path = "../../core/telemetry" } +structopt = "0.2" +transaction-factory = { path = "../../test-utils/transaction-factory" } +keyring = { package = "substrate-keyring", path = "../../core/keyring" } +indices = { package = "srml-indices", path = "../../srml/indices" } +timestamp = { package = "srml-timestamp", path = "../../srml/timestamp", default-features = false } +rand = "0.6" +finality_tracker = { package = "srml-finality-tracker", path = "../../srml/finality-tracker", default-features = false } [dev-dependencies] service-test = { package = "substrate-service-test", path = "../../core/service/test" } diff --git a/substrate/node/cli/src/factory_impl.rs b/substrate/node/cli/src/factory_impl.rs new file mode 100644 index 0000000000..a2ac6f5b29 --- /dev/null +++ b/substrate/node/cli/src/factory_impl.rs @@ -0,0 +1,259 @@ +// Copyright 2019 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 . + +//! Implementation of the transaction factory trait, which enables +//! using the cli to manufacture transactions and distribute them +//! to accounts. + +use rand::{Rng, SeedableRng}; +use rand::rngs::StdRng; + +use parity_codec::Decode; +use keyring::sr25519::Keyring; +use node_primitives::Hash; +use node_runtime::{Call, CheckedExtrinsic, UncheckedExtrinsic, BalancesCall}; +use primitives::sr25519; +use primitives::crypto::Pair; +use parity_codec::Encode; +use sr_primitives::generic::Era; +use sr_primitives::traits::{Block as BlockT, Header as HeaderT}; +use substrate_service::ServiceFactory; +use transaction_factory::RuntimeAdapter; +use transaction_factory::modes::Mode; +use crate::service; +use inherents::InherentData; +use timestamp; +use finality_tracker; + +// TODO get via api: >::minimum_period(). See #2587. +const MINIMUM_PERIOD: u64 = 99; + +pub struct FactoryState { + block_no: N, + + mode: Mode, + start_number: u64, + rounds: u64, + round: u64, + block_in_round: u64, + num: u64, +} + +type Number = <::Header as HeaderT>::Number; + +impl RuntimeAdapter for FactoryState { + type AccountId = node_primitives::AccountId; + type Balance = node_primitives::Balance; + type Block = node_primitives::Block; + type Phase = sr_primitives::generic::Phase; + type Secret = sr25519::Pair; + type Index = node_primitives::Index; + + type Number = Number; + + fn new( + mode: Mode, + num: u64, + rounds: u64, + ) -> FactoryState { + FactoryState { + mode, + num: num, + round: 0, + rounds, + block_in_round: 0, + block_no: 0, + start_number: 0, + } + } + + fn block_no(&self) -> Self::Number { + self.block_no + } + + fn block_in_round(&self) -> Self::Number { + self.block_in_round + } + + fn rounds(&self) -> Self::Number { + self.rounds + } + + fn num(&self) -> Self::Number { + self.num + } + + fn round(&self) -> Self::Number { + self.round + } + + fn start_number(&self) -> Self::Number { + self.start_number + } + + fn mode(&self) -> &Mode { + &self.mode + } + + fn set_block_no(&mut self, val: Self::Number) { + self.block_no = val; + } + + fn set_block_in_round(&mut self, val: Self::Number) { + self.block_in_round = val; + } + + fn set_round(&mut self, val: Self::Number) { + self.round = val; + } + + fn transfer_extrinsic( + &self, + sender: &Self::AccountId, + key: &Self::Secret, + destination: &Self::AccountId, + amount: &Self::Number, + prior_block_hash: &::Hash, + ) -> ::Extrinsic { + let index = self.extract_index(&sender, prior_block_hash); + let phase = self.extract_phase(*prior_block_hash); + + sign::(CheckedExtrinsic { + signed: Some((sender.clone(), index)), + function: Call::Balances( + BalancesCall::transfer( + indices::address::Address::Id( + destination.clone().into() + ), + (*amount).into() + ) + ) + }, key, &prior_block_hash, phase) + } + + fn inherent_extrinsics(&self) -> InherentData { + let timestamp = self.block_no * MINIMUM_PERIOD; + + let mut inherent = InherentData::new(); + inherent.put_data(timestamp::INHERENT_IDENTIFIER, ×tamp) + .expect("Failed putting timestamp inherent"); + inherent.put_data(finality_tracker::INHERENT_IDENTIFIER, &self.block_no) + .expect("Failed putting finalized number inherent"); + inherent + } + + fn minimum_balance() -> Self::Number { + // TODO get correct amount via api. See #2587. + 1337 + } + + fn master_account_id() -> Self::AccountId { + Keyring::Alice.pair().public() + } + + fn master_account_secret() -> Self::Secret { + Keyring::Alice.pair() + } + + /// Generates a random `AccountId` from `seed`. + fn gen_random_account_id(seed: &Self::Number) -> Self::AccountId { + let pair: sr25519::Pair = sr25519::Pair::from_seed(gen_seed_bytes(*seed)); + pair.public().into() + } + + /// Generates a random `Secret` from `seed`. + fn gen_random_account_secret(seed: &Self::Number) -> Self::Secret { + let pair: sr25519::Pair = sr25519::Pair::from_seed(gen_seed_bytes(*seed)); + pair + } + + fn extract_index( + &self, + _account_id: &Self::AccountId, + _block_hash: &::Hash, + ) -> Self::Index { + // TODO get correct index for account via api. See #2587. + // This currently prevents the factory from being used + // without a preceding purge of the database. + if self.mode == Mode::MasterToN || self.mode == Mode::MasterTo1 { + self.block_no() + } else { + match self.round() { + 0 => + // if round is 0 all transactions will be done with master as a sender + self.block_no(), + _ => + // if round is e.g. 1 every sender account will be new and not yet have + // any transactions done + 0 + } + } + } + + fn extract_phase( + &self, + _block_hash: ::Hash + ) -> Self::Phase { + // TODO get correct phase via api. See #2587. + // This currently prevents the factory from being used + // without a preceding purge of the database. + self.block_no + } +} + +fn gen_seed_bytes(seed: u64) -> [u8; 32] { + let mut rng: StdRng = SeedableRng::seed_from_u64(seed); + + let mut seed_bytes = [0u8; 32]; + for i in 0..32 { + seed_bytes[i] = rng.gen::(); + } + seed_bytes +} + +/// Creates an `UncheckedExtrinsic` containing the appropriate signature for +/// a `CheckedExtrinsics`. +fn sign( + xt: CheckedExtrinsic, + key: &sr25519::Pair, + prior_block_hash: &Hash, + phase: u64, +) -> ::Extrinsic { + let s = match xt.signed { + Some((signed, index)) => { + let era = Era::mortal(256, phase); + let payload = (index.into(), xt.function, era, prior_block_hash); + let signature = payload.using_encoded(|b| { + if b.len() > 256 { + key.sign(&sr_io::blake2_256(b)) + } else { + key.sign(b) + } + }).into(); + UncheckedExtrinsic { + signature: Some((indices::address::Address::Id(signed), signature, payload.0, era)), + function: payload.1, + } + } + None => UncheckedExtrinsic { + signature: None, + function: xt.function, + }, + }; + + let e = Encode::encode(&s); + Decode::decode(&mut &e[..]).expect("Failed to decode signed unchecked extrinsic") +} diff --git a/substrate/node/cli/src/lib.rs b/substrate/node/cli/src/lib.rs index 886c6eef77..ab1fd03ae7 100644 --- a/substrate/node/cli/src/lib.rs +++ b/substrate/node/cli/src/lib.rs @@ -22,16 +22,21 @@ pub use cli::error; pub mod chain_spec; mod service; +mod factory_impl; use tokio::prelude::Future; use tokio::runtime::{Builder as RuntimeBuilder, Runtime}; -pub use cli::{VersionInfo, IntoExit, NoCustom}; +pub use cli::{VersionInfo, IntoExit, NoCustom, SharedParams}; use substrate_service::{ServiceFactory, Roles as ServiceRoles}; use std::ops::Deref; use log::info; +use structopt::{StructOpt, clap::App}; +use cli::{AugmentClap, GetLogFilter}; +use crate::factory_impl::FactoryState; +use transaction_factory::RuntimeAdapter; /// The chain specification option. -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] pub enum ChainSpec { /// Whatever the current runtime is, with just Alice as an auth. Development, @@ -43,6 +48,68 @@ pub enum ChainSpec { StagingTestnet, } +/// Custom subcommands. +#[derive(Clone, Debug, StructOpt)] +pub enum CustomSubcommands { + /// The custom factory subcommmand for manufacturing transactions. + #[structopt( + name = "factory", + about = "Manufactures num transactions from Alice to random accounts. \ + Only supported for development or local testnet." + )] + Factory(FactoryCmd), +} + +impl GetLogFilter for CustomSubcommands { + fn get_log_filter(&self) -> Option { + None + } +} + +/// The `factory` command used to generate transactions. +/// Please note: this command currently only works on an empty database! +#[derive(Debug, StructOpt, Clone)] +pub struct FactoryCmd { + /// How often to repeat. This option only has an effect in mode `MasterToNToM`. + #[structopt(long="rounds", default_value = "1")] + pub rounds: u64, + + /// MasterToN: Manufacture `num` transactions from the master account + /// to `num` randomly created accounts, one each. + /// + /// MasterTo1: Manufacture `num` transactions from the master account + /// to exactly one other randomly created account. + /// + /// MasterToNToM: Manufacture `num` transactions from the master account + /// to `num` randomly created accounts. + /// From each of these randomly created accounts manufacture + /// a transaction to another randomly created account. + /// Repeat this `rounds` times. If `rounds` = 1 the behavior + /// is the same as `MasterToN`.{n} + /// A -> B, A -> C, A -> D, ... x `num`{n} + /// B -> E, C -> F, D -> G, ...{n} + /// ... x `rounds` + /// + /// These three modes control manufacturing. + #[structopt(long="mode", default_value = "MasterToN")] + pub mode: transaction_factory::Mode, + + /// Number of transactions to generate. In mode `MasterNToNToM` this is + /// the number of transactions per round. + #[structopt(long="num", default_value = "8")] + pub num: u64, + + #[allow(missing_docs)] + #[structopt(flatten)] + pub shared_params: SharedParams, +} + +impl AugmentClap for FactoryCmd { + fn augment_clap<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> { + FactoryCmd::augment_clap(app) + } +} + /// Get a chain config from a spec setting. impl ChainSpec { pub(crate) fn load(self) -> Result { @@ -78,7 +145,7 @@ pub fn run(args: I, exit: E, version: cli::VersionInfo) -> error::Resul T: Into + Clone, E: IntoExit, { - cli::parse_and_execute::( + let ret = cli::parse_and_execute::( load_spec, &version, "substrate-node", args, exit, |exit, _cli_args, _custom_args, config| { info!("{}", version.name); @@ -103,7 +170,35 @@ pub fn run(args: I, exit: E, version: cli::VersionInfo) -> error::Resul ), }.map_err(|e| format!("{:?}", e)) } - ).map_err(Into::into).map(|_| ()) + ); + + match &ret { + Ok(Some(CustomSubcommands::Factory(cli_args))) => { + let config = cli::create_config_with_db_path::( + load_spec, + &cli_args.shared_params, + &version, + )?; + + match ChainSpec::from(config.chain_spec.id()) { + Some(ref c) if c == &ChainSpec::Development || c == &ChainSpec::LocalTestnet => {}, + _ => panic!("Factory is only supported for development and local testnet."), + } + + let factory_state = FactoryState::new( + cli_args.mode.clone(), + cli_args.num, + cli_args.rounds, + ); + transaction_factory::factory::>( + factory_state, + config, + ).map_err(|e| format!("Error in transaction factory: {}", e))?; + + Ok(()) + }, + _ => ret.map_err(Into::into).map(|_| ()) + } } fn run_until_exit( diff --git a/substrate/test-utils/transaction-factory/Cargo.toml b/substrate/test-utils/transaction-factory/Cargo.toml new file mode 100644 index 0000000000..66faaf6b34 --- /dev/null +++ b/substrate/test-utils/transaction-factory/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "transaction-factory" +version = "0.0.1" +authors = ["Parity Technologies "] +edition = "2018" + +[dependencies] +cli = { package = "substrate-cli", path = "../../core/cli" } +client = { package = "substrate-client", path = "../../core/client" } +consensus_common = { package = "substrate-consensus-common", path = "../../core/consensus/common" } +log = "0.4" +parity-codec = { version = "3.3", default-features = false, features = ["derive"] } +primitives = { package = "substrate-primitives", path = "../../core/primitives", default-features = false } +sr_primitives = { package = "sr-primitives", path = "../../core/sr-primitives", default-features = false } +substrate-service = { path = "../../core/service" } + +[features] +default = ["std"] +std = [ + "parity-codec/std", + "primitives/std", +] diff --git a/substrate/test-utils/transaction-factory/src/complex_mode.rs b/substrate/test-utils/transaction-factory/src/complex_mode.rs new file mode 100644 index 0000000000..6200affaea --- /dev/null +++ b/substrate/test-utils/transaction-factory/src/complex_mode.rs @@ -0,0 +1,156 @@ +// Copyright 2019 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 . + +/// This module implements the `MasterToNToM` mode: +/// +/// Manufacture `num` transactions from the master account to `num` +/// randomly created accounts. From each of these randomly created +/// accounts manufacture a transaction to another randomly created +/// account. +/// Repeat this round `rounds` times. If `rounds` = 1 the behavior +/// is the same as `MasterToN`. +/// +/// A -> B +/// A -> C +/// A -> D +/// ... x `num` +/// +/// B -> E +/// C -> F +/// D -> G +/// ... +/// E -> H +/// F -> I +/// G -> J +/// ... +/// ... x `rounds` + +use std::sync::Arc; + +use log::info; +use client::block_builder::api::BlockBuilder; +use client::runtime_api::ConstructRuntimeApi; +use sr_primitives::generic::BlockId; +use sr_primitives::traits::{Block as BlockT, ProvideRuntimeApi, One, Zero}; +use substrate_service::{ + FactoryBlock, FullClient, ServiceFactory, ComponentClient, FullComponents +}; + +use crate::{RuntimeAdapter, create_block}; + +pub fn next( + factory_state: &mut RA, + client: &Arc>>, + prior_block_hash: ::Hash, + prior_block_id: BlockId, +) -> Option<::Block> +where + F: ServiceFactory, + F::RuntimeApi: ConstructRuntimeApi, FullClient>, + FullClient: ProvideRuntimeApi, + as ProvideRuntimeApi>::Api: BlockBuilder>, + RA: RuntimeAdapter, +{ + let total = factory_state.start_number() + factory_state.num() * factory_state.rounds(); + + if factory_state.block_no() >= total || factory_state.round() >= factory_state.rounds() { + return None; + } + + info!( + "Round {}: Creating {} transactions in total, {} per round. {} rounds in total.", + factory_state.round() + RA::Number::one(), + factory_state.num() * factory_state.rounds(), + factory_state.num(), + factory_state.rounds(), + ); + + let from = from::(factory_state); + + let seed = factory_state.start_number() + factory_state.block_no(); + let to = RA::gen_random_account_id(&seed); + + let mut amount; + if factory_state.round() == RA::Number::zero() { + amount = RA::minimum_balance() * factory_state.rounds(); + } else { + let rounds_left = factory_state.rounds() - factory_state.round(); + amount = RA::minimum_balance() * rounds_left; + }; + + let transfer = factory_state.transfer_extrinsic( + &from.0, + &from.1, + &to, + &amount, + &prior_block_hash, + ); + + let inherents = factory_state.inherent_extrinsics(); + let inherents = client.runtime_api().inherent_extrinsics(&prior_block_id, inherents) + .expect("Failed to create inherent extrinsics"); + + let block = create_block::(&client, transfer, inherents); + info!( + "Created block {} with hash {}. Transferring {} from {} to {}.", + factory_state.block_no() + RA::Number::one(), + prior_block_hash, + amount, + from.0, + to + ); + + factory_state.set_block_no(factory_state.block_no() + RA::Number::one()); + + let new_round = factory_state.block_no() > RA::Number::zero() + && factory_state.block_no() % factory_state.num() == RA::Number::zero(); + if new_round { + factory_state.set_round(factory_state.round() + RA::Number::one()); + factory_state.set_block_in_round(RA::Number::zero()); + } else { + factory_state.set_block_in_round(factory_state.block_in_round() + RA::Number::one()); + } + + Some(block) +} + +/// Return the account which received tokens at this point in the previous round. +fn from( + factory_state: &mut RA +) -> (::AccountId, ::Secret) +where RA: RuntimeAdapter +{ + let is_first_round = factory_state.round() == RA::Number::zero(); + match is_first_round { + true => { + // first round always uses master account + (RA::master_account_id(), RA::master_account_secret()) + }, + _ => { + // the account to which was sent in the last round + let is_round_one = factory_state.round() == RA::Number::one(); + let seed = match is_round_one { + true => factory_state.start_number() + factory_state.block_in_round(), + _ => { + let block_no_in_prior_round = + factory_state.num() * (factory_state.round() - RA::Number::one()) + factory_state.block_in_round(); + factory_state.start_number() + block_no_in_prior_round + } + }; + (RA::gen_random_account_id(&seed), RA::gen_random_account_secret(&seed)) + }, + } +} diff --git a/substrate/test-utils/transaction-factory/src/lib.rs b/substrate/test-utils/transaction-factory/src/lib.rs new file mode 100644 index 0000000000..02772daf18 --- /dev/null +++ b/substrate/test-utils/transaction-factory/src/lib.rs @@ -0,0 +1,179 @@ +// Copyright 2019 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 . + +//! Simple transaction factory which distributes tokens from a master +//! account to a specified number of newly created accounts. +//! +//! The factory currently only works on an empty database! + +use std::collections::HashMap; +use std::sync::Arc; +use std::ops::Mul; +use std::cmp::PartialOrd; +use std::fmt::Display; + +use log::info; + +use client::block_builder::api::BlockBuilder; +use client::runtime_api::ConstructRuntimeApi; +use consensus_common::{ + BlockOrigin, ImportBlock, InherentData, ForkChoiceStrategy, + SelectChain +}; +use consensus_common::block_import::BlockImport; +use parity_codec::{Decode, Encode}; +use sr_primitives::generic::BlockId; +use sr_primitives::traits::{ + Block as BlockT, Header as HeaderT, ProvideRuntimeApi, SimpleArithmetic, + One, Zero, +}; +use substrate_service::{ + FactoryBlock, FactoryFullConfiguration, FullClient, new_client, + ServiceFactory, ComponentClient, FullComponents}; +pub use crate::modes::Mode; + +pub mod modes; +mod complex_mode; +mod simple_modes; + +pub trait RuntimeAdapter { + type AccountId: Display; + type Balance: Display + Mul; + type Block: BlockT; + type Index: Copy; + type Number: Display + PartialOrd + SimpleArithmetic + Zero + One; + type Phase: Copy; + type Secret; + + fn new(mode: Mode, rounds: u64, start_number: u64) -> Self; + + fn block_no(&self) -> Self::Number; + fn block_in_round(&self) -> Self::Number; + fn mode(&self) -> &Mode; + fn num(&self) -> Self::Number; + fn rounds(&self) -> Self::Number; + fn round(&self) -> Self::Number; + fn start_number(&self) -> Self::Number; + + fn set_block_in_round(&mut self, val: Self::Number); + fn set_block_no(&mut self, val: Self::Number); + fn set_round(&mut self, val: Self::Number); + + fn transfer_extrinsic( + &self, + sender: &Self::AccountId, + key: &Self::Secret, + destination: &Self::AccountId, + amount: &Self::Number, + prior_block_hash: &::Hash, + ) -> ::Extrinsic; + + fn inherent_extrinsics(&self) -> InherentData; + + fn minimum_balance() -> Self::Number; + fn master_account_id() -> Self::AccountId; + fn master_account_secret() -> Self::Secret; + fn extract_index(&self, account_id: &Self::AccountId, block_hash: &::Hash) -> Self::Index; + fn extract_phase(&self, block_hash: ::Hash) -> Self::Phase; + fn gen_random_account_id(seed: &Self::Number) -> Self::AccountId; + fn gen_random_account_secret(seed: &Self::Number) -> Self::Secret; +} + +/// Manufactures transactions. The exact amount depends on +/// `mode`, `num` and `rounds`. +pub fn factory( + mut factory_state: RA, + mut config: FactoryFullConfiguration, +) -> cli::error::Result<()> +where + F: ServiceFactory, + F::RuntimeApi: ConstructRuntimeApi, FullClient>, + FullClient: ProvideRuntimeApi, + as ProvideRuntimeApi>::Api: BlockBuilder>, + RA: RuntimeAdapter, + <::Block as BlockT>::Hash: From, +{ + if *factory_state.mode() != Mode::MasterToNToM && factory_state.rounds() > RA::Number::one() { + return Err("The factory can only be used with rounds set to 1 in this mode.".into()); + } + + let client = new_client::(&config)?; + + let select_chain = F::build_select_chain(&mut config, client.clone())?; + + let best_header: Result<::Header, cli::error::Error> = + select_chain.best_chain().map_err(|e| format!("{:?}", e).into()); + let mut best_hash = best_header?.hash(); + let best_block_id = BlockId::::hash(best_hash); + + while let Some(block) = match factory_state.mode() { + Mode::MasterToNToM => + complex_mode::next::(&mut factory_state, &client, best_hash.into(), best_block_id), + _ => + simple_modes::next::(&mut factory_state, &client, best_hash.into(), best_block_id) + } { + best_hash = block.header().hash(); + import_block::(&client, block); + + info!("Imported block at {}", factory_state.block_no()); + } + + Ok(()) +} + +/// Create a baked block from a transfer extrinsic and timestamp inherent. +pub fn create_block( + client: &Arc>>, + transfer: ::Extrinsic, + inherent_extrinsics: Vec<::Extrinsic>, +) -> ::Block +where + F: ServiceFactory, + FullClient: ProvideRuntimeApi, + F::RuntimeApi: ConstructRuntimeApi, FullClient>, + as ProvideRuntimeApi>::Api: BlockBuilder>, + RA: RuntimeAdapter, +{ + let mut block = client.new_block().expect("Failed to create new block"); + block.push( + Decode::decode(&mut &transfer.encode()[..]) + .expect("Failed to decode transfer extrinsic") + ).expect("Failed to push transfer extrinsic into block"); + + for inherent in inherent_extrinsics { + block.push(inherent).expect("Failed ..."); + } + + block.bake().expect("Failed to bake block") +} + +fn import_block( + client: &Arc>>, + block: ::Block +) -> () where F: ServiceFactory +{ + let import = ImportBlock { + origin: BlockOrigin::File, + header: block.header().clone(), + post_digests: Vec::new(), + body: Some(block.extrinsics().to_vec()), + finalized: false, + justification: None, + auxiliary: Vec::new(), + fork_choice: ForkChoiceStrategy::LongestChain, + }; + client.import_block(import, HashMap::new()).expect("Failed to import block"); +} diff --git a/substrate/test-utils/transaction-factory/src/modes.rs b/substrate/test-utils/transaction-factory/src/modes.rs new file mode 100644 index 0000000000..f3d278bbbf --- /dev/null +++ b/substrate/test-utils/transaction-factory/src/modes.rs @@ -0,0 +1,41 @@ +// Copyright 2019 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 . + +//! The transaction factory can operate in different modes. See +//! the `simple_mode` and `complex_mode` modules for details. + +use std::str::FromStr; + +/// Token distribution modes. +#[derive(Debug, Clone, PartialEq)] +pub enum Mode { + MasterToN, + MasterTo1, + MasterToNToM +} + +impl FromStr for Mode { + type Err = String; + fn from_str(mode: &str) -> Result { + match mode { + "MasterToN" => Ok(Mode::MasterToN), + "MasterTo1" => Ok(Mode::MasterTo1), + "MasterToNToM" => Ok(Mode::MasterToNToM), + _ => Err(format!("Invalid mode: {}", mode)), + } + } +} + diff --git a/substrate/test-utils/transaction-factory/src/simple_modes.rs b/substrate/test-utils/transaction-factory/src/simple_modes.rs new file mode 100644 index 0000000000..4ce7b47e6f --- /dev/null +++ b/substrate/test-utils/transaction-factory/src/simple_modes.rs @@ -0,0 +1,106 @@ +// Copyright 2019 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 . + +/// This module implements two manufacturing modes: +/// +/// # MasterToN +/// Manufacture `num` transactions from the master account +/// to `num` randomly created accounts, one each. +/// +/// A -> B +/// A -> C +/// ... x `num` +/// +/// +/// # MasterTo1 +/// Manufacture `num` transactions from the master account +/// to exactly one other randomly created account. +/// +/// A -> B +/// A -> B +/// ... x `num` + +use std::sync::Arc; + +use log::info; +use client::block_builder::api::BlockBuilder; +use client::runtime_api::ConstructRuntimeApi; +use sr_primitives::traits::{Block as BlockT, ProvideRuntimeApi, One}; +use sr_primitives::generic::BlockId; +use substrate_service::{ + FactoryBlock, FullClient, ServiceFactory, ComponentClient, FullComponents +}; + +use crate::{Mode, RuntimeAdapter, create_block}; + +pub fn next( + factory_state: &mut RA, + client: &Arc>>, + prior_block_hash: ::Hash, + prior_block_id: BlockId, +) -> Option<::Block> +where + F: ServiceFactory, + F::RuntimeApi: ConstructRuntimeApi, FullClient>, + FullClient: ProvideRuntimeApi, + as ProvideRuntimeApi>::Api: BlockBuilder>, + RA: RuntimeAdapter, +{ + if factory_state.block_no() >= factory_state.num() { + return None; + } + + let from = (RA::master_account_id(), RA::master_account_secret()); + + let seed = match factory_state.mode() { + // choose the same receiver for all transactions + Mode::MasterTo1 => factory_state.start_number(), + + // different receiver for each transaction + Mode::MasterToN => factory_state.start_number() + factory_state.block_no(), + _ => unreachable!("Mode not covered!"), + }; + let to = RA::gen_random_account_id(&seed); + + let amount = RA::minimum_balance(); + + let transfer = factory_state.transfer_extrinsic( + &from.0, + &from.1, + &to, + &amount, + &prior_block_hash, + ); + + let inherents = RA::inherent_extrinsics(&factory_state); + let inherents = client.runtime_api().inherent_extrinsics(&prior_block_id, inherents) + .expect("Failed to create inherent extrinsics"); + + let block = create_block::(&client, transfer, inherents); + + factory_state.set_block_no(factory_state.block_no() + RA::Number::one()); + + info!( + "Created block {} with hash {}. Transferring {} from {} to {}.", + factory_state.block_no(), + prior_block_hash, + amount, + from.0, + to + ); + + Some(block) +}