From 3bc6d73ec6a1ada2a928cb85e2073ec077e02638 Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Thu, 4 Dec 2025 23:44:35 +0100 Subject: [PATCH 1/8] transaction payload builder added --- Cargo.lock | 2 +- Cargo.toml | 2 +- examples/kill_dev_entry.rs | 67 +++++++++++++++ src/lib.rs | 7 ++ src/scenario.rs | 146 +++++++++++++++++++++++++------- src/subxt_transaction.rs | 167 ++++++++++++++++++++----------------- src/transaction.rs | 43 ++++++---- 7 files changed, 309 insertions(+), 125 deletions(-) create mode 100644 examples/kill_dev_entry.rs diff --git a/Cargo.lock b/Cargo.lock index 6c69e84..95171ef 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3322,7 +3322,7 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "substrate-txtesttool" -version = "0.7.0" +version = "0.8.0" dependencies = [ "async-trait", "average", diff --git a/Cargo.toml b/Cargo.toml index b5910dd..60fab7a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "substrate-txtesttool" -version = "0.7.0" +version = "0.8.0" edition = "2021" description = "A library and CLI tool for sending transactions to substrate-based chains, enabling developers to test and monitor transaction scenarios." license = "Apache-2.0 OR GPL-3.0" diff --git a/examples/kill_dev_entry.rs b/examples/kill_dev_entry.rs new file mode 100644 index 0000000..7a3c521 --- /dev/null +++ b/examples/kill_dev_entry.rs @@ -0,0 +1,67 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is dual-licensed as Apache-2.0 or GPL-3.0. +// see LICENSE for license details. + +//! Example: Send `Balances::kill_dev_entry` transactions using a custom payload builder. +//! +//! This example demonstrates how to use `with_custom_sub_payload_builder` to send +//! custom transactions. Each account sends one transaction that deletes 5 entries +//! starting at index `5 * account_id`. +//! +//! Usage: +//! ```bash +//! cargo run --example kill_dev_entry -- --start-id 0 --last-id 99 +//! ``` + +use clap::Parser; +use substrate_txtesttool::scenario::{ChainType, ScenarioBuilder}; +use subxt::dynamic::Value; + +#[derive(Parser)] +#[clap(name = "kill_dev_entry")] +struct Cli { + /// Start account ID (inclusive) + #[clap(long)] + start_id: u32, + + /// Last account ID (inclusive) + #[clap(long)] + last_id: u32, + + /// The RPC endpoint of the node to be used. + #[clap(long, default_value = "ws://127.0.0.1:9933")] + ws: String, + + /// Send transactions threshold + #[clap(long, default_value_t = 10000)] + send_threshold: u32, +} + +#[tokio::main] +async fn main() -> Result<(), Box> { + substrate_txtesttool::init_logger(); + + let cli = Cli::parse(); + + let scenario_builder = ScenarioBuilder::new() + .with_rpc_uri(cli.ws) + .with_watched_txs(true) + .with_chain_type(ChainType::Sub) + .with_send_threshold(cli.send_threshold as usize) + .with_start_id(cli.start_id) + .with_last_id(cli.last_id) + .with_txs_count(1) + .with_legacy_backend(true) + .with_installed_ctrlc_stop_hook(true) + .with_custom_sub_payload_builder(|ctx| { + let x = ctx.account.parse::().unwrap(); + let start = Value::u128((5 * x) as u128); + let count = Value::u128(5); + subxt::dynamic::tx("Balances", "kill_dev_entry", vec![start, count]) + }); + + let scenario_executor = scenario_builder.build().await; + let _ = scenario_executor.execute().await; + + Ok(()) +} diff --git a/src/lib.rs b/src/lib.rs index 16c19a1..37d9c81 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -51,6 +51,13 @@ pub mod subxt_api_connector; pub mod subxt_transaction; pub mod transaction; +// Re-export commonly used types for custom payload builders +pub use subxt_transaction::{ + eth_transfer_payload_builder, remark_payload_builder, sub_transfer_payload_builder, + EthPayloadBuilderFn, EthTxBuildContext, PayloadBuilderFn, SubPayloadBuilderFn, + SubTxBuildContext, TxBuildContext, +}; + /// Initialize the logger for various binaries (e.g. ttxt or test binaries). pub fn init_logger() { use std::sync::Once; diff --git a/src/scenario.rs b/src/scenario.rs index 720d268..24fe90e 100644 --- a/src/scenario.rs +++ b/src/scenario.rs @@ -16,14 +16,17 @@ use crate::{ execution_log::TransactionExecutionLog, runner::{DefaultTxTask, Runner, TxTask}, subxt_transaction::{ - generate_ecdsa_keypair, generate_sr25519_keypair, EthTransaction, EthTransactionsSink, - SubstrateTransaction, SubstrateTransactionsSink, + eth_transfer_payload_builder, generate_ecdsa_keypair, generate_sr25519_keypair, + remark_payload_builder, sub_transfer_payload_builder, EthPayloadBuilderFn, + EthTransaction, EthTransactionsSink, EthTxBuildContext, SubPayloadBuilderFn, + SubstrateTransaction, SubstrateTransactionsSink, SubTxBuildContext, }, transaction::{ EthTransactionBuilder, SubstrateTransactionBuilder, Transaction, TransactionBuilder, - TransactionRecipe, TransactionsSink, + TransactionCall, TransactionRecipe, TransactionsSink, }, }; +use subxt::tx::DynamicPayload; #[derive(Clone, Debug)] /// Holds information relevant for transaction generation. @@ -163,6 +166,46 @@ impl ScenarioExecutor { } } +/// 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, @@ -170,7 +213,7 @@ pub struct ScenarioBuilder { last_id: Option, nonce_from: Option, txs_count: u32, - tx_recipe: Option, + tx_payload_builder_source: Option, mortality: Option, does_block_monitoring: bool, watched_txs: bool, @@ -194,7 +237,7 @@ impl Default for ScenarioBuilder { impl ScenarioBuilder { /// A default initializer of the builder, with a few defaults: - /// - `tx_recipe` is set to [`crate::transaction::TransactionCall::Transfer`] + /// - `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`. @@ -205,7 +248,9 @@ impl ScenarioBuilder { last_id: None, nonce_from: None, txs_count: 1, - tx_recipe: Some(TransactionRecipe::transfer(0)), + tx_payload_builder_source: Some(TxPayloadBuilderSource::Recipe( + TransactionRecipe::transfer(), + )), does_block_monitoring: false, mortality: None, watched_txs: false, @@ -269,27 +314,44 @@ impl ScenarioBuilder { /// Sets transaction recipe to a regular balances transfer. /// - /// The builder is already initialised with a transfer transaction recipe with a tip of 0. - /// If a tip is set, the builder will update the tip of the transaction recipe accordingly. + /// The builder is already initialised with a transfer transaction recipe. pub fn with_transfer_recipe(mut self) -> Self { - self.tx_recipe = Some(TransactionRecipe::transfer(self.tip)); + self.tx_payload_builder_source = + Some(TxPayloadBuilderSource::Recipe(TransactionRecipe::transfer())); self } /// Set a remark transaction recipe. - /// - /// If a tip is set, the builder will update the tip of the transaction recipe - /// accordingly. pub fn with_remark_recipe(mut self, remark: u32) -> Self { - self.tx_recipe = Some(TransactionRecipe::remark(remark, self.tip)); + 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_custom_sub_payload_builder(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_custom_eth_payload_builder(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 { - if let Some(r) = self.tx_recipe.as_mut() { - r.tip = tip - }; self.tip = tip; self } @@ -384,12 +446,19 @@ impl ScenarioBuilder { } /// Returns a set of tasks that handle transaction execution. - async fn build_transactions(&self, builder: B, sink: S) -> Vec> + async fn build_transactions( + &self, + builder: B, + sink: S, + tip: u128, + payload_builder: B::PayloadBuilder, + ) -> Vec> where H: BlockHash + 'static, T: Transaction + Send + 'static, S: TransactionsSink + 'static + Clone, B: TransactionBuilder + Send + Sync + 'static, + B::PayloadBuilder: Clone, { let mut tx_build_params = vec![]; if let Some(start_id) = self.start_id { @@ -429,6 +498,7 @@ impl ScenarioBuilder { let tx_build_params = Arc::>::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| { @@ -436,10 +506,7 @@ impl ScenarioBuilder { let tx_build_params = tx_build_params.clone(); let builder = builder.clone(); let sink = sink.clone(); - let recipe = self - .tx_recipe - .clone() - .expect("to be configured with a transaction recipe. qed."); + let payload_builder = payload_builder.clone(); let watched_txs = self.watched_txs; threads.push(tokio::task::spawn(async move { let mut txs = vec![]; @@ -453,7 +520,8 @@ impl ScenarioBuilder { &build_params.mortality, &sink, watched_txs, - &recipe, + tip, + &*payload_builder, ) .await, ); @@ -473,7 +541,7 @@ impl ScenarioBuilder { } /// Returns a runner of transactions for the configured scenario. - pub async fn build(self) -> ScenarioExecutor { + 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."); @@ -498,8 +566,16 @@ impl ScenarioBuilder { }; 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( @@ -514,7 +590,9 @@ impl ScenarioBuilder { self.use_legacy_backend, ); let sink = new_with_uri_with_accounts_description.await; - let txs = self.build_transactions(builder, sink.clone()).await; + let txs = self + .build_transactions(builder, sink.clone(), tip, payload_builder) + .await; let (stop_sender, runner) = Runner::, EthTransactionsSink>::new( send_threshold, @@ -530,6 +608,12 @@ impl ScenarioBuilder { 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(), @@ -543,7 +627,9 @@ impl ScenarioBuilder { self.use_legacy_backend, ) .await; - let txs = self.build_transactions(builder, sink.clone()).await; + let txs = self + .build_transactions(builder, sink.clone(), tip, payload_builder) + .await; let (stop_sender, runner) = Runner::, SubstrateTransactionsSink>::new( send_threshold, @@ -583,7 +669,7 @@ mod tests { 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).await; + 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)); @@ -594,7 +680,7 @@ mod tests { let scenario_builder = ScenarioBuilder::new() .with_account_id("alice".to_string()) .with_nonce_from(Some(0)); - let tasks = scenario_builder.build_transactions(builder, sink).await; + 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())); @@ -606,7 +692,7 @@ mod tests { .with_start_id(1) .with_nonce_from(Some(0)) .with_txs_count(10); - let tasks = scenario_builder.build_transactions(builder, sink).await; + 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); @@ -620,7 +706,7 @@ mod tests { .with_account_id("alice".to_string()) .with_nonce_from(Some(0)) .with_txs_count(10); - let tasks = scenario_builder.build_transactions(builder, sink).await; + 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); @@ -635,7 +721,7 @@ mod tests { .with_last_id(10) .with_nonce_from(Some(0)) .with_txs_count(10); - let tasks = scenario_builder.build_transactions(builder, sink).await; + 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); diff --git a/src/subxt_transaction.rs b/src/subxt_transaction.rs index 26d1d7c..8adab7d 100644 --- a/src/subxt_transaction.rs +++ b/src/subxt_transaction.rs @@ -4,8 +4,7 @@ use crate::{ helpers::StreamOf, scenario::AccountsDescription, transaction::{ - AccountMetadata, Transaction, TransactionCall, TransactionMonitor, TransactionRecipe, - TransactionStatus, TransactionsSink, + AccountMetadata, Transaction, TransactionMonitor, TransactionStatus, TransactionsSink, }, }; use async_trait::async_trait; @@ -17,6 +16,7 @@ use std::{ sync::Arc, time::{Duration, Instant}, }; +pub use subxt::dynamic; use subxt::{ backend::rpc::RpcClient, config::{ @@ -79,6 +79,73 @@ pub type SubstrateTransaction = SubxtTransaction; /// Holds the RPC API connection for transaction execution. pub type SubstrateTransactionsSink = SubxtTransactionsSink; +/// Context for building transaction payloads. +/// Generic over account type `A` to support both Substrate and Ethereum chains. +pub struct TxBuildContext<'a, A> { + /// The destination account ID. + pub to_account_id: &'a A, + /// The source account ID (signer). + pub from_account_id: &'a A, + /// Account string identifier (e.g. "0", "alice"). + pub account: &'a str, + /// Transaction nonce. + pub nonce: u128, +} + +/// Context type alias for Substrate chains. +pub type SubTxBuildContext<'a> = TxBuildContext<'a, AccountIdOf>; +/// Context type alias for Ethereum chains. +pub type EthTxBuildContext<'a> = TxBuildContext<'a, AccountId20>; + +/// Generic payload builder function type. +pub type PayloadBuilderFn = Arc) -> DynamicPayload + Send + Sync>; +/// Payload builder type alias for Substrate chains. +pub type SubPayloadBuilderFn = PayloadBuilderFn>; +/// Payload builder type alias for Ethereum chains. +pub type EthPayloadBuilderFn = PayloadBuilderFn; + +/// Creates a generic remark payload builder. +/// Works for any account type that implements `AsRef<[u8]>`. +/// Size is specified in kilobytes. +pub fn remark_payload_builder(size_kb: u32) -> PayloadBuilderFn +where + A: AsRef<[u8]> + Send + Sync + 'static, +{ + Arc::new(move |ctx| { + let i = hex::encode(ctx.to_account_id.as_ref()).as_bytes().last().copied().unwrap(); + let data = vec![i; size_kb as usize * 1024]; + subxt::dynamic::tx("System", "remark", vec![data]) + }) +} + +/// Creates a transfer payload builder for Substrate chains. +pub fn sub_transfer_payload_builder() -> SubPayloadBuilderFn { + Arc::new(|ctx| { + subxt::dynamic::tx( + "Balances", + "transfer_keep_alive", + vec![ + value!(Id(Value::from_bytes(ctx.to_account_id.clone()))), + Value::u128(1u32.into()), + ], + ) + }) +} + +/// Creates a transfer payload builder for Ethereum chains. +pub fn eth_transfer_payload_builder() -> EthPayloadBuilderFn { + Arc::new(|ctx| { + subxt::dynamic::tx( + "Balances", + "transfer_keep_alive", + vec![ + Value::unnamed_composite(vec![Value::from_bytes(*ctx.to_account_id)]), + Value::u128(1u32.into()), + ], + ) + }) +} + impl SubxtTransaction { pub fn new( transaction: SubmittableTransaction>, @@ -448,65 +515,8 @@ where } } -pub trait GenerateTxPayloadFunction>: - Fn(A, &TransactionRecipe) -> DynamicPayload + Copy + Send + 'static -{ -} - -impl> GenerateTxPayloadFunction for T where - T: Fn(A, &TransactionRecipe) -> DynamicPayload + Copy + Send + 'static -{ -} - -/// Generates a transaction payload given a signer account and a transaction recipe. -pub(crate) fn build_substrate_tx_payload( - to_account_id: AccountIdOf, - recipe: &TransactionRecipe, -) -> DynamicPayload { - trace!(target:LOG_TARGET,to_account=hex::encode(to_account_id.clone()),"build_payload (sub)" ); - - match recipe.call { - TransactionCall::Remark(s) => { - let i = hex::encode(to_account_id.clone()).as_bytes().last().copied().unwrap(); - let data = vec![i; s as usize * 1024]; - subxt::dynamic::tx("System", "remark", vec![data]) - }, - TransactionCall::Transfer => { - //works for rococo: - subxt::dynamic::tx( - "Balances", - "transfer_keep_alive", - vec![value!(Id(Value::from_bytes(to_account_id))), Value::u128(1u32.into())], - ) - }, - } -} - -/// Crates a raw eth transaction. -pub(crate) fn build_eth_tx_payload( - to_account_id: AccountId20, - recipe: &TransactionRecipe, -) -> DynamicPayload { - trace!(target:LOG_TARGET,to_account=hex::encode(to_account_id),"build_payload (eth)"); - match recipe.call { - TransactionCall::Remark(s) => { - let i = hex::encode(to_account_id).as_bytes().last().copied().unwrap(); - let data = vec![i; s as usize]; - subxt::dynamic::tx("System", "remark", vec![data]) - }, - TransactionCall::Transfer => subxt::dynamic::tx( - "Balances", - "transfer_keep_alive", - vec![ - Value::unnamed_composite(vec![Value::from_bytes(to_account_id)]), - Value::u128(1u32.into()), - ], - ), - } -} - #[allow(clippy::too_many_arguments)] -async fn create_online_transaction( +async fn create_online_transaction( from_keypair: &KP, nonce: u128, mortality: &Option, @@ -514,11 +524,10 @@ async fn create_online_transaction( sink: &SubxtTransactionsSink, from_account_id: &::AccountId, to_account_id: &::AccountId, - recipe: &TransactionRecipe, - generate_payload: G, + tip: u128, + payload_builder: &B, ) -> Result, Error> where - G: GenerateTxPayloadFunction>, AccountIdOf: Send + Sync + AsRef<[u8]>, KP: Signer + Clone + Send + Sync + 'static, <::ExtrinsicParams as subxt::config::ExtrinsicParams>::Params: From<( @@ -532,6 +541,7 @@ where ChargeTransactionPaymentParams, (), )>, + B: Fn(&TxBuildContext>) -> DynamicPayload + ?Sized, { // Needed because `Params` as associated type does not implement clone, and we need to // recreate the tx params in a loop when we can't create a partial tx with the online @@ -542,9 +552,9 @@ where fn tx_params( mortality: &Option, nonce: u64, - recipe: &TransactionRecipe, + tip: u128, ) -> as ExtrinsicParams>::Params { - let mut params = >::new().nonce(nonce).tip(recipe.tip); + let mut params = >::new().nonce(nonce).tip(tip); if let Some(mortal) = mortality { params = params.mortal(*mortal); } @@ -597,9 +607,10 @@ where )) } - let tx_call = generate_payload(to_account_id.clone(), recipe); + let ctx = TxBuildContext { to_account_id, from_account_id, account, nonce }; + let tx_call = payload_builder(&ctx); for _ in 0..DEFAULT_RETRIES_FOR_PARTIAL_TX_CREATION { - let params = tx_params(mortality, nonce as u64, recipe); + let params = tx_params(mortality, nonce as u64, tip); match sink.api().tx().create_partial(&tx_call, from_account_id, params.into()).await { Ok(tx) => return subxt_transaction(sink, tx, from_keypair, nonce, mortality, account).await, @@ -612,13 +623,13 @@ where } /// Builds a transaction with subxt. -pub(crate) async fn build_subxt_tx( +pub(crate) async fn build_subxt_tx( account: &str, nonce: &Option, mortality: &Option, sink: &SubxtTransactionsSink, - recipe: &TransactionRecipe, - generate_payload: G, + tip: u128, + payload_builder: &B, ) -> SubxtTransaction where AccountIdOf: Send + Sync + AsRef<[u8]>, @@ -635,7 +646,7 @@ where ChargeTransactionPaymentParams, (), )>, - G: GenerateTxPayloadFunction>, + B: Fn(&TxBuildContext>) -> DynamicPayload + ?Sized, { let to_account_id = sink.get_to_account_id(account).expect("to account exists"); let from_account_id = sink.get_from_account_id(account).expect("from account exists"); @@ -670,18 +681,24 @@ where sink, &from_account_id, &to_account_id, - recipe, - generate_payload, + tip, + payload_builder, ) .await .expect("failed to create mortal transaction") } else { let params = >::new() .nonce(nonce as u64) - .tip(recipe.tip) + .tip(tip) .build() .into(); - let tx_call = generate_payload(to_account_id, recipe); + let ctx = TxBuildContext { + to_account_id: &to_account_id, + from_account_id: &from_account_id, + account, + nonce, + }; + let tx_call = payload_builder(&ctx); let tx = SubxtTransaction::::new( sink.api() .tx() diff --git a/src/transaction.rs b/src/transaction.rs index edfc4dd..0f75b3f 100644 --- a/src/transaction.rs +++ b/src/transaction.rs @@ -9,9 +9,8 @@ use crate::{ helpers::StreamOf, runner::DefaultTxTask, subxt_transaction::{ - build_eth_tx_payload, build_substrate_tx_payload, build_subxt_tx, EthRuntimeConfig, - EthTransaction, EthTransactionsSink, HashOf, SubstrateTransaction, - SubstrateTransactionsSink, + build_subxt_tx, EthPayloadBuilderFn, EthRuntimeConfig, EthTransaction, EthTransactionsSink, + HashOf, SubPayloadBuilderFn, SubstrateTransaction, SubstrateTransactionsSink, }, }; use async_trait::async_trait; @@ -25,6 +24,7 @@ pub(crate) trait TransactionBuilder { type HashType: BlockHash; type Transaction: Transaction; type Sink: TransactionsSink; + type PayloadBuilder: Send + Sync; async fn build_transaction<'a>( &self, @@ -33,7 +33,8 @@ pub(crate) trait TransactionBuilder { mortality: &Option, sink: &Self::Sink, watched: bool, - recipe: &TransactionRecipe, + tip: u128, + payload_builder: &Self::PayloadBuilder, ) -> DefaultTxTask; } @@ -46,6 +47,8 @@ impl TransactionBuilder for SubstrateTransactionBuilder { type HashType = HashOf; type Transaction = SubstrateTransaction; type Sink = SubstrateTransactionsSink; + type PayloadBuilder = SubPayloadBuilderFn; + async fn build_transaction<'a>( &self, account: &'a str, @@ -53,17 +56,16 @@ impl TransactionBuilder for SubstrateTransactionBuilder { mortality: &Option, sink: &Self::Sink, watched: bool, - recipe: &TransactionRecipe, + tip: u128, + payload_builder: &Self::PayloadBuilder, ) -> DefaultTxTask { if !watched { DefaultTxTask::::new_unwatched( - build_subxt_tx(account, nonce, mortality, sink, recipe, build_substrate_tx_payload) - .await, + build_subxt_tx(account, nonce, mortality, sink, tip, &**payload_builder).await, ) } else { DefaultTxTask::::new_watched( - build_subxt_tx(account, nonce, mortality, sink, recipe, build_substrate_tx_payload) - .await, + build_subxt_tx(account, nonce, mortality, sink, tip, &**payload_builder).await, ) } } @@ -78,6 +80,8 @@ impl TransactionBuilder for EthTransactionBuilder { type HashType = HashOf; type Transaction = EthTransaction; type Sink = EthTransactionsSink; + type PayloadBuilder = EthPayloadBuilderFn; + async fn build_transaction<'a>( &self, account: &'a str, @@ -85,15 +89,16 @@ impl TransactionBuilder for EthTransactionBuilder { mortality: &Option, sink: &Self::Sink, watched: bool, - recipe: &TransactionRecipe, + tip: u128, + payload_builder: &Self::PayloadBuilder, ) -> DefaultTxTask { if !watched { DefaultTxTask::::new_unwatched( - build_subxt_tx(account, nonce, mortality, sink, recipe, build_eth_tx_payload).await, + build_subxt_tx(account, nonce, mortality, sink, tip, &**payload_builder).await, ) } else { DefaultTxTask::::new_watched( - build_subxt_tx(account, nonce, mortality, sink, recipe, build_eth_tx_payload).await, + build_subxt_tx(account, nonce, mortality, sink, tip, &**payload_builder).await, ) } } @@ -109,6 +114,8 @@ impl TransactionBuilder for FakeTransactionBuilder { type HashType = FakeHash; type Transaction = FakeTransaction; type Sink = FakeTransactionsSink; + type PayloadBuilder = (); + async fn build_transaction<'a>( &self, account: &'a str, @@ -116,7 +123,8 @@ impl TransactionBuilder for FakeTransactionBuilder { _mortality: &Option, sink: &Self::Sink, unwatched: bool, - _recipe: &TransactionRecipe, + _tip: u128, + _payload_builder: &Self::PayloadBuilder, ) -> DefaultTxTask { if unwatched { todo!() @@ -168,16 +176,15 @@ pub enum TransactionCall { /// Type of transaction to execute. pub struct TransactionRecipe { pub(crate) call: TransactionCall, - pub(crate) tip: u128, } impl TransactionRecipe { - pub fn transfer(tip: u128) -> Self { - Self { call: TransactionCall::Transfer, tip } + pub fn transfer() -> Self { + Self { call: TransactionCall::Transfer } } - pub fn remark(size: u32, tip: u128) -> Self { - Self { call: TransactionCall::Remark(size), tip } + pub fn remark(size: u32) -> Self { + Self { call: TransactionCall::Remark(size) } } } From bf1b3bd79e3f61c1fd11d83a53f0f8ddf610758b Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Fri, 5 Dec 2025 08:54:45 +0100 Subject: [PATCH 2/8] name corrected --- examples/kill_dev_entry.rs | 2 +- src/scenario.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/kill_dev_entry.rs b/examples/kill_dev_entry.rs index 7a3c521..25cd795 100644 --- a/examples/kill_dev_entry.rs +++ b/examples/kill_dev_entry.rs @@ -53,7 +53,7 @@ async fn main() -> Result<(), Box> { .with_txs_count(1) .with_legacy_backend(true) .with_installed_ctrlc_stop_hook(true) - .with_custom_sub_payload_builder(|ctx| { + .with_tx_payload_builder_sub(|ctx| { let x = ctx.account.parse::().unwrap(); let start = Value::u128((5 * x) as u128); let count = Value::u128(5); diff --git a/src/scenario.rs b/src/scenario.rs index 24fe90e..b83e41d 100644 --- a/src/scenario.rs +++ b/src/scenario.rs @@ -331,7 +331,7 @@ impl ScenarioBuilder { /// Set a custom payload builder for Substrate chains. /// /// The closure receives a `SubTxBuildContext` with account info, nonce, etc. - pub fn with_custom_sub_payload_builder(mut self, f: F) -> Self + pub fn with_tx_payload_builder_sub(mut self, f: F) -> Self where F: Fn(&SubTxBuildContext) -> DynamicPayload + Send + Sync + 'static, { @@ -342,7 +342,7 @@ impl ScenarioBuilder { /// Set a custom payload builder for Ethereum chains. /// /// The closure receives an `EthTxBuildContext` with account info, nonce, etc. - pub fn with_custom_eth_payload_builder(mut self, f: F) -> Self + pub fn with_tx_payload_builder_eth(mut self, f: F) -> Self where F: Fn(&EthTxBuildContext) -> DynamicPayload + Send + Sync + 'static, { From 8f2ba617cb6d9d670153cdd9ae2afbcf87182070 Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Fri, 5 Dec 2025 09:02:41 +0100 Subject: [PATCH 3/8] fmt + naming --- examples/kill_dev_entry.rs | 67 +++++++++++++++++++------------------- src/scenario.rs | 16 ++++----- 2 files changed, 41 insertions(+), 42 deletions(-) diff --git a/examples/kill_dev_entry.rs b/examples/kill_dev_entry.rs index 25cd795..466d525 100644 --- a/examples/kill_dev_entry.rs +++ b/examples/kill_dev_entry.rs @@ -20,48 +20,49 @@ use subxt::dynamic::Value; #[derive(Parser)] #[clap(name = "kill_dev_entry")] struct Cli { - /// Start account ID (inclusive) - #[clap(long)] - start_id: u32, + /// Start account ID (inclusive) + #[clap(long)] + start_id: u32, - /// Last account ID (inclusive) - #[clap(long)] - last_id: u32, + /// Last account ID (inclusive) + #[clap(long)] + last_id: u32, - /// The RPC endpoint of the node to be used. - #[clap(long, default_value = "ws://127.0.0.1:9933")] - ws: String, + /// The RPC endpoint of the node to be used. + #[clap(long, default_value = "ws://127.0.0.1:9933")] + ws: String, - /// Send transactions threshold - #[clap(long, default_value_t = 10000)] - send_threshold: u32, + /// Send transactions threshold + #[clap(long, default_value_t = 10000)] + send_threshold: u32, } #[tokio::main] async fn main() -> Result<(), Box> { - substrate_txtesttool::init_logger(); + substrate_txtesttool::init_logger(); - let cli = Cli::parse(); + let cli = Cli::parse(); - let scenario_builder = ScenarioBuilder::new() - .with_rpc_uri(cli.ws) - .with_watched_txs(true) - .with_chain_type(ChainType::Sub) - .with_send_threshold(cli.send_threshold as usize) - .with_start_id(cli.start_id) - .with_last_id(cli.last_id) - .with_txs_count(1) - .with_legacy_backend(true) - .with_installed_ctrlc_stop_hook(true) - .with_tx_payload_builder_sub(|ctx| { - let x = ctx.account.parse::().unwrap(); - let start = Value::u128((5 * x) as u128); - let count = Value::u128(5); - subxt::dynamic::tx("Balances", "kill_dev_entry", vec![start, count]) - }); + let scenario_builder = ScenarioBuilder::new() + .with_rpc_uri(cli.ws) + .with_watched_txs(true) + .with_chain_type(ChainType::Sub) + .with_send_threshold(cli.send_threshold as usize) + .with_start_id(cli.start_id) + .with_last_id(cli.last_id) + .with_txs_count(1) + .with_legacy_backend(true) + .with_installed_ctrlc_stop_hook(true) + .with_tx_payload_builder_sub(|ctx| { + let x = ctx.account.parse::().unwrap(); + const BATCH_SIZE: u32 = 5; + let start = Value::u128((BATCH_SIZE * x) as u128); + let count = Value::u128(BATCH_SIZE.into()); + subxt::dynamic::tx("TestPallet", "kill_dev_entry", vec![start, count]) + }); - let scenario_executor = scenario_builder.build().await; - let _ = scenario_executor.execute().await; + let scenario_executor = scenario_builder.build().await; + let _ = scenario_executor.execute().await; - Ok(()) + Ok(()) } diff --git a/src/scenario.rs b/src/scenario.rs index b83e41d..40d2325 100644 --- a/src/scenario.rs +++ b/src/scenario.rs @@ -17,9 +17,9 @@ use crate::{ 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, - SubstrateTransaction, SubstrateTransactionsSink, SubTxBuildContext, + remark_payload_builder, sub_transfer_payload_builder, EthPayloadBuilderFn, EthTransaction, + EthTransactionsSink, EthTxBuildContext, SubPayloadBuilderFn, SubTxBuildContext, + SubstrateTransaction, SubstrateTransactionsSink, }, transaction::{ EthTransactionBuilder, SubstrateTransactionBuilder, Transaction, TransactionBuilder, @@ -590,9 +590,8 @@ impl ScenarioBuilder { 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 txs = + self.build_transactions(builder, sink.clone(), tip, payload_builder).await; let (stop_sender, runner) = Runner::, EthTransactionsSink>::new( send_threshold, @@ -627,9 +626,8 @@ impl ScenarioBuilder { self.use_legacy_backend, ) .await; - let txs = self - .build_transactions(builder, sink.clone(), tip, payload_builder) - .await; + let txs = + self.build_transactions(builder, sink.clone(), tip, payload_builder).await; let (stop_sender, runner) = Runner::, SubstrateTransactionsSink>::new( send_threshold, From 39ef6ad455cfb8d2125380b17df5ab695e2c93f8 Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Fri, 5 Dec 2025 09:10:33 +0100 Subject: [PATCH 4/8] fmt + naming again --- examples/kill_dev_entry.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/kill_dev_entry.rs b/examples/kill_dev_entry.rs index 466d525..39713e3 100644 --- a/examples/kill_dev_entry.rs +++ b/examples/kill_dev_entry.rs @@ -2,11 +2,11 @@ // This file is dual-licensed as Apache-2.0 or GPL-3.0. // see LICENSE for license details. -//! Example: Send `Balances::kill_dev_entry` transactions using a custom payload builder. +//! Example: Send `TestPallet::kill_dev_entry` transactions using a custom payload builder. //! -//! This example demonstrates how to use `with_custom_sub_payload_builder` to send -//! custom transactions. Each account sends one transaction that deletes 5 entries -//! starting at index `5 * account_id`. +//! This example demonstrates how to use `with_tx_payload_builder_sub` build a custom spammer tool +//! using custom transactions. Each account sends one transaction that deletes 5 entries starting at +//! index `5 * account_id`. //! //! Usage: //! ```bash From 8cbd808f85ccb06b4d24f5400cdd784f5e8e300c Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Fri, 5 Dec 2025 09:11:48 +0100 Subject: [PATCH 5/8] again --- examples/kill_dev_entry.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/examples/kill_dev_entry.rs b/examples/kill_dev_entry.rs index 39713e3..dc74ed2 100644 --- a/examples/kill_dev_entry.rs +++ b/examples/kill_dev_entry.rs @@ -4,9 +4,8 @@ //! Example: Send `TestPallet::kill_dev_entry` transactions using a custom payload builder. //! -//! This example demonstrates how to use `with_tx_payload_builder_sub` build a custom spammer tool -//! using custom transactions. Each account sends one transaction that deletes 5 entries starting at -//! index `5 * account_id`. +//! This example demonstrates how to use `with_tx_payload_builder_sub` to build a custom spammer +//! tool using specific transactions. //! //! Usage: //! ```bash From 7428e226e3c461c5bccc082da31767329ba3d2bd Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Fri, 5 Dec 2025 13:29:25 +0100 Subject: [PATCH 6/8] clippy + some renames --- src/lib.rs | 2 +- src/scenario.rs | 16 +++++----- src/subxt_transaction.rs | 30 +++++++++--------- src/transaction.rs | 66 +++++++++++++++++----------------------- 4 files changed, 53 insertions(+), 61 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 37d9c81..f3dbca8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -55,7 +55,7 @@ pub mod transaction; pub use subxt_transaction::{ eth_transfer_payload_builder, remark_payload_builder, sub_transfer_payload_builder, EthPayloadBuilderFn, EthTxBuildContext, PayloadBuilderFn, SubPayloadBuilderFn, - SubTxBuildContext, TxBuildContext, + SubTxBuildContext, TxPayloadBuildContext, }; /// Initialize the logger for various binaries (e.g. ttxt or test binaries). diff --git a/src/scenario.rs b/src/scenario.rs index 40d2325..b8ccd7d 100644 --- a/src/scenario.rs +++ b/src/scenario.rs @@ -22,8 +22,8 @@ use crate::{ SubstrateTransaction, SubstrateTransactionsSink, }, transaction::{ - EthTransactionBuilder, SubstrateTransactionBuilder, Transaction, TransactionBuilder, - TransactionCall, TransactionRecipe, TransactionsSink, + BuildTransactionParams, EthTransactionBuilder, SubstrateTransactionBuilder, Transaction, + TransactionBuilder, TransactionCall, TransactionRecipe, TransactionsSink, }, }; use subxt::tx::DynamicPayload; @@ -515,12 +515,14 @@ impl ScenarioBuilder { txs.push( builder .build_transaction( - &build_params.account, - &build_params.nonce, - &build_params.mortality, - &sink, watched_txs, - tip, + BuildTransactionParams { + account: &build_params.account, + nonce: &build_params.nonce, + mortality: &build_params.mortality, + tip, + }, + &sink, &*payload_builder, ) .await, diff --git a/src/subxt_transaction.rs b/src/subxt_transaction.rs index 8adab7d..61ebd02 100644 --- a/src/subxt_transaction.rs +++ b/src/subxt_transaction.rs @@ -81,7 +81,7 @@ pub type SubstrateTransactionsSink = SubxtTransactionsSink { +pub struct TxPayloadBuildContext<'a, A> { /// The destination account ID. pub to_account_id: &'a A, /// The source account ID (signer). @@ -93,12 +93,13 @@ pub struct TxBuildContext<'a, A> { } /// Context type alias for Substrate chains. -pub type SubTxBuildContext<'a> = TxBuildContext<'a, AccountIdOf>; +pub type SubTxBuildContext<'a> = TxPayloadBuildContext<'a, AccountIdOf>; /// Context type alias for Ethereum chains. -pub type EthTxBuildContext<'a> = TxBuildContext<'a, AccountId20>; +pub type EthTxBuildContext<'a> = TxPayloadBuildContext<'a, AccountId20>; /// Generic payload builder function type. -pub type PayloadBuilderFn = Arc) -> DynamicPayload + Send + Sync>; +pub type PayloadBuilderFn = + Arc) -> DynamicPayload + Send + Sync>; /// Payload builder type alias for Substrate chains. pub type SubPayloadBuilderFn = PayloadBuilderFn>; /// Payload builder type alias for Ethereum chains. @@ -541,7 +542,7 @@ where ChargeTransactionPaymentParams, (), )>, - B: Fn(&TxBuildContext>) -> DynamicPayload + ?Sized, + B: Fn(&TxPayloadBuildContext>) -> DynamicPayload + ?Sized, { // Needed because `Params` as associated type does not implement clone, and we need to // recreate the tx params in a loop when we can't create a partial tx with the online @@ -607,7 +608,7 @@ where )) } - let ctx = TxBuildContext { to_account_id, from_account_id, account, nonce }; + let ctx = TxPayloadBuildContext { to_account_id, from_account_id, account, nonce }; let tx_call = payload_builder(&ctx); for _ in 0..DEFAULT_RETRIES_FOR_PARTIAL_TX_CREATION { let params = tx_params(mortality, nonce as u64, tip); @@ -623,12 +624,9 @@ where } /// Builds a transaction with subxt. -pub(crate) async fn build_subxt_tx( - account: &str, - nonce: &Option, - mortality: &Option, +pub(crate) async fn build_subxt_tx<'a, C, KP, B>( + params: &crate::transaction::BuildTransactionParams<'a>, sink: &SubxtTransactionsSink, - tip: u128, payload_builder: &B, ) -> SubxtTransaction where @@ -646,8 +644,10 @@ where ChargeTransactionPaymentParams, (), )>, - B: Fn(&TxBuildContext>) -> DynamicPayload + ?Sized, + B: Fn(&TxPayloadBuildContext>) -> DynamicPayload + ?Sized, { + let &crate::transaction::BuildTransactionParams { account, nonce, mortality, tip } = params; + let to_account_id = sink.get_to_account_id(account).expect("to account exists"); let from_account_id = sink.get_from_account_id(account).expect("from account exists"); let from_keypair = sink.get_from_key_pair(account).expect("from account exists"); @@ -687,12 +687,12 @@ where .await .expect("failed to create mortal transaction") } else { - let params = >::new() + let tx_params = >::new() .nonce(nonce as u64) .tip(tip) .build() .into(); - let ctx = TxBuildContext { + let ctx = TxPayloadBuildContext { to_account_id: &to_account_id, from_account_id: &from_account_id, account, @@ -702,7 +702,7 @@ where let tx = SubxtTransaction::::new( sink.api() .tx() - .create_partial_offline(&tx_call, params) + .create_partial_offline(&tx_call, tx_params) .unwrap() .sign(&from_keypair), nonce as u128, diff --git a/src/transaction.rs b/src/transaction.rs index 0f75b3f..b6993e8 100644 --- a/src/transaction.rs +++ b/src/transaction.rs @@ -18,6 +18,14 @@ use serde::{Deserialize, Serialize}; use std::any::Any; use subxt::{config::BlockHash, tx::TxStatus, OnlineClient, PolkadotConfig}; +/// Parameters for building a transaction. +pub(crate) struct BuildTransactionParams<'a> { + pub account: &'a str, + pub nonce: &'a Option, + pub mortality: &'a Option, + pub tip: u128, +} + /// Interface for transaction building. #[async_trait] pub(crate) trait TransactionBuilder { @@ -28,12 +36,9 @@ pub(crate) trait TransactionBuilder { async fn build_transaction<'a>( &self, - account: &'a str, - nonce: &Option, - mortality: &Option, - sink: &Self::Sink, watched: bool, - tip: u128, + params: BuildTransactionParams<'a>, + sink: &Self::Sink, payload_builder: &Self::PayloadBuilder, ) -> DefaultTxTask; } @@ -51,22 +56,16 @@ impl TransactionBuilder for SubstrateTransactionBuilder { async fn build_transaction<'a>( &self, - account: &'a str, - nonce: &Option, - mortality: &Option, - sink: &Self::Sink, watched: bool, - tip: u128, + params: BuildTransactionParams<'a>, + sink: &Self::Sink, payload_builder: &Self::PayloadBuilder, ) -> DefaultTxTask { - if !watched { - DefaultTxTask::::new_unwatched( - build_subxt_tx(account, nonce, mortality, sink, tip, &**payload_builder).await, - ) + let tx = build_subxt_tx(¶ms, sink, &**payload_builder).await; + if watched { + DefaultTxTask::::new_watched(tx) } else { - DefaultTxTask::::new_watched( - build_subxt_tx(account, nonce, mortality, sink, tip, &**payload_builder).await, - ) + DefaultTxTask::::new_unwatched(tx) } } } @@ -84,22 +83,16 @@ impl TransactionBuilder for EthTransactionBuilder { async fn build_transaction<'a>( &self, - account: &'a str, - nonce: &Option, - mortality: &Option, - sink: &Self::Sink, watched: bool, - tip: u128, + params: BuildTransactionParams<'a>, + sink: &Self::Sink, payload_builder: &Self::PayloadBuilder, ) -> DefaultTxTask { - if !watched { - DefaultTxTask::::new_unwatched( - build_subxt_tx(account, nonce, mortality, sink, tip, &**payload_builder).await, - ) + let tx = build_subxt_tx(¶ms, sink, &**payload_builder).await; + if watched { + DefaultTxTask::::new_watched(tx) } else { - DefaultTxTask::::new_watched( - build_subxt_tx(account, nonce, mortality, sink, tip, &**payload_builder).await, - ) + DefaultTxTask::::new_unwatched(tx) } } } @@ -118,26 +111,23 @@ impl TransactionBuilder for FakeTransactionBuilder { async fn build_transaction<'a>( &self, - account: &'a str, - _nonce: &Option, - _mortality: &Option, + watched: bool, + params: BuildTransactionParams<'a>, sink: &Self::Sink, - unwatched: bool, - _tip: u128, _payload_builder: &Self::PayloadBuilder, ) -> DefaultTxTask { - if unwatched { + if watched { todo!() }; let mut nonces = sink.nonces.write(); - let nonce = if let Some(nonce) = nonces.get_mut(&hex::encode(account)) { + let nonce = if let Some(nonce) = nonces.get_mut(&hex::encode(params.account)) { *nonce += 1; *nonce } else { - nonces.insert(hex::encode(account), 0); + nonces.insert(hex::encode(params.account), 0); 0 }; - let id = account.parse::().ok(); + let id = params.account.parse::().ok(); if let Some(i) = id { DefaultTxTask::::new_watched(FakeTransaction::new_multiple( From 64bba0e7694dfc8905bbe9a7c41c5fa36bfb4def Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Fri, 5 Dec 2025 13:35:56 +0100 Subject: [PATCH 7/8] clippy: lifetime --- src/subxt_transaction.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/subxt_transaction.rs b/src/subxt_transaction.rs index 61ebd02..118d88d 100644 --- a/src/subxt_transaction.rs +++ b/src/subxt_transaction.rs @@ -624,8 +624,8 @@ where } /// Builds a transaction with subxt. -pub(crate) async fn build_subxt_tx<'a, C, KP, B>( - params: &crate::transaction::BuildTransactionParams<'a>, +pub(crate) async fn build_subxt_tx( + params: &crate::transaction::BuildTransactionParams<'_>, sink: &SubxtTransactionsSink, payload_builder: &B, ) -> SubxtTransaction From 2ff546030543fec8382f24a5ec2530399b03dff9 Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Fri, 5 Dec 2025 13:36:45 +0100 Subject: [PATCH 8/8] clippy one more time --- src/execution_log.rs | 2 +- src/runner.rs | 6 +++--- src/subxt_api_connector.rs | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/execution_log.rs b/src/execution_log.rs index f77099b..2a91522 100644 --- a/src/execution_log.rs +++ b/src/execution_log.rs @@ -717,7 +717,7 @@ pub mod journal { writeln!(file, "hash,{}", CsvEntry::::header_line()).unwrap(); for (h, v) in raw_data { - writeln!(file, "{:?},{}", h, v).unwrap(); + writeln!(file, "{h:?},{v}").unwrap(); } } } diff --git a/src/runner.rs b/src/runner.rs index 3f00cc8..be238e0 100644 --- a/src/runner.rs +++ b/src/runner.rs @@ -330,15 +330,15 @@ where let default_file_name = self .executor_id .as_ref() - .map(|id| format!("ttxt_{}_{}.json", id, formatted_date)) - .unwrap_or(format!("ttxt_{}.json", formatted_date)); + .map(|id| format!("ttxt_{id}_{formatted_date}.json")) + .unwrap_or(format!("ttxt_{formatted_date}.json")); self.base_dir_path .as_ref() .map(|basedir| { let filename = self .log_file_name .as_ref() - .map(|filename| format!("{basedir}/{filename}_{}", formatted_date)) + .map(|filename| format!("{basedir}/{filename}_{formatted_date}")) .unwrap_or(format!("{basedir}/{default_file_name}")); filename }) diff --git a/src/subxt_api_connector.rs b/src/subxt_api_connector.rs index 60bcee8..40d60c2 100644 --- a/src/subxt_api_connector.rs +++ b/src/subxt_api_connector.rs @@ -49,7 +49,7 @@ pub(crate) async fn connect( }; } - let err = format!("Failed to connect to {} after {} attempts", url, MAX_ATTEMPTS); - info!("{}", err); + let err = format!("Failed to connect to {url} after {MAX_ATTEMPTS} attempts"); + info!("{err}"); Err(err.into()) }