From 8af61f561ba852493c5d972f1fdbd5bb739805fc Mon Sep 17 00:00:00 2001 From: Tyera Eulberg Date: Mon, 14 Jan 2019 00:10:03 -0700 Subject: [PATCH] Improve Wallet coverage (#2385) * Add trait for RpcRequestHandler trait for RpcClient and add MockRpcClient for unit tests * Add request_airdrop integration test * Add timestamp_tx, witness_tx, and cancel_tx to wallet integration tests; add wallet integration tests to test-stable * Add test cases * Ignore plentiful sleeps in unit tests --- ci/test-stable.sh | 4 +- src/lib.rs | 1 + src/replicator.rs | 10 +- src/rpc_mock.rs | 88 ++++ src/rpc_request.rs | 101 ++-- src/thin_client.rs | 26 +- src/vote_signer_proxy.rs | 15 +- tests/replicator.rs | 6 +- wallet/src/lib.rs | 1 + wallet/src/main.rs | 5 +- wallet/src/wallet.rs | 851 ++++++++++++-------------------- wallet/tests/pay.rs | 320 ++++++++++++ wallet/tests/request_airdrop.rs | 78 +++ 13 files changed, 894 insertions(+), 612 deletions(-) create mode 100644 src/rpc_mock.rs create mode 100644 wallet/src/lib.rs create mode 100644 wallet/tests/pay.rs create mode 100644 wallet/tests/request_airdrop.rs diff --git a/ci/test-stable.sh b/ci/test-stable.sh index 7a0681dee7..80b6e66b14 100755 --- a/ci/test-stable.sh +++ b/ci/test-stable.sh @@ -28,10 +28,10 @@ for program in programs/native/*; do done # Run integration tests serially -for test in tests/*.rs; do +for test in tests/*.rs wallet/tests/*.rs; do test=${test##*/} # basename x test=${test%.rs} # basename x .rs - _ cargo test --verbose --features="$FEATURES" --test="$test" -- --test-threads=1 --nocapture + _ cargo test --all --verbose --features="$FEATURES" --test="$test" -- --test-threads=1 --nocapture done echo --- ci/localnet-sanity.sh diff --git a/src/lib.rs b/src/lib.rs index dadd32d352..e656a134b8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -53,6 +53,7 @@ pub mod replicator; pub mod result; pub mod retransmit_stage; pub mod rpc; +pub mod rpc_mock; pub mod rpc_pubsub; pub mod rpc_request; pub mod runtime; diff --git a/src/replicator.rs b/src/replicator.rs index e9fbf6e38d..0d6569e8ab 100644 --- a/src/replicator.rs +++ b/src/replicator.rs @@ -7,7 +7,7 @@ use crate::db_ledger::DbLedger; use crate::gossip_service::GossipService; use crate::leader_scheduler::LeaderScheduler; use crate::result::Result; -use crate::rpc_request::{RpcClient, RpcRequest}; +use crate::rpc_request::{RpcClient, RpcRequest, RpcRequestHandler}; use crate::service::Service; use crate::storage_stage::ENTRIES_PER_SEGMENT; use crate::streamer::BlobReceiver; @@ -144,12 +144,12 @@ impl Replicator { RpcClient::new_from_socket(rpc_peers[node_idx].rpc) }; - storage_last_id = RpcRequest::GetStorageMiningLastId - .make_rpc_request(&rpc_client, 2, None) + storage_last_id = rpc_client + .make_rpc_request(2, RpcRequest::GetStorageMiningLastId, None) .expect("rpc request") .to_string(); - storage_entry_height = RpcRequest::GetStorageMiningEntryHeight - .make_rpc_request(&rpc_client, 2, None) + storage_entry_height = rpc_client + .make_rpc_request(2, RpcRequest::GetStorageMiningEntryHeight, None) .expect("rpc request") .as_u64() .unwrap(); diff --git a/src/rpc_mock.rs b/src/rpc_mock.rs new file mode 100644 index 0000000000..5d6404f885 --- /dev/null +++ b/src/rpc_mock.rs @@ -0,0 +1,88 @@ +// Implementation of RpcRequestHandler trait for testing Rpc requests without i/o + +use crate::rpc_request::{RpcRequest, RpcRequestHandler}; +use serde_json::{self, Number, Value}; +use solana_sdk::hash::Hash; +use solana_sdk::pubkey::Pubkey; +use solana_sdk::signature::{Keypair, KeypairUtil}; +use solana_sdk::system_transaction::SystemTransaction; +use solana_sdk::transaction::Transaction; +use std::error; +use std::io::{Error, ErrorKind}; +use std::net::SocketAddr; + +pub const PUBKEY: &str = "7RoSF9fUmdphVCpabEoefH81WwrW7orsWonXWqTXkKV8"; +pub const SIGNATURE: &str = + "43yNSFC6fYTuPgTNFFhF4axw7AfWxB2BPdurme8yrsWEYwm8299xh8n6TAHjGymiSub1XtyxTNyd9GBfY2hxoBw8"; + +#[derive(Clone)] +pub struct MockRpcClient { + pub addr: String, +} + +impl MockRpcClient { + pub fn new(addr: String) -> Self { + MockRpcClient { addr } + } +} + +impl RpcRequestHandler for MockRpcClient { + fn make_rpc_request( + &self, + _id: u64, + request: RpcRequest, + params: Option, + ) -> Result> { + if self.addr == "fails" { + return Ok(Value::Null); + } + let val = match request { + RpcRequest::ConfirmTransaction => { + if let Some(Value::Array(param_array)) = params { + if let Value::String(param_string) = ¶m_array[0] { + Value::Bool(param_string == SIGNATURE) + } else { + Value::Null + } + } else { + Value::Null + } + } + RpcRequest::GetBalance => { + let n = if self.addr == "airdrop" { 0 } else { 50 }; + Value::Number(Number::from(n)) + } + RpcRequest::GetLastId => Value::String(PUBKEY.to_string()), + RpcRequest::GetSignatureStatus => { + let str = if self.addr == "account_in_use" { + "AccountInUse" + } else if self.addr == "bad_sig_status" { + "Nonexistent" + } else { + "Confirmed" + }; + Value::String(str.to_string()) + } + RpcRequest::GetTransactionCount => Value::Number(Number::from(1234)), + RpcRequest::SendTransaction => Value::String(SIGNATURE.to_string()), + _ => Value::Null, + }; + Ok(val) + } +} + +pub fn request_airdrop_transaction( + _drone_addr: &SocketAddr, + _id: &Pubkey, + tokens: u64, + _last_id: Hash, +) -> Result { + if tokens == 0 { + Err(Error::new(ErrorKind::Other, "Airdrop failed"))? + } + let key = Keypair::new(); + let to = Keypair::new().pubkey(); + let last_id = Hash::default(); + let tx = Transaction::system_new(&key, to, 50, last_id); + Ok(tx) +} diff --git a/src/rpc_request.rs b/src/rpc_request.rs index b38177ab22..a4193c9c7a 100644 --- a/src/rpc_request.rs +++ b/src/rpc_request.rs @@ -6,6 +6,7 @@ use std::thread::sleep; use std::time::Duration; use std::{error, fmt}; +#[derive(Clone)] pub struct RpcClient { pub client: reqwest::Client, pub addr: String, @@ -35,55 +36,22 @@ impl RpcClient { addr, } } -} - -pub fn get_rpc_request_str(rpc_addr: SocketAddr) -> String { - format!("http://{}", rpc_addr) -} - -pub enum RpcRequest { - ConfirmTransaction, - GetAccountInfo, - GetBalance, - GetConfirmationTime, - GetLastId, - GetSignatureStatus, - GetTransactionCount, - RequestAirdrop, - SendTransaction, - RegisterNode, - SignVote, - DeregisterNode, - GetStorageMiningLastId, - GetStorageMiningEntryHeight, - GetStoragePubkeysForEntryHeight, -} - -impl RpcRequest { - pub fn make_rpc_request( - &self, - client: &RpcClient, - id: u64, - params: Option, - ) -> Result> { - self.retry_make_rpc_request(client, id, params, 0) - } pub fn retry_make_rpc_request( &self, - client: &RpcClient, id: u64, + request: &RpcRequest, params: Option, mut retries: usize, ) -> Result> { - let request = self.build_request_json(id, params); + let request_json = request.build_request_json(id, params); loop { - match client + match self .client - .post(&client.addr) + .post(&self.addr) .header(CONTENT_TYPE, "application/json") - .body(request.to_string()) + .body(request_json.to_string()) .send() { Ok(mut response) => { @@ -111,7 +79,52 @@ impl RpcRequest { } } } +} +pub fn get_rpc_request_str(rpc_addr: SocketAddr) -> String { + format!("http://{}", rpc_addr) +} + +pub trait RpcRequestHandler { + fn make_rpc_request( + &self, + id: u64, + request: RpcRequest, + params: Option, + ) -> Result>; +} + +impl RpcRequestHandler for RpcClient { + fn make_rpc_request( + &self, + id: u64, + request: RpcRequest, + params: Option, + ) -> Result> { + self.retry_make_rpc_request(id, &request, params, 0) + } +} + +#[derive(Debug, PartialEq)] +pub enum RpcRequest { + ConfirmTransaction, + GetAccountInfo, + GetBalance, + GetConfirmationTime, + GetLastId, + GetSignatureStatus, + GetTransactionCount, + RequestAirdrop, + SendTransaction, + RegisterNode, + SignVote, + DeregisterNode, + GetStorageMiningLastId, + GetStorageMiningEntryHeight, + GetStoragePubkeysForEntryHeight, +} + +impl RpcRequest { fn build_request_json(&self, id: u64, params: Option) -> Value { let jsonrpc = "2.0"; let method = match self { @@ -251,15 +264,15 @@ mod tests { let rpc_addr = receiver.recv().unwrap(); let rpc_client = RpcClient::new_from_socket(rpc_addr); - let balance = RpcRequest::GetBalance.make_rpc_request( - &rpc_client, + let balance = rpc_client.make_rpc_request( 1, + RpcRequest::GetBalance, Some(json!(["deadbeefXjn8o3yroDHxUtKsZZgoy4GPkPPXfouKNHhx"])), ); assert!(balance.is_ok()); assert_eq!(balance.unwrap().as_u64().unwrap(), 50); - let last_id = RpcRequest::GetLastId.make_rpc_request(&rpc_client, 2, None); + let last_id = rpc_client.make_rpc_request(2, RpcRequest::GetLastId, None); assert!(last_id.is_ok()); assert_eq!( last_id.unwrap().as_str().unwrap(), @@ -268,7 +281,7 @@ mod tests { // Send erroneous parameter let last_id = - RpcRequest::GetLastId.make_rpc_request(&rpc_client, 3, Some(json!("paramter"))); + rpc_client.make_rpc_request(3, RpcRequest::GetLastId, Some(json!("paramter"))); assert_eq!(last_id.is_err(), true); } @@ -302,9 +315,9 @@ mod tests { let rpc_addr = receiver.recv().unwrap(); let rpc_client = RpcClient::new_from_socket(rpc_addr); - let balance = RpcRequest::GetBalance.retry_make_rpc_request( - &rpc_client, + let balance = rpc_client.retry_make_rpc_request( 1, + &RpcRequest::GetBalance, Some(json!(["deadbeefXjn8o3yroDHxUtKsZZgoy4GPkPPXfouKNHhw"])), 10, ); diff --git a/src/thin_client.rs b/src/thin_client.rs index 63a7ee7b35..f6ea9ead72 100644 --- a/src/thin_client.rs +++ b/src/thin_client.rs @@ -8,7 +8,7 @@ use crate::cluster_info::{ClusterInfo, ClusterInfoError, NodeInfo}; use crate::gossip_service::GossipService; use crate::packet::PACKET_DATA_SIZE; use crate::result::{Error, Result}; -use crate::rpc_request::{RpcClient, RpcRequest}; +use crate::rpc_request::{RpcClient, RpcRequest, RpcRequestHandler}; use bincode::serialize; use bs58; use hashbrown::HashMap; @@ -149,7 +149,9 @@ impl ThinClient { pub fn get_account_userdata(&mut self, pubkey: &Pubkey) -> io::Result>> { let params = json!([format!("{}", pubkey)]); - let resp = RpcRequest::GetAccountInfo.make_rpc_request(&self.rpc_client, 1, Some(params)); + let resp = self + .rpc_client + .make_rpc_request(1, RpcRequest::GetAccountInfo, Some(params)); if let Ok(account_json) = resp { let account: Account = serde_json::from_value(account_json).expect("deserialize account"); @@ -167,7 +169,9 @@ impl ThinClient { pub fn get_balance(&mut self, pubkey: &Pubkey) -> io::Result { trace!("get_balance sending request to {}", self.rpc_addr); let params = json!([format!("{}", pubkey)]); - let resp = RpcRequest::GetAccountInfo.make_rpc_request(&self.rpc_client, 1, Some(params)); + let resp = self + .rpc_client + .make_rpc_request(1, RpcRequest::GetAccountInfo, Some(params)); if let Ok(account_json) = resp { let account: Account = serde_json::from_value(account_json).expect("deserialize account"); @@ -193,7 +197,9 @@ impl ThinClient { let mut done = false; while !done { debug!("get_confirmation_time send_to {}", &self.rpc_addr); - let resp = RpcRequest::GetConfirmationTime.make_rpc_request(&self.rpc_client, 1, None); + let resp = self + .rpc_client + .make_rpc_request(1, RpcRequest::GetConfirmationTime, None); if let Ok(value) = resp { done = true; @@ -212,7 +218,9 @@ impl ThinClient { debug!("transaction_count"); let mut tries_left = 5; while tries_left > 0 { - let resp = RpcRequest::GetTransactionCount.make_rpc_request(&self.rpc_client, 1, None); + let resp = self + .rpc_client + .make_rpc_request(1, RpcRequest::GetTransactionCount, None); if let Ok(value) = resp { debug!("transaction_count recv_response: {:?}", value); @@ -233,7 +241,9 @@ impl ThinClient { let mut done = false; while !done { debug!("get_last_id send_to {}", &self.rpc_addr); - let resp = RpcRequest::GetLastId.make_rpc_request(&self.rpc_client, 1, None); + let resp = self + .rpc_client + .make_rpc_request(1, RpcRequest::GetLastId, None); if let Ok(value) = resp { done = true; @@ -309,9 +319,9 @@ impl ThinClient { let now = Instant::now(); let mut done = false; while !done { - let resp = RpcRequest::ConfirmTransaction.make_rpc_request( - &self.rpc_client, + let resp = self.rpc_client.make_rpc_request( 1, + RpcRequest::ConfirmTransaction, Some(params.clone()), ); diff --git a/src/vote_signer_proxy.rs b/src/vote_signer_proxy.rs index a5f46c7ae7..0cea818040 100644 --- a/src/vote_signer_proxy.rs +++ b/src/vote_signer_proxy.rs @@ -48,24 +48,27 @@ impl VoteSigner for RemoteVoteSigner { msg: &[u8], ) -> jsonrpc_core::Result { let params = json!([pubkey, sig, msg]); - let resp = RpcRequest::RegisterNode - .retry_make_rpc_request(&self.rpc_client, 1, Some(params), 5) + let resp = self + .rpc_client + .retry_make_rpc_request(1, &RpcRequest::RegisterNode, Some(params), 5) .unwrap(); let vote_account: Pubkey = serde_json::from_value(resp).unwrap(); Ok(vote_account) } fn sign(&self, pubkey: Pubkey, sig: &Signature, msg: &[u8]) -> jsonrpc_core::Result { let params = json!([pubkey, sig, msg]); - let resp = RpcRequest::SignVote - .make_rpc_request(&self.rpc_client, 1, Some(params)) + let resp = self + .rpc_client + .retry_make_rpc_request(1, &RpcRequest::SignVote, Some(params), 0) .unwrap(); let vote_signature: Signature = serde_json::from_value(resp).unwrap(); Ok(vote_signature) } fn deregister(&self, pubkey: Pubkey, sig: &Signature, msg: &[u8]) -> jsonrpc_core::Result<()> { let params = json!([pubkey, sig, msg]); - let _resp = RpcRequest::DeregisterNode - .retry_make_rpc_request(&self.rpc_client, 1, Some(params), 5) + let _resp = self + .rpc_client + .retry_make_rpc_request(1, &RpcRequest::DeregisterNode, Some(params), 5) .unwrap(); Ok(()) } diff --git a/tests/replicator.rs b/tests/replicator.rs index 8b9389e28a..4915f9a900 100644 --- a/tests/replicator.rs +++ b/tests/replicator.rs @@ -156,15 +156,15 @@ fn test_replicator_startup() { // chacha is not enabled #[cfg(feature = "chacha")] { - use solana::rpc_request::{RpcClient, RpcRequest}; + use solana::rpc_request::{RpcClient, RpcRequest, RpcRequestHandler}; use std::thread::sleep; let rpc_client = RpcClient::new_from_socket(validator_node_info.rpc); let mut non_zero_pubkeys = false; for _ in 0..30 { let params = json!([0]); - let pubkeys = RpcRequest::GetStoragePubkeysForEntryHeight - .make_rpc_request(&rpc_client, 1, Some(params)) + let pubkeys = rpc_client + .make_rpc_request(1, RpcRequest::GetStoragePubkeysForEntryHeight, Some(params)) .unwrap(); info!("pubkeys: {:?}", pubkeys); if pubkeys.as_array().unwrap().len() != 0 { diff --git a/wallet/src/lib.rs b/wallet/src/lib.rs new file mode 100644 index 0000000000..2fff25cab2 --- /dev/null +++ b/wallet/src/lib.rs @@ -0,0 +1 @@ +pub mod wallet; diff --git a/wallet/src/main.rs b/wallet/src/main.rs index e084786884..56a60fb76a 100644 --- a/wallet/src/main.rs +++ b/wallet/src/main.rs @@ -1,9 +1,7 @@ -mod wallet; - -use crate::wallet::{parse_command, process_command, WalletConfig, WalletError}; use clap::{crate_version, App, Arg, ArgMatches, SubCommand}; use solana::socketaddr; use solana_sdk::signature::{gen_keypair_file, read_keypair, KeypairUtil}; +use solana_wallet::wallet::{parse_command, process_command, WalletConfig, WalletError}; use std::error; use std::net::SocketAddr; @@ -53,6 +51,7 @@ pub fn parse_args(matches: &ArgMatches<'_>) -> Result, pub proxy: Option, pub drone_port: Option, + pub rpc_client: Option, } impl Default for WalletConfig { @@ -96,6 +103,7 @@ impl Default for WalletConfig { timeout: None, proxy: None, drone_port: None, + rpc_client: None, } } } @@ -122,7 +130,7 @@ pub fn parse_command( ("address", Some(_address_matches)) => Ok(WalletCommand::Address), ("airdrop", Some(airdrop_matches)) => { let tokens = airdrop_matches.value_of("tokens").unwrap().parse()?; - Ok(WalletCommand::AirDrop(tokens)) + Ok(WalletCommand::Airdrop(tokens)) } ("balance", Some(_balance_matches)) => Ok(WalletCommand::Balance), ("cancel", Some(cancel_matches)) => { @@ -311,23 +319,28 @@ pub fn process_command(config: &WalletConfig) -> Result unreachable!(), // Request an airdrop from Solana Drone; - WalletCommand::AirDrop(tokens) => { + WalletCommand::Airdrop(tokens) => { println!( "Requesting airdrop of {:?} tokens from {}", tokens, drone_addr ); let params = json!([format!("{}", config.id.pubkey())]); - let previous_balance = match RpcRequest::GetBalance - .make_rpc_request(&rpc_client, 1, Some(params))? + let previous_balance = match rpc_client + .make_rpc_request(1, RpcRequest::GetBalance, Some(params))? .as_u64() { Some(tokens) => tokens, @@ -340,8 +353,8 @@ pub fn process_command(config: &WalletConfig) -> Result Result { - println!("Balance requested..."); let params = json!([format!("{}", config.id.pubkey())]); - let balance = RpcRequest::GetBalance - .make_rpc_request(&rpc_client, 1, Some(params))? + let balance = rpc_client + .make_rpc_request(1, RpcRequest::GetBalance, Some(params))? .as_u64(); match balance { Some(0) => Ok("No account found! Request an airdrop to get started.".to_string()), @@ -368,16 +380,16 @@ pub fn process_command(config: &WalletConfig) -> Result { let last_id = get_last_id(&rpc_client)?; - let tx = + let mut tx = Transaction::budget_new_signature(&config.id, pubkey, config.id.pubkey(), last_id); - let signature_str = send_tx(&rpc_client, &tx)?; + let signature_str = send_and_confirm_tx(&rpc_client, &mut tx, &config.id)?; Ok(signature_str.to_string()) } // Confirm the last client transaction by signature WalletCommand::Confirm(signature) => { let params = json!([format!("{}", signature)]); - let confirmation = RpcRequest::ConfirmTransaction - .make_rpc_request(&rpc_client, 1, Some(params))? + let confirmation = rpc_client + .make_rpc_request(1, RpcRequest::ConfirmTransaction, Some(params))? .as_bool(); match confirmation { Some(b) => { @@ -395,8 +407,8 @@ pub fn process_command(config: &WalletConfig) -> Result { let params = json!([format!("{}", config.id.pubkey())]); - let balance = RpcRequest::GetBalance - .make_rpc_request(&rpc_client, 1, Some(params))? + let balance = rpc_client + .make_rpc_request(1, RpcRequest::GetBalance, Some(params))? .as_u64(); if let Some(tokens) = balance { if tokens < 1 { @@ -469,8 +481,8 @@ pub fn process_command(config: &WalletConfig) -> Result { - let transaction_count = RpcRequest::GetTransactionCount - .make_rpc_request(&rpc_client, 1, None)? + let transaction_count = rpc_client + .make_rpc_request(1, RpcRequest::GetTransactionCount, None)? .as_u64(); match transaction_count { Some(count) => Ok(count.to_string()), @@ -484,8 +496,8 @@ pub fn process_command(config: &WalletConfig) -> Result Result Result Result Result Result Result Result Result Result { - let params = json!(format!("{}", config.id.pubkey())); - let balance = RpcRequest::GetBalance - .make_rpc_request(&rpc_client, 1, Some(params))? + let params = json!([format!("{}", config.id.pubkey())]); + let balance = rpc_client + .make_rpc_request(1, RpcRequest::GetBalance, Some(params))? .as_u64(); if let Some(0) = balance { - let params = json!([format!("{}", config.id.pubkey()), 1]); - RpcRequest::RequestAirdrop - .make_rpc_request(&rpc_client, 1, Some(params)) + request_and_confirm_airdrop(&rpc_client, &drone_addr, &config.id.pubkey(), 1) .unwrap(); } let last_id = get_last_id(&rpc_client)?; - let tx = Transaction::budget_new_timestamp(&config.id, pubkey, to, dt, last_id); - let signature_str = send_tx(&rpc_client, &tx)?; + let mut tx = Transaction::budget_new_timestamp(&config.id, pubkey, to, dt, last_id); + let signature_str = send_and_confirm_tx(&rpc_client, &mut tx, &config.id)?; Ok(signature_str.to_string()) } // Apply witness signature to contract WalletCommand::Witness(to, pubkey) => { let params = json!([format!("{}", config.id.pubkey())]); - let balance = RpcRequest::GetBalance - .make_rpc_request(&rpc_client, 1, Some(params))? + let balance = rpc_client + .make_rpc_request(1, RpcRequest::GetBalance, Some(params))? .as_u64(); if let Some(0) = balance { - let params = json!([format!("{}", config.id.pubkey()), 1]); - RpcRequest::RequestAirdrop - .make_rpc_request(&rpc_client, 1, Some(params)) + request_and_confirm_airdrop(&rpc_client, &drone_addr, &config.id.pubkey(), 1) .unwrap(); } let last_id = get_last_id(&rpc_client)?; - let tx = Transaction::budget_new_signature(&config.id, pubkey, to, last_id); - let signature_str = send_tx(&rpc_client, &tx)?; + let mut tx = Transaction::budget_new_signature(&config.id, pubkey, to, last_id); + let signature_str = send_and_confirm_tx(&rpc_client, &mut tx, &config.id)?; Ok(signature_str.to_string()) } @@ -645,7 +653,7 @@ pub fn process_command(config: &WalletConfig) -> Result Result> { - let result = RpcRequest::GetLastId.make_rpc_request(rpc_client, 1, None)?; + let result = rpc_client.make_rpc_request(1, RpcRequest::GetLastId, None)?; if result.as_str().is_none() { Err(WalletError::RpcRequestError( "Received bad last_id".to_string(), @@ -661,7 +669,7 @@ fn get_last_id(rpc_client: &RpcClient) -> Result> { fn send_tx(rpc_client: &RpcClient, tx: &Transaction) -> Result> { let serialized = serialize(tx).unwrap(); let params = json!([serialized]); - let signature = RpcRequest::SendTransaction.make_rpc_request(rpc_client, 2, Some(params))?; + let signature = rpc_client.make_rpc_request(2, RpcRequest::SendTransaction, Some(params))?; if signature.as_str().is_none() { Err(WalletError::RpcRequestError( "Received result of an unexpected type".to_string(), @@ -676,7 +684,7 @@ fn confirm_tx( ) -> Result> { let params = json!([signature.to_string()]); let signature_status = - RpcRequest::GetSignatureStatus.make_rpc_request(rpc_client, 1, Some(params))?; + rpc_client.make_rpc_request(1, RpcRequest::GetSignatureStatus, Some(params))?; if let Some(status) = signature_status.as_str() { let rpc_status = RpcSignatureStatus::from_str(status).map_err(|_| { WalletError::RpcRequestError("Unable to parse signature status".to_string()) @@ -693,7 +701,7 @@ fn send_and_confirm_tx( rpc_client: &RpcClient, tx: &mut Transaction, signer: &Keypair, -) -> Result<(), Box> { +) -> Result> { let mut send_retries = 3; while send_retries > 0 { let mut status_retries = 4; @@ -708,7 +716,9 @@ fn send_and_confirm_tx( } else { break status; } - sleep(Duration::from_secs(1)); + if cfg!(not(test)) { + sleep(Duration::from_secs(1)); + } }; match status { RpcSignatureStatus::AccountInUse => { @@ -716,7 +726,7 @@ fn send_and_confirm_tx( send_retries -= 1; } RpcSignatureStatus::Confirmed => { - return Ok(()); + return Ok(signature_str); } _ => { return Err(WalletError::RpcRequestError(format!( @@ -742,7 +752,7 @@ fn resign_tx( Ok(()) } -fn request_and_confirm_airdrop( +pub fn request_and_confirm_airdrop( rpc_client: &RpcClient, drone_addr: &SocketAddr, id: &Pubkey, @@ -764,7 +774,9 @@ fn request_and_confirm_airdrop( } else { break status; } - sleep(Duration::from_secs(1)); + if cfg!(not(test)) { + sleep(Duration::from_secs(1)); + } }; match status { RpcSignatureStatus::AccountInUse => { @@ -783,7 +795,9 @@ fn request_and_confirm_airdrop( ))?; } next_last_id_retries -= 1; - sleep(Duration::from_secs(1)); + if cfg!(not(test)) { + sleep(Duration::from_secs(1)); + } } send_retries -= 1; if send_retries == 0 { @@ -811,59 +825,22 @@ mod tests { use super::*; use clap::{App, Arg, SubCommand}; use serde_json::Value; - use solana::bank::Bank; - use solana::cluster_info::Node; - use solana::db_ledger::create_tmp_genesis; - use solana::fullnode::Fullnode; - use solana::leader_scheduler::LeaderScheduler; - use solana::vote_signer_proxy::VoteSignerProxy; - use solana_drone::drone::run_local_drone; + use solana::rpc_mock::{PUBKEY, SIGNATURE}; use solana_sdk::signature::{gen_keypair_file, read_keypair, read_pkcs8, Keypair, KeypairUtil}; - use solana_vote_signer::rpc::LocalVoteSigner; use std::fs; - use std::fs::remove_dir_all; + use std::net::{Ipv4Addr, SocketAddr}; use std::path::Path; - use std::sync::mpsc::channel; - use std::sync::{Arc, RwLock}; - use std::thread::sleep; - use std::time::Duration; #[test] - fn test_resign_tx() { - let leader_keypair = Arc::new(Keypair::new()); - let leader_pubkey = leader_keypair.pubkey().clone(); - let leader = Node::new_localhost_with_pubkey(leader_keypair.pubkey()); - let leader_data = leader.info.clone(); - let (_alice, ledger_path) = - create_tmp_genesis("wallet_request_airdrop", 10_000_000, leader_data.id, 1000); - let signer = VoteSignerProxy::new(&leader_keypair, Box::new(LocalVoteSigner::default())); - let _server = Fullnode::new( - leader, - &ledger_path, - leader_keypair, - Arc::new(signer), - None, - false, - LeaderScheduler::from_bootstrap_leader(leader_pubkey), - None, - ); + fn test_wallet_config_drone_addr() { + let mut config = WalletConfig::default(); + assert_eq!(config.drone_addr(), socketaddr!(0, DRONE_PORT)); - let rpc_client = RpcClient::new_from_socket(leader_data.rpc); + config.drone_port = Some(1234); + assert_eq!(config.drone_addr(), socketaddr!(0, 1234)); - let key = Keypair::new(); - let to = Keypair::new().pubkey(); - let last_id = Hash::default(); - let prev_tx = Transaction::system_new(&key, to, 50, last_id); - let mut tx = Transaction::system_new(&key, to, 50, last_id); - - resign_tx(&rpc_client, &mut tx, &key).unwrap(); - - assert_ne!(prev_tx, tx); - assert_ne!(prev_tx.signatures, tx.signatures); - assert_ne!(prev_tx.last_id, tx.last_id); - assert_eq!(prev_tx.fee, tx.fee); - assert_eq!(prev_tx.account_keys, tx.account_keys); - assert_eq!(prev_tx.instructions, tx.instructions); + let rpc_addr = config.rpc_addr(socketaddr!(0, 9876)); + assert_eq!(rpc_addr, "http://0.0.0.0:9876"); } #[test] @@ -1033,7 +1010,7 @@ mod tests { .get_matches_from(vec!["test", "airdrop", "50"]); assert_eq!( parse_command(pubkey, &test_airdrop).unwrap(), - WalletCommand::AirDrop(50) + WalletCommand::Airdrop(50) ); let test_bad_airdrop = test_commands .clone() @@ -1194,113 +1171,156 @@ mod tests { ]); assert!(parse_command(pubkey, &test_bad_timestamp).is_err()); } + #[test] fn test_wallet_process_command() { - let bob_pubkey = Keypair::new().pubkey(); - - let leader_keypair = Arc::new(Keypair::new()); - let leader_pubkey = leader_keypair.pubkey().clone(); - let leader = Node::new_localhost_with_pubkey(leader_keypair.pubkey()); - let leader_data = leader.info.clone(); - let (alice, ledger_path) = - create_tmp_genesis("wallet_process_command", 10_000_000, leader_data.id, 1000); - let signer = VoteSignerProxy::new(&leader_keypair, Box::new(LocalVoteSigner::default())); - let server = Fullnode::new( - leader, - &ledger_path, - leader_keypair, - Arc::new(signer), - None, - false, - LeaderScheduler::from_bootstrap_leader(leader_pubkey), - None, - ); - - let (sender, receiver) = channel(); - run_local_drone(alice.keypair(), sender); - let drone_addr = receiver.recv().unwrap(); - + // Success cases let mut config = WalletConfig::default(); - config.network = leader_data.gossip; - config.drone_port = Some(drone_addr.port()); + config.rpc_client = Some(RpcClient::new("succeeds".to_string())); - let tokens = 50; - config.command = WalletCommand::AirDrop(tokens); - assert_eq!( - process_command(&config).unwrap(), - format!("Your balance is: {:?}", tokens) - ); + let keypair = Keypair::new(); + let pubkey = keypair.pubkey().to_string(); + config.id = keypair; + config.command = WalletCommand::Address; + assert_eq!(process_command(&config).unwrap(), pubkey); config.command = WalletCommand::Balance; - assert_eq!( - process_command(&config).unwrap(), - format!("Your balance is: {:?}", tokens) + assert_eq!(process_command(&config).unwrap(), "Your balance is: 50"); + + let process_id = Keypair::new().pubkey(); + config.command = WalletCommand::Cancel(process_id); + assert_eq!(process_command(&config).unwrap(), SIGNATURE); + + let good_signature = Signature::new(&bs58::decode(SIGNATURE).into_vec().unwrap()); + config.command = WalletCommand::Confirm(good_signature); + assert_eq!(process_command(&config).unwrap(), "Confirmed"); + let missing_signature = Signature::new(&bs58::decode("5VERv8NMvzbJMEkV8xnrLkEaWRtSz9CosKDYjCJjBRnbJLgp8uirBgmQpjKhoR4tjF3ZpRzrFmBV6UjKdiSZkQUW").into_vec().unwrap()); + config.command = WalletCommand::Confirm(missing_signature); + assert_eq!(process_command(&config).unwrap(), "Not found"); + + config.command = WalletCommand::GetTransactionCount; + assert_eq!(process_command(&config).unwrap(), "1234"); + + let bob_pubkey = Keypair::new().pubkey(); + config.command = WalletCommand::Pay(10, bob_pubkey, None, None, None, None); + let signature = process_command(&config); + assert!(signature.is_ok()); + assert_eq!(signature.unwrap(), SIGNATURE.to_string()); + + let date_string = "\"2018-09-19T17:30:59Z\""; + let dt: DateTime = serde_json::from_str(&date_string).unwrap(); + config.command = WalletCommand::Pay( + 10, + bob_pubkey, + Some(dt), + Some(config.id.pubkey()), + None, + None, ); + let result = process_command(&config); + assert!(result.is_ok()); + let json: Value = serde_json::from_str(&result.unwrap()).unwrap(); + assert_eq!( + json.as_object() + .unwrap() + .get("signature") + .unwrap() + .as_str() + .unwrap(), + SIGNATURE.to_string() + ); + + let witness = Keypair::new().pubkey(); + config.command = WalletCommand::Pay( + 10, + bob_pubkey, + None, + None, + Some(vec![witness]), + Some(config.id.pubkey()), + ); + let result = process_command(&config); + assert!(result.is_ok()); + let json: Value = serde_json::from_str(&result.unwrap()).unwrap(); + assert_eq!( + json.as_object() + .unwrap() + .get("signature") + .unwrap() + .as_str() + .unwrap(), + SIGNATURE.to_string() + ); + + let process_id = Keypair::new().pubkey(); + config.command = WalletCommand::TimeElapsed(bob_pubkey, process_id, dt); + let signature = process_command(&config); + assert!(signature.is_ok()); + assert_eq!(signature.unwrap(), SIGNATURE.to_string()); + + let witness = Keypair::new().pubkey(); + config.command = WalletCommand::Witness(bob_pubkey, witness); + let signature = process_command(&config); + assert!(signature.is_ok()); + assert_eq!(signature.unwrap(), SIGNATURE.to_string()); + + // Need airdrop cases + config.command = WalletCommand::Airdrop(50); + assert!(process_command(&config).is_err()); + + config.rpc_client = Some(RpcClient::new("airdrop".to_string())); + config.command = WalletCommand::TimeElapsed(bob_pubkey, process_id, dt); + let signature = process_command(&config); + assert!(signature.is_ok()); + assert_eq!(signature.unwrap(), SIGNATURE.to_string()); + + let witness = Keypair::new().pubkey(); + config.command = WalletCommand::Witness(bob_pubkey, witness); + let signature = process_command(&config); + assert!(signature.is_ok()); + assert_eq!(signature.unwrap(), SIGNATURE.to_string()); + + // Failture cases + config.rpc_client = Some(RpcClient::new("fails".to_string())); + + config.command = WalletCommand::Airdrop(50); + assert!(process_command(&config).is_err()); + + config.command = WalletCommand::Balance; + assert!(process_command(&config).is_err()); + + let any_signature = Signature::new(&bs58::decode(SIGNATURE).into_vec().unwrap()); + config.command = WalletCommand::Confirm(any_signature); + assert!(process_command(&config).is_err()); + + config.command = WalletCommand::GetTransactionCount; + assert!(process_command(&config).is_err()); config.command = WalletCommand::Pay(10, bob_pubkey, None, None, None, None); - let sig_response = process_command(&config); - assert!(sig_response.is_ok()); + assert!(process_command(&config).is_err()); - let signatures = bs58::decode(sig_response.unwrap()) - .into_vec() - .expect("base58-encoded signature"); - let signature = Signature::new(&signatures); - config.command = WalletCommand::Confirm(signature); - assert_eq!(process_command(&config).unwrap(), "Confirmed"); - - config.command = WalletCommand::Balance; - assert_eq!( - process_command(&config).unwrap(), - format!("Your balance is: {:?}", tokens - 10) - ); - - server.close().unwrap(); - remove_dir_all(ledger_path).unwrap(); - } - #[test] - fn test_wallet_request_airdrop() { - let leader_keypair = Arc::new(Keypair::new()); - let leader_pubkey = leader_keypair.pubkey().clone(); - let leader = Node::new_localhost_with_pubkey(leader_keypair.pubkey()); - let leader_data = leader.info.clone(); - let (alice, ledger_path) = - create_tmp_genesis("wallet_request_airdrop", 10_000_000, leader_data.id, 1000); - let signer = VoteSignerProxy::new(&leader_keypair, Box::new(LocalVoteSigner::default())); - let server = Fullnode::new( - leader, - &ledger_path, - leader_keypair, - Arc::new(signer), + config.command = WalletCommand::Pay( + 10, + bob_pubkey, + Some(dt), + Some(config.id.pubkey()), None, - false, - LeaderScheduler::from_bootstrap_leader(leader_pubkey), None, ); + assert!(process_command(&config).is_err()); - let (sender, receiver) = channel(); - run_local_drone(alice.keypair(), sender); - let drone_addr = receiver.recv().unwrap(); + config.command = WalletCommand::Pay( + 10, + bob_pubkey, + None, + None, + Some(vec![witness]), + Some(config.id.pubkey()), + ); + assert!(process_command(&config).is_err()); - let mut bob_config = WalletConfig::default(); - bob_config.network = leader_data.gossip; - bob_config.drone_port = Some(drone_addr.port()); - bob_config.command = WalletCommand::AirDrop(50); - - let sig_response = process_command(&bob_config); - assert!(sig_response.is_ok()); - - let rpc_client = RpcClient::new_from_socket(leader_data.rpc); - - let params = json!([format!("{}", bob_config.id.pubkey())]); - let balance = RpcRequest::GetBalance - .make_rpc_request(&rpc_client, 1, Some(params)) - .unwrap() - .as_u64() - .unwrap(); - assert_eq!(balance, 50); - - server.close().unwrap(); - remove_dir_all(ledger_path).unwrap(); + config.command = WalletCommand::TimeElapsed(bob_pubkey, process_id, dt); + assert!(process_command(&config).is_err()); } fn tmp_file_path(name: &str) -> String { @@ -1326,374 +1346,123 @@ mod tests { fs::remove_file(&outfile).unwrap(); assert!(!Path::new(&outfile).exists()); } + #[test] - #[ignore] - fn test_wallet_timestamp_tx() { - let bob_pubkey = Keypair::new().pubkey(); + fn test_wallet_get_last_id() { + let rpc_client = RpcClient::new("succeeds".to_string()); - let leader_keypair = Arc::new(Keypair::new()); - let leader = Node::new_localhost_with_pubkey(leader_keypair.pubkey()); - let leader_data = leader.info.clone(); - let (alice, ledger_path) = - create_tmp_genesis("wallet_timestamp_tx", 10_000_000, leader_data.id, 1000); - let mut bank = Bank::new(&alice); + let vec = bs58::decode(PUBKEY).into_vec().unwrap(); + let expected_last_id = Hash::new(&vec); - let leader_scheduler = Arc::new(RwLock::new(LeaderScheduler::from_bootstrap_leader( - leader_data.id, - ))); - bank.leader_scheduler = leader_scheduler; - let vote_account_keypair = Arc::new(Keypair::new()); - let last_id = bank.last_id(); - let signer = - VoteSignerProxy::new(&vote_account_keypair, Box::new(LocalVoteSigner::default())); - let server = Fullnode::new_with_bank( - leader_keypair, - Arc::new(signer), - bank, - None, - 0, - &last_id, - leader, - None, - &ledger_path, - false, - None, - ); - sleep(Duration::from_millis(900)); + let last_id = get_last_id(&rpc_client); + assert!(last_id.is_ok()); + assert_eq!(last_id.unwrap(), expected_last_id); - let (sender, receiver) = channel(); - run_local_drone(alice.keypair(), sender); - let drone_addr = receiver.recv().unwrap(); + let rpc_client = RpcClient::new("fails".to_string()); - let rpc_client = RpcClient::new_from_socket(leader_data.rpc); - - let mut config_payer = WalletConfig::default(); - config_payer.network = leader_data.gossip; - config_payer.drone_port = Some(drone_addr.port()); - - let mut config_witness = WalletConfig::default(); - config_witness.network = leader_data.gossip; - config_witness.drone_port = Some(drone_addr.port()); - - assert_ne!(config_payer.id.pubkey(), config_witness.id.pubkey()); - - request_and_confirm_airdrop(&rpc_client, &drone_addr, &config_payer.id.pubkey(), 50) - .unwrap(); - - // Make transaction (from config_payer to bob_pubkey) requiring timestamp from config_witness - let date_string = "\"2018-09-19T17:30:59Z\""; - let dt: DateTime = serde_json::from_str(&date_string).unwrap(); - config_payer.command = WalletCommand::Pay( - 10, - bob_pubkey, - Some(dt), - Some(config_witness.id.pubkey()), - None, - None, - ); - let sig_response = process_command(&config_payer); - assert!(sig_response.is_ok()); - - let object: Value = serde_json::from_str(&sig_response.unwrap()).unwrap(); - let process_id_str = object.get("processId").unwrap().as_str().unwrap(); - let process_id_vec = bs58::decode(process_id_str) - .into_vec() - .expect("base58-encoded public key"); - let process_id = Pubkey::new(&process_id_vec); - - let params = json!([format!("{}", config_payer.id.pubkey())]); - let config_payer_balance = RpcRequest::GetBalance - .make_rpc_request(&rpc_client, 1, Some(params)) - .unwrap() - .as_u64() - .unwrap(); - assert_eq!(config_payer_balance, 39); - let params = json!([format!("{}", process_id)]); - let contract_balance = RpcRequest::GetBalance - .make_rpc_request(&rpc_client, 1, Some(params)) - .unwrap() - .as_u64() - .unwrap(); - assert_eq!(contract_balance, 11); - let params = json!([format!("{}", bob_pubkey)]); - let recipient_balance = RpcRequest::GetBalance - .make_rpc_request(&rpc_client, 1, Some(params)) - .unwrap() - .as_u64() - .unwrap(); - assert_eq!(recipient_balance, 0); - - // Sign transaction by config_witness - config_witness.command = WalletCommand::TimeElapsed(bob_pubkey, process_id, dt); - let sig_response = process_command(&config_witness); - assert!(sig_response.is_ok()); - - let params = json!([format!("{}", config_payer.id.pubkey())]); - let config_payer_balance = RpcRequest::GetBalance - .make_rpc_request(&rpc_client, 1, Some(params)) - .unwrap() - .as_u64() - .unwrap(); - assert_eq!(config_payer_balance, 39); - let params = json!([format!("{}", process_id)]); - let contract_balance = RpcRequest::GetBalance - .make_rpc_request(&rpc_client, 1, Some(params)) - .unwrap() - .as_u64() - .unwrap(); - assert_eq!(contract_balance, 1); - let params = json!([format!("{}", bob_pubkey)]); - let recipient_balance = RpcRequest::GetBalance - .make_rpc_request(&rpc_client, 1, Some(params)) - .unwrap() - .as_u64() - .unwrap(); - assert_eq!(recipient_balance, 10); - - server.close().unwrap(); - remove_dir_all(ledger_path).unwrap(); + let last_id = get_last_id(&rpc_client); + assert!(last_id.is_err()); } + #[test] - #[ignore] - fn test_wallet_witness_tx() { - let bob_pubkey = Keypair::new().pubkey(); - let leader_keypair = Arc::new(Keypair::new()); - let leader = Node::new_localhost_with_pubkey(leader_keypair.pubkey()); - let leader_data = leader.info.clone(); - let (alice, ledger_path) = - create_tmp_genesis("wallet_witness_tx", 10_000_000, leader_data.id, 1000); - let mut bank = Bank::new(&alice); + fn test_wallet_send_tx() { + let rpc_client = RpcClient::new("succeeds".to_string()); - let mut config_payer = WalletConfig::default(); - let mut config_witness = WalletConfig::default(); - let leader_scheduler = Arc::new(RwLock::new(LeaderScheduler::from_bootstrap_leader( - leader_data.id, - ))); - bank.leader_scheduler = leader_scheduler; - let vote_account_keypair = Arc::new(Keypair::new()); - let last_id = bank.last_id(); - let signer = - VoteSignerProxy::new(&vote_account_keypair, Box::new(LocalVoteSigner::default())); - let server = Fullnode::new_with_bank( - leader_keypair, - Arc::new(signer), - bank, - None, - 0, - &last_id, - leader, - None, - &ledger_path, - false, - None, - ); - sleep(Duration::from_millis(900)); + let key = Keypair::new(); + let to = Keypair::new().pubkey(); + let last_id = Hash::default(); + let tx = Transaction::system_new(&key, to, 50, last_id); - let (sender, receiver) = channel(); - run_local_drone(alice.keypair(), sender); - let drone_addr = receiver.recv().unwrap(); + let signature = send_tx(&rpc_client, &tx); + assert!(signature.is_ok()); + assert_eq!(signature.unwrap(), SIGNATURE.to_string()); - let rpc_client = RpcClient::new_from_socket(leader_data.rpc); + let rpc_client = RpcClient::new("fails".to_string()); - assert_ne!(config_payer.id.pubkey(), config_witness.id.pubkey()); - - request_and_confirm_airdrop(&rpc_client, &drone_addr, &config_payer.id.pubkey(), 50) - .unwrap(); - - // Make transaction (from config_payer to bob_pubkey) requiring witness signature from config_witness - config_payer.command = WalletCommand::Pay( - 10, - bob_pubkey, - None, - None, - Some(vec![config_witness.id.pubkey()]), - None, - ); - let sig_response = process_command(&config_payer); - assert!(sig_response.is_ok()); - - let object: Value = serde_json::from_str(&sig_response.unwrap()).unwrap(); - let process_id_str = object.get("processId").unwrap().as_str().unwrap(); - let process_id_vec = bs58::decode(process_id_str) - .into_vec() - .expect("base58-encoded public key"); - let process_id = Pubkey::new(&process_id_vec); - - let params = json!([format!("{}", config_payer.id.pubkey())]); - let config_payer_balance = RpcRequest::GetBalance - .make_rpc_request(&rpc_client, 1, Some(params)) - .unwrap() - .as_u64() - .unwrap(); - assert_eq!(config_payer_balance, 39); - let params = json!([format!("{}", process_id)]); - let contract_balance = RpcRequest::GetBalance - .make_rpc_request(&rpc_client, 1, Some(params)) - .unwrap() - .as_u64() - .unwrap(); - assert_eq!(contract_balance, 11); - let params = json!([format!("{}", bob_pubkey)]); - let recipient_balance = RpcRequest::GetBalance - .make_rpc_request(&rpc_client, 1, Some(params)) - .unwrap() - .as_u64() - .unwrap(); - assert_eq!(recipient_balance, 0); - - // Sign transaction by config_witness - config_witness.command = WalletCommand::Witness(bob_pubkey, process_id); - let sig_response = process_command(&config_witness); - assert!(sig_response.is_ok()); - - let params = json!([format!("{}", config_payer.id.pubkey())]); - let config_payer_balance = RpcRequest::GetBalance - .make_rpc_request(&rpc_client, 1, Some(params)) - .unwrap() - .as_u64() - .unwrap(); - assert_eq!(config_payer_balance, 39); - let params = json!([format!("{}", process_id)]); - let contract_balance = RpcRequest::GetBalance - .make_rpc_request(&rpc_client, 1, Some(params)) - .unwrap() - .as_u64() - .unwrap(); - assert_eq!(contract_balance, 1); - let params = json!([format!("{}", bob_pubkey)]); - let recipient_balance = RpcRequest::GetBalance - .make_rpc_request(&rpc_client, 1, Some(params)) - .unwrap() - .as_u64() - .unwrap(); - assert_eq!(recipient_balance, 10); - - server.close().unwrap(); - remove_dir_all(ledger_path).unwrap(); + let signature = send_tx(&rpc_client, &tx); + assert!(signature.is_err()); } + #[test] - #[ignore] - fn test_wallet_cancel_tx() { - let bob_pubkey = Keypair::new().pubkey(); - let leader_keypair = Arc::new(Keypair::new()); - let leader = Node::new_localhost_with_pubkey(leader_keypair.pubkey()); - let leader_data = leader.info.clone(); + fn test_wallet_confirm_tx() { + let rpc_client = RpcClient::new("succeeds".to_string()); + let signature = "good_signature"; + let status = confirm_tx(&rpc_client, &signature); + assert!(status.is_ok()); + assert_eq!(status.unwrap(), RpcSignatureStatus::Confirmed); - let (alice, ledger_path) = - create_tmp_genesis("wallet_cancel_tx", 10_000_000, leader_data.id, 1000); - let mut bank = Bank::new(&alice); + let rpc_client = RpcClient::new("bad_sig_status".to_string()); + let signature = "bad_status"; + let status = confirm_tx(&rpc_client, &signature); + assert!(status.is_err()); - let leader_scheduler = Arc::new(RwLock::new(LeaderScheduler::from_bootstrap_leader( - leader_data.id, - ))); - bank.leader_scheduler = leader_scheduler; - let vote_account_keypair = Arc::new(Keypair::new()); - let last_id = bank.last_id(); - let signer = - VoteSignerProxy::new(&vote_account_keypair, Box::new(LocalVoteSigner::default())); - let server = Fullnode::new_with_bank( - leader_keypair, - Arc::new(signer), - bank, - None, - 0, - &last_id, - leader, - None, - &ledger_path, - false, - None, - ); - sleep(Duration::from_millis(900)); + let rpc_client = RpcClient::new("fails".to_string()); + let signature = "bad_status_fmt"; + let status = confirm_tx(&rpc_client, &signature); + assert!(status.is_err()); + } - let (sender, receiver) = channel(); - run_local_drone(alice.keypair(), sender); - let drone_addr = receiver.recv().unwrap(); + #[test] + fn test_wallet_send_and_confirm_tx() { + let rpc_client = RpcClient::new("succeeds".to_string()); - let rpc_client = RpcClient::new_from_socket(leader_data.rpc); + let key = Keypair::new(); + let to = Keypair::new().pubkey(); + let last_id = Hash::default(); + let mut tx = Transaction::system_new(&key, to, 50, last_id); - let mut config_payer = WalletConfig::default(); - config_payer.network = leader_data.gossip; - config_payer.drone_port = Some(drone_addr.port()); + let signer = Keypair::new(); - let mut config_witness = WalletConfig::default(); - config_witness.network = leader_data.gossip; - config_witness.drone_port = Some(drone_addr.port()); + let result = send_and_confirm_tx(&rpc_client, &mut tx, &signer); + assert!(result.is_ok()); - assert_ne!(config_payer.id.pubkey(), config_witness.id.pubkey()); + let rpc_client = RpcClient::new("account_in_use".to_string()); + let result = send_and_confirm_tx(&rpc_client, &mut tx, &signer); + assert!(result.is_err()); - request_and_confirm_airdrop(&rpc_client, &drone_addr, &config_payer.id.pubkey(), 50) - .unwrap(); + let rpc_client = RpcClient::new("fails".to_string()); + let result = send_and_confirm_tx(&rpc_client, &mut tx, &signer); + assert!(result.is_err()); + } - // Make transaction (from config_payer to bob_pubkey) requiring witness signature from config_witness - config_payer.command = WalletCommand::Pay( - 10, - bob_pubkey, - None, - None, - Some(vec![config_witness.id.pubkey()]), - Some(config_payer.id.pubkey()), - ); - let sig_response = process_command(&config_payer); - assert!(sig_response.is_ok()); + #[test] + fn test_wallet_resign_tx() { + let rpc_client = RpcClient::new("succeeds".to_string()); - let object: Value = serde_json::from_str(&sig_response.unwrap()).unwrap(); - let process_id_str = object.get("processId").unwrap().as_str().unwrap(); - let process_id_vec = bs58::decode(process_id_str) + let key = Keypair::new(); + let to = Keypair::new().pubkey(); + let vec = bs58::decode("HUu3LwEzGRsUkuJS121jzkPJW39Kq62pXCTmTa1F9jDL") .into_vec() - .expect("base58-encoded public key"); - let process_id = Pubkey::new(&process_id_vec); + .unwrap(); + let last_id = Hash::new(&vec); + let prev_tx = Transaction::system_new(&key, to, 50, last_id); + let mut tx = Transaction::system_new(&key, to, 50, last_id); - let params = json!([format!("{}", config_payer.id.pubkey())]); - let config_payer_balance = RpcRequest::GetBalance - .make_rpc_request(&rpc_client, 1, Some(params)) - .unwrap() - .as_u64() - .unwrap(); - assert_eq!(config_payer_balance, 39); - let params = json!([format!("{}", process_id)]); - let contract_balance = RpcRequest::GetBalance - .make_rpc_request(&rpc_client, 1, Some(params)) - .unwrap() - .as_u64() - .unwrap(); - assert_eq!(contract_balance, 11); - let params = json!([format!("{}", bob_pubkey)]); - let recipient_balance = RpcRequest::GetBalance - .make_rpc_request(&rpc_client, 1, Some(params)) - .unwrap() - .as_u64() - .unwrap(); - assert_eq!(recipient_balance, 0); + resign_tx(&rpc_client, &mut tx, &key).unwrap(); - // Sign transaction by config_witness - config_payer.command = WalletCommand::Cancel(process_id); - let sig_response = process_command(&config_payer); - assert!(sig_response.is_ok()); + assert_ne!(prev_tx, tx); + assert_ne!(prev_tx.signatures, tx.signatures); + assert_ne!(prev_tx.last_id, tx.last_id); + assert_eq!(prev_tx.fee, tx.fee); + assert_eq!(prev_tx.account_keys, tx.account_keys); + assert_eq!(prev_tx.instructions, tx.instructions); + } - let params = json!([format!("{}", config_payer.id.pubkey())]); - let config_payer_balance = RpcRequest::GetBalance - .make_rpc_request(&rpc_client, 1, Some(params)) - .unwrap() - .as_u64() - .unwrap(); - assert_eq!(config_payer_balance, 49); - let params = json!([format!("{}", process_id)]); - let contract_balance = RpcRequest::GetBalance - .make_rpc_request(&rpc_client, 1, Some(params)) - .unwrap() - .as_u64() - .unwrap(); - assert_eq!(contract_balance, 1); - let params = json!([format!("{}", bob_pubkey)]); - let recipient_balance = RpcRequest::GetBalance - .make_rpc_request(&rpc_client, 1, Some(params)) - .unwrap() - .as_u64() - .unwrap(); - assert_eq!(recipient_balance, 0); + #[test] + fn test_request_and_confirm_airdrop() { + let rpc_client = RpcClient::new("succeeds".to_string()); + let drone_addr = socketaddr!(0, 0); + let id = Keypair::new().pubkey(); + let tokens = 50; + assert_eq!( + request_and_confirm_airdrop(&rpc_client, &drone_addr, &id, tokens).unwrap(), + () + ); - server.close().unwrap(); - remove_dir_all(ledger_path).unwrap(); + let rpc_client = RpcClient::new("account_in_use".to_string()); + assert!(request_and_confirm_airdrop(&rpc_client, &drone_addr, &id, tokens).is_err()); + + let tokens = 0; + assert!(request_and_confirm_airdrop(&rpc_client, &drone_addr, &id, tokens).is_err()); } } diff --git a/wallet/tests/pay.rs b/wallet/tests/pay.rs new file mode 100644 index 0000000000..f6e969a62e --- /dev/null +++ b/wallet/tests/pay.rs @@ -0,0 +1,320 @@ +use chrono::prelude::*; +use serde_json::{json, Value}; +use solana::bank::Bank; +use solana::cluster_info::Node; +use solana::db_ledger::create_tmp_ledger_with_mint; +use solana::fullnode::Fullnode; +use solana::leader_scheduler::LeaderScheduler; +use solana::mint::Mint; +use solana::rpc_request::{RpcClient, RpcRequest, RpcRequestHandler}; +use solana::vote_signer_proxy::VoteSignerProxy; +use solana_drone::drone::run_local_drone; +use solana_sdk::pubkey::Pubkey; +use solana_sdk::signature::{Keypair, KeypairUtil}; +use solana_vote_signer::rpc::LocalVoteSigner; +use solana_wallet::wallet::{ + process_command, request_and_confirm_airdrop, WalletCommand, WalletConfig, +}; +use std::fs::remove_dir_all; +use std::sync::mpsc::channel; +use std::sync::{Arc, RwLock}; +use std::thread::sleep; +use std::time::Duration; + +fn check_balance(expected_balance: u64, client: &RpcClient, params: Value) { + let balance = client + .make_rpc_request(1, RpcRequest::GetBalance, Some(params)) + .unwrap() + .as_u64() + .unwrap(); + assert_eq!(balance, expected_balance); +} + +#[test] +fn test_wallet_timestamp_tx() { + let leader_keypair = Arc::new(Keypair::new()); + let leader = Node::new_localhost_with_pubkey(leader_keypair.pubkey()); + let leader_data = leader.info.clone(); + + let alice = Mint::new(10_000); + let mut bank = Bank::new(&alice); + let bob_pubkey = Keypair::new().pubkey(); + let ledger_path = create_tmp_ledger_with_mint("thin_client", &alice); + let entry_height = alice.create_entries().len() as u64; + + let leader_scheduler = Arc::new(RwLock::new(LeaderScheduler::from_bootstrap_leader( + leader_data.id, + ))); + bank.leader_scheduler = leader_scheduler; + let vote_account_keypair = Arc::new(Keypair::new()); + let vote_signer = + VoteSignerProxy::new(&vote_account_keypair, Box::new(LocalVoteSigner::default())); + let last_id = bank.last_id(); + let server = Fullnode::new_with_bank( + leader_keypair, + Arc::new(vote_signer), + bank, + None, + entry_height, + &last_id, + leader, + None, + &ledger_path, + false, + None, + ); + sleep(Duration::from_millis(900)); + + let (sender, receiver) = channel(); + run_local_drone(alice.keypair(), sender); + let drone_addr = receiver.recv().unwrap(); + + let rpc_client = RpcClient::new_from_socket(leader_data.rpc); + + let mut config_payer = WalletConfig::default(); + config_payer.network = leader_data.gossip; + config_payer.drone_port = Some(drone_addr.port()); + + let mut config_witness = WalletConfig::default(); + config_witness.network = leader_data.gossip; + config_witness.drone_port = Some(drone_addr.port()); + + assert_ne!(config_payer.id.pubkey(), config_witness.id.pubkey()); + + request_and_confirm_airdrop(&rpc_client, &drone_addr, &config_payer.id.pubkey(), 50).unwrap(); + let params = json!([format!("{}", config_payer.id.pubkey())]); + check_balance(50, &rpc_client, params); + + // Make transaction (from config_payer to bob_pubkey) requiring timestamp from config_witness + let date_string = "\"2018-09-19T17:30:59Z\""; + let dt: DateTime = serde_json::from_str(&date_string).unwrap(); + config_payer.command = WalletCommand::Pay( + 10, + bob_pubkey, + Some(dt), + Some(config_witness.id.pubkey()), + None, + None, + ); + let sig_response = process_command(&config_payer); + assert!(sig_response.is_ok()); + + let object: Value = serde_json::from_str(&sig_response.unwrap()).unwrap(); + let process_id_str = object.get("processId").unwrap().as_str().unwrap(); + let process_id_vec = bs58::decode(process_id_str) + .into_vec() + .expect("base58-encoded public key"); + let process_id = Pubkey::new(&process_id_vec); + + let params = json!([format!("{}", config_payer.id.pubkey())]); + check_balance(39, &rpc_client, params); // config_payer balance + let params = json!([format!("{}", process_id)]); + check_balance(11, &rpc_client, params); // contract balance + let params = json!([format!("{}", bob_pubkey)]); + check_balance(0, &rpc_client, params); // recipient balance + + // Sign transaction by config_witness + config_witness.command = WalletCommand::TimeElapsed(bob_pubkey, process_id, dt); + let sig_response = process_command(&config_witness); + assert!(sig_response.is_ok()); + + let params = json!([format!("{}", config_payer.id.pubkey())]); + check_balance(39, &rpc_client, params); // config_payer balance + let params = json!([format!("{}", process_id)]); + check_balance(1, &rpc_client, params); // contract balance + let params = json!([format!("{}", bob_pubkey)]); + check_balance(10, &rpc_client, params); // recipient balance + + server.close().unwrap(); + remove_dir_all(ledger_path).unwrap(); +} + +#[test] +fn test_wallet_witness_tx() { + let leader_keypair = Arc::new(Keypair::new()); + let leader = Node::new_localhost_with_pubkey(leader_keypair.pubkey()); + let leader_data = leader.info.clone(); + + let alice = Mint::new(10_000); + let mut bank = Bank::new(&alice); + let bob_pubkey = Keypair::new().pubkey(); + let ledger_path = create_tmp_ledger_with_mint("thin_client", &alice); + let entry_height = alice.create_entries().len() as u64; + + let leader_scheduler = Arc::new(RwLock::new(LeaderScheduler::from_bootstrap_leader( + leader_data.id, + ))); + bank.leader_scheduler = leader_scheduler; + let vote_account_keypair = Arc::new(Keypair::new()); + let vote_signer = + VoteSignerProxy::new(&vote_account_keypair, Box::new(LocalVoteSigner::default())); + let last_id = bank.last_id(); + let server = Fullnode::new_with_bank( + leader_keypair, + Arc::new(vote_signer), + bank, + None, + entry_height, + &last_id, + leader, + None, + &ledger_path, + false, + None, + ); + sleep(Duration::from_millis(900)); + + let (sender, receiver) = channel(); + run_local_drone(alice.keypair(), sender); + let drone_addr = receiver.recv().unwrap(); + + let rpc_client = RpcClient::new_from_socket(leader_data.rpc); + + let mut config_payer = WalletConfig::default(); + config_payer.network = leader_data.gossip; + config_payer.drone_port = Some(drone_addr.port()); + + let mut config_witness = WalletConfig::default(); + config_witness.network = leader_data.gossip; + config_witness.drone_port = Some(drone_addr.port()); + + assert_ne!(config_payer.id.pubkey(), config_witness.id.pubkey()); + + request_and_confirm_airdrop(&rpc_client, &drone_addr, &config_payer.id.pubkey(), 50).unwrap(); + + // Make transaction (from config_payer to bob_pubkey) requiring witness signature from config_witness + config_payer.command = WalletCommand::Pay( + 10, + bob_pubkey, + None, + None, + Some(vec![config_witness.id.pubkey()]), + None, + ); + let sig_response = process_command(&config_payer); + assert!(sig_response.is_ok()); + + let object: Value = serde_json::from_str(&sig_response.unwrap()).unwrap(); + let process_id_str = object.get("processId").unwrap().as_str().unwrap(); + let process_id_vec = bs58::decode(process_id_str) + .into_vec() + .expect("base58-encoded public key"); + let process_id = Pubkey::new(&process_id_vec); + + let params = json!([format!("{}", config_payer.id.pubkey())]); + check_balance(39, &rpc_client, params); // config_payer balance + let params = json!([format!("{}", process_id)]); + check_balance(11, &rpc_client, params); // contract balance + let params = json!([format!("{}", bob_pubkey)]); + check_balance(0, &rpc_client, params); // recipient balance + + // Sign transaction by config_witness + config_witness.command = WalletCommand::Witness(bob_pubkey, process_id); + let sig_response = process_command(&config_witness); + assert!(sig_response.is_ok()); + + let params = json!([format!("{}", config_payer.id.pubkey())]); + check_balance(39, &rpc_client, params); // config_payer balance + let params = json!([format!("{}", process_id)]); + check_balance(1, &rpc_client, params); // contract balance + let params = json!([format!("{}", bob_pubkey)]); + check_balance(10, &rpc_client, params); // recipient balance + + server.close().unwrap(); + remove_dir_all(ledger_path).unwrap(); +} + +#[test] +fn test_wallet_cancel_tx() { + let leader_keypair = Arc::new(Keypair::new()); + let leader = Node::new_localhost_with_pubkey(leader_keypair.pubkey()); + let leader_data = leader.info.clone(); + + let alice = Mint::new(10_000); + let mut bank = Bank::new(&alice); + let bob_pubkey = Keypair::new().pubkey(); + let ledger_path = create_tmp_ledger_with_mint("thin_client", &alice); + let entry_height = alice.create_entries().len() as u64; + + let leader_scheduler = Arc::new(RwLock::new(LeaderScheduler::from_bootstrap_leader( + leader_data.id, + ))); + bank.leader_scheduler = leader_scheduler; + let vote_account_keypair = Arc::new(Keypair::new()); + let vote_signer = + VoteSignerProxy::new(&vote_account_keypair, Box::new(LocalVoteSigner::default())); + let last_id = bank.last_id(); + let server = Fullnode::new_with_bank( + leader_keypair, + Arc::new(vote_signer), + bank, + None, + entry_height, + &last_id, + leader, + None, + &ledger_path, + false, + None, + ); + sleep(Duration::from_millis(900)); + + let (sender, receiver) = channel(); + run_local_drone(alice.keypair(), sender); + let drone_addr = receiver.recv().unwrap(); + + let rpc_client = RpcClient::new_from_socket(leader_data.rpc); + + let mut config_payer = WalletConfig::default(); + config_payer.network = leader_data.gossip; + config_payer.drone_port = Some(drone_addr.port()); + + let mut config_witness = WalletConfig::default(); + config_witness.network = leader_data.gossip; + config_witness.drone_port = Some(drone_addr.port()); + + assert_ne!(config_payer.id.pubkey(), config_witness.id.pubkey()); + + request_and_confirm_airdrop(&rpc_client, &drone_addr, &config_payer.id.pubkey(), 50).unwrap(); + + // Make transaction (from config_payer to bob_pubkey) requiring witness signature from config_witness + config_payer.command = WalletCommand::Pay( + 10, + bob_pubkey, + None, + None, + Some(vec![config_witness.id.pubkey()]), + Some(config_payer.id.pubkey()), + ); + let sig_response = process_command(&config_payer); + assert!(sig_response.is_ok()); + + let object: Value = serde_json::from_str(&sig_response.unwrap()).unwrap(); + let process_id_str = object.get("processId").unwrap().as_str().unwrap(); + let process_id_vec = bs58::decode(process_id_str) + .into_vec() + .expect("base58-encoded public key"); + let process_id = Pubkey::new(&process_id_vec); + + let params = json!([format!("{}", config_payer.id.pubkey())]); + check_balance(39, &rpc_client, params); // config_payer balance + let params = json!([format!("{}", process_id)]); + check_balance(11, &rpc_client, params); // contract balance + let params = json!([format!("{}", bob_pubkey)]); + check_balance(0, &rpc_client, params); // recipient balance + + // Sign transaction by config_witness + config_payer.command = WalletCommand::Cancel(process_id); + let sig_response = process_command(&config_payer); + assert!(sig_response.is_ok()); + + let params = json!([format!("{}", config_payer.id.pubkey())]); + check_balance(49, &rpc_client, params); // config_payer balance + let params = json!([format!("{}", process_id)]); + check_balance(1, &rpc_client, params); // contract balance + let params = json!([format!("{}", bob_pubkey)]); + check_balance(0, &rpc_client, params); // recipient balance + + server.close().unwrap(); + remove_dir_all(ledger_path).unwrap(); +} diff --git a/wallet/tests/request_airdrop.rs b/wallet/tests/request_airdrop.rs new file mode 100644 index 0000000000..2a8123898d --- /dev/null +++ b/wallet/tests/request_airdrop.rs @@ -0,0 +1,78 @@ +use serde_json::json; +use solana::bank::Bank; +use solana::cluster_info::Node; +use solana::db_ledger::create_tmp_ledger_with_mint; +use solana::fullnode::Fullnode; +use solana::leader_scheduler::LeaderScheduler; +use solana::mint::Mint; +use solana::rpc_request::{RpcClient, RpcRequest, RpcRequestHandler}; +use solana::vote_signer_proxy::VoteSignerProxy; +use solana_drone::drone::run_local_drone; +use solana_sdk::signature::{Keypair, KeypairUtil}; +use solana_vote_signer::rpc::LocalVoteSigner; +use solana_wallet::wallet::{process_command, WalletCommand, WalletConfig}; +use std::fs::remove_dir_all; +use std::sync::mpsc::channel; +use std::sync::{Arc, RwLock}; +use std::thread::sleep; +use std::time::Duration; + +#[test] +fn test_wallet_request_airdrop() { + let leader_keypair = Arc::new(Keypair::new()); + let leader = Node::new_localhost_with_pubkey(leader_keypair.pubkey()); + let leader_data = leader.info.clone(); + + let alice = Mint::new(10_000); + let mut bank = Bank::new(&alice); + let ledger_path = create_tmp_ledger_with_mint("thin_client", &alice); + let entry_height = alice.create_entries().len() as u64; + + let leader_scheduler = Arc::new(RwLock::new(LeaderScheduler::from_bootstrap_leader( + leader_data.id, + ))); + bank.leader_scheduler = leader_scheduler; + let vote_account_keypair = Arc::new(Keypair::new()); + let vote_signer = + VoteSignerProxy::new(&vote_account_keypair, Box::new(LocalVoteSigner::default())); + let last_id = bank.last_id(); + let server = Fullnode::new_with_bank( + leader_keypair, + Arc::new(vote_signer), + bank, + None, + entry_height, + &last_id, + leader, + None, + &ledger_path, + false, + None, + ); + sleep(Duration::from_millis(900)); + + let (sender, receiver) = channel(); + run_local_drone(alice.keypair(), sender); + let drone_addr = receiver.recv().unwrap(); + + let mut bob_config = WalletConfig::default(); + bob_config.network = leader_data.gossip; + bob_config.drone_port = Some(drone_addr.port()); + bob_config.command = WalletCommand::Airdrop(50); + + let sig_response = process_command(&bob_config); + assert!(sig_response.is_ok()); + + let rpc_client = RpcClient::new_from_socket(leader_data.rpc); + + let params = json!([format!("{}", bob_config.id.pubkey())]); + let balance = rpc_client + .make_rpc_request(1, RpcRequest::GetBalance, Some(params)) + .unwrap() + .as_u64() + .unwrap(); + assert_eq!(balance, 50); + + server.close().unwrap(); + remove_dir_all(ledger_path).unwrap(); +}