Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add aptos move run-repeadetly command to submit multiple transactions #13387

Draft
wants to merge 4 commits into
base: igor/bulk_txn_submit_script
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions Cargo.lock

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

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ members = [
"execution/executor-service",
"execution/executor-test-helpers",
"execution/executor-types",
"experimental/bulk-txn-submit",
"experimental/execution/ptx-executor",
"experimental/runtimes",
"experimental/storage/layered-map",
Expand Down Expand Up @@ -324,6 +325,7 @@ aptos-enum-conversion-derive = { path = "crates/aptos-enum-conversion-derive" }
aptos-executor-service = { path = "execution/executor-service" }
aptos-executor-test-helpers = { path = "execution/executor-test-helpers" }
aptos-executor-types = { path = "execution/executor-types" }
aptos-experimental-bulk-txn-submit = { path = "experimental/bulk-txn-submit" }
aptos-experimental-layered-map = { path = "experimental/storage/layered-map" }
aptos-experimental-ptx-executor = { path = "experimental/execution/ptx-executor" }
aptos-experimental-runtimes = { path = "experimental/runtimes" }
Expand Down
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())
}
2 changes: 1 addition & 1 deletion crates/transaction-emitter-lib/src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<f32>,
Expand Down
Loading
Loading