diff --git a/crates/transaction-emitter-lib/src/args.rs b/crates/transaction-emitter-lib/src/args.rs index 45fe819576cbb..99ff392327ac5 100644 --- a/crates/transaction-emitter-lib/src/args.rs +++ b/crates/transaction-emitter-lib/src/args.rs @@ -194,7 +194,7 @@ pub struct EmitArgs { #[clap(long, default_value = "false")] /// Skip minting account during initialization - pub skip_minting_accounts: bool, + pub skip_funding_accounts: bool, #[clap(long)] pub latency_polling_interval_s: Option, diff --git a/crates/transaction-emitter-lib/src/cluster.rs b/crates/transaction-emitter-lib/src/cluster.rs index b0cd4e73a4be5..a1584b3ef992f 100644 --- a/crates/transaction-emitter-lib/src/cluster.rs +++ b/crates/transaction-emitter-lib/src/cluster.rs @@ -1,7 +1,7 @@ // Copyright © Aptos Foundation // SPDX-License-Identifier: Apache-2.0 -use crate::{emitter::query_sequence_number, instance::Instance, ClusterArgs}; +use crate::{emitter::load_specific_account, instance::Instance, ClusterArgs}; use anyhow::{anyhow, bail, format_err, Result}; use aptos_crypto::{ ed25519::{Ed25519PrivateKey, Ed25519PublicKey}, @@ -9,9 +9,7 @@ use aptos_crypto::{ }; use aptos_logger::{info, warn}; use aptos_rest_client::{Client as RestClient, State}; -use aptos_sdk::types::{ - account_config::aptos_test_root_address, chain_id::ChainId, AccountKey, LocalAccount, -}; +use aptos_sdk::types::{chain_id::ChainId, AccountKey, LocalAccount}; use futures::{stream::FuturesUnordered, StreamExt}; use rand::seq::SliceRandom; use std::{convert::TryFrom, time::Instant}; @@ -200,22 +198,7 @@ impl Cluster { } pub async fn load_coin_source_account(&self, client: &RestClient) -> Result { - let account_key = self.account_key(); - let address = if self.coin_source_is_root { - aptos_test_root_address() - } else { - account_key.authentication_key().account_address() - }; - - let sequence_number = query_sequence_number(client, address).await.map_err(|e| { - format_err!( - "query_sequence_number on {:?} for account {} failed: {:?}", - client, - address, - e - ) - })?; - Ok(LocalAccount::new(address, account_key, sequence_number)) + load_specific_account(self.account_key(), self.coin_source_is_root, client).await } pub fn random_instance(&self) -> Instance { diff --git a/crates/transaction-emitter-lib/src/emitter/account_minter.rs b/crates/transaction-emitter-lib/src/emitter/account_minter.rs index 6b41ddb4cb7cd..30d591d3936b4 100644 --- a/crates/transaction-emitter-lib/src/emitter/account_minter.rs +++ b/crates/transaction-emitter-lib/src/emitter/account_minter.rs @@ -1,8 +1,13 @@ // Copyright © Aptos Foundation // SPDX-License-Identifier: Apache-2.0 -use crate::{emitter::local_account_generator::LocalAccountGenerator, EmitJobRequest}; +use super::{ + local_account_generator::LocalAccountGenerator, parse_seed, + transaction_executor::RestApiReliableTransactionSubmitter, +}; +use crate::EmitJobRequest; use anyhow::{anyhow, bail, format_err, Context, Result}; +use aptos_config::config::DEFAULT_MAX_SUBMIT_TRANSACTION_BATCH_SIZE; use aptos_crypto::{ ed25519::{Ed25519PrivateKey, Ed25519PublicKey}, encoding_type::EncodingType, @@ -16,14 +21,11 @@ use aptos_sdk::{ }, }; use aptos_transaction_generator_lib::{ - CounterState, ReliableTransactionSubmitter, RootAccountHandle, SEND_AMOUNT, + CounterState, ReliableTransactionSubmitter, RootAccountHandle, }; use aptos_types::account_address::AccountAddress; -use core::{ - cmp::min, - result::Result::{Err, Ok}, -}; -use futures::StreamExt; +use core::result::Result::{Err, Ok}; +use futures::{future::try_join_all, StreamExt}; use rand::{rngs::StdRng, Rng, SeedableRng}; use std::{ path::Path, @@ -34,7 +36,8 @@ use std::{ pub struct SourceAccountManager<'t> { pub source_account: Arc, pub txn_executor: &'t dyn ReliableTransactionSubmitter, - pub req: &'t EmitJobRequest, + pub mint_to_root: bool, + pub prompt_before_spending: bool, pub txn_factory: TransactionFactory, } @@ -60,7 +63,7 @@ impl<'t> SourceAccountManager<'t> { .txn_executor .get_account_balance(self.source_account_address()) .await?; - Ok(if self.req.mint_to_root { + Ok(if self.mint_to_root { // We have a root account, so amount of funds minted is not a problem // We can have multiple txn emitter running simultaneously, each coming to this check at the same time. // So they might all pass the check, but not be able to consume funds they need. So we check more conservatively @@ -107,7 +110,7 @@ impl<'t> SourceAccountManager<'t> { )); } - if self.req.prompt_before_spending { + if self.prompt_before_spending { if !prompt_yes(&format!( "plan will consume in total {} balance for {}, are you sure you want to proceed", amount, @@ -156,7 +159,7 @@ impl<'t> SourceAccountManager<'t> { pub struct AccountMinter<'t> { txn_factory: TransactionFactory, - rng: StdRng, + account_rng: StdRng, source_account: &'t SourceAccountManager<'t>, } @@ -164,39 +167,12 @@ impl<'t> AccountMinter<'t> { pub fn new( source_account: &'t SourceAccountManager<'t>, txn_factory: TransactionFactory, - rng: StdRng, + account_rng: StdRng, ) -> Self { Self { source_account, txn_factory, - rng, - } - } - - pub fn get_needed_balance_per_account(&self, req: &EmitJobRequest, num_accounts: usize) -> u64 { - if let Some(val) = req.coins_per_account_override { - info!(" with {} balance each because of override", val); - val - } else { - // round up: - let txnx_per_account = - (req.expected_max_txns + num_accounts as u64 - 1) / num_accounts as u64; - let min_balance = req.max_gas_per_txn * req.gas_price; - let coins_per_account = txnx_per_account - .checked_mul(SEND_AMOUNT + req.get_expected_gas_per_txn() * req.gas_price) - .unwrap() - .checked_add(min_balance) - .unwrap(); // extra coins for secure to pay none zero gas price - - info!( - " with {} balance each because of expecting {} txns per account, with {} gas at {} gas price per txn, and min balance {}", - coins_per_account, - txnx_per_account, - req.get_expected_gas_per_txn(), - req.gas_price, - min_balance, - ); - coins_per_account + account_rng, } } @@ -254,21 +230,22 @@ impl<'t> AccountMinter<'t> { pub async fn create_and_fund_accounts( &mut self, txn_executor: &dyn ReliableTransactionSubmitter, - req: &EmitJobRequest, account_generator: Box, - max_submit_batch_size: usize, local_accounts: Vec>, + coins_per_account: u64, + max_submit_batch_size: usize, + mint_to_root: bool, + create_secondary_source_account: bool, ) -> Result<()> { let num_accounts = local_accounts.len(); info!( - "Account creation plan created for {} accounts and {} txns:", - num_accounts, req.expected_max_txns, + "Account creation plan created for {} accounts and {} coins per account", + num_accounts, coins_per_account, ); let expected_num_seed_accounts = (num_accounts / 50).clamp(1, (num_accounts as f32).sqrt() as usize + 1); - let coins_per_account = self.get_needed_balance_per_account(req, num_accounts); let expected_children_per_seed_account = (num_accounts + expected_num_seed_accounts - 1) / expected_num_seed_accounts; @@ -280,7 +257,7 @@ impl<'t> AccountMinter<'t> { self.txn_factory.get_gas_unit_price(), ); let coins_for_source = Self::funds_needed_for_multi_transfer( - if req.mint_to_root { "root" } else { "source" }, + if mint_to_root { "root" } else { "source" }, expected_num_seed_accounts as u64, coins_per_seed_account, self.txn_factory.get_max_gas_amount(), @@ -293,8 +270,8 @@ impl<'t> AccountMinter<'t> { .await? { // recheck value makes sense for auto-approval. - let max_allowed = (3 * req.expected_max_txns as u128) - .checked_mul((req.get_expected_gas_per_txn() * req.gas_price).into()) + let max_allowed = (3 * coins_per_account as u128) + .checked_mul(num_accounts as u128) .unwrap(); assert!(coins_for_source as u128 <= max_allowed, "Overhead too large to consume funds without approval - estimated total coins needed for load test ({}) are larger than expected_max_txns * expected_gas_per_txn, multiplied by 3 to account for rounding up and overheads ({})", @@ -303,7 +280,7 @@ impl<'t> AccountMinter<'t> { ); } - let new_source_account = if !req.coordination_delay_between_instances.is_zero() { + let new_source_account = if create_secondary_source_account { Some( self.create_new_source_account(txn_executor, coins_for_source) .await?, @@ -410,20 +387,18 @@ impl<'t> AccountMinter<'t> { "Creating and funding seeds accounts (txn {} gas price)", self.txn_factory.get_gas_unit_price() ); - let mut i = 0; - let mut seed_accounts = vec![]; let source_account = match new_source_account { None => self.source_account.get_root_account().clone(), Some(param_account) => Arc::new(param_account), }; - while i < seed_account_num { - let batch_size = min(max_submit_batch_size, seed_account_num - i); - let mut rng = StdRng::from_rng(self.rng()).unwrap(); - let mut batch = account_generator - .gen_local_accounts(txn_executor, batch_size, &mut rng) - .await?; + + let seed_accounts = account_generator + .gen_local_accounts(txn_executor, seed_account_num, self.account_rng()) + .await?; + + for chunk in seed_accounts.chunks(max_submit_batch_size) { let txn_factory = &self.txn_factory; - let create_requests: Vec<_> = batch + let create_requests: Vec<_> = chunk .iter() .map(|account| { create_and_fund_account_request( @@ -437,9 +412,6 @@ impl<'t> AccountMinter<'t> { txn_executor .execute_transactions_with_counter(&create_requests, counters) .await?; - - i += batch_size; - seed_accounts.append(&mut batch); } Ok(seed_accounts) @@ -483,7 +455,7 @@ impl<'t> AccountMinter<'t> { root_account.set_sequence_number(new_sequence_number); } - let new_source_account = LocalAccount::generate(self.rng()); + let new_source_account = LocalAccount::generate(self.account_rng()); let txn = create_and_fund_account_request( root_account.clone(), coins_for_source, @@ -516,8 +488,8 @@ impl<'t> AccountMinter<'t> { bail!("Couldn't create new source account"); } - pub fn rng(&mut self) -> &mut StdRng { - &mut self.rng + pub fn account_rng(&mut self) -> &mut StdRng { + &mut self.account_rng } } @@ -593,3 +565,151 @@ pub fn prompt_yes(prompt: &str) -> bool { } result.unwrap() } + +pub struct BulkAccountCreationConfig { + max_submit_batch_size: usize, + skip_funding_accounts: bool, + seed: Option<[u8; 32]>, + mint_to_root: bool, + prompt_before_spending: bool, + create_secondary_source_account: bool, + expected_gas_per_transfer: u64, + expected_gas_per_account_create: u64, +} + +impl BulkAccountCreationConfig { + pub fn new( + max_submit_batch_size: usize, + skip_funding_accounts: bool, + seed: Option<&str>, + mint_to_root: bool, + prompt_before_spending: bool, + create_secondary_source_account: bool, + expected_gas_per_transfer: u64, + expected_gas_per_account_create: u64, + ) -> Self { + Self { + max_submit_batch_size, + skip_funding_accounts, + seed: seed.map(parse_seed), + mint_to_root, + prompt_before_spending, + create_secondary_source_account, + expected_gas_per_transfer, + expected_gas_per_account_create, + } + } +} + +impl From<&EmitJobRequest> for BulkAccountCreationConfig { + fn from(req: &EmitJobRequest) -> Self { + Self { + max_submit_batch_size: DEFAULT_MAX_SUBMIT_TRANSACTION_BATCH_SIZE, + skip_funding_accounts: req.skip_funding_accounts, + seed: req.account_minter_seed, + mint_to_root: req.mint_to_root, + prompt_before_spending: req.prompt_before_spending, + create_secondary_source_account: !req.coordination_delay_between_instances.is_zero(), + expected_gas_per_transfer: req.get_expected_gas_per_transfer(), + expected_gas_per_account_create: req.get_expected_gas_per_account_create(), + } + } +} + +pub async fn bulk_create_accounts( + coin_source_account: Arc, + txn_executor: &RestApiReliableTransactionSubmitter, + txn_factory: &TransactionFactory, + account_generator: Box, + config: BulkAccountCreationConfig, + num_accounts: usize, + coins_per_account: u64, +) -> Result> { + let source_account_manager = SourceAccountManager { + source_account: coin_source_account, + txn_executor, + mint_to_root: config.mint_to_root, + prompt_before_spending: config.prompt_before_spending, + txn_factory: txn_factory.clone(), + }; + + let seed = config.seed.unwrap_or_else(|| { + let mut rng = StdRng::from_entropy(); + rng.gen() + }); + info!( + "AccountMinter Seed (reuse accounts by passing into --account-minter-seed): {:?}", + seed + ); + + let accounts = account_generator + .gen_local_accounts(txn_executor, num_accounts, &mut StdRng::from_seed(seed)) + .await?; + info!( + "Generated and fetched re-usable accounts for seed {:?}", + seed + ); + + let all_accounts_already_exist = accounts.iter().all(|account| account.sequence_number() > 0); + let send_money_gas = if all_accounts_already_exist { + config.expected_gas_per_transfer + } else { + config.expected_gas_per_account_create + }; + + let mut account_minter = AccountMinter::new( + &source_account_manager, + txn_factory.clone().with_max_gas_amount(send_money_gas), + // Wrap seed once, to not have conflicts between worker and seed accounts. + // We also don't want to continue from the same rng, as number of accounts will affect + // seed accounts. + StdRng::from_seed(StdRng::from_seed(seed).gen()), + ); + + if !config.skip_funding_accounts { + let accounts: Vec<_> = accounts.into_iter().map(Arc::new).collect(); + account_minter + .create_and_fund_accounts( + txn_executor, + account_generator, + accounts.clone(), + coins_per_account, + config.max_submit_batch_size, + config.mint_to_root, + config.create_secondary_source_account, + ) + .await?; + let accounts: Vec<_> = accounts + .into_iter() + .map(|a| Arc::try_unwrap(a).unwrap()) + .collect(); + info!("Accounts created and funded"); + Ok(accounts) + } else { + info!( + "Account reuse plan created for {} accounts and min balance {}", + accounts.len(), + coins_per_account, + ); + + let balance_futures = accounts + .iter() + .map(|account| txn_executor.get_account_balance(account.address())); + let balances: Vec<_> = try_join_all(balance_futures).await?; + accounts + .iter() + .zip(balances) + .for_each(|(account, balance)| { + assert!( + balance >= coins_per_account, + "Account {} has balance {} < needed_min_balance {}", + account.address(), + balance, + coins_per_account + ); + }); + + info!("Skipping funding accounts"); + Ok(accounts) + } +} diff --git a/crates/transaction-emitter-lib/src/emitter/mod.rs b/crates/transaction-emitter-lib/src/emitter/mod.rs index 4b31491d81b42..06302259b27c8 100644 --- a/crates/transaction-emitter-lib/src/emitter/mod.rs +++ b/crates/transaction-emitter-lib/src/emitter/mod.rs @@ -8,8 +8,8 @@ pub mod submission_worker; pub mod transaction_executor; use crate::emitter::{ - account_minter::{AccountMinter, SourceAccountManager}, - local_account_generator::{create_account_generator, LocalAccountGenerator}, + account_minter::{bulk_create_accounts, SourceAccountManager}, + local_account_generator::create_account_generator, stats::{DynamicStatsTracking, TxnStats}, submission_worker::SubmissionWorker, transaction_executor::RestApiReliableTransactionSubmitter, @@ -22,11 +22,12 @@ use aptos_rest_client::{aptos_api_types::AptosErrorCode, error::RestError, Clien use aptos_sdk::{ move_types::account_address::AccountAddress, transaction_builder::{aptos_stdlib, TransactionFactory}, - types::{transaction::SignedTransaction, LocalAccount}, + types::{transaction::SignedTransaction, AccountKey, LocalAccount}, }; use aptos_transaction_generator_lib::{ - create_txn_generator_creator, AccountType, ReliableTransactionSubmitter, TransactionType, + create_txn_generator_creator, AccountType, TransactionType, SEND_AMOUNT, }; +use aptos_types::account_config::aptos_test_root_address; use futures::future::{try_join_all, FutureExt}; use once_cell::sync::Lazy; use rand::{rngs::StdRng, seq::IteratorRandom, Rng}; @@ -46,11 +47,11 @@ use tokio::{runtime::Handle, task::JoinHandle, time}; // Max is 100k TPS for 3 hours const MAX_TXNS: u64 = 1_000_000_000; -const MAX_RETRIES: usize = 12; - // TODO Transfer cost increases during Coin => FA migration, we can reduce back later. -const EXPECTED_GAS_PER_TRANSFER: u64 = 10; -const EXPECTED_GAS_PER_ACCOUNT_CREATE: u64 = 2000 + 8; +pub const EXPECTED_GAS_PER_TRANSFER: u64 = 10; +pub const EXPECTED_GAS_PER_ACCOUNT_CREATE: u64 = 2000 + 8; + +const MAX_RETRIES: usize = 12; // This retry policy is used for important client calls necessary for setting // up the test (e.g. account creation) and collecting its results (e.g. checking @@ -148,6 +149,7 @@ pub struct EmitJobRequest { transaction_mix_per_phase: Vec>, account_type: AccountType, + max_gas_per_txn: u64, init_max_gas_per_txn: Option, @@ -163,7 +165,7 @@ pub struct EmitJobRequest { init_gas_price_multiplier: u64, mint_to_root: bool, - skip_minting_accounts: bool, + skip_funding_accounts: bool, txn_expiration_time_secs: u64, init_expiration_multiplier: f64, @@ -193,7 +195,7 @@ impl Default for EmitJobRequest { init_max_gas_per_txn: None, init_gas_price_multiplier: 2, mint_to_root: false, - skip_minting_accounts: false, + skip_funding_accounts: false, txn_expiration_time_secs: 60, init_expiration_multiplier: 3.0, init_retry_interval: Duration::from_secs(10), @@ -336,8 +338,8 @@ impl EmitJobRequest { self } - pub fn skip_minting_accounts(mut self) -> Self { - self.skip_minting_accounts = true; + pub fn skip_funding_accounts(mut self) -> Self { + self.skip_funding_accounts = true; self } @@ -464,7 +466,7 @@ impl EmitJobRequest { }; info!( - " Transaction emitter targeting {} TPS, expecting {} TPS", + " Transaction emitter targetting {} TPS, expecting {} TPS", tps, num_accounts * transactions_per_account / wait_seconds as usize ); @@ -703,20 +705,21 @@ impl TxnEmitter { .with_transaction_expiration_time(init_expiration_time); let init_retries: usize = usize::try_from(init_expiration_time / req.init_retry_interval.as_secs()).unwrap(); - let seed = req.account_minter_seed.unwrap_or_else(|| self.rng.gen()); let account_generator = create_account_generator(req.account_type); - let mut all_accounts = create_accounts( + let mut all_accounts = bulk_create_accounts( root_account.clone(), + &RestApiReliableTransactionSubmitter::new( + req.rest_clients.clone(), + init_retries, + req.init_retry_interval, + ), &init_txn_factory, account_generator, - &req, - mode_params.max_submit_batch_size, - req.skip_minting_accounts, - seed, + (&req).into(), num_accounts, - init_retries, + get_needed_balance_per_account_from_req(&req, num_accounts), ) .await?; @@ -724,16 +727,17 @@ impl TxnEmitter { let stats = Arc::new(DynamicStatsTracking::new(stats_tracking_phases)); let tokio_handle = Handle::current(); - let txn_executor = RestApiReliableTransactionSubmitter { - rest_clients: req.rest_clients.clone(), - max_retries: init_retries, - retry_after: req.init_retry_interval, - }; + let txn_executor = RestApiReliableTransactionSubmitter::new( + req.rest_clients.clone(), + init_retries, + req.init_retry_interval, + ); let source_account_manager = SourceAccountManager { source_account: root_account.clone(), txn_executor: &txn_executor, - req: &req, txn_factory: init_txn_factory.clone(), + mint_to_root: req.mint_to_root, + prompt_before_spending: req.prompt_before_spending, }; let (txn_generator_creator, _, _) = create_txn_generator_creator( &req.transaction_mix_per_phase, @@ -994,8 +998,9 @@ pub async fn query_sequence_numbers<'a, I>( where I: Iterator, { - let futures = addresses - .map(|address| RETRY_POLICY.retry(move || get_account_if_exists(client, *address))); + let futures = addresses.map(|address| { + RETRY_POLICY.retry(move || get_account_address_and_seq_num(client, *address)) + }); let (seq_nums, timestamps): (Vec<_>, Vec<_>) = try_join_all(futures) .await @@ -1008,14 +1013,23 @@ where Ok((seq_nums, timestamps.into_iter().min().unwrap())) } -async fn get_account_if_exists( +async fn get_account_address_and_seq_num( client: &RestClient, address: AccountAddress, ) -> Result<((AccountAddress, u64), u64)> { + get_account_seq_num(client, address) + .await + .map(|(seq_num, ts)| ((address, seq_num), ts)) +} + +pub async fn get_account_seq_num( + client: &RestClient, + address: AccountAddress, +) -> Result<(u64, u64)> { let result = client.get_account_bcs(address).await; match &result { Ok(resp) => Ok(( - (address, resp.inner().sequence_number()), + resp.inner().sequence_number(), Duration::from_micros(resp.state().timestamp_usecs).as_secs(), )), Err(e) => { @@ -1023,7 +1037,7 @@ async fn get_account_if_exists( if let RestError::Api(api_error) = e { if let AptosErrorCode::AccountNotFound = api_error.error.error_code { return Ok(( - (address, 0), + 0, Duration::from_micros(api_error.state.as_ref().unwrap().timestamp_usecs) .as_secs(), )); @@ -1035,6 +1049,28 @@ async fn get_account_if_exists( } } +pub async fn load_specific_account( + account_key: AccountKey, + is_root: bool, + client: &RestClient, +) -> Result { + let address = if is_root { + aptos_test_root_address() + } else { + account_key.authentication_key().account_address() + }; + + let sequence_number = query_sequence_number(client, address).await.map_err(|e| { + format_err!( + "query_sequence_number on {:?} for account {} failed: {:?}", + client, + address, + e + ) + })?; + Ok(LocalAccount::new(address, account_key, sequence_number)) +} + pub fn gen_transfer_txn_request( sender: &mut LocalAccount, receiver: &AccountAddress, @@ -1060,103 +1096,50 @@ pub fn parse_seed(seed_string: &str) -> [u8; 32] { .expect("failed to convert to array") } -pub async fn create_accounts( - root_account: Arc, - txn_factory: &TransactionFactory, - account_generator: Box, - req: &EmitJobRequest, - max_submit_batch_size: usize, - skip_minting_accounts: bool, - seed: [u8; 32], +pub fn get_needed_balance_per_account( + num_workload_transactions: u64, + gas_per_workload_transaction: u64, + octas_per_workload_transaction: u64, num_accounts: usize, - retries: usize, -) -> Result> { - info!( - "Using reliable/retriable init transaction executor with {} retries, every {}s", - retries, - req.init_retry_interval.as_secs_f32() - ); + gas_price: u64, + max_gas_per_txn: u64, +) -> u64 { + // round up: + let txnx_per_account = + (num_workload_transactions + num_accounts as u64 - 1) / num_accounts as u64; + let coins_per_account = txnx_per_account + .checked_mul(octas_per_workload_transaction + gas_per_workload_transaction * gas_price) + .unwrap() + .checked_add(max_gas_per_txn * gas_price) + .unwrap(); info!( - "AccountMinter Seed (reuse accounts by passing into --account-minter-seed): {:?}", - seed - ); - let txn_executor = RestApiReliableTransactionSubmitter { - rest_clients: req.rest_clients.clone(), - max_retries: retries, - retry_after: req.init_retry_interval, - }; - let source_account_manager = SourceAccountManager { - source_account: root_account, - txn_executor: &txn_executor, - req, - txn_factory: txn_factory.clone(), - }; - - let mut rng = StdRng::from_seed(seed); - - let accounts = account_generator - .gen_local_accounts(&txn_executor, num_accounts, &mut rng) - .await?; - - info!("Generated re-usable accounts for seed {:?}", seed); - - let all_accounts_already_exist = accounts.iter().all(|account| account.sequence_number() > 0); - let send_money_gas = if all_accounts_already_exist { - req.get_expected_gas_per_transfer() - } else { - req.get_expected_gas_per_account_create() - }; - - let mut account_minter = AccountMinter::new( - &source_account_manager, - txn_factory.clone().with_max_gas_amount(send_money_gas), - StdRng::from_seed(seed), + "Needed {} balance for each account because of expecting {} txns per account with {} gas and {} octas, with leaving {} gas for max_txn_gas, all at {} gas price", + coins_per_account, + txnx_per_account, + gas_per_workload_transaction, + octas_per_workload_transaction, + max_gas_per_txn, + gas_price, ); + coins_per_account +} - if !skip_minting_accounts { - let accounts: Vec<_> = accounts.into_iter().map(Arc::new).collect(); - account_minter - .create_and_fund_accounts( - &txn_executor, - req, - account_generator, - max_submit_batch_size, - accounts.clone(), - ) - .await?; - let accounts: Vec<_> = accounts - .into_iter() - .map(|a| Arc::try_unwrap(a).unwrap()) - .collect(); - info!("Accounts created and funded"); - Ok(accounts) - } else { +pub fn get_needed_balance_per_account_from_req(req: &EmitJobRequest, num_accounts: usize) -> u64 { + if let Some(val) = req.coins_per_account_override { info!( - "Account reuse plan created for {} accounts and {} txns:", - accounts.len(), - req.expected_max_txns, + "Needed {} balance for each account because of override", + val ); - - let needed_min_balance = account_minter.get_needed_balance_per_account(req, accounts.len()); - let balance_futures = accounts - .iter() - .map(|account| txn_executor.get_account_balance(account.address())); - let balances: Vec<_> = try_join_all(balance_futures).await?; - accounts - .iter() - .zip(balances) - .for_each(|(account, balance)| { - assert!( - balance >= needed_min_balance, - "Account {} has balance {} < needed_min_balance {}", - account.address(), - balance, - needed_min_balance - ); - }); - - info!("Skipping minting accounts"); - Ok(accounts) + val + } else { + get_needed_balance_per_account( + req.expected_max_txns, + req.get_expected_gas_per_txn(), + SEND_AMOUNT, + num_accounts, + req.gas_price, + req.max_gas_per_txn, + ) } } diff --git a/crates/transaction-emitter-lib/src/emitter/transaction_executor.rs b/crates/transaction-emitter-lib/src/emitter/transaction_executor.rs index 9db067bbe6e0f..4a2cae20c5f13 100644 --- a/crates/transaction-emitter-lib/src/emitter/transaction_executor.rs +++ b/crates/transaction-emitter-lib/src/emitter/transaction_executor.rs @@ -3,7 +3,7 @@ use super::RETRY_POLICY; use anyhow::{Context, Result}; -use aptos_logger::{debug, sample, sample::SampleRate, warn}; +use aptos_logger::{debug, info, sample, sample::SampleRate, warn}; use aptos_rest_client::{aptos_api_types::AptosErrorCode, error::RestError, Client as RestClient}; use aptos_sdk::{ move_types::account_address::AccountAddress, types::transaction::SignedTransaction, @@ -19,12 +19,25 @@ use std::{ // Reliable/retrying transaction executor, used for initializing pub struct RestApiReliableTransactionSubmitter { - pub rest_clients: Vec, - pub max_retries: usize, - pub retry_after: Duration, + rest_clients: Vec, + max_retries: usize, + retry_after: Duration, } impl RestApiReliableTransactionSubmitter { + pub fn new(rest_clients: Vec, max_retries: usize, retry_after: Duration) -> Self { + info!( + "Using reliable/retriable init transaction executor with {} retries, every {}s", + max_retries, + retry_after.as_secs_f32() + ); + Self { + rest_clients, + max_retries, + retry_after, + } + } + fn random_rest_client(&self) -> &RestClient { let mut rng = thread_rng(); self.rest_clients.choose(&mut rng).unwrap() diff --git a/crates/transaction-emitter-lib/src/wrappers.rs b/crates/transaction-emitter-lib/src/wrappers.rs index 813c4a1ae07dc..4314371229bbb 100644 --- a/crates/transaction-emitter-lib/src/wrappers.rs +++ b/crates/transaction-emitter-lib/src/wrappers.rs @@ -5,18 +5,19 @@ use crate::{ args::{ClusterArgs, EmitArgs}, cluster::Cluster, emitter::{ - create_accounts, local_account_generator::PrivateKeyAccountGenerator, parse_seed, - stats::TxnStats, EmitJobMode, EmitJobRequest, NumAccountsMode, TxnEmitter, + account_minter::bulk_create_accounts, get_needed_balance_per_account_from_req, + local_account_generator::PrivateKeyAccountGenerator, stats::TxnStats, + transaction_executor::RestApiReliableTransactionSubmitter, EmitJobMode, EmitJobRequest, + NumAccountsMode, TxnEmitter, }, instance::Instance, CreateAccountsArgs, }; use anyhow::{bail, Context, Result}; -use aptos_config::config::DEFAULT_MAX_SUBMIT_TRANSACTION_BATCH_SIZE; use aptos_logger::{error, info}; use aptos_sdk::transaction_builder::TransactionFactory; use aptos_transaction_generator_lib::{args::TransactionTypeArg, WorkflowProgress}; -use rand::{rngs::StdRng, Rng, SeedableRng}; +use rand::{rngs::StdRng, SeedableRng}; use std::{ sync::Arc, time::{Duration, Instant}, @@ -156,8 +157,8 @@ pub async fn emit_transactions_with_cluster( .latency_polling_interval(Duration::from_secs_f32(latency_polling_interval_s)); } - if args.skip_minting_accounts { - emit_job_request = emit_job_request.skip_minting_accounts(); + if args.skip_funding_accounts { + emit_job_request = emit_job_request.skip_funding_accounts(); } let coin_source_account = std::sync::Arc::new(coin_source_account); @@ -185,30 +186,32 @@ pub async fn create_accounts_command( let txn_factory = TransactionFactory::new(cluster.chain_id) .with_transaction_expiration_time(60) .with_max_gas_amount(create_accounts_args.max_gas_per_txn); - let emit_job_request = - EmitJobRequest::new(cluster.all_instances().map(Instance::rest_client).collect()) - .init_gas_price_multiplier(1) - .expected_gas_per_txn(create_accounts_args.max_gas_per_txn) - .max_gas_per_txn(create_accounts_args.max_gas_per_txn) - .coins_per_account_override(0) - .expected_max_txns(0) - .prompt_before_spending(); - let seed = match &create_accounts_args.account_minter_seed { - Some(str) => parse_seed(str), - None => StdRng::from_entropy().gen(), - }; - - create_accounts( + let rest_clients = cluster + .all_instances() + .map(Instance::rest_client) + .collect::>(); + let mut emit_job_request = EmitJobRequest::new(rest_clients.clone()) + .init_gas_price_multiplier(1) + .expected_gas_per_txn(create_accounts_args.max_gas_per_txn) + .max_gas_per_txn(create_accounts_args.max_gas_per_txn) + .coins_per_account_override(0) + .expected_max_txns(0) + .prompt_before_spending(); + + if let Some(seed) = &create_accounts_args.account_minter_seed { + emit_job_request = emit_job_request.account_minter_seed(seed); + } + + bulk_create_accounts( coin_source_account, + &RestApiReliableTransactionSubmitter::new(rest_clients, 6, Duration::from_secs(10)), &txn_factory, Box::new(PrivateKeyAccountGenerator), - &emit_job_request, - DEFAULT_MAX_SUBMIT_TRANSACTION_BATCH_SIZE, - false, - seed, + (&emit_job_request).into(), create_accounts_args.count, - 4, + get_needed_balance_per_account_from_req(&emit_job_request, create_accounts_args.count), ) .await?; + Ok(()) } diff --git a/sdk/src/transaction_builder.rs b/sdk/src/transaction_builder.rs index bb28f20c1c95e..9f5e958fef9b8 100644 --- a/sdk/src/transaction_builder.rs +++ b/sdk/src/transaction_builder.rs @@ -137,6 +137,10 @@ impl TransactionFactory { self.transaction_expiration_time } + pub fn get_chain_id(&self) -> ChainId { + self.chain_id + } + pub fn payload(&self, payload: TransactionPayload) -> TransactionBuilder { self.transaction_builder(payload) }