snapshot before rebranding
This commit is contained in:
@@ -0,0 +1,732 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
//! Provides transactions sending scenarios together with associated builders and executors.
|
||||
|
||||
use std::{collections::HashMap, ops::Range, sync::Arc, time::Duration};
|
||||
|
||||
use clap::{Subcommand, ValueEnum};
|
||||
use futures::executor::block_on;
|
||||
use subxt::utils::H256;
|
||||
use subxt_core::config::Hash as BlockHash;
|
||||
use tokio::sync::mpsc::Sender;
|
||||
|
||||
use crate::{
|
||||
block_monitor::BlockMonitor,
|
||||
execution_log::TransactionExecutionLog,
|
||||
runner::{DefaultTxTask, Runner, TxTask},
|
||||
subxt_transaction::{
|
||||
eth_transfer_payload_builder, generate_ecdsa_keypair, generate_sr25519_keypair,
|
||||
remark_payload_builder, sub_transfer_payload_builder, EthPayloadBuilderFn, EthTransaction,
|
||||
EthTransactionsSink, EthTxBuildContext, SubPayloadBuilderFn, SubTxBuildContext,
|
||||
SubstrateTransaction, SubstrateTransactionsSink,
|
||||
},
|
||||
transaction::{
|
||||
BuildTransactionParams, EthTransactionBuilder, SubstrateTransactionBuilder, Transaction,
|
||||
TransactionBuilder, TransactionCall, TransactionRecipe, TransactionsSink,
|
||||
},
|
||||
};
|
||||
use subxt::tx::DynamicPayload;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
/// Holds information relevant for transaction generation.
|
||||
pub(crate) struct TransactionBuildParams {
|
||||
pub account: String,
|
||||
pub nonce: Option<u128>,
|
||||
pub mortality: Option<u64>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
/// Describes the account types that will participate
|
||||
/// in a [`ScenarioType`].
|
||||
pub enum AccountsDescription {
|
||||
Keyring(String),
|
||||
Derived(Range<u32>),
|
||||
}
|
||||
|
||||
#[derive(ValueEnum, Clone)]
|
||||
pub enum ChainType {
|
||||
/// Substrate compatible chain.
|
||||
Sub,
|
||||
/// Etheruem compatible chain.
|
||||
Eth,
|
||||
/// A fake chain used for experiments & tests.
|
||||
Fake,
|
||||
}
|
||||
|
||||
#[derive(Subcommand, Clone)]
|
||||
/// This enum represents different transactions sending scenarios.
|
||||
pub enum ScenarioType {
|
||||
/// Send single transaction to the node.
|
||||
OneShot {
|
||||
/// Account identifier to be used. It can be keyring account (alice, bob,...) or number of
|
||||
/// pre-funded account, index used for derivation.
|
||||
#[clap(long, default_value = "alice")]
|
||||
account: String,
|
||||
/// Nonce used for the account.
|
||||
#[clap(long)]
|
||||
nonce: Option<u128>,
|
||||
},
|
||||
/// Send multiple transactions to the node using a single account.
|
||||
FromSingleAccount {
|
||||
/// Account identifier to be used. It can be keyring account (alice, bob,...) or number of
|
||||
/// pre-funded account, index used for derivation.
|
||||
#[clap(long, default_value = "alice")]
|
||||
account: String,
|
||||
/// Starting nonce for 1st transaction in the batch. If not given the current nonce for
|
||||
/// the account will be fetched from node for the first transaction in the batch.
|
||||
#[clap(long)]
|
||||
from: Option<u128>,
|
||||
/// Number of transaction in the batch.
|
||||
#[clap(long, default_value_t = 1)]
|
||||
count: u32,
|
||||
},
|
||||
/// Send multiple transactions to the node using multiple accounts.
|
||||
FromManyAccounts {
|
||||
/// First account identifier to be used (index of the pre-funded account used for a
|
||||
/// derivation).
|
||||
#[clap(long)]
|
||||
start_id: u32,
|
||||
/// Last account identifier to be used.
|
||||
#[clap(long)]
|
||||
last_id: u32,
|
||||
/// Starting nonce of transactions batch. If not given the current nonce for each account
|
||||
/// will be fetched from node.
|
||||
#[clap(long)]
|
||||
from: Option<u128>,
|
||||
/// Number of transaction in the batch per account.
|
||||
#[clap(long, default_value_t = 1)]
|
||||
count: u32,
|
||||
},
|
||||
}
|
||||
|
||||
pub type EthScenarioRunner = Runner<DefaultTxTask<EthTransaction>, EthTransactionsSink>;
|
||||
pub struct EthScenarioExecutor {
|
||||
stop_sender: Sender<()>,
|
||||
runner: EthScenarioRunner,
|
||||
}
|
||||
|
||||
pub type SubstrateScenarioRunner =
|
||||
Runner<DefaultTxTask<SubstrateTransaction>, SubstrateTransactionsSink>;
|
||||
pub struct SubstrateScenarioExecutor {
|
||||
stop_sender: Sender<()>,
|
||||
runner: SubstrateScenarioRunner,
|
||||
}
|
||||
|
||||
impl SubstrateScenarioExecutor {
|
||||
pub(crate) fn new(stop_sender: Sender<()>, runner: SubstrateScenarioRunner) -> Self {
|
||||
SubstrateScenarioExecutor { stop_sender, runner }
|
||||
}
|
||||
}
|
||||
|
||||
impl EthScenarioExecutor {
|
||||
pub(crate) fn new(stop_sender: Sender<()>, runner: EthScenarioRunner) -> Self {
|
||||
EthScenarioExecutor { stop_sender, runner }
|
||||
}
|
||||
}
|
||||
|
||||
/// Multi-chain scenario executor.
|
||||
pub enum ScenarioExecutor {
|
||||
Eth(EthScenarioExecutor),
|
||||
Substrate(SubstrateScenarioExecutor),
|
||||
}
|
||||
|
||||
impl ScenarioExecutor {
|
||||
/// Executes the encapsulated scenario to send out transactions.
|
||||
///
|
||||
/// Executes the set of transaction sending tasks, and follows a transaction status on the node
|
||||
/// side until a final state is reached.
|
||||
///
|
||||
/// It returns a mapping of transaction hashes to their respective execution log entries,
|
||||
/// providing a detailed view of the transaction's execution process.
|
||||
///
|
||||
/// It is subject to the configured timeout, and if it will be reached, will return a subset of
|
||||
/// the execution logs.
|
||||
pub async fn execute(self) -> HashMap<H256, Arc<TransactionExecutionLog<H256>>> {
|
||||
match self {
|
||||
ScenarioExecutor::Eth(mut inner) => inner.runner.run().await,
|
||||
ScenarioExecutor::Substrate(mut inner) => inner.runner.run().await,
|
||||
}
|
||||
}
|
||||
|
||||
/// Installs a ctrl_c handler which sends a stop notification on the executor
|
||||
/// stop sender channel, to notify the stop of the scenario for displaying partial stats about
|
||||
/// the transactions execution.
|
||||
///
|
||||
/// Can be called only once for the lifetime of a the program execution.
|
||||
fn install_ctrlc_stop_hook(&self) {
|
||||
let stop_sender = match &self {
|
||||
ScenarioExecutor::Eth(inner) => inner.stop_sender.clone(),
|
||||
ScenarioExecutor::Substrate(inner) => inner.stop_sender.clone(),
|
||||
};
|
||||
ctrlc::set_handler(move || {
|
||||
block_on(stop_sender.send(())).expect("Could not send signal on channel.")
|
||||
})
|
||||
.expect("Error setting Ctrl-C handler");
|
||||
}
|
||||
}
|
||||
|
||||
/// Source of the transaction payload builder.
|
||||
enum TxPayloadBuilderSource {
|
||||
/// Recipe to be resolved to a builder in `build()` based on chain_type.
|
||||
Recipe(TransactionRecipe),
|
||||
/// Custom Substrate payload builder.
|
||||
SubCustom(SubPayloadBuilderFn),
|
||||
/// Custom Ethereum payload builder.
|
||||
EthCustom(EthPayloadBuilderFn),
|
||||
}
|
||||
|
||||
impl TxPayloadBuilderSource {
|
||||
/// Resolve the source into a Substrate payload builder.
|
||||
fn into_sub_builder(self) -> SubPayloadBuilderFn {
|
||||
match self {
|
||||
Self::SubCustom(f) => f,
|
||||
Self::Recipe(recipe) => match recipe.call {
|
||||
TransactionCall::Remark(size_kb) => remark_payload_builder(size_kb),
|
||||
TransactionCall::Transfer => sub_transfer_payload_builder(),
|
||||
},
|
||||
Self::EthCustom(_) => {
|
||||
panic!("EthCustom payload builder cannot be used with ChainType::Sub")
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Resolve the source into an Ethereum payload builder.
|
||||
fn into_eth_builder(self) -> EthPayloadBuilderFn {
|
||||
match self {
|
||||
Self::EthCustom(f) => f,
|
||||
Self::Recipe(recipe) => match recipe.call {
|
||||
TransactionCall::Remark(size_kb) => remark_payload_builder(size_kb),
|
||||
TransactionCall::Transfer => eth_transfer_payload_builder(),
|
||||
},
|
||||
Self::SubCustom(_) => {
|
||||
panic!("SubCustom payload builder cannot be used with ChainType::Eth")
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Building logic for the execution of a scenario.
|
||||
pub struct ScenarioBuilder {
|
||||
account_id: Option<String>,
|
||||
start_id: Option<u32>,
|
||||
last_id: Option<u32>,
|
||||
nonce_from: Option<u128>,
|
||||
txs_count: u32,
|
||||
tx_payload_builder_source: Option<TxPayloadBuilderSource>,
|
||||
mortality: Option<u64>,
|
||||
does_block_monitoring: bool,
|
||||
watched_txs: bool,
|
||||
send_threshold: Option<usize>,
|
||||
rpc_uri: Option<String>,
|
||||
chain_type: Option<ChainType>,
|
||||
installs_ctrl_c_stop_hook: bool,
|
||||
executor_id: Option<String>,
|
||||
tip: u128,
|
||||
log_file_name_prefix: Option<String>,
|
||||
base_dir_path: Option<String>,
|
||||
timeout: Option<Duration>,
|
||||
use_legacy_backend: bool,
|
||||
}
|
||||
|
||||
impl Default for ScenarioBuilder {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl ScenarioBuilder {
|
||||
/// A default initializer of the builder, with a few defaults:
|
||||
/// - `tx_payload_builder_source` is set to transfer recipe.
|
||||
/// - `does_block_monitoring` is set to `false`.
|
||||
/// - `installs_ctrl_c_stop_hook` is set to `false`.
|
||||
/// - `send_threshold` is set to `1000`.
|
||||
pub fn new() -> Self {
|
||||
ScenarioBuilder {
|
||||
account_id: None,
|
||||
start_id: None,
|
||||
last_id: None,
|
||||
nonce_from: None,
|
||||
txs_count: 1,
|
||||
tx_payload_builder_source: Some(TxPayloadBuilderSource::Recipe(
|
||||
TransactionRecipe::transfer(),
|
||||
)),
|
||||
does_block_monitoring: false,
|
||||
mortality: None,
|
||||
watched_txs: false,
|
||||
send_threshold: Some(1000),
|
||||
rpc_uri: None,
|
||||
chain_type: None,
|
||||
installs_ctrl_c_stop_hook: false,
|
||||
executor_id: None,
|
||||
tip: 0,
|
||||
log_file_name_prefix: None,
|
||||
base_dir_path: None,
|
||||
timeout: None,
|
||||
use_legacy_backend: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Configure the account id for building a batch of transactions based on a single signer.
|
||||
/// The setter parameter is a string that can be in the form of a number, in which case it will
|
||||
/// behave the same as using `with_start_id` (without `with_last_id`), but it can receive
|
||||
/// a derivation path like the usual Polkadot development accounts (e.g. "alice", "bob", etc).
|
||||
pub fn with_account_id(mut self, account_id: String) -> Self {
|
||||
self.account_id = Some(account_id);
|
||||
self
|
||||
}
|
||||
|
||||
/// Configure the account id for the first signer used for the transactions building.
|
||||
/// If the builder isn't configured with a last signer account id, then the scenario
|
||||
/// builder will build transactions only for the account specified with this setter.
|
||||
///
|
||||
/// It is usually used in pair with `with_last_id`, to set an ids range where each id will be
|
||||
/// the last part of a derivation path used for multiple accounts generation, each being a
|
||||
/// signer for a batch of transactions.
|
||||
pub fn with_start_id(mut self, start_id: u32) -> Self {
|
||||
self.start_id = Some(start_id);
|
||||
self
|
||||
}
|
||||
|
||||
/// Last id of an account signer that is also representing the end of an ids range,
|
||||
/// each id being the last part of a derivation path used to generate accounts that sign a set
|
||||
/// of transactions (see
|
||||
/// [`crate::subxt_transaction::derive_accounts`]).
|
||||
pub fn with_last_id(mut self, last_id: u32) -> Self {
|
||||
self.last_id = Some(last_id);
|
||||
self
|
||||
}
|
||||
|
||||
/// The start of a nonce counter that's incremented with each built transaction, in relation to
|
||||
/// a specific signer account (that can be also part of a range of accounts as it happens when
|
||||
/// both `start_id` and `last_id` parameters of the builder are set), while the number of the
|
||||
/// built transactions is lower than `txs_count`.
|
||||
pub fn with_nonce_from(mut self, nonce_from: Option<u128>) -> Self {
|
||||
self.nonce_from = nonce_from;
|
||||
self
|
||||
}
|
||||
|
||||
/// The number of the transactions that will be built in relation to a signer account.
|
||||
pub fn with_txs_count(mut self, txs_count: u32) -> Self {
|
||||
self.txs_count = txs_count;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets transaction recipe to a regular balances transfer.
|
||||
///
|
||||
/// The builder is already initialised with a transfer transaction recipe.
|
||||
pub fn with_transfer_recipe(mut self) -> Self {
|
||||
self.tx_payload_builder_source =
|
||||
Some(TxPayloadBuilderSource::Recipe(TransactionRecipe::transfer()));
|
||||
self
|
||||
}
|
||||
|
||||
/// Set a remark transaction recipe.
|
||||
pub fn with_remark_recipe(mut self, remark: u32) -> Self {
|
||||
self.tx_payload_builder_source =
|
||||
Some(TxPayloadBuilderSource::Recipe(TransactionRecipe::remark(remark)));
|
||||
self
|
||||
}
|
||||
|
||||
/// Set a custom payload builder for Substrate chains.
|
||||
///
|
||||
/// The closure receives a `SubTxBuildContext` with account info, nonce, etc.
|
||||
pub fn with_tx_payload_builder_sub<F>(mut self, f: F) -> Self
|
||||
where
|
||||
F: Fn(&SubTxBuildContext) -> DynamicPayload + Send + Sync + 'static,
|
||||
{
|
||||
self.tx_payload_builder_source = Some(TxPayloadBuilderSource::SubCustom(Arc::new(f)));
|
||||
self
|
||||
}
|
||||
|
||||
/// Set a custom payload builder for Ethereum chains.
|
||||
///
|
||||
/// The closure receives an `EthTxBuildContext` with account info, nonce, etc.
|
||||
pub fn with_tx_payload_builder_eth<F>(mut self, f: F) -> Self
|
||||
where
|
||||
F: Fn(&EthTxBuildContext) -> DynamicPayload + Send + Sync + 'static,
|
||||
{
|
||||
self.tx_payload_builder_source = Some(TxPayloadBuilderSource::EthCustom(Arc::new(f)));
|
||||
self
|
||||
}
|
||||
|
||||
/// Allows to specify transaction tip. This indirectly controls priority of transaction.
|
||||
pub fn with_tip(mut self, tip: u128) -> Self {
|
||||
self.tip = tip;
|
||||
self
|
||||
}
|
||||
|
||||
/// Spawns block monitor. Allows to monitor the transaction finalization status for unwatched
|
||||
/// transactions.
|
||||
pub fn with_block_monitoring(mut self, does_block_monitoring: bool) -> Self {
|
||||
self.does_block_monitoring = does_block_monitoring;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets for how many blocks a transaction is considered valid.
|
||||
///
|
||||
/// Note: using this setter can increase the transaction creation times which can impact heavy
|
||||
/// load tests that create millions of transactions. This method instructs a scenario to use
|
||||
/// an online client for txs creation, since creating mortal txs requires knowledge about the
|
||||
/// last finalized block on chain.
|
||||
pub fn with_mortality(mut self, mortality: u64) -> Self {
|
||||
self.mortality = Some(mortality);
|
||||
self
|
||||
}
|
||||
|
||||
/// Defines the URI of the node where transactions are dispatched.
|
||||
pub fn with_rpc_uri(mut self, rpc_uri: String) -> Self {
|
||||
self.rpc_uri = Some(rpc_uri);
|
||||
self
|
||||
}
|
||||
|
||||
/// Send transactions using `submit_and_watch` method. Progress of all transcations will be
|
||||
/// monitored. If using unwatched transaction `Self::with_block_monitoring` may be useful for
|
||||
/// tracking finalization of transactions.
|
||||
pub fn with_watched_txs(mut self, watched_txs: bool) -> Self {
|
||||
self.watched_txs = watched_txs;
|
||||
self
|
||||
}
|
||||
|
||||
/// Allows to specify the chain type.
|
||||
pub fn with_chain_type(mut self, chain_type: ChainType) -> Self {
|
||||
self.chain_type = Some(chain_type);
|
||||
self
|
||||
}
|
||||
|
||||
/// Specifies how many transactions in transaction pool on the node side will be maintained at
|
||||
/// the fork of the best chain.
|
||||
///
|
||||
/// `usize::MAX` means that the count of `pending_extrinsics` on node side is not called, and an
|
||||
/// executor will send as much as possible.
|
||||
pub fn with_send_threshold(mut self, send_threshold: usize) -> Self {
|
||||
self.send_threshold = Some(send_threshold);
|
||||
self
|
||||
}
|
||||
|
||||
/// If specified, the stats will be printed when `stop` signal is sent to process.
|
||||
pub fn with_installed_ctrlc_stop_hook(mut self, installs_ctrl_c_stop_hook: bool) -> Self {
|
||||
self.installs_ctrl_c_stop_hook = installs_ctrl_c_stop_hook;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets a maximum duration for the scenario execution.
|
||||
///
|
||||
/// If specified, execution will be limited to the given timeout, ensuring the executor returns
|
||||
/// with logs if the duration is reached. Typically, the scenario will complete earlier, but
|
||||
/// the timeout acts as a safeguard to prevent indefinite execution.
|
||||
pub fn with_timeout_in_secs(mut self, secs: u64) -> Self {
|
||||
self.timeout = Some(Duration::from_secs(secs));
|
||||
self
|
||||
}
|
||||
|
||||
/// Defines the log prefix for the executor instance being built.
|
||||
pub fn with_executor_id(mut self, executor_id: String) -> Self {
|
||||
self.executor_id = Some(executor_id);
|
||||
self
|
||||
}
|
||||
|
||||
/// Defines the prefix of the log name.
|
||||
pub fn with_log_file_name_prefix(mut self, log_file_name_prefix: String) -> Self {
|
||||
self.log_file_name_prefix = Some(log_file_name_prefix);
|
||||
self
|
||||
}
|
||||
|
||||
/// Defines the path of the directory where the log file will be stored.
|
||||
pub fn with_base_dir_path(mut self, base_dir_path: String) -> Self {
|
||||
self.base_dir_path = Some(base_dir_path);
|
||||
self
|
||||
}
|
||||
|
||||
/// Use legacy backend. In some scenarios using this may help overcome some RPC related
|
||||
/// problems. Shall be removed in some point in future.
|
||||
pub fn with_legacy_backend(mut self, use_legacy_backend: bool) -> Self {
|
||||
self.use_legacy_backend = use_legacy_backend;
|
||||
self
|
||||
}
|
||||
|
||||
/// Returns a set of tasks that handle transaction execution.
|
||||
async fn build_transactions<H, T, S, B>(
|
||||
&self,
|
||||
builder: B,
|
||||
sink: S,
|
||||
tip: u128,
|
||||
payload_builder: B::PayloadBuilder,
|
||||
) -> Vec<DefaultTxTask<T>>
|
||||
where
|
||||
H: BlockHash + 'static,
|
||||
T: Transaction<HashType = H> + Send + 'static,
|
||||
S: TransactionsSink<H> + 'static + Clone,
|
||||
B: TransactionBuilder<HashType = H, Transaction = T, Sink = S> + Send + Sync + 'static,
|
||||
B::PayloadBuilder: Clone,
|
||||
{
|
||||
let mut tx_build_params = vec![];
|
||||
if let Some(start_id) = self.start_id {
|
||||
let last_id = self.last_id.unwrap_or(start_id);
|
||||
for account in start_id..=last_id {
|
||||
let mut nonce = self.nonce_from;
|
||||
for _ in 0..self.txs_count {
|
||||
tx_build_params.push(TransactionBuildParams {
|
||||
account: account.to_string(),
|
||||
nonce,
|
||||
mortality: self.mortality,
|
||||
});
|
||||
nonce = nonce.map(|n| n + 1);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let mut nonce = self.nonce_from;
|
||||
let account = self
|
||||
.account_id
|
||||
.clone()
|
||||
.expect("to have configured an account id for transactions generation");
|
||||
for _ in 0..self.txs_count {
|
||||
tx_build_params.push(TransactionBuildParams {
|
||||
account: account.clone(),
|
||||
nonce,
|
||||
mortality: self.mortality,
|
||||
});
|
||||
nonce = nonce.map(|n| n + 1);
|
||||
}
|
||||
}
|
||||
|
||||
let n = tx_build_params.len();
|
||||
let t = std::cmp::min(
|
||||
n,
|
||||
std::thread::available_parallelism().unwrap_or(1usize.try_into().unwrap()).get(),
|
||||
);
|
||||
|
||||
let tx_build_params = Arc::<Vec<TransactionBuildParams>>::from(tx_build_params);
|
||||
let builder = Arc::new(builder);
|
||||
let payload_builder = Arc::new(payload_builder);
|
||||
let mut threads = Vec::new();
|
||||
|
||||
(0..t).for_each(|thread_idx| {
|
||||
let chunk = ((thread_idx * n) / t)..(((thread_idx + 1) * n) / t);
|
||||
let tx_build_params = tx_build_params.clone();
|
||||
let builder = builder.clone();
|
||||
let sink = sink.clone();
|
||||
let payload_builder = payload_builder.clone();
|
||||
let watched_txs = self.watched_txs;
|
||||
threads.push(tokio::task::spawn(async move {
|
||||
let mut txs = vec![];
|
||||
for i in chunk {
|
||||
let build_params = tx_build_params[i].clone();
|
||||
txs.push(
|
||||
builder
|
||||
.build_transaction(
|
||||
watched_txs,
|
||||
BuildTransactionParams {
|
||||
account: &build_params.account,
|
||||
nonce: &build_params.nonce,
|
||||
mortality: &build_params.mortality,
|
||||
tip,
|
||||
},
|
||||
&sink,
|
||||
&*payload_builder,
|
||||
)
|
||||
.await,
|
||||
);
|
||||
}
|
||||
txs
|
||||
}));
|
||||
});
|
||||
|
||||
let mut results = vec![];
|
||||
for handle in threads {
|
||||
let result = handle.await.unwrap();
|
||||
results.push(result);
|
||||
}
|
||||
let mut txs: Vec<_> = results.into_iter().flatten().collect();
|
||||
txs.sort_by_key(|k| k.tx().nonce());
|
||||
txs
|
||||
}
|
||||
|
||||
/// Returns a runner of transactions for the configured scenario.
|
||||
pub async fn build(mut self) -> ScenarioExecutor {
|
||||
let does_block_monitoring = self.does_block_monitoring;
|
||||
let send_threshold =
|
||||
self.send_threshold.expect("to have configured the send threshold. qed.");
|
||||
let rpc_uri = self.rpc_uri.clone().expect("to have configured the rpc uri. qed.");
|
||||
let chain_type = self.chain_type.clone().expect("to have a configured chain type. qed");
|
||||
let accounts_description = if let Some(start_id) = self.start_id {
|
||||
let last_id = self.last_id.unwrap_or(start_id);
|
||||
AccountsDescription::Derived(start_id..last_id + 1)
|
||||
} else if let Some(account_description) = self
|
||||
.account_id
|
||||
.clone()
|
||||
.and_then(|id| id.parse::<u32>().ok())
|
||||
.map(|id| AccountsDescription::Derived(id..id + 1))
|
||||
{
|
||||
account_description
|
||||
} else {
|
||||
AccountsDescription::Keyring(
|
||||
self.account_id
|
||||
.clone()
|
||||
.expect("to have configured an account id for transactions generation"),
|
||||
)
|
||||
};
|
||||
|
||||
let installs_ctrlc_stop_hook = self.installs_ctrl_c_stop_hook;
|
||||
let tip = self.tip;
|
||||
|
||||
match chain_type {
|
||||
ChainType::Eth => {
|
||||
let payload_builder = self
|
||||
.tx_payload_builder_source
|
||||
.take()
|
||||
.expect("No payload source configured")
|
||||
.into_eth_builder();
|
||||
|
||||
let builder = EthTransactionBuilder::default();
|
||||
let new_with_uri_with_accounts_description =
|
||||
EthTransactionsSink::new_with_uri_with_accounts_description(
|
||||
rpc_uri.as_str(),
|
||||
accounts_description,
|
||||
generate_ecdsa_keypair,
|
||||
if does_block_monitoring {
|
||||
Some(BlockMonitor::new(rpc_uri.as_str()).await)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
self.use_legacy_backend,
|
||||
);
|
||||
let sink = new_with_uri_with_accounts_description.await;
|
||||
let txs =
|
||||
self.build_transactions(builder, sink.clone(), tip, payload_builder).await;
|
||||
let (stop_sender, runner) =
|
||||
Runner::<DefaultTxTask<EthTransaction>, EthTransactionsSink>::new(
|
||||
send_threshold,
|
||||
sink,
|
||||
txs.into_iter().rev().collect(),
|
||||
self.log_file_name_prefix,
|
||||
self.base_dir_path,
|
||||
self.executor_id,
|
||||
self.timeout,
|
||||
);
|
||||
let executor = ScenarioExecutor::Eth(EthScenarioExecutor::new(stop_sender, runner));
|
||||
installs_ctrlc_stop_hook.then(|| executor.install_ctrlc_stop_hook());
|
||||
executor
|
||||
},
|
||||
ChainType::Sub => {
|
||||
let payload_builder = self
|
||||
.tx_payload_builder_source
|
||||
.take()
|
||||
.expect("No payload source configured")
|
||||
.into_sub_builder();
|
||||
|
||||
let builder = SubstrateTransactionBuilder::default();
|
||||
let sink = SubstrateTransactionsSink::new_with_uri_with_accounts_description(
|
||||
rpc_uri.as_str(),
|
||||
accounts_description,
|
||||
generate_sr25519_keypair,
|
||||
if does_block_monitoring {
|
||||
Some(BlockMonitor::new(rpc_uri.as_str()).await)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
self.use_legacy_backend,
|
||||
)
|
||||
.await;
|
||||
let txs =
|
||||
self.build_transactions(builder, sink.clone(), tip, payload_builder).await;
|
||||
let (stop_sender, runner) =
|
||||
Runner::<DefaultTxTask<SubstrateTransaction>, SubstrateTransactionsSink>::new(
|
||||
send_threshold,
|
||||
sink,
|
||||
txs.into_iter().rev().collect(),
|
||||
self.log_file_name_prefix,
|
||||
self.base_dir_path,
|
||||
self.executor_id,
|
||||
self.timeout,
|
||||
);
|
||||
|
||||
let executor = ScenarioExecutor::Substrate(SubstrateScenarioExecutor::new(
|
||||
stop_sender,
|
||||
runner,
|
||||
));
|
||||
installs_ctrlc_stop_hook.then(|| executor.install_ctrlc_stop_hook());
|
||||
executor
|
||||
},
|
||||
ChainType::Fake => unimplemented!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{
|
||||
fake_transaction_sink::FakeTransactionsSink,
|
||||
scenario::ScenarioBuilder,
|
||||
transaction::{AccountMetadata, FakeTransactionBuilder},
|
||||
};
|
||||
|
||||
use crate::{runner::TxTask, transaction::Transaction};
|
||||
|
||||
#[tokio::test]
|
||||
async fn build_tx_tasks_based_on_scenario_type() {
|
||||
// One shot from derived account based on number id.
|
||||
let sink = FakeTransactionsSink::default();
|
||||
let builder = FakeTransactionBuilder;
|
||||
let scenario_builder = ScenarioBuilder::new().with_start_id(0).with_nonce_from(Some(0));
|
||||
let tasks = scenario_builder.build_transactions(builder, sink, 0, ()).await;
|
||||
assert_eq!(tasks.len(), 1);
|
||||
assert_eq!(tasks[0].tx().nonce(), 0);
|
||||
assert_eq!(tasks[0].tx().account_metadata(), AccountMetadata::Derived(0));
|
||||
|
||||
// One shot from derived account.
|
||||
let sink = FakeTransactionsSink::default();
|
||||
let builder = FakeTransactionBuilder;
|
||||
let scenario_builder = ScenarioBuilder::new()
|
||||
.with_account_id("alice".to_string())
|
||||
.with_nonce_from(Some(0));
|
||||
let tasks = scenario_builder.build_transactions(builder, sink, 0, ()).await;
|
||||
assert_eq!(tasks.len(), 1);
|
||||
assert_eq!(tasks[0].tx().nonce(), 0);
|
||||
assert_eq!(tasks[0].tx().account_metadata(), AccountMetadata::KeyRing("alice".to_string()));
|
||||
|
||||
// Build from single derived account based on number id.
|
||||
let sink = FakeTransactionsSink::default();
|
||||
let builder = FakeTransactionBuilder;
|
||||
let scenario_builder = ScenarioBuilder::new()
|
||||
.with_start_id(1)
|
||||
.with_nonce_from(Some(0))
|
||||
.with_txs_count(10);
|
||||
let tasks = scenario_builder.build_transactions(builder, sink, 0, ()).await;
|
||||
assert_eq!(tasks.len(), 10);
|
||||
for (i, task) in tasks.iter().enumerate() {
|
||||
assert_eq!(task.tx().nonce(), i as u128);
|
||||
assert_eq!(task.tx().account_metadata(), AccountMetadata::Derived(1));
|
||||
}
|
||||
|
||||
// Buld from single account keyring.
|
||||
let sink = FakeTransactionsSink::default();
|
||||
let builder = FakeTransactionBuilder;
|
||||
let scenario_builder = ScenarioBuilder::new()
|
||||
.with_account_id("alice".to_string())
|
||||
.with_nonce_from(Some(0))
|
||||
.with_txs_count(10);
|
||||
let tasks = scenario_builder.build_transactions(builder, sink, 0, ()).await;
|
||||
assert_eq!(tasks.len(), 10);
|
||||
for (i, task) in tasks.iter().enumerate() {
|
||||
assert_eq!(task.tx().nonce(), i as u128);
|
||||
assert_eq!(task.tx().account_metadata(), AccountMetadata::KeyRing("alice".to_string()));
|
||||
}
|
||||
|
||||
// Buld from many derived accounts based on number ids.
|
||||
let sink = FakeTransactionsSink::default();
|
||||
let builder = FakeTransactionBuilder;
|
||||
let scenario_builder = ScenarioBuilder::new()
|
||||
.with_start_id(5)
|
||||
.with_last_id(10)
|
||||
.with_nonce_from(Some(0))
|
||||
.with_txs_count(10);
|
||||
let tasks = scenario_builder.build_transactions(builder, sink, 0, ()).await;
|
||||
assert_eq!(tasks.len(), 60);
|
||||
for (i, task) in tasks.iter().enumerate() {
|
||||
assert_eq!(task.tx().nonce(), i as u128 / 6);
|
||||
assert_eq!(task.tx().account_metadata(), AccountMetadata::Derived((i as u32 % 6) + 5));
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user