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

[cli] Use simulation errors from API #5526

Merged
merged 1 commit into from
Dec 8, 2022
Merged
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
47 changes: 28 additions & 19 deletions crates/aptos-rest-client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ use anyhow::{anyhow, Result};
use aptos_api_types::{
deserialize_from_string,
mime_types::{BCS, BCS_SIGNED_TRANSACTION as BCS_CONTENT_TYPE},
AptosError, BcsBlock, Block, Bytecode, ExplainVMStatus, GasEstimation, HexEncodedBytes,
IndexResponse, MoveModuleId, TransactionData, TransactionOnChainData,
TransactionsBatchSubmissionResult, UserTransaction, VersionedEvent,
AptosError, BcsBlock, Block, GasEstimation, HexEncodedBytes, IndexResponse, MoveModuleId,
TransactionData, TransactionOnChainData, TransactionsBatchSubmissionResult, UserTransaction,
VersionedEvent,
};
use aptos_crypto::HashValue;
use aptos_logger::{debug, info, sample, sample::SampleRate};
Expand All @@ -36,16 +36,13 @@ use aptos_types::{
contract_event::EventWithVersion,
transaction::SignedTransaction,
};
use futures::executor::block_on;
use move_binary_format::CompiledModule;
use move_core_types::language_storage::{ModuleId, StructTag};
use move_core_types::language_storage::StructTag;
use reqwest::header::ACCEPT;
use reqwest::{header::CONTENT_TYPE, Client as ReqwestClient, StatusCode};
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use serde_json::{json, Value};
use std::collections::BTreeMap;
use std::future::Future;
use std::rc::Rc;
use std::time::Duration;
use tokio::time::Instant;
use url::Url;
Expand Down Expand Up @@ -326,6 +323,30 @@ impl Client {
self.json(response).await
}

pub async fn simulate_with_gas_estimation(
&self,
txn: &SignedTransaction,
estimate_max_gas_amount: bool,
estimate_max_gas_unit_price: bool,
) -> AptosResult<Response<Vec<UserTransaction>>> {
let txn_payload = bcs::to_bytes(txn)?;

let url = self.build_path(&format!(
"transactions/simulate?estimate_max_gas_amount={}&estimate_gas_unit_price={}",
estimate_max_gas_amount, estimate_max_gas_unit_price
))?;

let response = self
.inner
.post(url)
.header(CONTENT_TYPE, BCS_CONTENT_TYPE)
.body(txn_payload)
.send()
.await?;

self.json(response).await
}

