From e57c04021539adcba01c6e9c1951c25f7a3cd5e7 Mon Sep 17 00:00:00 2001 From: Avinash Sivakumar Date: Sun, 21 Nov 2021 11:44:07 -0800 Subject: [PATCH] [shuffle] replace json rpc with api --- shuffle/cli/src/account.rs | 73 ++++++----- shuffle/cli/src/deploy.rs | 151 +--------------------- shuffle/cli/src/shared.rs | 165 +++++++++++++++++++++--- shuffle/cli/src/test.rs | 25 ++-- shuffle/integration-tests/src/helper.rs | 6 +- shuffle/integration-tests/src/lib.rs | 12 +- 6 files changed, 211 insertions(+), 221 deletions(-) diff --git a/shuffle/cli/src/account.rs b/shuffle/cli/src/account.rs index 2538d433b9ec2..cb619ea60622e 100644 --- a/shuffle/cli/src/account.rs +++ b/shuffle/cli/src/account.rs @@ -1,13 +1,13 @@ // Copyright (c) The Diem Core Contributors // SPDX-License-Identifier: Apache-2.0 -use crate::shared::{send_transaction, Home, Network, NetworkHome, LOCALHOST_NAME}; +use crate::shared::{DevApiClient, Home, Network, NetworkHome, LOCALHOST_NAME}; use anyhow::{anyhow, Result}; use diem_crypto::PrivateKey; use diem_infallible::duration_since_epoch; use diem_sdk::{ - client::{BlockingClient, FaucetClient}, + client::FaucetClient, transaction_builder::{Currency, TransactionFactory}, types::LocalAccount, }; @@ -49,15 +49,18 @@ pub async fn handle(home: &Home, root: Option, network: Network) -> Res } None => { println!("Connecting to {}...", network.get_json_rpc_url()); - let client = BlockingClient::new(network.get_json_rpc_url()); + let client = DevApiClient::new(reqwest::Client::new(), network.get_dev_api_url())?; let factory = TransactionFactory::new(ChainId::test()); if let Some(input_root_key) = root { network_home.copy_key_to_latest(input_root_key.as_path())? } - let mut treasury_account = get_treasury_account(&client, home.get_root_key_path())?; - create_account_via_dev_api(&mut treasury_account, &new_account, &factory, &client)?; + let mut treasury_account = + get_treasury_account(&client, home.get_root_key_path()).await?; + create_account_via_dev_api(&mut treasury_account, &new_account, &factory, &client) + .await?; create_account_via_dev_api(&mut treasury_account, &test_account, &factory, &client) + .await } } } @@ -134,13 +137,14 @@ fn generate_test_account(network_home: &NetworkHome) -> Result { )) } -pub fn get_treasury_account(client: &BlockingClient, root_key_path: &Path) -> Result { +pub async fn get_treasury_account( + client: &DevApiClient, + root_key_path: &Path, +) -> Result { let treasury_account_key = load_key(root_key_path); let treasury_seq_num = client - .get_account(account_config::treasury_compliance_account_address())? - .into_inner() - .unwrap() - .sequence_number; + .get_account_sequence_number(account_config::treasury_compliance_account_address()) + .await?; Ok(LocalAccount::new( account_config::treasury_compliance_account_address(), treasury_account_key, @@ -148,35 +152,34 @@ pub fn get_treasury_account(client: &BlockingClient, root_key_path: &Path) -> Re )) } -pub fn create_account_via_dev_api( +pub async fn create_account_via_dev_api( treasury_account: &mut LocalAccount, new_account: &LocalAccount, factory: &TransactionFactory, - client: &BlockingClient, + client: &DevApiClient, ) -> Result<()> { - println!("Creating a new account onchain..."); - if client - .get_account(new_account.address()) - .unwrap() - .into_inner() - .is_some() - { - println!("Account already exists: {}", new_account.address()); - } else { - let create_new_account_txn = treasury_account.sign_with_transaction_builder( - factory.payload(encode_create_parent_vasp_account_script_function( - Currency::XUS.type_tag(), - 0, - new_account.address(), - new_account.authentication_key().prefix().to_vec(), - vec![], - false, - )), - ); - send_transaction(client, create_new_account_txn)?; - println!("Successfully created account {}", new_account.address()); - } - println!("Public key: {}", new_account.public_key()); + match client.get_account_resources(new_account.address()).await { + Ok(_) => println!("Account already exists: {}", new_account.address()), + Err(_) => { + println!("Creating a new account onchain..."); + let create_new_account_txn = treasury_account.sign_with_transaction_builder( + factory.payload(encode_create_parent_vasp_account_script_function( + Currency::XUS.type_tag(), + 0, + new_account.address(), + new_account.authentication_key().prefix().to_vec(), + vec![], + false, + )), + ); + let bytes = bcs::to_bytes(&create_new_account_txn)?; + let json = client.post_transactions(bytes).await?; + let hash = DevApiClient::get_hash_from_post_txn(json)?; + client.check_txn_executed_from_hash(hash.as_str()).await?; + println!("Successfully created account {}", new_account.address()); + println!("Public key: {}", new_account.public_key()); + } + }; Ok(()) } diff --git a/shuffle/cli/src/deploy.rs b/shuffle/cli/src/deploy.rs index cd0d4147fb8da..bd5567c745e85 100644 --- a/shuffle/cli/src/deploy.rs +++ b/shuffle/cli/src/deploy.rs @@ -13,14 +13,7 @@ use diem_sdk::{ }; use diem_types::{chain_id::ChainId, transaction::authenticator::AuthenticationKey}; use generate_key::load_key; -use serde_json::Value; -use std::{ - io, - io::Write, - path::Path, - thread, time, - time::{Duration, Instant}, -}; +use std::path::Path; use url::Url; /// Deploys shuffle's main Move Package to the sender's address. @@ -63,7 +56,7 @@ pub async fn deploy( module.serialize(&mut binary)?; let hash = send_module_transaction(&client, account, binary).await?; - check_txn_executed_from_hash(&client, hash.as_str()).await?; + client.check_txn_executed_from_hash(hash.as_str()).await?; } Ok(()) @@ -80,144 +73,6 @@ async fn send_module_transaction( )); let bytes = bcs::to_bytes(&publish_txn)?; let json = client.post_transactions(bytes).await?; - let hash = get_hash_from_post_txn(json)?; + let hash = DevApiClient::get_hash_from_post_txn(json)?; Ok(hash) } - -async fn check_txn_executed_from_hash(client: &DevApiClient, hash: &str) -> Result<()> { - let mut json = client.get_transactions_by_hash(hash).await?; - let start = Instant::now(); - while json["type"] == "pending_transaction" { - thread::sleep(time::Duration::from_secs(1)); - json = client.get_transactions_by_hash(hash).await?; - let duration = start.elapsed(); - if duration > Duration::from_secs(10) { - break; - } - } - confirm_successful_execution(&mut io::stdout(), &json, hash) -} - -fn confirm_successful_execution(writer: &mut W, json: &Value, hash: &str) -> Result<()> -where - W: Write, -{ - if is_execution_successful(json)? { - return Ok(()); - } - writeln!(writer, "{:#?}", json)?; - Err(anyhow!(format!( - "Transaction with hash {} didn't execute successfully", - hash - ))) -} - -fn is_execution_successful(json: &Value) -> Result { - json["success"] - .as_bool() - .ok_or_else(|| anyhow!("Unable to access success key")) -} - -fn get_hash_from_post_txn(json: Value) -> Result { - Ok(json["hash"].as_str().unwrap().to_string()) -} - -#[cfg(test)] -mod test { - use super::*; - use serde_json::json; - - fn post_txn_json_output() -> Value { - json!({ - "type":"pending_transaction", - "hash":"0xbca2738726dc456f23762372ab0dd2f450ec3ec20271e5318ae37e9d42ee2bb8", - "sender":"0x24163afcc6e33b0a9473852e18327fa9", - "sequence_number":"10", - "max_gas_amount":"1000000", - "gas_unit_price":"0", - "gas_currency_code":"XUS", - "expiration_timestamp_secs":"1635872777", - "payload":{} - }) - } - - fn get_transactions_by_hash_json_output_success() -> Value { - json!({ - "type":"user_transaction", - "version":"3997", - "hash":"0x89e59bb50521334a69c06a315b6dd191a8da4c1c7a40ce27a8f96f12959496eb", - "state_root_hash":"0x7a0b81379ab8786f34fcff804e5fb413255467c28f09672e8d22bfaa4e029102", - "event_root_hash":"0x414343554d554c41544f525f504c414345484f4c4445525f4841534800000000", - "gas_used":"8", - "success":true, - "vm_status":"Executed successfully", - "sender":"0x24163afcc6e33b0a9473852e18327fa9", - "sequence_number":"14", - "max_gas_amount":"1000000", - "gas_unit_price":"0", - "gas_currency_code":"XUS", - "expiration_timestamp_secs":"1635873470", - "payload":{} - }) - } - - fn get_transactions_by_hash_json_output_fail() -> Value { - json!({ - "type":"user_transaction", - "version":"3997", - "hash":"0xbad59bb50521334a69c06a315b6dd191a8da4c1c7a40ce27a8f96f12959496eb", - "state_root_hash":"0x7a0b81379ab8786f34fcff804e5fb413255467c28f09672e8d22bfaa4e029102", - "event_root_hash":"0x414343554d554c41544f525f504c414345484f4c4445525f4841534800000000", - "gas_used":"8", - "success":false, - "vm_status":"miscellaneous error", - "sender":"0x24163afcc6e33b0a9473852e18327fa9", - "sequence_number":"14", - "max_gas_amount":"1000000", - "gas_unit_price":"0", - "gas_currency_code":"XUS", - "expiration_timestamp_secs":"1635873470", - "payload":{} - }) - } - - #[test] - fn test_confirm_is_execution_successful() { - let successful_txn = get_transactions_by_hash_json_output_success(); - assert_eq!(is_execution_successful(&successful_txn).unwrap(), true); - - let failed_txn = get_transactions_by_hash_json_output_fail(); - assert_eq!(is_execution_successful(&failed_txn).unwrap(), false); - } - - #[test] - fn test_get_hash_from_post_txn() { - let txn = post_txn_json_output(); - let hash = get_hash_from_post_txn(txn).unwrap(); - assert_eq!( - hash, - "0xbca2738726dc456f23762372ab0dd2f450ec3ec20271e5318ae37e9d42ee2bb8" - ); - } - - #[test] - fn test_print_confirmation_with_success_value() { - let successful_txn = get_transactions_by_hash_json_output_success(); - let mut stdout = Vec::new(); - let good_hash = "0xbca2738726dc456f23762372ab0dd2f450ec3ec20271e5318ae37e9d42ee2bb8"; - - confirm_successful_execution(&mut stdout, &successful_txn, good_hash).unwrap(); - assert_eq!(String::from_utf8(stdout).unwrap().as_str(), "".to_string()); - - let failed_txn = get_transactions_by_hash_json_output_fail(); - let mut stdout = Vec::new(); - let bad_hash = "0xbad59bb50521334a69c06a315b6dd191a8da4c1c7a40ce27a8f96f12959496eb"; - assert_eq!( - confirm_successful_execution(&mut stdout, &failed_txn, bad_hash).is_err(), - true - ); - - let fail_string = format!("{:#?}\n", &failed_txn); - assert_eq!(String::from_utf8(stdout).unwrap().as_str(), fail_string) - } -} diff --git a/shuffle/cli/src/shared.rs b/shuffle/cli/src/shared.rs index 053380b853317..e2cb4fe947ed3 100644 --- a/shuffle/cli/src/shared.rs +++ b/shuffle/cli/src/shared.rs @@ -3,7 +3,7 @@ use anyhow::{anyhow, Result}; use diem_api_types::mime_types; use diem_crypto::ed25519::{Ed25519PrivateKey, Ed25519PublicKey}; -use diem_sdk::client::{AccountAddress, BlockingClient}; +use diem_sdk::client::AccountAddress; use diem_types::transaction::authenticator::AuthenticationKey; use directories::BaseDirs; use move_package::compilation::compiled_package::CompiledPackage; @@ -17,10 +17,12 @@ use std::{ collections::{BTreeMap, HashMap}, fs, fs::File, + io, io::Write, path::{Path, PathBuf}, str::FromStr, - time::Duration, + thread, time, + time::{Duration, Instant}, }; use transaction_builder_generator as buildgen; use transaction_builder_generator::SourceInstaller as BuildgenSourceInstaller; @@ -57,24 +59,6 @@ pub fn read_project_config(project_path: &Path) -> Result { Ok(read_config) } -/// Send a transaction to the blockchain through the blocking client. -pub fn send_transaction( - client: &BlockingClient, - tx: diem_types::transaction::SignedTransaction, -) -> Result<()> { - use diem_json_rpc_types::views::VMStatusView; - - client.submit(&tx)?; - let status = client - .wait_for_signed_transaction(&tx, Some(std::time::Duration::from_secs(60)), None)? - .into_inner() - .vm_status; - if status != VMStatusView::Executed { - return Err(anyhow::anyhow!("transaction execution failed: {}", status)); - } - Ok(()) -} - /// Checks the current directory, and then parent directories for a Shuffle.toml /// file to indicate the base project directory. pub fn get_shuffle_project_path(cwd: &Path) -> Result { @@ -166,7 +150,7 @@ impl DevApiClient { .await } - async fn get_account_resources(&self, address: AccountAddress) -> Result { + pub async fn get_account_resources(&self, address: AccountAddress) -> Result { let path = self .url .join(format!("accounts/{}/resources", address.to_hex_literal()).as_str())?; @@ -246,6 +230,44 @@ impl DevApiClient { let seq_number: u64 = seq_number_string.parse()?; Ok(seq_number) } + + pub async fn check_txn_executed_from_hash(&self, hash: &str) -> Result<()> { + let mut json = self.get_transactions_by_hash(hash).await?; + let start = Instant::now(); + while json["type"] == "pending_transaction" { + thread::sleep(time::Duration::from_secs(1)); + json = self.get_transactions_by_hash(hash).await?; + let duration = start.elapsed(); + if duration > Duration::from_secs(15) { + break; + } + } + DevApiClient::confirm_successful_execution(&mut io::stdout(), &json, hash) + } + + fn confirm_successful_execution(writer: &mut W, json: &Value, hash: &str) -> Result<()> + where + W: Write, + { + if DevApiClient::is_execution_successful(json)? { + return Ok(()); + } + writeln!(writer, "{:#?}", json)?; + Err(anyhow!(format!( + "Transaction with hash {} didn't execute successfully", + hash + ))) + } + + fn is_execution_successful(json: &Value) -> Result { + json["success"] + .as_bool() + .ok_or_else(|| anyhow!("Unable to access success key")) + } + + pub fn get_hash_from_post_txn(json: Value) -> Result { + Ok(json["hash"].as_str().unwrap().to_string()) + } } pub struct NetworkHome { @@ -1031,4 +1053,105 @@ mod test { .unwrap(); assert_eq!(home.check_networks_toml_exists().is_err(), false); } + + fn post_txn_json_output() -> Value { + json!({ + "type":"pending_transaction", + "hash":"0xbca2738726dc456f23762372ab0dd2f450ec3ec20271e5318ae37e9d42ee2bb8", + "sender":"0x24163afcc6e33b0a9473852e18327fa9", + "sequence_number":"10", + "max_gas_amount":"1000000", + "gas_unit_price":"0", + "gas_currency_code":"XUS", + "expiration_timestamp_secs":"1635872777", + "payload":{} + }) + } + + fn get_transactions_by_hash_json_output_success() -> Value { + json!({ + "type":"user_transaction", + "version":"3997", + "hash":"0x89e59bb50521334a69c06a315b6dd191a8da4c1c7a40ce27a8f96f12959496eb", + "state_root_hash":"0x7a0b81379ab8786f34fcff804e5fb413255467c28f09672e8d22bfaa4e029102", + "event_root_hash":"0x414343554d554c41544f525f504c414345484f4c4445525f4841534800000000", + "gas_used":"8", + "success":true, + "vm_status":"Executed successfully", + "sender":"0x24163afcc6e33b0a9473852e18327fa9", + "sequence_number":"14", + "max_gas_amount":"1000000", + "gas_unit_price":"0", + "gas_currency_code":"XUS", + "expiration_timestamp_secs":"1635873470", + "payload":{} + }) + } + + fn get_transactions_by_hash_json_output_fail() -> Value { + json!({ + "type":"user_transaction", + "version":"3997", + "hash":"0xbad59bb50521334a69c06a315b6dd191a8da4c1c7a40ce27a8f96f12959496eb", + "state_root_hash":"0x7a0b81379ab8786f34fcff804e5fb413255467c28f09672e8d22bfaa4e029102", + "event_root_hash":"0x414343554d554c41544f525f504c414345484f4c4445525f4841534800000000", + "gas_used":"8", + "success":false, + "vm_status":"miscellaneous error", + "sender":"0x24163afcc6e33b0a9473852e18327fa9", + "sequence_number":"14", + "max_gas_amount":"1000000", + "gas_unit_price":"0", + "gas_currency_code":"XUS", + "expiration_timestamp_secs":"1635873470", + "payload":{} + }) + } + + #[test] + fn test_confirm_is_execution_successful() { + let successful_txn = get_transactions_by_hash_json_output_success(); + assert_eq!( + DevApiClient::is_execution_successful(&successful_txn).unwrap(), + true + ); + + let failed_txn = get_transactions_by_hash_json_output_fail(); + assert_eq!( + DevApiClient::is_execution_successful(&failed_txn).unwrap(), + false + ); + } + + #[test] + fn test_get_hash_from_post_txn() { + let txn = post_txn_json_output(); + let hash = DevApiClient::get_hash_from_post_txn(txn).unwrap(); + assert_eq!( + hash, + "0xbca2738726dc456f23762372ab0dd2f450ec3ec20271e5318ae37e9d42ee2bb8" + ); + } + + #[test] + fn test_print_confirmation_with_success_value() { + let successful_txn = get_transactions_by_hash_json_output_success(); + let mut stdout = Vec::new(); + let good_hash = "0xbca2738726dc456f23762372ab0dd2f450ec3ec20271e5318ae37e9d42ee2bb8"; + + DevApiClient::confirm_successful_execution(&mut stdout, &successful_txn, good_hash) + .unwrap(); + assert_eq!(String::from_utf8(stdout).unwrap().as_str(), "".to_string()); + + let failed_txn = get_transactions_by_hash_json_output_fail(); + let mut stdout = Vec::new(); + let bad_hash = "0xbad59bb50521334a69c06a315b6dd191a8da4c1c7a40ce27a8f96f12959496eb"; + assert_eq!( + DevApiClient::confirm_successful_execution(&mut stdout, &failed_txn, bad_hash).is_err(), + true + ); + + let fail_string = format!("{:#?}\n", &failed_txn); + assert_eq!(String::from_utf8(stdout).unwrap().as_str(), fail_string) + } } diff --git a/shuffle/cli/src/test.rs b/shuffle/cli/src/test.rs index f7e5ae20e633a..835ab9387ca29 100644 --- a/shuffle/cli/src/test.rs +++ b/shuffle/cli/src/test.rs @@ -3,14 +3,14 @@ use crate::{ account, deploy, - shared::{self, normalized_network_name, Home, Network, NetworkHome, MAIN_PKG_PATH}, + shared::{ + self, normalized_network_name, DevApiClient, Home, Network, NetworkHome, MAIN_PKG_PATH, + }, }; use anyhow::{anyhow, Result}; use diem_crypto::PrivateKey; use diem_sdk::{ - client::{AccountAddress, BlockingClient}, - transaction_builder::TransactionFactory, - types::LocalAccount, + client::AccountAddress, transaction_builder::TransactionFactory, types::LocalAccount, }; use diem_types::{chain_id::ChainId, transaction::authenticator::AuthenticationKey}; use move_cli::package::cli::{self, UnitTestResult}; @@ -37,7 +37,7 @@ pub async fn run_e2e_tests( shared::generate_typescript_libraries(project_path)?; println!("Connecting to {}...", network.get_json_rpc_url()); - let client = BlockingClient::new(network.get_json_rpc_url().as_str()); + let client = DevApiClient::new(reqwest::Client::new(), network.get_dev_api_url())?; let factory = TransactionFactory::new(ChainId::test()); let test_account = create_account( @@ -45,13 +45,15 @@ pub async fn run_e2e_tests( network_home.get_test_key_path(), &client, &factory, - )?; + ) + .await?; let _receiver_account = create_account( home.get_root_key_path(), network_home.get_test_key_path(), // TODO: update to a different key to sender &client, &factory, - )?; + ) + .await?; deploy::handle(&network_home, project_path, network.get_dev_api_url()).await?; run_deno_test( @@ -63,19 +65,20 @@ pub async fn run_e2e_tests( ) } -fn create_account( +async fn create_account( root_key_path: &Path, account_key_path: &Path, - client: &BlockingClient, + client: &DevApiClient, factory: &TransactionFactory, ) -> Result { - let mut treasury_account = account::get_treasury_account(client, root_key_path)?; + let mut treasury_account = account::get_treasury_account(client, root_key_path).await?; // TODO: generate random key by using let account_key = generate_key::generate_key(); let account_key = generate_key::load_key(account_key_path); let public_key = account_key.public_key(); let derived_address = AuthenticationKey::ed25519(&public_key).derived_address(); let new_account = LocalAccount::new(derived_address, account_key, 0); - account::create_account_via_dev_api(&mut treasury_account, &new_account, factory, client)?; + account::create_account_via_dev_api(&mut treasury_account, &new_account, factory, client) + .await?; Ok(new_account) } diff --git a/shuffle/integration-tests/src/helper.rs b/shuffle/integration-tests/src/helper.rs index 3a0faa3228116..dfe954b88145f 100644 --- a/shuffle/integration-tests/src/helper.rs +++ b/shuffle/integration-tests/src/helper.rs @@ -74,19 +74,19 @@ impl ShuffleTestHelper { self.tmp_dir.path().join("project") } - pub fn create_account( + pub async fn create_account( &self, treasury_account: &mut LocalAccount, new_account: &LocalAccount, factory: TransactionFactory, - client: BlockingClient, + client: &DevApiClient, ) -> Result<()> { let bytes: &[u8] = &new_account.private_key().to_bytes(); let private_key = Ed25519PrivateKey::try_from(bytes).map_err(anyhow::Error::new)?; self.network_home().save_key_as_latest(private_key)?; self.network_home() .generate_latest_address_file(new_account.public_key())?; - account::create_account_via_dev_api(treasury_account, new_account, &factory, &client) + account::create_account_via_dev_api(treasury_account, new_account, &factory, client).await } pub fn create_project(&self) -> Result<()> { diff --git a/shuffle/integration-tests/src/lib.rs b/shuffle/integration-tests/src/lib.rs index 8f95fb37dc0bb..a8fc8d59b2310 100644 --- a/shuffle/integration-tests/src/lib.rs +++ b/shuffle/integration-tests/src/lib.rs @@ -6,8 +6,11 @@ mod helper; use crate::helper::ShuffleTestHelper; use forge::{AdminContext, AdminTest, Result, Test}; use move_cli::package::cli::UnitTestResult; +use shuffle::shared::DevApiClient; use smoke_test::scripts_and_modules::enable_open_publishing; +use std::str::FromStr; use tokio::runtime::Runtime; +use url::Url; pub struct SamplePackageEndToEnd; @@ -61,6 +64,10 @@ impl AdminTest for TypescriptSdkIntegration { fn bootstrap_shuffle(ctx: &mut AdminContext<'_>) -> Result { let client = ctx.client(); + let dev_api_client = DevApiClient::new( + reqwest::Client::new(), + Url::from_str(ctx.chain_info().rest_api())?, + )?; let factory = ctx.chain_info().transaction_factory(); enable_open_publishing(&client, &factory, ctx.chain_info().root_account())?; @@ -70,11 +77,10 @@ fn bootstrap_shuffle(ctx: &mut AdminContext<'_>) -> Result { // let mut account = ctx.random_account(); // TODO: Support arbitrary addresses let mut account = ShuffleTestHelper::hardcoded_0x2416_account(&client)?; let tc = ctx.chain_info().treasury_compliance_account(); - helper.create_account(tc, &account, factory, client)?; - let rt = Runtime::new().unwrap(); let handle = rt.handle().clone(); - handle.block_on(helper.deploy_project(&mut account, ctx.chain_info().rest_api()))?; + handle.block_on(helper.create_account(tc, &account, factory, &dev_api_client))?; + handle.block_on(helper.deploy_project(&mut account, ctx.chain_info().rest_api()))?; Ok(helper) }