Skip to content

Commit

Permalink
Add aptos move run-repeadetly command to submit same transaction mu…
Browse files Browse the repository at this point in the history
…ltiple times
  • Loading branch information
igor-aptos committed May 22, 2024
1 parent e025332 commit c9c3119
Show file tree
Hide file tree
Showing 5 changed files with 202 additions and 30 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/aptos/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ aptos-cached-packages = { workspace = true }
aptos-cli-common = { workspace = true }
aptos-config = { workspace = true }
aptos-crypto = { workspace = true }
aptos-experimental-bulk-txn-submit = { workspace = true }
aptos-faucet-core = { workspace = true }
aptos-framework = { workspace = true }
aptos-gas-profiling = { workspace = true }
Expand Down
93 changes: 63 additions & 30 deletions crates/aptos/src/common/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1526,11 +1526,11 @@ pub struct TransactionOptions {

impl TransactionOptions {
/// Builds a rest client
fn rest_client(&self) -> CliTypedResult<Client> {
pub(crate) fn rest_client(&self) -> CliTypedResult<Client> {
self.rest_options.client(&self.profile_options)
}

pub fn get_transaction_account_type(&self) -> CliTypedResult<AccountType> {
pub(crate) fn get_transaction_account_type(&self) -> CliTypedResult<AccountType> {
if self.private_key_options.private_key.is_some()
|| self.private_key_options.private_key_file.is_some()
{
Expand Down Expand Up @@ -1602,14 +1602,35 @@ impl TransactionOptions {
.into_inner())
}

/// Submit a transaction
pub async fn submit_transaction(
pub(crate) fn get_now_timestamp_checked(
&self,
payload: TransactionPayload,
) -> CliTypedResult<Transaction> {
let client = self.rest_client()?;
let (sender_public_key, sender_address) = self.get_public_key_and_address()?;
onchain_timestamp_usecs: u64,
) -> CliTypedResult<u64> {
// Retrieve local time, and ensure it's within an expected skew of the blockchain
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map_err(|err| CliError::UnexpectedError(err.to_string()))?
.as_secs();
let now_usecs = now * US_IN_SECS;

// Warn local user that clock is skewed behind the blockchain.
// There will always be a little lag from real time to blockchain time
if now_usecs < onchain_timestamp_usecs - ACCEPTED_CLOCK_SKEW_US {
eprintln!("Local clock is is skewed from blockchain clock. Clock is more than {} seconds behind the blockchain {}", ACCEPTED_CLOCK_SKEW_US, onchain_timestamp_usecs / US_IN_SECS );
}
Ok(now)
}

pub(crate) async fn compute_gas_price_and_max_gas(
&self,
payload: &TransactionPayload,
client: &Client,
sender_address: &AccountAddress,
sender_public_key: &Ed25519PublicKey,
sequence_number: u64,
chain_id: ChainId,
expiration_time_secs: u64,
) -> CliTypedResult<(u64, u64)> {
// Ask to confirm price if the gas unit price is estimated above the lowest value when
// it is automatically estimated
let ask_to_confirm_price;
Expand All @@ -1623,27 +1644,6 @@ impl TransactionOptions {
gas_unit_price
};

// Get sequence number for account
let (account, state) = get_account_with_state(&client, sender_address).await?;
let sequence_number = account.sequence_number;

// Retrieve local time, and ensure it's within an expected skew of the blockchain
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map_err(|err| CliError::UnexpectedError(err.to_string()))?
.as_secs();
let now_usecs = now * US_IN_SECS;

// Warn local user that clock is skewed behind the blockchain.
// There will always be a little lag from real time to blockchain time
if now_usecs < state.timestamp_usecs - ACCEPTED_CLOCK_SKEW_US {
eprintln!("Local clock is is skewed from blockchain clock. Clock is more than {} seconds behind the blockchain {}", ACCEPTED_CLOCK_SKEW_US, state.timestamp_usecs / US_IN_SECS );
}
let expiration_time_secs = now + self.gas_options.expiration_secs;

let chain_id = ChainId::new(state.chain_id);
// TODO: Check auth key against current private key and provide a better message

let max_gas = if let Some(max_gas) = self.gas_options.max_gas {
// If the gas unit price was estimated ask, but otherwise you've chosen hwo much you want to spend
if ask_to_confirm_price {
Expand All @@ -1657,7 +1657,7 @@ impl TransactionOptions {

let unsigned_transaction = transaction_factory
.payload(payload.clone())
.sender(sender_address)
.sender(*sender_address)
.sequence_number(sequence_number)
.expiration_timestamp_secs(expiration_time_secs)
.build();
Expand Down Expand Up @@ -1698,6 +1698,39 @@ impl TransactionOptions {
adjusted_max_gas
};

Ok((gas_unit_price, max_gas))
}

/// Submit a transaction
pub async fn submit_transaction(
&self,
payload: TransactionPayload,
) -> CliTypedResult<Transaction> {
let client = self.rest_client()?;
let (sender_public_key, sender_address) = self.get_public_key_and_address()?;

// Get sequence number for account
let (account, state) = get_account_with_state(&client, sender_address).await?;
let sequence_number = account.sequence_number;

let now = self.get_now_timestamp_checked(state.timestamp_usecs)?;
let expiration_time_secs = now + self.gas_options.expiration_secs;

let chain_id = ChainId::new(state.chain_id);
// TODO: Check auth key against current private key and provide a better message

let (gas_unit_price, max_gas) = self
.compute_gas_price_and_max_gas(
&payload,
&client,
&sender_address,
&sender_public_key,
sequence_number,
chain_id,
expiration_time_secs,
)
.await?;

// Sign and submit transaction
let transaction_factory = TransactionFactory::new(chain_id)
.with_gas_unit_price(gas_unit_price)
Expand Down
41 changes: 41 additions & 0 deletions crates/aptos/src/move_tool/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright © Aptos Foundation
// SPDX-License-Identifier: Apache-2.0

use self::submit_repeatedly::submit_repeatedly;
use crate::{
account::derive_resource_account::ResourceAccountSeed,
common::{
Expand Down Expand Up @@ -72,6 +73,7 @@ mod manifest;
pub mod package_hooks;
mod show;
pub mod stored_package;
mod submit_repeatedly;

/// Tool for Move related operations
///
Expand Down Expand Up @@ -99,6 +101,7 @@ pub enum MoveTool {
Publish(PublishPackage),
Run(RunFunction),
RunScript(RunScript),
RunRepeatedly(RunFunctionRepeatedly),
#[clap(subcommand, hide = true)]
Show(show::ShowTool),
Test(TestPackage),
Expand Down Expand Up @@ -131,6 +134,7 @@ impl MoveTool {
MoveTool::Prove(tool) => tool.execute_serialized().await,
MoveTool::Publish(tool) => tool.execute_serialized().await,
MoveTool::Run(tool) => tool.execute_serialized().await,
MoveTool::RunRepeatedly(tool) => tool.execute_serialized().await,
MoveTool::RunScript(tool) => tool.execute_serialized().await,
MoveTool::Show(tool) => tool.execute_serialized().await,
MoveTool::Test(tool) => tool.execute_serialized().await,
Expand Down Expand Up @@ -1446,6 +1450,43 @@ impl CliCommand<TransactionSummary> for RunFunction {
}
}

/// Run a Move function
#[derive(Parser)]
pub struct RunFunctionRepeatedly {
#[clap(flatten)]
pub(crate) entry_function_args: EntryFunctionArguments,
#[clap(flatten)]
pub(crate) txn_options: TransactionOptions,

#[clap(long)]
num_times: usize,

#[clap(long, default_value = "10")]
single_request_api_batch_size: usize,

#[clap(long, default_value = "10")]
parallel_requests_outstanding: usize,
}

#[async_trait]
impl CliCommand<String> for RunFunctionRepeatedly {
fn command_name(&self) -> &'static str {
"RunFunctionRepeatedly"
}

async fn execute(self) -> CliTypedResult<String> {
submit_repeatedly(
&self.txn_options,
TransactionPayload::EntryFunction(self.entry_function_args.try_into()?),
self.num_times,
self.single_request_api_batch_size,
self.parallel_requests_outstanding,
)
.await
.map(|v| format!("Committed {} txns", v))
}
}

/// Run a view function
#[derive(Parser)]
pub struct ViewFunction {
Expand Down
96 changes: 96 additions & 0 deletions crates/aptos/src/move_tool/submit_repeatedly.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// Copyright (c) Aptos Foundation
// SPDX-License-Identifier: Apache-2.0

use crate::common::{
types::{AccountType, CliError, CliTypedResult, TransactionOptions},
utils::{get_account_with_state, prompt_yes_with_override},
};
use aptos_experimental_bulk_txn_submit::{
coordinator::execute_txn_list, workloads::FixedPayloadSignedTransactionBuilder,
};
use aptos_sdk::{transaction_builder::TransactionFactory, types::LocalAccount};
use aptos_types::{chain_id::ChainId, transaction::TransactionPayload};
use std::time::Duration;

/// For transaction payload and options, either get gas profile or submit for execution.
pub async fn submit_repeatedly(
txn_options_ref: &TransactionOptions,
payload: TransactionPayload,
num_times: usize,
single_request_api_batch_size: usize,
parallel_requests_outstanding: usize,
) -> CliTypedResult<usize> {
if txn_options_ref.profile_gas || txn_options_ref.benchmark || txn_options_ref.local {
return Err(CliError::UnexpectedError(
"Cannot perform profiling, benchmarking or local execution for submit repeatedly."
.to_string(),
));
}

let client = txn_options_ref.rest_client()?;
let (sender_public_key, sender_address) = txn_options_ref.get_public_key_and_address()?;

// Get sequence number for account
let (account, state) = get_account_with_state(&client, sender_address).await?;
let sequence_number = account.sequence_number;

let sender_account = match txn_options_ref.get_transaction_account_type()? {
AccountType::Local => {
let (private_key, _) = txn_options_ref.get_key_and_address()?;
LocalAccount::new(sender_address, private_key, sequence_number)
},
AccountType::HardwareWallet => {
return Err(CliError::UnexpectedError(
"Cannot use hardware wallet to submit repeatedly.".to_string(),
));
},
};

let now = txn_options_ref.get_now_timestamp_checked(state.timestamp_usecs)?;
let expiration_time_secs = now + txn_options_ref.gas_options.expiration_secs;

let chain_id = ChainId::new(state.chain_id);
// TODO: Check auth key against current private key and provide a better message

let (gas_unit_price, max_gas) = txn_options_ref
.compute_gas_price_and_max_gas(
&payload,
&client,
&sender_address,
&sender_public_key,
sequence_number,
chain_id,
expiration_time_secs,
)
.await?;

// Sign and submit transaction
let transaction_factory = TransactionFactory::new(chain_id)
.with_gas_unit_price(gas_unit_price)
.with_max_gas_amount(max_gas)
.with_transaction_expiration_time(txn_options_ref.gas_options.expiration_secs);

prompt_yes_with_override(
&format!(
"About to submit {} transactions and spend up to {} APT. Continue?",
num_times,
num_times as f32 * gas_unit_price as f32 * max_gas as f32 / 1e8
),
txn_options_ref.prompt_options,
)?;

let results = execute_txn_list(
vec![sender_account],
vec![client],
(0..num_times).map(|_| ()).collect::<Vec<()>>(),
single_request_api_batch_size,
parallel_requests_outstanding,
Duration::from_secs_f32(0.05),
transaction_factory,
FixedPayloadSignedTransactionBuilder::new(payload),
true,
)
.await?;

Ok(results.into_iter().filter(|v| *v == "success").count())
}

0 comments on commit c9c3119

Please sign in to comment.