4c8f281051
- Remove nightly-only features from .rustfmt.toml and vendor/ss58-registry/rustfmt.toml - Removed features: imports_granularity, wrap_comments, comment_width, reorder_impl_items, spaces_around_ranges, binop_separator, match_arm_blocks, trailing_semicolon, trailing_comma - Format all 898 affected files with stable rustfmt - Ensures long-term reliability without nightly toolchain dependency
733 lines
24 KiB
Rust
733 lines
24 KiB
Rust
// 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 pezkuwi_subxt::utils::H256;
|
|
use pezkuwi_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, BizinikiwTransaction,
|
|
BizinikiwTransactionsSink, EthPayloadBuilderFn, EthTransaction, EthTransactionsSink,
|
|
EthTxBuildContext, SubPayloadBuilderFn, SubTxBuildContext,
|
|
},
|
|
transaction::{
|
|
BizinikiwTransactionBuilder, BuildTransactionParams, EthTransactionBuilder, Transaction,
|
|
TransactionBuilder, TransactionCall, TransactionRecipe, TransactionsSink,
|
|
},
|
|
};
|
|
use pezkuwi_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 BizinikiwScenarioRunner =
|
|
Runner<DefaultTxTask<BizinikiwTransaction>, BizinikiwTransactionsSink>;
|
|
pub struct BizinikiwScenarioExecutor {
|
|
stop_sender: Sender<()>,
|
|
runner: BizinikiwScenarioRunner,
|
|
}
|
|
|
|
impl BizinikiwScenarioExecutor {
|
|
pub(crate) fn new(stop_sender: Sender<()>, runner: BizinikiwScenarioRunner) -> Self {
|
|
BizinikiwScenarioExecutor { 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(BizinikiwScenarioExecutor),
|
|
}
|
|
|
|
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 = BizinikiwTransactionBuilder::default();
|
|
let sink = BizinikiwTransactionsSink::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<BizinikiwTransaction>, BizinikiwTransactionsSink>::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(BizinikiwScenarioExecutor::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));
|
|
}
|
|
}
|
|
}
|