diff --git a/crates/aptos-api-tester/src/consts.rs b/crates/aptos-api-tester/src/consts.rs index 1a6def38cc4d5..76fd1a022d169 100644 --- a/crates/aptos-api-tester/src/consts.rs +++ b/crates/aptos-api-tester/src/consts.rs @@ -1,11 +1,20 @@ // Copyright © Aptos Foundation +use crate::utils::NetworkName; use once_cell::sync::Lazy; use std::{env, time::Duration}; use url::Url; // Node and faucet constants +// TODO: consider making this a CLI argument +pub static NETWORK_NAME: Lazy = Lazy::new(|| { + env::var("NETWORK_NAME") + .ok() + .and_then(|s| s.parse().ok()) + .unwrap_or(NetworkName::Devnet) +}); + pub static DEVNET_NODE_URL: Lazy = Lazy::new(|| Url::parse("https://fullnode.devnet.aptoslabs.com").unwrap()); diff --git a/crates/aptos-api-tester/src/main.rs b/crates/aptos-api-tester/src/main.rs index 5565476786576..947bb07e7ef3f 100644 --- a/crates/aptos-api-tester/src/main.rs +++ b/crates/aptos-api-tester/src/main.rs @@ -17,7 +17,7 @@ use crate::utils::{NetworkName, TestName}; use anyhow::Result; use aptos_logger::{info, Level, Logger}; use aptos_push_metrics::MetricsPusher; -use consts::{NUM_THREADS, STACK_SIZE}; +use consts::{NETWORK_NAME, NUM_THREADS, STACK_SIZE}; use futures::future::join_all; use std::time::{SystemTime, UNIX_EPOCH}; use tokio::runtime::{Builder, Runtime}; @@ -59,11 +59,18 @@ async fn test_flows(runtime: &Runtime, network_name: NetworkName) -> Result<()> TestName::PublishModule.run(network_name, &test_time).await; }); + // Flow 5: View function + let test_time = run_id.clone(); + let handle_viewfunction = runtime.spawn(async move { + TestName::ViewFunction.run(network_name, &test_time).await; + }); + join_all(vec![ handle_newaccount, handle_cointransfer, handle_nfttransfer, handle_publishmodule, + handle_viewfunction, ]) .await; Ok(()) @@ -82,10 +89,8 @@ fn main() -> Result<()> { let _mp = MetricsPusher::start_for_local_run("api-tester"); // run tests - // TODO: separate the running of the two networks runtime.block_on(async { - let _ = test_flows(&runtime, NetworkName::Testnet).await; - let _ = test_flows(&runtime, NetworkName::Devnet).await; + let _ = test_flows(&runtime, *NETWORK_NAME).await; }); Ok(()) diff --git a/crates/aptos-api-tester/src/strings.rs b/crates/aptos-api-tester/src/strings.rs index 998435cc06de6..99dbb40c312a9 100644 --- a/crates/aptos-api-tester/src/strings.rs +++ b/crates/aptos-api-tester/src/strings.rs @@ -13,6 +13,7 @@ pub const FAIL_WRONG_TOKEN_DATA: &str = "wrong token data"; // Error messages +pub const ERROR_BAD_BALANCE_STRING: &str = "bad balance string"; pub const ERROR_COULD_NOT_BUILD_PACKAGE: &str = "failed to build package"; pub const ERROR_COULD_NOT_CHECK: &str = "persistency check never started"; pub const ERROR_COULD_NOT_CREATE_ACCOUNT: &str = "failed to create account"; @@ -21,8 +22,10 @@ pub const ERROR_COULD_NOT_CREATE_AND_SUBMIT_TRANSACTION: &str = pub const ERROR_COULD_NOT_FINISH_TRANSACTION: &str = "failed to finish transaction"; pub const ERROR_COULD_NOT_FUND_ACCOUNT: &str = "failed to fund account"; pub const ERROR_COULD_NOT_SERIALIZE: &str = "failed to serialize"; +pub const ERROR_COULD_NOT_VIEW: &str = "view function failed"; pub const ERROR_NO_ACCOUNT_DATA: &str = "can't find account data"; pub const ERROR_NO_BALANCE: &str = "can't find account balance"; +pub const ERROR_NO_BALANCE_STRING: &str = "the API did not return a balance string"; pub const ERROR_NO_BYTECODE: &str = "can't find bytecode"; pub const ERROR_NO_COLLECTION_DATA: &str = "can't find collection data"; pub const ERROR_NO_MESSAGE: &str = "can't find message"; @@ -53,3 +56,4 @@ pub const PUBLISH_MODULE: &str = "publish_module"; pub const CHECK_MODULE_DATA: &str = "check_module_data"; pub const SET_MESSAGE: &str = "set_message"; pub const CHECK_MESSAGE: &str = "check_message"; +pub const CHECK_VIEW_ACCOUNT_BALANCE: &str = "check_view_account_balance"; diff --git a/crates/aptos-api-tester/src/tests/mod.rs b/crates/aptos-api-tester/src/tests/mod.rs index b82beb1942321..73e66cdf0ef58 100644 --- a/crates/aptos-api-tester/src/tests/mod.rs +++ b/crates/aptos-api-tester/src/tests/mod.rs @@ -4,3 +4,4 @@ pub mod coin_transfer; pub mod new_account; pub mod publish_module; pub mod tokenv1_transfer; +pub mod view_function; diff --git a/crates/aptos-api-tester/src/tests/publish_module.rs b/crates/aptos-api-tester/src/tests/publish_module.rs index 68d96d0d0bf5a..620395a29bbfc 100644 --- a/crates/aptos-api-tester/src/tests/publish_module.rs +++ b/crates/aptos-api-tester/src/tests/publish_module.rs @@ -32,7 +32,7 @@ use move_core_types::{ident_str, language_storage::ModuleId}; use std::{collections::BTreeMap, path::PathBuf}; static MODULE_NAME: &str = "message"; -static MESSAGE: &str = "test message"; +static TEST_MESSAGE: &str = "test message"; /// Tests module publishing and interaction. Checks that: /// - can publish module @@ -293,7 +293,7 @@ async fn check_module_data( async fn set_message(client: &Client, account: &mut LocalAccount) -> Result<(), TestFailure> { // set up message - let message = match bcs::to_bytes(MESSAGE) { + let message = match bcs::to_bytes(TEST_MESSAGE) { Ok(data) => data, Err(e) => { error!( @@ -341,7 +341,7 @@ async fn set_message(client: &Client, account: &mut LocalAccount) -> Result<(), async fn check_message(client: &Client, address: AccountAddress) -> Result<(), TestFailure> { // expected - let expected = MESSAGE.to_string(); + let expected = TEST_MESSAGE.to_string(); // actual let actual = match get_message(client, address).await { diff --git a/crates/aptos-api-tester/src/tests/view_function.rs b/crates/aptos-api-tester/src/tests/view_function.rs new file mode 100644 index 0000000000000..345daf3ef1338 --- /dev/null +++ b/crates/aptos-api-tester/src/tests/view_function.rs @@ -0,0 +1,177 @@ +// Copyright © Aptos Foundation + +use crate::{ + consts::FUND_AMOUNT, + persistent_check, + strings::{ + CHECK_ACCOUNT_DATA, CHECK_VIEW_ACCOUNT_BALANCE, ERROR_BAD_BALANCE_STRING, + ERROR_COULD_NOT_FUND_ACCOUNT, ERROR_COULD_NOT_VIEW, ERROR_NO_BALANCE_STRING, + FAIL_WRONG_BALANCE, SETUP, + }, + time_fn, + utils::{ + check_balance, create_and_fund_account, emit_step_metrics, NetworkName, TestFailure, + TestName, + }, +}; +use anyhow::anyhow; +use aptos_api_types::{ViewRequest, U64}; +use aptos_logger::error; +use aptos_rest_client::Client; +use aptos_sdk::types::LocalAccount; +use aptos_types::account_address::AccountAddress; + +/// Tests view function use. Checks that: +/// - view function returns correct value +pub async fn test(network_name: NetworkName, run_id: &str) -> Result<(), TestFailure> { + // setup + let (client, account) = emit_step_metrics( + time_fn!(setup, network_name), + TestName::ViewFunction, + SETUP, + network_name, + run_id, + )?; + + // check account data persistently + emit_step_metrics( + time_fn!( + persistent_check::address, + CHECK_ACCOUNT_DATA, + check_account_data, + &client, + account.address() + ), + TestName::ViewFunction, + CHECK_ACCOUNT_DATA, + network_name, + run_id, + )?; + + // check account balance from view function persistently + emit_step_metrics( + time_fn!( + persistent_check::address, + CHECK_VIEW_ACCOUNT_BALANCE, + check_view_account_balance, + &client, + account.address() + ), + TestName::ViewFunction, + CHECK_VIEW_ACCOUNT_BALANCE, + network_name, + run_id, + )?; + + Ok(()) +} + +// Steps + +async fn setup(network_name: NetworkName) -> Result<(Client, LocalAccount), TestFailure> { + // spin up clients + let client = network_name.get_client(); + let faucet_client = network_name.get_faucet_client(); + + // create account + let account = match create_and_fund_account(&faucet_client, TestName::ViewFunction).await { + Ok(account) => account, + Err(e) => { + error!( + "test: {} part: {} ERROR: {}, with error {:?}", + TestName::ViewFunction.to_string(), + SETUP, + ERROR_COULD_NOT_FUND_ACCOUNT, + e + ); + return Err(e.into()); + }, + }; + + Ok((client, account)) +} + +async fn check_account_data(client: &Client, account: AccountAddress) -> Result<(), TestFailure> { + check_balance(TestName::ViewFunction, client, account, U64(FUND_AMOUNT)).await?; + + Ok(()) +} + +async fn check_view_account_balance( + client: &Client, + address: AccountAddress, +) -> Result<(), TestFailure> { + // expected + let expected = U64(FUND_AMOUNT); + + // actual + + // get client response + let response = match client + .view( + &ViewRequest { + function: "0x1::coin::balance".parse()?, + type_arguments: vec!["0x1::aptos_coin::AptosCoin".parse()?], + arguments: vec![serde_json::Value::String(address.to_hex_literal())], + }, + None, + ) + .await + { + Ok(response) => response, + Err(e) => { + error!( + "test: {} part: {} ERROR: {}, with error {:?}", + TestName::ViewFunction.to_string(), + CHECK_VIEW_ACCOUNT_BALANCE, + ERROR_COULD_NOT_VIEW, + e + ); + return Err(e.into()); + }, + }; + + // get the string value from the serde_json value + let value = match response.inner()[0].as_str() { + Some(value) => value, + None => { + error!( + "test: {} part: {} ERROR: {}, with error {:?}", + TestName::ViewFunction.to_string(), + CHECK_VIEW_ACCOUNT_BALANCE, + ERROR_NO_BALANCE_STRING, + response.inner() + ); + return Err(anyhow!(ERROR_NO_BALANCE_STRING).into()); + }, + }; + + // parse the string into a U64 + let actual = match value.parse::() { + Ok(value) => U64(value), + Err(e) => { + error!( + "test: {} part: {} ERROR: {}, with error {:?}", + TestName::ViewFunction.to_string(), + CHECK_VIEW_ACCOUNT_BALANCE, + ERROR_BAD_BALANCE_STRING, + e + ); + return Err(e.into()); + }, + }; + + // compare + if expected != actual { + error!( + "test: {} part: {} FAIL: {}, expected {:?}, got {:?}", + TestName::ViewFunction.to_string(), + CHECK_VIEW_ACCOUNT_BALANCE, + FAIL_WRONG_BALANCE, + expected, + actual + ); + } + + Ok(()) +} diff --git a/crates/aptos-api-tester/src/utils.rs b/crates/aptos-api-tester/src/utils.rs index fd7e854c8ea68..1ed118b8a558d 100644 --- a/crates/aptos-api-tester/src/utils.rs +++ b/crates/aptos-api-tester/src/utils.rs @@ -6,16 +6,16 @@ use crate::{ }, counters::{test_error, test_fail, test_latency, test_step_latency, test_success}, strings::{ERROR_NO_BALANCE, FAIL_WRONG_BALANCE}, - tests::{coin_transfer, new_account, publish_module, tokenv1_transfer}, + tests::{coin_transfer, new_account, publish_module, tokenv1_transfer, view_function}, time_fn, }; -use anyhow::Result; +use anyhow::{anyhow, Error, Result}; use aptos_api_types::U64; use aptos_logger::{error, info}; use aptos_rest_client::{error::RestError, Client, FaucetClient}; use aptos_sdk::types::LocalAccount; use aptos_types::account_address::AccountAddress; -use std::env; +use std::{env, num::ParseIntError, str::FromStr}; // Test failure @@ -27,15 +27,21 @@ pub enum TestFailure { Error(anyhow::Error), } +impl From for TestFailure { + fn from(e: anyhow::Error) -> TestFailure { + TestFailure::Error(e) + } +} + impl From for TestFailure { fn from(e: RestError) -> TestFailure { TestFailure::Error(e.into()) } } -impl From for TestFailure { - fn from(e: anyhow::Error) -> TestFailure { - TestFailure::Error(e) +impl From for TestFailure { + fn from(e: ParseIntError) -> TestFailure { + TestFailure::Error(e.into()) } } @@ -47,17 +53,7 @@ pub enum TestName { CoinTransfer, TokenV1Transfer, PublishModule, -} - -impl ToString for TestName { - fn to_string(&self) -> String { - match &self { - TestName::NewAccount => "new_account".to_string(), - TestName::CoinTransfer => "coin_transfer".to_string(), - TestName::TokenV1Transfer => "tokenv1_transfer".to_string(), - TestName::PublishModule => "publish_module".to_string(), - } - } + ViewFunction, } impl TestName { @@ -67,12 +63,25 @@ impl TestName { TestName::CoinTransfer => time_fn!(coin_transfer::test, network_name, run_id), TestName::TokenV1Transfer => time_fn!(tokenv1_transfer::test, network_name, run_id), TestName::PublishModule => time_fn!(publish_module::test, network_name, run_id), + TestName::ViewFunction => time_fn!(view_function::test, network_name, run_id), }; emit_test_metrics(output, *self, network_name, run_id); } } +impl ToString for TestName { + fn to_string(&self) -> String { + match &self { + TestName::NewAccount => "new_account".to_string(), + TestName::CoinTransfer => "coin_transfer".to_string(), + TestName::TokenV1Transfer => "tokenv1_transfer".to_string(), + TestName::PublishModule => "publish_module".to_string(), + TestName::ViewFunction => "view_function".to_string(), + } + } +} + // Network name #[derive(Clone, Copy)] @@ -90,6 +99,18 @@ impl ToString for NetworkName { } } +impl FromStr for NetworkName { + type Err = Error; + + fn from_str(s: &str) -> Result { + match s { + "testnet" => Ok(NetworkName::Testnet), + "devnet" => Ok(NetworkName::Devnet), + _ => Err(anyhow!("invalid network name")), + } + } +} + impl NetworkName { /// Create a REST client. pub fn get_client(&self) -> Client {