pub async fn simulate_bcs(
&self,
txn: &SignedTransaction,
Expand Down Expand Up @@ -1550,15 +1571,3 @@ enum WaitForTransactionResult<T> {
Pending(State),
Success(Response<T>),
}

impl ExplainVMStatus for Client {
// TODO: Add some caching
fn get_module_bytecode(&self, module_id: &ModuleId) -> Result<Rc<dyn Bytecode>> {
let bytes =
block_on(self.get_account_module_bcs(*module_id.address(), module_id.name().as_str()))?
.into_inner();

let compiled_module = CompiledModule::deserialize(bytes.as_ref())?;
Ok(Rc::new(compiled_module) as Rc<dyn Bytecode>)
}
}
95 changes: 13 additions & 82 deletions crates/aptos/src/common/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use aptos_crypto::{
};
use aptos_global_constants::adjust_gas_headroom;
use aptos_keygen::KeyGen;
use aptos_rest_client::aptos_api_types::{ExplainVMStatus, HashValue, UserTransaction};
use aptos_rest_client::aptos_api_types::HashValue;
use aptos_rest_client::error::RestError;
use aptos_rest_client::{Client, Transaction};
use aptos_sdk::{transaction_builder::TransactionFactory, types::LocalAccount};
Expand All @@ -45,7 +45,6 @@ use std::{
};
use thiserror::Error;

const MAX_POSSIBLE_GAS_UNITS: u64 = 1_000_000;
pub const DEFAULT_PROFILE: &str = "default";

/// A common result to be returned to users
Expand Down Expand Up @@ -1341,33 +1340,28 @@ impl TransactionOptions {
sender_key.public_key(),
Ed25519Signature::try_from([0u8; 64].as_ref()).unwrap(),
);
// TODO: Cleanup to use the gas price estimation here
let simulated_txn = client
.simulate_bcs_with_gas_estimation(&signed_transaction, true, false)

let txns = client
.simulate_with_gas_estimation(&signed_transaction, true, false)
.await?
.into_inner();
let simulated_txn = txns.first().unwrap();

// Check if the transaction will pass, if it doesn't then fail
// TODO: Add move resolver so we can explain the VM status with a proper error map
let status = simulated_txn.info.status();
if !status.is_success() {
let status = client.explain_vm_status(status);
return Err(CliError::SimulationError(status));
if !simulated_txn.info.success {
return Err(CliError::SimulationError(
simulated_txn.info.vm_status.clone(),
));
}

// Take the gas used and use a headroom factor on it
let adjusted_max_gas = adjust_gas_headroom(
simulated_txn.info.gas_used(),
simulated_txn
.transaction
.as_signed_user_txn()
.expect("Should be signed user transaction")
.max_gas_amount(),
);
let gas_used = simulated_txn.info.gas_used.0;
let adjusted_max_gas =
adjust_gas_headroom(gas_used, simulated_txn.request.max_gas_amount.0);

// Ask if you want to accept the estimate amount
let upper_cost_bound = adjusted_max_gas * gas_unit_price;
let lower_cost_bound = simulated_txn.info.gas_used() * gas_unit_price;
let lower_cost_bound = gas_used * gas_unit_price;
let message = format!(
"Do you want to submit a transaction for a range of [{} - {}] Octas at a gas unit price of {} Octas?",
lower_cost_bound,
Expand All @@ -1392,69 +1386,6 @@ impl TransactionOptions {
Ok(response.into_inner())
}

pub async fn simulate_transaction(
&self,
payload: TransactionPayload,
gas_price: Option<u64>,
amount_transfer: Option<u64>,
) -> CliTypedResult<UserTransaction> {
let client = self.rest_client()?;
let (sender_key, sender_address) = self.get_key_and_address()?;

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

// Estimate gas price if necessary
let gas_price = if let Some(gas_price) = gas_price {
gas_price
} else {
self.estimate_gas_price().await?
};
// Simulate transaction
// To get my known possible max gas, I need to get my current balance
let account_balance = client
.get_account_balance(sender_address)
.await?
.into_inner()
.coin
.value
.0;

let max_possible_gas = if gas_price == 0 {
MAX_POSSIBLE_GAS_UNITS
} else if let Some(amount) = amount_transfer {
std::cmp::min(
account_balance
.saturating_sub(amount)
.saturating_div(gas_price),
MAX_POSSIBLE_GAS_UNITS,
)
} else {
std::cmp::min(
account_balance.saturating_div(gas_price),
MAX_POSSIBLE_GAS_UNITS,
)
};

let transaction_factory = TransactionFactory::new(chain_id(&client).await?)
.with_gas_unit_price(gas_price)
.with_max_gas_amount(max_possible_gas);

let unsigned_transaction = transaction_factory
.payload(payload)
.sender(sender_address)
.sequence_number(sequence_number)
.build();

let signed_transaction = SignedTransaction::new(
unsigned_transaction,
sender_key.public_key(),
Ed25519Signature::try_from([0u8; 64].as_ref()).unwrap(),
);
let txns = client.simulate(&signed_transaction).await?.into_inner();
Ok(txns.first().unwrap().clone())
}

pub async fn estimate_gas_price(&self) -> CliTypedResult<u64> {
let client = self.rest_client()?;
client
Expand Down