Files
pezkuwi-sdk/bizinikiwi/utils/txtesttool/src/subxt_transaction.rs
T
pezkuwichain 4c8f281051 style: Migrate to stable-only rustfmt configuration
- 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
2025-12-23 09:37:11 +03:00

767 lines
23 KiB
Rust

use crate::{
block_monitor::BlockMonitor,
error::Error,
helpers::StreamOf,
scenario::AccountsDescription,
transaction::{
AccountMetadata, Transaction, TransactionMonitor, TransactionStatus, TransactionsSink,
},
};
use async_trait::async_trait;
use futures::StreamExt;
use parking_lot::RwLock;
pub use pezkuwi_subxt::dynamic;
use pezkuwi_subxt::{
backend::rpc::RpcClient,
config::{
transaction_extensions::{
ChargeAssetTxPaymentParams, ChargeTransactionPaymentParams, CheckMortalityParams,
CheckNonceParams,
},
DefaultExtrinsicParams, ExtrinsicParams,
},
dynamic::{At, Value},
ext::scale_value::value,
tx::{DynamicPayload, PartialTransaction, Signer, SubmittableTransaction},
OnlineClient, PezkuwiConfig,
};
use pezkuwi_subxt_core::{config::DefaultExtrinsicParamsBuilder, utils::AccountId20};
use pezkuwi_subxt_signer::{
eth::{dev as eth_dev, Keypair as EthKeypair, Signature},
sr25519::{dev as sr25519_dev, Keypair as SrPair},
};
use std::{
any::Any,
collections::HashMap,
sync::Arc,
time::{Duration, Instant},
};
use tracing::{debug, error, trace};
const LOG_TARGET: &str = "subxt_tx";
const DEFAULT_RETRIES_FOR_PARTIAL_TX_CREATION: usize = 10;
#[derive(Clone)]
/// Ethereum runtime config definition for subxt usage purposes.
pub enum EthRuntimeConfig {}
impl pezkuwi_subxt::Config for EthRuntimeConfig {
type AccountId = AccountId20;
type Address = AccountId20;
type Signature = Signature;
type Hasher = pezkuwi_subxt::config::bizinikiwi::BlakeTwo256;
type Header = pezkuwi_subxt::config::bizinikiwi::BizinikiwiHeader<
u32,
pezkuwi_subxt::config::bizinikiwi::BlakeTwo256,
>;
type ExtrinsicParams = pezkuwi_subxt::config::DefaultExtrinsicParams<Self>;
type AssetId = u32;
}
/// Type alias for subxt config hash (Output of Hasher).
pub(crate) type HashOf<C> =
<<C as pezkuwi_subxt::Config>::Hasher as pezkuwi_subxt::config::Hasher>::Output;
/// Type alias for subxt account id.
pub(crate) type AccountIdOf<C> = <C as pezkuwi_subxt::Config>::AccountId;
/// A subxt transaction abstraction.
#[derive(Clone)]
pub struct SubxtTransaction<C: pezkuwi_subxt::Config> {
transaction: Arc<SubmittableTransaction<C, OnlineClient<C>>>,
nonce: u128,
valid_until: Option<u64>,
account_metadata: AccountMetadata,
}
/// Transaction type thart runs on `Ethereum` compatible chains.
pub type EthTransaction = SubxtTransaction<EthRuntimeConfig>;
/// Holds the RPC API connection for transaction execution.
pub type EthTransactionsSink = SubxtTransactionsSink<EthRuntimeConfig, EthKeypair>;
/// Transaction type that runs on `bizinikiwi` compatible chains.
pub type BizinikiwTransaction = SubxtTransaction<PezkuwiConfig>;
/// Holds the RPC API connection for transaction execution.
pub type BizinikiwTransactionsSink = SubxtTransactionsSink<PezkuwiConfig, SrPair>;
/// Context for building transaction payloads.
/// Generic over account type `A` to support both Substrate and Ethereum chains.
pub struct TxPayloadBuildContext<'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 Bizinikiwi chains.
pub type SubTxBuildContext<'a> = TxPayloadBuildContext<'a, AccountIdOf<PezkuwiConfig>>;
/// Context type alias for Ethereum chains.
pub type EthTxBuildContext<'a> = TxPayloadBuildContext<'a, AccountId20>;
/// Generic payload builder function type.
pub type PayloadBuilderFn<A> =
Arc<dyn Fn(&TxPayloadBuildContext<A>) -> DynamicPayload + Send + Sync>;
/// Payload builder type alias for Bizinikiwi chains.
pub type SubPayloadBuilderFn = PayloadBuilderFn<AccountIdOf<PezkuwiConfig>>;
/// Payload builder type alias for Ethereum chains.
pub type EthPayloadBuilderFn = PayloadBuilderFn<AccountId20>;
/// 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<A>(size_kb: u32) -> PayloadBuilderFn<A>
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];
pezkuwi_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| {
pezkuwi_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| {
pezkuwi_subxt::dynamic::tx(
"Balances",
"transfer_keep_alive",
vec![
Value::unnamed_composite(vec![Value::from_bytes(*ctx.to_account_id)]),
Value::u128(1u32.into()),
],
)
})
}
impl<C: pezkuwi_subxt::Config> SubxtTransaction<C> {
pub fn new(
transaction: SubmittableTransaction<C, OnlineClient<C>>,
nonce: u128,
valid_until: Option<u64>,
account_metadata: AccountMetadata,
) -> Self {
Self { transaction: Arc::new(transaction), nonce, account_metadata, valid_until }
}
}
// type TransactionSubxt2 = pezkuwi_subxt::tx::DynamicPayload;
impl<C: pezkuwi_subxt::Config> Transaction for SubxtTransaction<C> {
type HashType = HashOf<C>;
fn hash(&self) -> Self::HashType {
self.transaction.hash()
}
fn as_any(&self) -> &dyn Any {
self
}
fn nonce(&self) -> u128 {
self.nonce
}
fn account_metadata(&self) -> AccountMetadata {
self.account_metadata.clone()
}
fn valid_until(&self) -> &Option<u64> {
&self.valid_until
}
}
#[derive(Clone)]
pub struct SubxtTransactionsSink<C: pezkuwi_subxt::Config, KP: Signer<C>> {
api: OnlineClient<C>,
from_accounts: Arc<RwLock<HashMap<String, (KP, AccountMetadata)>>>,
to_accounts: Arc<RwLock<HashMap<String, (KP, AccountMetadata)>>>,
nonces: Arc<RwLock<HashMap<String, u128>>>,
rpc_client: RpcClient,
current_pending_extrinsics: Arc<RwLock<Option<(Instant, usize)>>>,
block_monitor: Option<BlockMonitor<C>>,
}
const EXPECT_CONNECT: &str = "should connect to rpc client";
impl<C, KP> SubxtTransactionsSink<C, KP>
where
AccountIdOf<C>: Send + Sync + AsRef<[u8]>,
KP: Signer<C> + Clone + Send + Sync + 'static,
C: pezkuwi_subxt::Config,
{
pub async fn new() -> Self {
Self {
api: crate::subxt_api_connector::connect("ws://127.0.0.1:9933", false)
.await
.expect(EXPECT_CONNECT),
from_accounts: Default::default(),
to_accounts: Default::default(),
nonces: Default::default(),
rpc_client: RpcClient::from_url("ws://127.0.0.1:9933").await.expect(EXPECT_CONNECT),
current_pending_extrinsics: Arc::new(None.into()),
block_monitor: None,
}
}
pub async fn new_with_uri(uri: &String) -> Self {
Self {
api: crate::subxt_api_connector::connect(uri, false).await.expect(EXPECT_CONNECT),
from_accounts: Default::default(),
to_accounts: Default::default(),
nonces: Default::default(),
rpc_client: RpcClient::from_url(uri).await.expect(EXPECT_CONNECT),
current_pending_extrinsics: Arc::new(None.into()),
block_monitor: None,
}
}
pub async fn new_with_uri_with_accounts_description<G>(
uri: &str,
accounts_description: AccountsDescription,
generate_pair: G,
block_monitor: Option<BlockMonitor<C>>,
use_legacy_backend: bool,
) -> Self
where
G: GenerateKeyPairFunction<KP>,
{
let from_accounts =
derive_accounts(accounts_description.clone(), SENDER_SEED, generate_pair);
let to_accounts = derive_accounts(accounts_description, RECEIVER_SEED, generate_pair);
Self {
api: crate::subxt_api_connector::connect(uri, use_legacy_backend)
.await
.expect(EXPECT_CONNECT),
from_accounts: Arc::from(RwLock::from(from_accounts)),
to_accounts: Arc::from(RwLock::from(to_accounts)),
nonces: Default::default(),
rpc_client: crate::helpers::client(uri).await.expect(EXPECT_CONNECT).into(),
current_pending_extrinsics: Arc::new(None.into()),
block_monitor,
}
}
fn api(&self) -> OnlineClient<C> {
self.api.clone()
}
pub fn get_from_account_id(&self, account: &str) -> Option<AccountIdOf<C>> {
self.from_accounts.read().get(account).map(|a| a.0.account_id())
}
fn get_to_account_id(&self, account: &str) -> Option<AccountIdOf<C>> {
self.to_accounts.read().get(account).map(|a| a.0.account_id())
}
fn get_to_account_metadata(&self, account: &str) -> Option<AccountMetadata> {
self.to_accounts.read().get(account).map(|a| a.1.clone())
}
fn get_from_key_pair(&self, account: &str) -> Option<KP> {
self.from_accounts.read().get(account).map(|k| k.0.clone())
}
pub async fn check_account_nonce(
&self,
account: AccountIdOf<C>,
) -> Result<u128, Box<dyn std::error::Error>> {
let is_nonce_set = {
let nonces = self.nonces.read();
nonces.get(&hex::encode(account.clone())).cloned()
};
let remote_nonce = if let Some(nonce) = is_nonce_set {
nonce
} else {
check_account_nonce(self.api.clone(), account.clone()).await?
};
let mut nonces = self.nonces.write();
if let Some(nonce) = nonces.get_mut(&hex::encode(account.clone())) {
*nonce += 1;
Ok(*nonce)
} else {
nonces.insert(hex::encode(account), remote_nonce);
Ok(remote_nonce)
}
}
async fn update_count(&self) {
let i = Instant::now();
let xts_len = self
.rpc_client
.request::<Vec<serde_json::Value>>(
"author_pendingExtrinsics",
pezkuwi_subxt_rpcs::rpc_params!(),
)
.await
.expect("author_pendingExtrinsics should not fail")
.len();
*self.current_pending_extrinsics.write() = Some((i, xts_len));
}
}
/// Fetches an account storage and returns its nonce.
pub async fn check_account_nonce<C: pezkuwi_subxt::Config>(
api: OnlineClient<C>,
account: AccountIdOf<C>,
) -> Result<u128, Box<dyn std::error::Error>>
where
AccountIdOf<C>: Send + Sync + AsRef<[u8]>,
{
let storage_query = pezkuwi_subxt::dynamic::storage("System", "Account");
let storage_at = api.storage().at_latest().await?;
let storage_value = storage_at
.try_fetch(storage_query, (Value::from_bytes(account.clone()),))
.await?
.ok_or_else(|| {
format!("Sender account {:?} does not exist", hex::encode(account.clone()))
})?;
let value: pezkuwi_subxt::dynamic::Value =
storage_value.decode().map_err(|e| format!("Failed to decode storage: {e:?}"))?;
debug!(target:LOG_TARGET,"account has free balance: {:?}", value.at("data").at("free"));
debug!(target:LOG_TARGET,"account has nonce: {:?}", value.at("nonce"));
// info!("account has nonce: {:#?}", value);
let nonce = value
.at("nonce")
.ok_or("nonce is not set for the account")?
.as_u128()
.ok_or("nonce is not u128")?;
Ok(nonce)
}
#[async_trait]
impl<C, KP> TransactionsSink<HashOf<C>> for SubxtTransactionsSink<C, KP>
where
AccountIdOf<C>: Send + Sync + AsRef<[u8]>,
C: pezkuwi_subxt::Config,
KP: Signer<C> + Clone + Send + Sync + 'static,
{
async fn submit_and_watch(
&self,
tx: &dyn Transaction<HashType = HashOf<C>>,
) -> Result<StreamOf<TransactionStatus<HashOf<C>>>, Error> {
let tx = tx.as_any().downcast_ref::<SubxtTransaction<C>>().unwrap();
let result = tx.transaction.submit_and_watch().await;
match result {
Ok(stream) => Ok(stream.map(|e| e.unwrap().into()).boxed()),
Err(e) => Err(e.into()),
}
}
async fn submit(&self, tx: &dyn Transaction<HashType = HashOf<C>>) -> Result<HashOf<C>, Error> {
let tx = tx.as_any().downcast_ref::<SubxtTransaction<C>>().unwrap();
tx.transaction.submit().await.map_err(|e| e.into())
}
/// Current count of transactions being processed by sink.
async fn pending_extrinsics(&self) -> usize {
let current_pending_extrinsics = { *self.current_pending_extrinsics.read() };
if let Some((ts, _)) = current_pending_extrinsics {
if ts.elapsed() > Duration::from_millis(1000) {
self.update_count().await;
}
} else {
self.update_count().await;
}
self.current_pending_extrinsics
.read()
.expect("current_pending_extrinsics cannot be None")
.1
}
fn transaction_monitor(&self) -> Option<&dyn TransactionMonitor<HashOf<C>>> {
self.block_monitor.as_ref().map(|m| m as &dyn TransactionMonitor<HashOf<C>>)
}
}
/// Types of accounts generation.
#[derive(Debug, Clone)]
pub enum AccountGenerateRequest {
Keyring(String),
Derived(String, u32),
}
/// Seed user for sender accounts.
pub const SENDER_SEED: &str = "//Sender";
/// Seed used for receiver accounts.
pub(crate) const RECEIVER_SEED: &str = "//Receiver";
/// Generates ecdsa based keypairs.
pub fn generate_ecdsa_keypair(description: AccountGenerateRequest) -> EthKeypair {
match description {
AccountGenerateRequest::Keyring(name) => match name.as_str() {
"alice" | "alith" => eth_dev::alith(),
"bob" | "baltathar" => eth_dev::baltathar(),
"charlie" | "charleth" => eth_dev::charleth(),
"dave" | "dorothy" => eth_dev::dorothy(),
"eve" | "ethan" => eth_dev::ethan(),
"ferdie" | "faith" => eth_dev::faith(),
_ => panic!("unknown keyring name"),
},
AccountGenerateRequest::Derived(seed, i) => {
use std::str::FromStr;
let derivation = format!("{seed}//{i}");
let u = pezkuwi_subxt_signer::SecretUri::from_str(&derivation).unwrap();
<pezkuwi_subxt_signer::ecdsa::Keypair>::from_uri(&u).unwrap().into()
},
}
}
/// Generates sr25519 based keypairs.
pub fn generate_sr25519_keypair(description: AccountGenerateRequest) -> SrPair {
match description {
AccountGenerateRequest::Keyring(name) => match name.as_str() {
"alice" | "alith" => sr25519_dev::alice(),
"bob" | "baltathar" => sr25519_dev::bob(),
"charlie" | "charleth" => sr25519_dev::charlie(),
"dave" | "dorothy" => sr25519_dev::dave(),
"eve" | "ethan" => sr25519_dev::eve(),
"ferdie" | "faith" => sr25519_dev::ferdie(),
_ => panic!("unknown keyring name"),
},
AccountGenerateRequest::Derived(seed, i) => {
use std::str::FromStr;
let derivation = format!("{seed}//{i}");
let u = pezkuwi_subxt_signer::SecretUri::from_str(&derivation).unwrap();
<pezkuwi_subxt_signer::sr25519::Keypair>::from_uri(&u).unwrap()
},
}
}
/// Interface for implementors of keypairs generators.
pub trait GenerateKeyPairFunction<KP>:
Fn(AccountGenerateRequest) -> KP + Copy + Send + 'static
{
}
impl<T, KP> GenerateKeyPairFunction<KP> for T where
T: Fn(AccountGenerateRequest) -> KP + Copy + Send + 'static
{
}
/// Logic that derives accounts from a certain seed.
pub fn derive_accounts<C, KP, G>(
accounts_description: AccountsDescription,
seed: &str,
generate: G,
) -> HashMap<String, (KP, AccountMetadata)>
where
C: pezkuwi_subxt::Config,
KP: Signer<C> + Send + Sync + 'static,
G: GenerateKeyPairFunction<KP>,
{
match accounts_description {
AccountsDescription::Derived(range) => {
let from_id = range.start as usize;
let to_id = range.end as usize;
let n = to_id - from_id;
let t = std::cmp::min(
n,
std::thread::available_parallelism().unwrap_or(1usize.try_into().unwrap()).get(),
);
let mut threads = Vec::new();
(0..t).for_each(|thread_idx| {
// let chunk = (thread_idx * (n / t))..((thread_idx + 1) * (n / t));
let chunk =
(from_id + (thread_idx * n) / t)..(from_id + ((thread_idx + 1) * n) / t);
let seed = seed.to_string().clone();
threads.push(std::thread::spawn(move || {
chunk
.into_iter()
.map(move |i| {
(
i.to_string(),
(
generate(AccountGenerateRequest::Derived(
seed.to_string(),
i as u32,
)),
AccountMetadata::Derived(i as u32),
),
)
})
.collect::<Vec<_>>()
}));
});
threads
.into_iter()
.flat_map(|h| h.join().unwrap())
// .map(|p| (p, funds))
.collect()
},
AccountsDescription::Keyring(account) => HashMap::from([(
account.clone(),
(
generate(AccountGenerateRequest::Keyring(account.clone())),
AccountMetadata::KeyRing(account),
),
)]),
}
}
#[allow(clippy::too_many_arguments)]
async fn create_online_transaction<C: pezkuwi_subxt::Config, KP, B>(
from_keypair: &KP,
nonce: u128,
mortality: &Option<u64>,
account: &str,
sink: &SubxtTransactionsSink<C, KP>,
from_account_id: &<C as pezkuwi_subxt::Config>::AccountId,
to_account_id: &<C as pezkuwi_subxt::Config>::AccountId,
tip: u128,
payload_builder: &B,
) -> Result<SubxtTransaction<C>, Error>
where
AccountIdOf<C>: Send + Sync + AsRef<[u8]>,
KP: Signer<C> + Clone + Send + Sync + 'static,
<<C as pezkuwi_subxt::Config>::ExtrinsicParams as pezkuwi_subxt::config::ExtrinsicParams<C>>::Params: From<(
(),
(),
(),
CheckNonceParams,
(),
CheckMortalityParams<C>,
ChargeAssetTxPaymentParams<C>,
ChargeTransactionPaymentParams,
(),
)>,
B: Fn(&TxPayloadBuildContext<AccountIdOf<C>>) -> 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
// client, due to various RPC related issues or state not being up to date (currently we
// handle an error which happens when trying to create a partial tx that is based on a
// certain finalized block returned by the RPC, which is then reported as not found).
// Retrying seems to fix the issue.
fn tx_params<CC: pezkuwi_subxt::Config>(
mortality: &Option<u64>,
nonce: u64,
tip: u128,
) -> <DefaultExtrinsicParams<CC> as ExtrinsicParams<CC>>::Params {
let mut params = <DefaultExtrinsicParamsBuilder<CC>>::new().nonce(nonce).tip(tip);
if let Some(mortal) = mortality {
params = params.mortal(*mortal);
}
params.build()
}
// Creates a subxt transaction.
//
// The mortality of the transaction involves setting up a block until the transaction is valid,
// which needs fetching the last finalized block number on chain similarly to subxt:
// https://github.com/paritytech/subxt/blob/77b6abccbacf194f3889610024e2f4024e8c2822/subxt/src/tx/tx_client.rs#L600
async fn subxt_transaction<CC: pezkuwi_subxt::Config, KEYP>(
sink: &SubxtTransactionsSink<CC, KEYP>,
mut partial_tx: PartialTransaction<CC, OnlineClient<CC>>,
from_keypair: &KEYP,
nonce: u128,
mortality: &Option<u64>,
account: &str,
) -> Result<SubxtTransaction<CC>, Error>
where
KEYP: Signer<CC> + Clone + Send + Sync + 'static,
AccountIdOf<CC>: Send + Sync + AsRef<[u8]>,
{
let block_number = if mortality.is_some() {
let block_ref = sink
.api()
.backend()
.latest_finalized_block_ref()
.await
.expect("to get the last finalized block ref. qed");
let block = sink
.api()
.blocks()
.at(block_ref)
.await
.expect("to get the corresponding block header. qed");
Some(block.number().into())
} else {
None
};
let submittable_tx = partial_tx.sign(from_keypair);
let hash = submittable_tx.hash();
debug!(target:LOG_TARGET,"built mortal tx hash: {:?}", hash);
Ok(SubxtTransaction::<CC>::new(
submittable_tx,
nonce,
mortality.and_then(|mortal| block_number.map(|number| number + mortal)),
sink.get_to_account_metadata(account).expect("account metadata exists"),
))
}
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);
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
},
Err(_) => continue,
}
}
error!(target: LOG_TARGET, "Attempting transaction creation with the online client, to factor in the provided mortality, failed.");
Err(Error::Other("failed to create transaction with online client".to_string()))
}
/// Builds a transaction with subxt.
pub(crate) async fn build_subxt_tx<C, KP, B>(
params: &crate::transaction::BuildTransactionParams<'_>,
sink: &SubxtTransactionsSink<C, KP>,
payload_builder: &B,
) -> SubxtTransaction<C>
where
AccountIdOf<C>: Send + Sync + AsRef<[u8]>,
C: pezkuwi_subxt::Config,
KP: Signer<C> + Clone + Send + Sync + 'static,
<<C as pezkuwi_subxt::Config>::ExtrinsicParams as pezkuwi_subxt::config::ExtrinsicParams<C>>::Params: From<(
(),
(),
(),
CheckNonceParams,
(),
CheckMortalityParams<C>,
ChargeAssetTxPaymentParams<C>,
ChargeTransactionPaymentParams,
(),
)>,
B: Fn(&TxPayloadBuildContext<AccountIdOf<C>>) -> 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");
let nonce = if let Some(nonce) = nonce {
trace!("nonce for {:?} -> {:?}", account, nonce);
*nonce
} else {
let nonce = sink
.check_account_nonce(from_account_id.clone())
.await
.expect("account nonce shall exists");
trace!("checked nonce for {:?} -> {:?}", account, nonce);
nonce
};
debug!(
target:LOG_TARGET,
account,
nonce,
?mortality,
from_account=hex::encode(from_account_id.clone()),
to_account=hex::encode(to_account_id.clone()),
"build_subxt_tx"
);
if mortality.is_some() {
create_online_transaction(
&from_keypair,
nonce,
mortality,
account,
sink,
&from_account_id,
&to_account_id,
tip,
payload_builder,
)
.await
.expect("failed to create mortal transaction")
} else {
let tx_params = <DefaultExtrinsicParamsBuilder<C>>::new()
.nonce(nonce as u64)
.tip(tip)
.build()
.into();
let ctx = TxPayloadBuildContext {
to_account_id: &to_account_id,
from_account_id: &from_account_id,
account,
nonce,
};
let tx_call = payload_builder(&ctx);
let tx = SubxtTransaction::<C>::new(
sink.api()
.tx()
.create_partial_offline(&tx_call, tx_params)
.unwrap()
.sign(&from_keypair),
nonce as u128,
None,
sink.get_to_account_metadata(account).expect("account metadata exists"),
);
debug!(target:LOG_TARGET,"built immortal tx hash: {:?}", tx.hash());
tx
}
}
#[cfg(test)]
mod tests {
use pezkuwi_subxt::BizinikiwConfig;
use crate::{
subxt_transaction::{
derive_accounts, generate_sr25519_keypair, AccountGenerateRequest, SENDER_SEED,
},
transaction::AccountMetadata,
};
#[tokio::test]
async fn test_derive_accounts_len() {
let accounts = derive_accounts::<BizinikiwConfig, pezkuwi_subxt_signer::sr25519::Keypair, _>(
crate::scenario::AccountsDescription::Derived(0..11),
SENDER_SEED,
generate_sr25519_keypair,
);
assert_eq!(accounts.len(), 11);
for (i, (kp, meta)) in accounts {
let id = i.parse::<u32>().unwrap();
assert_eq!(
kp.public_key().0,
generate_sr25519_keypair(AccountGenerateRequest::Derived(
SENDER_SEED.to_string(),
id
))
.public_key()
.0
);
assert_eq!(AccountMetadata::Derived(id), meta);
}
let accounts = derive_accounts::<BizinikiwConfig, pezkuwi_subxt_signer::sr25519::Keypair, _>(
crate::scenario::AccountsDescription::Keyring("alice".to_string()),
SENDER_SEED,
generate_sr25519_keypair,
);
assert_eq!(accounts.len(), 1);
assert_eq!(
accounts.get("alice").unwrap().0.public_key().0,
generate_sr25519_keypair(AccountGenerateRequest::Keyring("alice".to_string()))
.public_key()
.0
);
assert_eq!(accounts.get("alice").unwrap().1, AccountMetadata::KeyRing("alice".to_string()))
}
}