2018-11-14 18:57:34 -08:00
|
|
|
use bincode::serialize;
|
2018-09-14 01:58:39 -07:00
|
|
|
use bs58;
|
2018-09-19 12:44:16 -07:00
|
|
|
use chrono::prelude::*;
|
2018-09-14 01:58:39 -07:00
|
|
|
use clap::ArgMatches;
|
2019-02-27 11:17:32 -08:00
|
|
|
use log::*;
|
2018-10-08 11:10:47 -07:00
|
|
|
use serde_json;
|
2018-12-14 20:39:10 -08:00
|
|
|
use serde_json::json;
|
2019-01-13 23:10:03 -08:00
|
|
|
#[cfg(test)]
|
|
|
|
use solana::rpc_mock::{request_airdrop_transaction, MockRpcClient as RpcClient};
|
|
|
|
#[cfg(not(test))]
|
|
|
|
use solana::rpc_request::RpcClient;
|
2019-01-15 12:54:48 -08:00
|
|
|
use solana::rpc_request::{get_rpc_request_str, RpcRequest};
|
2019-02-17 10:17:58 -08:00
|
|
|
use solana::rpc_service::RPC_PORT;
|
|
|
|
use solana::rpc_status::RpcSignatureStatus;
|
2019-01-13 23:10:03 -08:00
|
|
|
#[cfg(not(test))]
|
|
|
|
use solana_drone::drone::request_airdrop_transaction;
|
|
|
|
use solana_drone::drone::DRONE_PORT;
|
2018-12-03 13:32:31 -08:00
|
|
|
use solana_sdk::bpf_loader;
|
2018-12-04 14:38:19 -08:00
|
|
|
use solana_sdk::budget_program;
|
|
|
|
use solana_sdk::budget_transaction::BudgetTransaction;
|
2018-11-16 08:04:46 -08:00
|
|
|
use solana_sdk::hash::Hash;
|
2018-12-04 15:18:35 -08:00
|
|
|
use solana_sdk::loader_transaction::LoaderTransaction;
|
2018-10-25 11:13:08 -07:00
|
|
|
use solana_sdk::pubkey::Pubkey;
|
2018-12-03 10:26:28 -08:00
|
|
|
use solana_sdk::signature::{Keypair, KeypairUtil, Signature};
|
2018-12-04 20:19:48 -08:00
|
|
|
use solana_sdk::system_transaction::SystemTransaction;
|
2019-02-27 10:37:38 -08:00
|
|
|
use solana_sdk::timing::{DEFAULT_TICKS_PER_SLOT, NUM_TICKS_PER_SECOND};
|
2018-11-29 16:18:47 -08:00
|
|
|
use solana_sdk::transaction::Transaction;
|
2018-12-12 13:13:18 -08:00
|
|
|
use std::fs::File;
|
|
|
|
use std::io::Read;
|
2019-01-16 20:43:00 -08:00
|
|
|
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
|
2018-10-24 09:01:19 -07:00
|
|
|
use std::str::FromStr;
|
2018-11-29 14:08:26 -08:00
|
|
|
use std::thread::sleep;
|
|
|
|
use std::time::Duration;
|
2018-09-14 01:58:39 -07:00
|
|
|
use std::{error, fmt, mem};
|
|
|
|
|
2018-10-22 21:21:33 -07:00
|
|
|
const USERDATA_CHUNK_SIZE: usize = 256;
|
|
|
|
|
2018-09-14 01:59:09 -07:00
|
|
|
#[derive(Debug, PartialEq)]
|
2018-09-14 01:58:39 -07:00
|
|
|
pub enum WalletCommand {
|
|
|
|
Address,
|
2019-01-13 23:10:03 -08:00
|
|
|
Airdrop(u64),
|
2018-09-18 14:42:32 -07:00
|
|
|
Balance,
|
|
|
|
Cancel(Pubkey),
|
2018-09-14 01:58:39 -07:00
|
|
|
Confirm(Signature),
|
2018-10-22 21:21:33 -07:00
|
|
|
Deploy(String),
|
2018-10-25 10:20:17 -07:00
|
|
|
GetTransactionCount,
|
2018-09-19 16:44:03 -07:00
|
|
|
// Pay(tokens, to, timestamp, timestamp_pubkey, witness(es), cancelable)
|
2018-09-18 14:42:32 -07:00
|
|
|
Pay(
|
2018-11-05 08:36:22 -08:00
|
|
|
u64,
|
2018-09-18 14:42:32 -07:00
|
|
|
Pubkey,
|
|
|
|
Option<DateTime<Utc>>,
|
|
|
|
Option<Pubkey>,
|
2018-09-19 12:44:16 -07:00
|
|
|
Option<Vec<Pubkey>>,
|
2018-09-22 16:51:21 -07:00
|
|
|
Option<Pubkey>,
|
2018-09-18 14:42:32 -07:00
|
|
|
),
|
2018-09-22 16:51:21 -07:00
|
|
|
// TimeElapsed(to, process_id, timestamp)
|
|
|
|
TimeElapsed(Pubkey, Pubkey, DateTime<Utc>),
|
2018-09-24 09:23:16 -07:00
|
|
|
// Witness(to, process_id)
|
|
|
|
Witness(Pubkey, Pubkey),
|
2018-09-14 01:58:39 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
pub enum WalletError {
|
|
|
|
CommandNotRecognized(String),
|
|
|
|
BadParameter(String),
|
2018-10-22 21:21:33 -07:00
|
|
|
DynamicProgramError(String),
|
2018-09-20 22:27:06 -07:00
|
|
|
RpcRequestError(String),
|
2018-09-14 01:58:39 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
impl fmt::Display for WalletError {
|
2018-12-08 21:44:20 -08:00
|
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
2018-09-14 01:58:39 -07:00
|
|
|
write!(f, "invalid")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl error::Error for WalletError {
|
|
|
|
fn description(&self) -> &str {
|
|
|
|
"invalid"
|
|
|
|
}
|
|
|
|
|
2018-12-08 21:44:20 -08:00
|
|
|
fn cause(&self) -> Option<&dyn error::Error> {
|
2018-09-14 01:58:39 -07:00
|
|
|
// Generic error, underlying cause isn't tracked.
|
|
|
|
None
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub struct WalletConfig {
|
|
|
|
pub id: Keypair,
|
|
|
|
pub command: WalletCommand,
|
2019-01-16 20:43:00 -08:00
|
|
|
pub drone_host: Option<IpAddr>,
|
|
|
|
pub drone_port: u16,
|
|
|
|
pub host: IpAddr,
|
2019-01-13 23:10:03 -08:00
|
|
|
pub rpc_client: Option<RpcClient>,
|
2019-01-16 20:43:00 -08:00
|
|
|
pub rpc_host: Option<IpAddr>,
|
|
|
|
pub rpc_port: u16,
|
|
|
|
pub rpc_tls: bool,
|
2018-09-14 01:58:39 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Default for WalletConfig {
|
|
|
|
fn default() -> WalletConfig {
|
|
|
|
WalletConfig {
|
|
|
|
command: WalletCommand::Balance,
|
2019-01-16 20:43:00 -08:00
|
|
|
drone_host: None,
|
|
|
|
drone_port: DRONE_PORT,
|
|
|
|
host: IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)),
|
|
|
|
id: Keypair::new(),
|
2019-01-13 23:10:03 -08:00
|
|
|
rpc_client: None,
|
2019-01-16 20:43:00 -08:00
|
|
|
rpc_host: None,
|
|
|
|
rpc_port: RPC_PORT,
|
|
|
|
rpc_tls: false,
|
2018-09-14 01:58:39 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-11-09 14:10:44 -08:00
|
|
|
impl WalletConfig {
|
2019-01-04 13:25:47 -08:00
|
|
|
pub fn drone_addr(&self) -> SocketAddr {
|
2019-01-16 20:43:00 -08:00
|
|
|
SocketAddr::new(self.drone_host.unwrap_or(self.host), self.drone_port)
|
2018-11-09 14:36:08 -08:00
|
|
|
}
|
2018-11-09 16:27:36 -08:00
|
|
|
|
2019-01-11 14:37:52 -08:00
|
|
|
pub fn rpc_addr(&self) -> String {
|
2019-01-16 20:43:00 -08:00
|
|
|
get_rpc_request_str(
|
|
|
|
SocketAddr::new(self.rpc_host.unwrap_or(self.host), self.rpc_port),
|
|
|
|
self.rpc_tls,
|
|
|
|
)
|
2018-11-09 16:27:36 -08:00
|
|
|
}
|
2018-11-09 14:10:44 -08:00
|
|
|
}
|
|
|
|
|
2018-09-14 01:58:39 -07:00
|
|
|
pub fn parse_command(
|
|
|
|
pubkey: Pubkey,
|
2018-12-08 21:44:20 -08:00
|
|
|
matches: &ArgMatches<'_>,
|
|
|
|
) -> Result<WalletCommand, Box<dyn error::Error>> {
|
2018-09-14 01:58:39 -07:00
|
|
|
let response = match matches.subcommand() {
|
2018-09-20 22:27:06 -07:00
|
|
|
("address", Some(_address_matches)) => Ok(WalletCommand::Address),
|
2018-09-14 01:58:39 -07:00
|
|
|
("airdrop", Some(airdrop_matches)) => {
|
|
|
|
let tokens = airdrop_matches.value_of("tokens").unwrap().parse()?;
|
2019-01-13 23:10:03 -08:00
|
|
|
Ok(WalletCommand::Airdrop(tokens))
|
2018-09-14 01:58:39 -07:00
|
|
|
}
|
2018-09-20 22:27:06 -07:00
|
|
|
("balance", Some(_balance_matches)) => Ok(WalletCommand::Balance),
|
2018-09-19 12:44:16 -07:00
|
|
|
("cancel", Some(cancel_matches)) => {
|
|
|
|
let pubkey_vec = bs58::decode(cancel_matches.value_of("process-id").unwrap())
|
|
|
|
.into_vec()
|
|
|
|
.expect("base58-encoded public key");
|
|
|
|
|
|
|
|
if pubkey_vec.len() != mem::size_of::<Pubkey>() {
|
|
|
|
eprintln!("{}", cancel_matches.usage());
|
|
|
|
Err(WalletError::BadParameter("Invalid public key".to_string()))?;
|
|
|
|
}
|
|
|
|
let process_id = Pubkey::new(&pubkey_vec);
|
|
|
|
Ok(WalletCommand::Cancel(process_id))
|
|
|
|
}
|
2018-09-20 22:27:06 -07:00
|
|
|
("confirm", Some(confirm_matches)) => {
|
|
|
|
let signatures = bs58::decode(confirm_matches.value_of("signature").unwrap())
|
|
|
|
.into_vec()
|
|
|
|
.expect("base58-encoded signature");
|
|
|
|
|
|
|
|
if signatures.len() == mem::size_of::<Signature>() {
|
|
|
|
let signature = Signature::new(&signatures);
|
|
|
|
Ok(WalletCommand::Confirm(signature))
|
|
|
|
} else {
|
|
|
|
eprintln!("{}", confirm_matches.usage());
|
|
|
|
Err(WalletError::BadParameter("Invalid signature".to_string()))
|
|
|
|
}
|
|
|
|
}
|
2018-10-22 21:21:33 -07:00
|
|
|
("deploy", Some(deploy_matches)) => Ok(WalletCommand::Deploy(
|
|
|
|
deploy_matches
|
|
|
|
.value_of("program-location")
|
|
|
|
.unwrap()
|
|
|
|
.to_string(),
|
|
|
|
)),
|
2018-10-25 10:20:17 -07:00
|
|
|
("get-transaction-count", Some(_matches)) => Ok(WalletCommand::GetTransactionCount),
|
2018-09-14 01:58:39 -07:00
|
|
|
("pay", Some(pay_matches)) => {
|
2018-09-19 12:44:16 -07:00
|
|
|
let tokens = pay_matches.value_of("tokens").unwrap().parse()?;
|
2018-09-14 01:58:39 -07:00
|
|
|
let to = if pay_matches.is_present("to") {
|
|
|
|
let pubkey_vec = bs58::decode(pay_matches.value_of("to").unwrap())
|
|
|
|
.into_vec()
|
|
|
|
.expect("base58-encoded public key");
|
|
|
|
|
|
|
|
if pubkey_vec.len() != mem::size_of::<Pubkey>() {
|
|
|
|
eprintln!("{}", pay_matches.usage());
|
2018-09-19 12:44:16 -07:00
|
|
|
Err(WalletError::BadParameter(
|
|
|
|
"Invalid to public key".to_string(),
|
|
|
|
))?;
|
2018-09-14 01:58:39 -07:00
|
|
|
}
|
|
|
|
Pubkey::new(&pubkey_vec)
|
|
|
|
} else {
|
|
|
|
pubkey
|
|
|
|
};
|
2018-09-19 12:44:16 -07:00
|
|
|
let timestamp = if pay_matches.is_present("timestamp") {
|
|
|
|
// Parse input for serde_json
|
2018-09-21 20:48:46 -07:00
|
|
|
let date_string = if !pay_matches.value_of("timestamp").unwrap().contains('Z') {
|
2018-09-19 12:44:16 -07:00
|
|
|
format!("\"{}Z\"", pay_matches.value_of("timestamp").unwrap())
|
|
|
|
} else {
|
|
|
|
format!("\"{}\"", pay_matches.value_of("timestamp").unwrap())
|
|
|
|
};
|
|
|
|
Some(serde_json::from_str(&date_string)?)
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
};
|
|
|
|
let timestamp_pubkey = if pay_matches.is_present("timestamp-pubkey") {
|
|
|
|
let pubkey_vec = bs58::decode(pay_matches.value_of("timestamp-pubkey").unwrap())
|
|
|
|
.into_vec()
|
|
|
|
.expect("base58-encoded public key");
|
2018-09-14 01:58:39 -07:00
|
|
|
|
2018-09-19 12:44:16 -07:00
|
|
|
if pubkey_vec.len() != mem::size_of::<Pubkey>() {
|
|
|
|
eprintln!("{}", pay_matches.usage());
|
|
|
|
Err(WalletError::BadParameter(
|
|
|
|
"Invalid timestamp public key".to_string(),
|
|
|
|
))?;
|
|
|
|
}
|
|
|
|
Some(Pubkey::new(&pubkey_vec))
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
};
|
|
|
|
let witness_vec = if pay_matches.is_present("witness") {
|
|
|
|
let witnesses = pay_matches.values_of("witness").unwrap();
|
|
|
|
let mut collection = Vec::new();
|
|
|
|
for witness in witnesses {
|
|
|
|
let pubkey_vec = bs58::decode(witness)
|
|
|
|
.into_vec()
|
|
|
|
.expect("base58-encoded public key");
|
|
|
|
|
|
|
|
if pubkey_vec.len() != mem::size_of::<Pubkey>() {
|
|
|
|
eprintln!("{}", pay_matches.usage());
|
|
|
|
Err(WalletError::BadParameter(
|
|
|
|
"Invalid witness public key".to_string(),
|
|
|
|
))?;
|
|
|
|
}
|
|
|
|
collection.push(Pubkey::new(&pubkey_vec));
|
|
|
|
}
|
|
|
|
Some(collection)
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
};
|
|
|
|
let cancelable = if pay_matches.is_present("cancelable") {
|
2018-09-22 16:51:21 -07:00
|
|
|
Some(pubkey)
|
2018-09-19 12:44:16 -07:00
|
|
|
} else {
|
|
|
|
None
|
|
|
|
};
|
|
|
|
|
|
|
|
Ok(WalletCommand::Pay(
|
|
|
|
tokens,
|
|
|
|
to,
|
|
|
|
timestamp,
|
|
|
|
timestamp_pubkey,
|
|
|
|
witness_vec,
|
|
|
|
cancelable,
|
|
|
|
))
|
|
|
|
}
|
|
|
|
("send-signature", Some(sig_matches)) => {
|
2018-09-24 09:23:16 -07:00
|
|
|
let pubkey_vec = bs58::decode(sig_matches.value_of("to").unwrap())
|
|
|
|
.into_vec()
|
|
|
|
.expect("base58-encoded public key");
|
|
|
|
|
|
|
|
if pubkey_vec.len() != mem::size_of::<Pubkey>() {
|
|
|
|
eprintln!("{}", sig_matches.usage());
|
|
|
|
Err(WalletError::BadParameter("Invalid public key".to_string()))?;
|
|
|
|
}
|
|
|
|
let to = Pubkey::new(&pubkey_vec);
|
|
|
|
|
2018-09-19 12:44:16 -07:00
|
|
|
let pubkey_vec = bs58::decode(sig_matches.value_of("process-id").unwrap())
|
|
|
|
.into_vec()
|
|
|
|
.expect("base58-encoded public key");
|
2018-09-14 01:58:39 -07:00
|
|
|
|
2018-09-19 12:44:16 -07:00
|
|
|
if pubkey_vec.len() != mem::size_of::<Pubkey>() {
|
|
|
|
eprintln!("{}", sig_matches.usage());
|
|
|
|
Err(WalletError::BadParameter("Invalid public key".to_string()))?;
|
|
|
|
}
|
|
|
|
let process_id = Pubkey::new(&pubkey_vec);
|
2018-09-24 09:23:16 -07:00
|
|
|
Ok(WalletCommand::Witness(to, process_id))
|
2018-09-19 12:44:16 -07:00
|
|
|
}
|
|
|
|
("send-timestamp", Some(timestamp_matches)) => {
|
2018-09-22 16:51:21 -07:00
|
|
|
let pubkey_vec = bs58::decode(timestamp_matches.value_of("to").unwrap())
|
|
|
|
.into_vec()
|
|
|
|
.expect("base58-encoded public key");
|
|
|
|
|
|
|
|
if pubkey_vec.len() != mem::size_of::<Pubkey>() {
|
|
|
|
eprintln!("{}", timestamp_matches.usage());
|
|
|
|
Err(WalletError::BadParameter("Invalid public key".to_string()))?;
|
|
|
|
}
|
|
|
|
let to = Pubkey::new(&pubkey_vec);
|
|
|
|
|
2018-09-19 12:44:16 -07:00
|
|
|
let pubkey_vec = bs58::decode(timestamp_matches.value_of("process-id").unwrap())
|
|
|
|
.into_vec()
|
|
|
|
.expect("base58-encoded public key");
|
|
|
|
|
|
|
|
if pubkey_vec.len() != mem::size_of::<Pubkey>() {
|
|
|
|
eprintln!("{}", timestamp_matches.usage());
|
|
|
|
Err(WalletError::BadParameter("Invalid public key".to_string()))?;
|
|
|
|
}
|
|
|
|
let process_id = Pubkey::new(&pubkey_vec);
|
2018-09-19 16:44:03 -07:00
|
|
|
let dt = if timestamp_matches.is_present("datetime") {
|
2018-09-19 12:44:16 -07:00
|
|
|
// Parse input for serde_json
|
|
|
|
let date_string = if !timestamp_matches
|
|
|
|
.value_of("datetime")
|
|
|
|
.unwrap()
|
2018-09-21 20:48:46 -07:00
|
|
|
.contains('Z')
|
2018-09-19 12:44:16 -07:00
|
|
|
{
|
|
|
|
format!("\"{}Z\"", timestamp_matches.value_of("datetime").unwrap())
|
|
|
|
} else {
|
|
|
|
format!("\"{}\"", timestamp_matches.value_of("datetime").unwrap())
|
|
|
|
};
|
|
|
|
serde_json::from_str(&date_string)?
|
|
|
|
} else {
|
|
|
|
Utc::now()
|
|
|
|
};
|
2018-09-22 16:51:21 -07:00
|
|
|
Ok(WalletCommand::TimeElapsed(to, process_id, dt))
|
2018-09-14 01:58:39 -07:00
|
|
|
}
|
|
|
|
("", None) => {
|
2018-09-24 09:23:16 -07:00
|
|
|
eprintln!("{}", matches.usage());
|
2018-09-14 01:58:39 -07:00
|
|
|
Err(WalletError::CommandNotRecognized(
|
|
|
|
"no subcommand given".to_string(),
|
|
|
|
))
|
|
|
|
}
|
|
|
|
_ => unreachable!(),
|
|
|
|
}?;
|
|
|
|
Ok(response)
|
|
|
|
}
|
|
|
|
|
2019-02-09 13:15:44 -08:00
|
|
|
type ProcessResult = Result<String, Box<dyn error::Error>>;
|
|
|
|
|
|
|
|
fn process_airdrop(
|
|
|
|
rpc_client: &RpcClient,
|
|
|
|
config: &WalletConfig,
|
|
|
|
drone_addr: SocketAddr,
|
|
|
|
tokens: u64,
|
|
|
|
) -> ProcessResult {
|
|
|
|
println!(
|
|
|
|
"Requesting airdrop of {:?} tokens from {}",
|
|
|
|
tokens, drone_addr
|
|
|
|
);
|
2019-02-09 19:51:50 -08:00
|
|
|
let previous_balance = match rpc_client.retry_get_balance(1, config.id.pubkey(), 5)? {
|
2019-02-09 13:15:44 -08:00
|
|
|
Some(tokens) => tokens,
|
|
|
|
None => Err(WalletError::RpcRequestError(
|
|
|
|
"Received result of an unexpected type".to_string(),
|
|
|
|
))?,
|
|
|
|
};
|
|
|
|
|
|
|
|
request_and_confirm_airdrop(&rpc_client, &drone_addr, &config.id, tokens)?;
|
|
|
|
|
|
|
|
let current_balance = rpc_client
|
2019-02-09 19:51:50 -08:00
|
|
|
.retry_get_balance(1, config.id.pubkey(), 5)?
|
2019-02-09 13:15:44 -08:00
|
|
|
.unwrap_or(previous_balance);
|
|
|
|
|
|
|
|
if current_balance < previous_balance {
|
|
|
|
Err(format!(
|
|
|
|
"Airdrop failed: current_balance({}) < previous_balance({})",
|
|
|
|
current_balance, previous_balance
|
|
|
|
))?;
|
|
|
|
}
|
|
|
|
if current_balance - previous_balance < tokens {
|
|
|
|
Err(format!(
|
|
|
|
"Airdrop failed: Account balance increased by {} instead of {}",
|
|
|
|
current_balance - previous_balance,
|
|
|
|
tokens
|
|
|
|
))?;
|
|
|
|
}
|
|
|
|
Ok(format!("Your balance is: {:?}", current_balance))
|
|
|
|
}
|
|
|
|
|
|
|
|
fn process_balance(config: &WalletConfig, rpc_client: &RpcClient) -> ProcessResult {
|
2019-02-09 19:51:50 -08:00
|
|
|
let balance = rpc_client.retry_get_balance(1, config.id.pubkey(), 5)?;
|
2019-02-09 13:15:44 -08:00
|
|
|
match balance {
|
|
|
|
Some(0) => Ok("No account found! Request an airdrop to get started.".to_string()),
|
|
|
|
Some(tokens) => Ok(format!("Your balance is: {:?}", tokens)),
|
|
|
|
None => Err(WalletError::RpcRequestError(
|
|
|
|
"Received result of an unexpected type".to_string(),
|
|
|
|
))?,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn process_confirm(rpc_client: &RpcClient, signature: Signature) -> ProcessResult {
|
|
|
|
let params = json!([format!("{}", signature)]);
|
|
|
|
let confirmation = rpc_client
|
|
|
|
.retry_make_rpc_request(1, &RpcRequest::ConfirmTransaction, Some(params), 5)?
|
|
|
|
.as_bool();
|
|
|
|
match confirmation {
|
|
|
|
Some(b) => {
|
|
|
|
if b {
|
|
|
|
Ok("Confirmed".to_string())
|
|
|
|
} else {
|
|
|
|
Ok("Not found".to_string())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
None => Err(WalletError::RpcRequestError(
|
|
|
|
"Received result of an unexpected type".to_string(),
|
|
|
|
))?,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn process_deploy(
|
|
|
|
rpc_client: &RpcClient,
|
|
|
|
config: &WalletConfig,
|
|
|
|
program_location: &str,
|
|
|
|
) -> ProcessResult {
|
2019-02-09 19:51:50 -08:00
|
|
|
let balance = rpc_client.retry_get_balance(1, config.id.pubkey(), 5)?;
|
2019-02-09 13:15:44 -08:00
|
|
|
if let Some(tokens) = balance {
|
|
|
|
if tokens < 1 {
|
|
|
|
Err(WalletError::DynamicProgramError(
|
|
|
|
"Insufficient funds".to_string(),
|
|
|
|
))?
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-02 10:09:09 -08:00
|
|
|
let block_hash = get_recent_block_hash(&rpc_client)?;
|
2019-02-09 13:15:44 -08:00
|
|
|
let program_id = Keypair::new();
|
|
|
|
let mut file = File::open(program_location).map_err(|err| {
|
|
|
|
WalletError::DynamicProgramError(
|
|
|
|
format!("Unable to open program file: {}", err).to_string(),
|
|
|
|
)
|
|
|
|
})?;
|
|
|
|
let mut program_userdata = Vec::new();
|
|
|
|
file.read_to_end(&mut program_userdata).map_err(|err| {
|
|
|
|
WalletError::DynamicProgramError(
|
|
|
|
format!("Unable to read program file: {}", err).to_string(),
|
|
|
|
)
|
|
|
|
})?;
|
|
|
|
|
|
|
|
let mut tx = SystemTransaction::new_program_account(
|
|
|
|
&config.id,
|
|
|
|
program_id.pubkey(),
|
2019-03-02 10:09:09 -08:00
|
|
|
block_hash,
|
2019-02-09 13:15:44 -08:00
|
|
|
1,
|
|
|
|
program_userdata.len() as u64,
|
|
|
|
bpf_loader::id(),
|
|
|
|
0,
|
|
|
|
);
|
2019-02-27 11:17:32 -08:00
|
|
|
trace!("Creating program account");
|
2019-02-27 15:56:21 -08:00
|
|
|
send_and_confirm_transaction(&rpc_client, &mut tx, &config.id).map_err(|_| {
|
2019-02-09 13:15:44 -08:00
|
|
|
WalletError::DynamicProgramError("Program allocate space failed".to_string())
|
|
|
|
})?;
|
|
|
|
|
2019-02-27 11:17:32 -08:00
|
|
|
trace!("Writing program data");
|
|
|
|
let write_transactions: Vec<_> = program_userdata
|
|
|
|
.chunks(USERDATA_CHUNK_SIZE)
|
|
|
|
.zip(0..)
|
|
|
|
.map(|(chunk, i)| {
|
|
|
|
LoaderTransaction::new_write(
|
|
|
|
&program_id,
|
|
|
|
bpf_loader::id(),
|
|
|
|
(i * USERDATA_CHUNK_SIZE) as u32,
|
|
|
|
chunk.to_vec(),
|
2019-03-02 10:09:09 -08:00
|
|
|
block_hash,
|
2019-02-27 11:17:32 -08:00
|
|
|
0,
|
|
|
|
)
|
|
|
|
})
|
|
|
|
.collect();
|
|
|
|
send_and_confirm_transactions(&rpc_client, write_transactions, &program_id)?;
|
2019-02-09 13:15:44 -08:00
|
|
|
|
2019-02-27 11:17:32 -08:00
|
|
|
trace!("Finalizing program account");
|
2019-03-02 10:09:09 -08:00
|
|
|
let mut tx = LoaderTransaction::new_finalize(&program_id, bpf_loader::id(), block_hash, 0);
|
2019-02-27 15:56:21 -08:00
|
|
|
send_and_confirm_transaction(&rpc_client, &mut tx, &program_id).map_err(|_| {
|
2019-02-09 13:15:44 -08:00
|
|
|
WalletError::DynamicProgramError("Program finalize transaction failed".to_string())
|
|
|
|
})?;
|
|
|
|
|
|
|
|
Ok(json!({
|
|
|
|
"programId": format!("{}", program_id.pubkey()),
|
|
|
|
})
|
|
|
|
.to_string())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn process_pay(
|
|
|
|
rpc_client: &RpcClient,
|
|
|
|
config: &WalletConfig,
|
|
|
|
tokens: u64,
|
|
|
|
to: Pubkey,
|
|
|
|
timestamp: Option<DateTime<Utc>>,
|
|
|
|
timestamp_pubkey: Option<Pubkey>,
|
|
|
|
witnesses: &Option<Vec<Pubkey>>,
|
|
|
|
cancelable: Option<Pubkey>,
|
|
|
|
) -> ProcessResult {
|
2019-03-02 10:09:09 -08:00
|
|
|
let block_hash = get_recent_block_hash(&rpc_client)?;
|
2019-02-09 13:15:44 -08:00
|
|
|
|
|
|
|
if timestamp == None && *witnesses == None {
|
2019-03-02 10:09:09 -08:00
|
|
|
let mut tx = SystemTransaction::new_account(&config.id, to, tokens, block_hash, 0);
|
2019-02-27 15:56:21 -08:00
|
|
|
let signature_str = send_and_confirm_transaction(&rpc_client, &mut tx, &config.id)?;
|
2019-02-09 13:15:44 -08:00
|
|
|
Ok(signature_str.to_string())
|
|
|
|
} else if *witnesses == None {
|
|
|
|
let dt = timestamp.unwrap();
|
|
|
|
let dt_pubkey = match timestamp_pubkey {
|
|
|
|
Some(pubkey) => pubkey,
|
|
|
|
None => config.id.pubkey(),
|
|
|
|
};
|
|
|
|
|
|
|
|
let contract_funds = Keypair::new();
|
|
|
|
let contract_state = Keypair::new();
|
|
|
|
let budget_program_id = budget_program::id();
|
|
|
|
|
|
|
|
// Create account for contract funds
|
|
|
|
let mut tx = SystemTransaction::new_program_account(
|
|
|
|
&config.id,
|
|
|
|
contract_funds.pubkey(),
|
2019-03-02 10:09:09 -08:00
|
|
|
block_hash,
|
2019-02-09 13:15:44 -08:00
|
|
|
tokens,
|
|
|
|
0,
|
|
|
|
budget_program_id,
|
|
|
|
0,
|
|
|
|
);
|
2019-02-27 15:56:21 -08:00
|
|
|
send_and_confirm_transaction(&rpc_client, &mut tx, &config.id)?;
|
2019-02-09 13:15:44 -08:00
|
|
|
|
|
|
|
// Create account for contract state
|
|
|
|
let mut tx = SystemTransaction::new_program_account(
|
|
|
|
&config.id,
|
|
|
|
contract_state.pubkey(),
|
2019-03-02 10:09:09 -08:00
|
|
|
block_hash,
|
2019-02-09 13:15:44 -08:00
|
|
|
1,
|
|
|
|
196,
|
|
|
|
budget_program_id,
|
|
|
|
0,
|
|
|
|
);
|
2019-02-27 15:56:21 -08:00
|
|
|
send_and_confirm_transaction(&rpc_client, &mut tx, &config.id)?;
|
2019-02-09 13:15:44 -08:00
|
|
|
|
|
|
|
// Initializing contract
|
|
|
|
let mut tx = BudgetTransaction::new_on_date(
|
|
|
|
&contract_funds,
|
|
|
|
to,
|
|
|
|
contract_state.pubkey(),
|
|
|
|
dt,
|
|
|
|
dt_pubkey,
|
|
|
|
cancelable,
|
|
|
|
tokens,
|
2019-03-02 10:09:09 -08:00
|
|
|
block_hash,
|
2019-02-09 13:15:44 -08:00
|
|
|
);
|
2019-02-27 15:56:21 -08:00
|
|
|
let signature_str = send_and_confirm_transaction(&rpc_client, &mut tx, &config.id)?;
|
2019-02-09 13:15:44 -08:00
|
|
|
|
|
|
|
Ok(json!({
|
|
|
|
"signature": signature_str,
|
|
|
|
"processId": format!("{}", contract_state.pubkey()),
|
|
|
|
})
|
|
|
|
.to_string())
|
|
|
|
} else if timestamp == None {
|
2019-03-02 10:09:09 -08:00
|
|
|
let block_hash = get_recent_block_hash(&rpc_client)?;
|
2019-02-09 13:15:44 -08:00
|
|
|
|
|
|
|
let witness = if let Some(ref witness_vec) = *witnesses {
|
|
|
|
witness_vec[0]
|
|
|
|
} else {
|
|
|
|
Err(WalletError::BadParameter(
|
|
|
|
"Could not parse required signature pubkey(s)".to_string(),
|
|
|
|
))?
|
|
|
|
};
|
|
|
|
|
|
|
|
let contract_funds = Keypair::new();
|
|
|
|
let contract_state = Keypair::new();
|
|
|
|
let budget_program_id = budget_program::id();
|
|
|
|
|
|
|
|
// Create account for contract funds
|
|
|
|
let mut tx = SystemTransaction::new_program_account(
|
|
|
|
&config.id,
|
|
|
|
contract_funds.pubkey(),
|
2019-03-02 10:09:09 -08:00
|
|
|
block_hash,
|
2019-02-09 13:15:44 -08:00
|
|
|
tokens,
|
|
|
|
0,
|
|
|
|
budget_program_id,
|
|
|
|
0,
|
|
|
|
);
|
2019-02-27 15:56:21 -08:00
|
|
|
send_and_confirm_transaction(&rpc_client, &mut tx, &config.id)?;
|
2019-02-09 13:15:44 -08:00
|
|
|
|
|
|
|
// Create account for contract state
|
|
|
|
let mut tx = SystemTransaction::new_program_account(
|
|
|
|
&config.id,
|
|
|
|
contract_state.pubkey(),
|
2019-03-02 10:09:09 -08:00
|
|
|
block_hash,
|
2019-02-09 13:15:44 -08:00
|
|
|
1,
|
|
|
|
196,
|
|
|
|
budget_program_id,
|
|
|
|
0,
|
|
|
|
);
|
2019-02-27 15:56:21 -08:00
|
|
|
send_and_confirm_transaction(&rpc_client, &mut tx, &config.id)?;
|
2019-02-09 13:15:44 -08:00
|
|
|
|
|
|
|
// Initializing contract
|
|
|
|
let mut tx = BudgetTransaction::new_when_signed(
|
|
|
|
&contract_funds,
|
|
|
|
to,
|
|
|
|
contract_state.pubkey(),
|
|
|
|
witness,
|
|
|
|
cancelable,
|
|
|
|
tokens,
|
2019-03-02 10:09:09 -08:00
|
|
|
block_hash,
|
2019-02-09 13:15:44 -08:00
|
|
|
);
|
2019-02-27 15:56:21 -08:00
|
|
|
let signature_str = send_and_confirm_transaction(&rpc_client, &mut tx, &config.id)?;
|
2019-02-09 13:15:44 -08:00
|
|
|
|
|
|
|
Ok(json!({
|
|
|
|
"signature": signature_str,
|
|
|
|
"processId": format!("{}", contract_state.pubkey()),
|
|
|
|
})
|
|
|
|
.to_string())
|
|
|
|
} else {
|
|
|
|
Ok("Combo transactions not yet handled".to_string())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn process_cancel(rpc_client: &RpcClient, config: &WalletConfig, pubkey: Pubkey) -> ProcessResult {
|
2019-03-02 10:09:09 -08:00
|
|
|
let block_hash = get_recent_block_hash(&rpc_client)?;
|
2019-03-02 10:20:10 -08:00
|
|
|
let mut tx =
|
|
|
|
BudgetTransaction::new_signature(&config.id, pubkey, config.id.pubkey(), block_hash);
|
2019-02-27 15:56:21 -08:00
|
|
|
let signature_str = send_and_confirm_transaction(&rpc_client, &mut tx, &config.id)?;
|
2019-02-09 13:15:44 -08:00
|
|
|
Ok(signature_str.to_string())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn process_get_transaction_count(rpc_client: &RpcClient) -> ProcessResult {
|
|
|
|
let transaction_count = rpc_client
|
|
|
|
.retry_make_rpc_request(1, &RpcRequest::GetTransactionCount, None, 5)?
|
|
|
|
.as_u64();
|
|
|
|
match transaction_count {
|
|
|
|
Some(count) => Ok(count.to_string()),
|
|
|
|
None => Err(WalletError::RpcRequestError(
|
|
|
|
"Received result of an unexpected type".to_string(),
|
|
|
|
))?,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn process_time_elapsed(
|
|
|
|
rpc_client: &RpcClient,
|
|
|
|
config: &WalletConfig,
|
|
|
|
drone_addr: SocketAddr,
|
|
|
|
to: Pubkey,
|
|
|
|
pubkey: Pubkey,
|
|
|
|
dt: DateTime<Utc>,
|
|
|
|
) -> ProcessResult {
|
2019-02-09 19:51:50 -08:00
|
|
|
let balance = rpc_client.retry_get_balance(1, config.id.pubkey(), 5)?;
|
2019-02-09 13:15:44 -08:00
|
|
|
|
|
|
|
if let Some(0) = balance {
|
|
|
|
request_and_confirm_airdrop(&rpc_client, &drone_addr, &config.id, 1)?;
|
|
|
|
}
|
|
|
|
|
2019-03-02 10:09:09 -08:00
|
|
|
let block_hash = get_recent_block_hash(&rpc_client)?;
|
2019-02-09 13:15:44 -08:00
|
|
|
|
2019-03-02 10:09:09 -08:00
|
|
|
let mut tx = BudgetTransaction::new_timestamp(&config.id, pubkey, to, dt, block_hash);
|
2019-02-27 15:56:21 -08:00
|
|
|
let signature_str = send_and_confirm_transaction(&rpc_client, &mut tx, &config.id)?;
|
2019-02-09 13:15:44 -08:00
|
|
|
|
|
|
|
Ok(signature_str.to_string())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn process_witness(
|
|
|
|
rpc_client: &RpcClient,
|
|
|
|
config: &WalletConfig,
|
|
|
|
drone_addr: SocketAddr,
|
|
|
|
to: Pubkey,
|
|
|
|
pubkey: Pubkey,
|
|
|
|
) -> ProcessResult {
|
2019-02-09 19:51:50 -08:00
|
|
|
let balance = rpc_client.retry_get_balance(1, config.id.pubkey(), 5)?;
|
2019-02-09 13:15:44 -08:00
|
|
|
|
|
|
|
if let Some(0) = balance {
|
|
|
|
request_and_confirm_airdrop(&rpc_client, &drone_addr, &config.id, 1)?;
|
|
|
|
}
|
|
|
|
|
2019-03-02 10:09:09 -08:00
|
|
|
let block_hash = get_recent_block_hash(&rpc_client)?;
|
|
|
|
let mut tx = BudgetTransaction::new_signature(&config.id, pubkey, to, block_hash);
|
2019-02-27 15:56:21 -08:00
|
|
|
let signature_str = send_and_confirm_transaction(&rpc_client, &mut tx, &config.id)?;
|
2019-02-09 13:15:44 -08:00
|
|
|
|
|
|
|
Ok(signature_str.to_string())
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn process_command(config: &WalletConfig) -> ProcessResult {
|
2018-11-09 15:37:34 -08:00
|
|
|
if let WalletCommand::Address = config.command {
|
2018-11-09 14:42:11 -08:00
|
|
|
// Get address of this client
|
2018-11-09 15:37:34 -08:00
|
|
|
return Ok(format!("{}", config.id.pubkey()));
|
2018-11-09 14:42:11 -08:00
|
|
|
}
|
|
|
|
|
2019-01-04 13:25:47 -08:00
|
|
|
let drone_addr = config.drone_addr();
|
2019-01-13 23:10:03 -08:00
|
|
|
let rpc_client = if config.rpc_client.is_none() {
|
2019-01-11 14:37:52 -08:00
|
|
|
let rpc_addr = config.rpc_addr();
|
2019-01-13 23:10:03 -08:00
|
|
|
RpcClient::new(rpc_addr)
|
|
|
|
} else {
|
|
|
|
// Primarily for testing
|
|
|
|
config.rpc_client.clone().unwrap()
|
|
|
|
};
|
2018-11-09 14:36:08 -08:00
|
|
|
|
2018-09-14 01:58:39 -07:00
|
|
|
match config.command {
|
2018-09-20 22:27:06 -07:00
|
|
|
// Get address of this client
|
2018-11-09 14:42:11 -08:00
|
|
|
WalletCommand::Address => unreachable!(),
|
2018-11-14 18:57:34 -08:00
|
|
|
|
2019-02-09 13:15:44 -08:00
|
|
|
// Request an airdrop from Solana Drone;
|
|
|
|
WalletCommand::Airdrop(tokens) => process_airdrop(&rpc_client, config, drone_addr, tokens),
|
2018-11-14 18:57:34 -08:00
|
|
|
|
2018-09-18 14:42:32 -07:00
|
|
|
// Check client balance
|
2019-02-09 13:15:44 -08:00
|
|
|
WalletCommand::Balance => process_balance(config, &rpc_client),
|
|
|
|
|
2018-09-18 14:42:32 -07:00
|
|
|
// Cancel a contract by contract Pubkey
|
2019-02-09 13:15:44 -08:00
|
|
|
WalletCommand::Cancel(pubkey) => process_cancel(&rpc_client, config, pubkey),
|
|
|
|
|
2018-09-14 01:58:39 -07:00
|
|
|
// Confirm the last client transaction by signature
|
2019-02-09 13:15:44 -08:00
|
|
|
WalletCommand::Confirm(signature) => process_confirm(&rpc_client, signature),
|
|
|
|
|
2018-10-22 21:21:33 -07:00
|
|
|
// Deploy a custom program to the chain
|
|
|
|
WalletCommand::Deploy(ref program_location) => {
|
2019-02-09 13:15:44 -08:00
|
|
|
process_deploy(&rpc_client, config, program_location)
|
|
|
|
}
|
2018-10-22 21:21:33 -07:00
|
|
|
|
2019-02-09 13:15:44 -08:00
|
|
|
WalletCommand::GetTransactionCount => process_get_transaction_count(&rpc_client),
|
2018-10-24 09:01:19 -07:00
|
|
|
|
2018-09-21 14:27:03 -07:00
|
|
|
// If client has positive balance, pay tokens to another address
|
2018-09-19 16:44:03 -07:00
|
|
|
WalletCommand::Pay(tokens, to, timestamp, timestamp_pubkey, ref witnesses, cancelable) => {
|
2019-02-09 13:15:44 -08:00
|
|
|
process_pay(
|
|
|
|
&rpc_client,
|
|
|
|
config,
|
|
|
|
tokens,
|
|
|
|
to,
|
|
|
|
timestamp,
|
|
|
|
timestamp_pubkey,
|
|
|
|
witnesses,
|
|
|
|
cancelable,
|
|
|
|
)
|
2018-09-19 16:44:03 -07:00
|
|
|
}
|
2019-02-09 13:15:44 -08:00
|
|
|
|
2018-09-19 16:44:03 -07:00
|
|
|
// Apply time elapsed to contract
|
2018-09-22 16:51:21 -07:00
|
|
|
WalletCommand::TimeElapsed(to, pubkey, dt) => {
|
2019-02-09 13:15:44 -08:00
|
|
|
process_time_elapsed(&rpc_client, config, drone_addr, to, pubkey, dt)
|
2018-09-14 01:58:39 -07:00
|
|
|
}
|
2019-02-09 13:15:44 -08:00
|
|
|
|
2018-09-18 14:42:32 -07:00
|
|
|
// Apply witness signature to contract
|
2018-09-24 09:23:16 -07:00
|
|
|
WalletCommand::Witness(to, pubkey) => {
|
2019-02-09 13:15:44 -08:00
|
|
|
process_witness(&rpc_client, config, drone_addr, to, pubkey)
|
2018-09-19 16:44:03 -07:00
|
|
|
}
|
2018-09-14 01:58:39 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-02 10:01:13 -08:00
|
|
|
fn get_recent_block_hash(rpc_client: &RpcClient) -> Result<Hash, Box<dyn error::Error>> {
|
2019-03-02 10:12:14 -08:00
|
|
|
let result = rpc_client.retry_make_rpc_request(1, &RpcRequest::GetRecentBlockHash, None, 5)?;
|
2018-09-22 10:33:06 -07:00
|
|
|
if result.as_str().is_none() {
|
|
|
|
Err(WalletError::RpcRequestError(
|
2019-03-02 10:09:09 -08:00
|
|
|
"Received bad block_hash".to_string(),
|
2018-09-22 10:33:06 -07:00
|
|
|
))?
|
|
|
|
}
|
2019-03-02 10:09:09 -08:00
|
|
|
let block_hash_str = result.as_str().unwrap();
|
|
|
|
let block_hash_vec = bs58::decode(block_hash_str)
|
2018-09-22 10:33:06 -07:00
|
|
|
.into_vec()
|
2019-03-02 10:09:09 -08:00
|
|
|
.map_err(|_| WalletError::RpcRequestError("Received bad block_hash".to_string()))?;
|
|
|
|
Ok(Hash::new(&block_hash_vec))
|
2018-09-22 10:33:06 -07:00
|
|
|
}
|
|
|
|
|
2019-03-02 10:09:09 -08:00
|
|
|
fn get_next_block_hash(
|
2019-02-27 11:17:32 -08:00
|
|
|
rpc_client: &RpcClient,
|
2019-03-02 10:09:09 -08:00
|
|
|
previous_block_hash: &Hash,
|
2019-02-27 11:17:32 -08:00
|
|
|
) -> Result<Hash, Box<dyn error::Error>> {
|
2019-03-02 10:09:09 -08:00
|
|
|
let mut next_block_hash_retries = 3;
|
2019-02-27 11:17:32 -08:00
|
|
|
loop {
|
2019-03-02 10:09:09 -08:00
|
|
|
let next_block_hash = get_recent_block_hash(rpc_client)?;
|
2019-02-27 11:17:32 -08:00
|
|
|
if cfg!(not(test)) {
|
2019-03-02 10:09:09 -08:00
|
|
|
if next_block_hash != *previous_block_hash {
|
|
|
|
return Ok(next_block_hash);
|
2019-02-27 11:17:32 -08:00
|
|
|
}
|
|
|
|
} else {
|
2019-03-02 10:01:13 -08:00
|
|
|
// When using MockRpcClient, get_recent_block_hash() returns a constant value
|
2019-03-02 10:09:09 -08:00
|
|
|
return Ok(next_block_hash);
|
2019-02-27 11:17:32 -08:00
|
|
|
}
|
2019-03-02 10:09:09 -08:00
|
|
|
if next_block_hash_retries == 0 {
|
2019-02-27 11:17:32 -08:00
|
|
|
Err(WalletError::RpcRequestError(
|
|
|
|
format!(
|
2019-03-02 10:09:09 -08:00
|
|
|
"Unable to fetch new block_hash, block_hash stuck at {:?}",
|
|
|
|
next_block_hash
|
2019-02-27 11:17:32 -08:00
|
|
|
)
|
|
|
|
.to_string(),
|
|
|
|
))?;
|
|
|
|
}
|
2019-03-02 10:09:09 -08:00
|
|
|
next_block_hash_retries -= 1;
|
2019-02-27 11:17:32 -08:00
|
|
|
// Retry ~twice during a slot
|
|
|
|
sleep(Duration::from_millis(
|
2019-03-01 13:10:17 -08:00
|
|
|
500 * DEFAULT_TICKS_PER_SLOT / NUM_TICKS_PER_SECOND,
|
2019-02-27 11:17:32 -08:00
|
|
|
));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-02-27 15:56:21 -08:00
|
|
|
fn send_transaction(
|
|
|
|
rpc_client: &RpcClient,
|
|
|
|
transaction: &Transaction,
|
|
|
|
) -> Result<String, Box<dyn error::Error>> {
|
|
|
|
let serialized = serialize(transaction).unwrap();
|
2018-11-14 18:57:34 -08:00
|
|
|
let params = json!([serialized]);
|
2019-01-15 12:54:48 -08:00
|
|
|
let signature =
|
|
|
|
rpc_client.retry_make_rpc_request(2, &RpcRequest::SendTransaction, Some(params), 5)?;
|
2018-09-22 16:51:21 -07:00
|
|
|
if signature.as_str().is_none() {
|
|
|
|
Err(WalletError::RpcRequestError(
|
|
|
|
"Received result of an unexpected type".to_string(),
|
|
|
|
))?
|
|
|
|
}
|
|
|
|
Ok(signature.as_str().unwrap().to_string())
|
|
|
|
}
|
|
|
|
|
2019-02-27 15:56:21 -08:00
|
|
|
fn confirm_transaction(
|
2018-11-27 21:08:14 -08:00
|
|
|
rpc_client: &RpcClient,
|
|
|
|
signature: &str,
|
2018-12-08 21:44:20 -08:00
|
|
|
) -> Result<RpcSignatureStatus, Box<dyn error::Error>> {
|
2018-11-14 18:57:34 -08:00
|
|
|
let params = json!([signature.to_string()]);
|
2018-10-24 09:01:19 -07:00
|
|
|
let signature_status =
|
2019-01-15 12:54:48 -08:00
|
|
|
rpc_client.retry_make_rpc_request(1, &RpcRequest::GetSignatureStatus, Some(params), 5)?;
|
2018-10-24 09:01:19 -07:00
|
|
|
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())
|
|
|
|
})?;
|
|
|
|
Ok(rpc_status)
|
|
|
|
} else {
|
2018-10-22 21:21:33 -07:00
|
|
|
Err(WalletError::RpcRequestError(
|
|
|
|
"Received result of an unexpected type".to_string(),
|
|
|
|
))?
|
|
|
|
}
|
2018-10-24 09:01:19 -07:00
|
|
|
}
|
|
|
|
|
2019-02-27 15:56:21 -08:00
|
|
|
fn send_and_confirm_transaction(
|
2018-12-08 21:52:29 -08:00
|
|
|
rpc_client: &RpcClient,
|
2019-02-27 15:56:21 -08:00
|
|
|
transaction: &mut Transaction,
|
2018-12-10 11:20:55 -08:00
|
|
|
signer: &Keypair,
|
2019-01-13 23:10:03 -08:00
|
|
|
) -> Result<String, Box<dyn error::Error>> {
|
2019-01-15 13:01:55 -08:00
|
|
|
let mut send_retries = 5;
|
|
|
|
loop {
|
2018-10-24 09:01:19 -07:00
|
|
|
let mut status_retries = 4;
|
2019-02-27 15:56:21 -08:00
|
|
|
let signature_str = send_transaction(rpc_client, transaction)?;
|
2018-10-24 09:01:19 -07:00
|
|
|
let status = loop {
|
2019-02-27 15:56:21 -08:00
|
|
|
let status = confirm_transaction(rpc_client, &signature_str)?;
|
2018-10-24 09:01:19 -07:00
|
|
|
if status == RpcSignatureStatus::SignatureNotFound {
|
|
|
|
status_retries -= 1;
|
|
|
|
if status_retries == 0 {
|
|
|
|
break status;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
break status;
|
|
|
|
}
|
2019-01-13 23:10:03 -08:00
|
|
|
if cfg!(not(test)) {
|
2019-02-27 10:37:38 -08:00
|
|
|
// Retry ~twice during a slot
|
|
|
|
sleep(Duration::from_millis(
|
2019-03-01 13:10:17 -08:00
|
|
|
500 * DEFAULT_TICKS_PER_SLOT / NUM_TICKS_PER_SECOND,
|
2019-02-27 10:37:38 -08:00
|
|
|
));
|
2019-01-13 23:10:03 -08:00
|
|
|
}
|
2018-10-24 09:01:19 -07:00
|
|
|
};
|
|
|
|
match status {
|
2019-01-15 13:01:55 -08:00
|
|
|
RpcSignatureStatus::AccountInUse | RpcSignatureStatus::SignatureNotFound => {
|
2019-03-02 10:09:09 -08:00
|
|
|
// Fetch a new block_hash and re-sign the transaction before sending it again
|
2019-02-27 15:56:21 -08:00
|
|
|
resign_transaction(rpc_client, transaction, signer)?;
|
2018-12-10 11:20:55 -08:00
|
|
|
send_retries -= 1;
|
|
|
|
}
|
|
|
|
RpcSignatureStatus::Confirmed => {
|
2019-01-13 23:10:03 -08:00
|
|
|
return Ok(signature_str);
|
2018-12-10 11:20:55 -08:00
|
|
|
}
|
|
|
|
_ => {
|
2019-01-15 13:01:55 -08:00
|
|
|
send_retries = 0;
|
2018-12-10 11:20:55 -08:00
|
|
|
}
|
|
|
|
}
|
2019-01-15 13:01:55 -08:00
|
|
|
if send_retries == 0 {
|
|
|
|
Err(WalletError::RpcRequestError(format!(
|
|
|
|
"Transaction {:?} failed: {:?}",
|
|
|
|
signature_str, status
|
|
|
|
)))?;
|
|
|
|
}
|
2018-12-10 11:20:55 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-02-27 11:17:32 -08:00
|
|
|
fn send_and_confirm_transactions(
|
2018-12-10 11:20:55 -08:00
|
|
|
rpc_client: &RpcClient,
|
2019-02-27 11:17:32 -08:00
|
|
|
mut transactions: Vec<Transaction>,
|
|
|
|
signer: &Keypair,
|
2018-12-10 11:20:55 -08:00
|
|
|
) -> Result<(), Box<dyn error::Error>> {
|
2019-02-27 11:17:32 -08:00
|
|
|
let mut send_retries = 5;
|
|
|
|
loop {
|
|
|
|
let mut status_retries = 4;
|
|
|
|
|
|
|
|
// Send all transactions
|
|
|
|
let mut transactions_signatures = vec![];
|
|
|
|
for transaction in transactions {
|
|
|
|
if cfg!(not(test)) {
|
|
|
|
// Delay ~1 tick between write transactions in an attempt to reduce AccountInUse errors
|
|
|
|
// since all the write transactions modify the same program account
|
2019-03-01 13:10:17 -08:00
|
|
|
sleep(Duration::from_millis(1000 / NUM_TICKS_PER_SECOND));
|
2019-02-27 11:17:32 -08:00
|
|
|
}
|
|
|
|
|
2019-02-27 15:56:21 -08:00
|
|
|
let signature = send_transaction(&rpc_client, &transaction).ok();
|
2019-02-27 11:17:32 -08:00
|
|
|
transactions_signatures.push((transaction, signature))
|
2019-01-15 13:01:55 -08:00
|
|
|
}
|
2019-02-27 11:17:32 -08:00
|
|
|
|
|
|
|
// Collect statuses for all the transactions, drop those that are confirmed
|
|
|
|
while status_retries > 0 {
|
|
|
|
status_retries -= 1;
|
|
|
|
|
|
|
|
if cfg!(not(test)) {
|
|
|
|
// Retry ~twice during a slot
|
|
|
|
sleep(Duration::from_millis(
|
2019-03-01 13:10:17 -08:00
|
|
|
500 * DEFAULT_TICKS_PER_SLOT / NUM_TICKS_PER_SECOND,
|
2019-02-27 11:17:32 -08:00
|
|
|
));
|
|
|
|
}
|
|
|
|
|
|
|
|
transactions_signatures = transactions_signatures
|
|
|
|
.into_iter()
|
|
|
|
.filter(|(_transaction, signature)| {
|
|
|
|
if let Some(signature) = signature {
|
|
|
|
if let Ok(status) = confirm_transaction(rpc_client, &signature) {
|
|
|
|
return status != RpcSignatureStatus::Confirmed;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
true
|
|
|
|
})
|
|
|
|
.collect();
|
|
|
|
|
|
|
|
if transactions_signatures.is_empty() {
|
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if send_retries == 0 {
|
2019-01-15 13:01:55 -08:00
|
|
|
Err(WalletError::RpcRequestError(
|
2019-02-27 11:17:32 -08:00
|
|
|
"Transactions failed".to_string(),
|
2019-01-15 13:01:55 -08:00
|
|
|
))?;
|
|
|
|
}
|
2019-02-27 11:17:32 -08:00
|
|
|
send_retries -= 1;
|
|
|
|
|
2019-03-02 10:09:09 -08:00
|
|
|
// Re-sign any failed transactions with a new block_hash and retry
|
|
|
|
let block_hash =
|
|
|
|
get_next_block_hash(rpc_client, &transactions_signatures[0].0.recent_block_hash)?;
|
2019-02-27 11:17:32 -08:00
|
|
|
transactions = transactions_signatures
|
|
|
|
.into_iter()
|
|
|
|
.map(|(mut transaction, _)| {
|
2019-03-02 10:09:09 -08:00
|
|
|
transaction.sign(&[signer], block_hash);
|
2019-02-27 11:17:32 -08:00
|
|
|
transaction
|
|
|
|
})
|
|
|
|
.collect();
|
|
|
|
}
|
|
|
|
}
|
2019-01-15 13:01:55 -08:00
|
|
|
|
2019-02-27 15:56:21 -08:00
|
|
|
fn resign_transaction(
|
2019-02-27 11:17:32 -08:00
|
|
|
rpc_client: &RpcClient,
|
|
|
|
tx: &mut Transaction,
|
|
|
|
signer_key: &Keypair,
|
|
|
|
) -> Result<(), Box<dyn error::Error>> {
|
2019-03-02 10:09:09 -08:00
|
|
|
let block_hash = get_next_block_hash(rpc_client, &tx.recent_block_hash)?;
|
|
|
|
tx.sign(&[signer_key], block_hash);
|
2018-12-10 11:20:55 -08:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2019-01-13 23:10:03 -08:00
|
|
|
pub fn request_and_confirm_airdrop(
|
2018-12-10 11:20:55 -08:00
|
|
|
rpc_client: &RpcClient,
|
|
|
|
drone_addr: &SocketAddr,
|
2019-01-15 16:24:09 -08:00
|
|
|
signer: &Keypair,
|
2018-12-10 11:20:55 -08:00
|
|
|
tokens: u64,
|
|
|
|
) -> Result<(), Box<dyn error::Error>> {
|
2019-03-02 10:09:09 -08:00
|
|
|
let block_hash = get_recent_block_hash(rpc_client)?;
|
|
|
|
let mut tx = request_airdrop_transaction(drone_addr, &signer.pubkey(), tokens, block_hash)?;
|
2019-02-27 15:56:21 -08:00
|
|
|
send_and_confirm_transaction(rpc_client, &mut tx, signer)?;
|
2019-01-15 16:24:09 -08:00
|
|
|
Ok(())
|
2018-10-22 21:21:33 -07:00
|
|
|
}
|
|
|
|
|
2018-09-14 01:58:39 -07:00
|
|
|
#[cfg(test)]
|
2018-09-14 01:59:09 -07:00
|
|
|
mod tests {
|
|
|
|
use super::*;
|
|
|
|
use clap::{App, Arg, SubCommand};
|
2018-10-08 11:10:47 -07:00
|
|
|
use serde_json::Value;
|
2019-01-13 23:10:03 -08:00
|
|
|
use solana::rpc_mock::{PUBKEY, SIGNATURE};
|
2019-01-16 20:43:00 -08:00
|
|
|
use solana::socketaddr;
|
2018-12-12 13:13:18 -08:00
|
|
|
use solana_sdk::signature::{gen_keypair_file, read_keypair, read_pkcs8, Keypair, KeypairUtil};
|
|
|
|
use std::fs;
|
2019-01-13 23:10:03 -08:00
|
|
|
use std::net::{Ipv4Addr, SocketAddr};
|
2019-01-17 08:49:16 -08:00
|
|
|
use std::path::{Path, PathBuf};
|
2018-09-14 01:59:09 -07:00
|
|
|
|
2018-12-10 11:20:55 -08:00
|
|
|
#[test]
|
2019-01-13 23:10:03 -08:00
|
|
|
fn test_wallet_config_drone_addr() {
|
|
|
|
let mut config = WalletConfig::default();
|
2019-01-16 20:43:00 -08:00
|
|
|
assert_eq!(
|
|
|
|
config.drone_addr(),
|
|
|
|
SocketAddr::new(config.host, config.drone_port)
|
|
|
|
);
|
|
|
|
|
|
|
|
config.drone_port = 1234;
|
|
|
|
assert_eq!(config.drone_addr(), SocketAddr::new(config.host, 1234));
|
|
|
|
|
|
|
|
config.drone_host = Some(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 2)));
|
|
|
|
assert_eq!(
|
|
|
|
config.drone_addr(),
|
|
|
|
SocketAddr::new(config.drone_host.unwrap(), 1234)
|
|
|
|
);
|
|
|
|
}
|
2019-01-11 14:37:52 -08:00
|
|
|
|
2019-01-16 20:43:00 -08:00
|
|
|
#[test]
|
|
|
|
fn test_wallet_config_rpc_addr() {
|
|
|
|
let mut config = WalletConfig::default();
|
|
|
|
assert_eq!(config.rpc_addr(), "http://127.0.0.1:8899");
|
|
|
|
config.rpc_port = 1234;
|
|
|
|
assert_eq!(config.rpc_addr(), "http://127.0.0.1:1234");
|
|
|
|
config.rpc_host = Some(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 2)));
|
|
|
|
assert_eq!(config.rpc_addr(), "http://127.0.0.2:1234");
|
2018-12-10 11:20:55 -08:00
|
|
|
}
|
|
|
|
|
2018-09-14 01:59:09 -07:00
|
|
|
#[test]
|
2018-09-26 10:17:45 -07:00
|
|
|
fn test_wallet_parse_command() {
|
2018-09-14 01:59:09 -07:00
|
|
|
let test_commands = App::new("test")
|
2018-09-19 12:44:16 -07:00
|
|
|
.subcommand(SubCommand::with_name("address").about("Get your public key"))
|
2018-09-14 01:59:09 -07:00
|
|
|
.subcommand(
|
|
|
|
SubCommand::with_name("airdrop")
|
|
|
|
.about("Request a batch of tokens")
|
|
|
|
.arg(
|
|
|
|
Arg::with_name("tokens")
|
2018-09-19 12:44:16 -07:00
|
|
|
.index(1)
|
|
|
|
.value_name("NUM")
|
2018-09-14 01:59:09 -07:00
|
|
|
.takes_value(true)
|
|
|
|
.required(true)
|
|
|
|
.help("The number of tokens to request"),
|
|
|
|
),
|
2018-12-07 19:01:28 -08:00
|
|
|
)
|
|
|
|
.subcommand(SubCommand::with_name("balance").about("Get your balance"))
|
2018-09-19 12:44:16 -07:00
|
|
|
.subcommand(
|
|
|
|
SubCommand::with_name("cancel")
|
|
|
|
.about("Cancel a transfer")
|
|
|
|
.arg(
|
|
|
|
Arg::with_name("process-id")
|
|
|
|
.index(1)
|
|
|
|
.value_name("PROCESS_ID")
|
|
|
|
.takes_value(true)
|
|
|
|
.required(true)
|
|
|
|
.help("The process id of the transfer to cancel"),
|
|
|
|
),
|
2018-12-07 19:01:28 -08:00
|
|
|
)
|
|
|
|
.subcommand(
|
2018-09-19 12:44:16 -07:00
|
|
|
SubCommand::with_name("confirm")
|
|
|
|
.about("Confirm transaction by signature")
|
|
|
|
.arg(
|
|
|
|
Arg::with_name("signature")
|
|
|
|
.index(1)
|
|
|
|
.value_name("SIGNATURE")
|
|
|
|
.takes_value(true)
|
|
|
|
.required(true)
|
|
|
|
.help("The transaction signature to confirm"),
|
|
|
|
),
|
2018-12-07 19:01:28 -08:00
|
|
|
)
|
|
|
|
.subcommand(
|
2018-11-12 12:48:59 -08:00
|
|
|
SubCommand::with_name("deploy")
|
|
|
|
.about("Deploy a program")
|
|
|
|
.arg(
|
|
|
|
Arg::with_name("program-location")
|
|
|
|
.index(1)
|
|
|
|
.value_name("PATH")
|
|
|
|
.takes_value(true)
|
|
|
|
.required(true)
|
|
|
|
.help("/path/to/program.o"),
|
2018-12-03 13:32:31 -08:00
|
|
|
), // TODO: Add "loader" argument; current default is bpf_loader
|
2018-12-07 19:01:28 -08:00
|
|
|
)
|
|
|
|
.subcommand(
|
2018-11-12 12:48:59 -08:00
|
|
|
SubCommand::with_name("get-transaction-count")
|
|
|
|
.about("Get current transaction count"),
|
2018-12-07 19:01:28 -08:00
|
|
|
)
|
|
|
|
.subcommand(
|
2018-09-14 01:59:09 -07:00
|
|
|
SubCommand::with_name("pay")
|
|
|
|
.about("Send a payment")
|
|
|
|
.arg(
|
2018-09-19 12:44:16 -07:00
|
|
|
Arg::with_name("to")
|
|
|
|
.index(1)
|
|
|
|
.value_name("PUBKEY")
|
|
|
|
.takes_value(true)
|
|
|
|
.required(true)
|
|
|
|
.help("The pubkey of recipient"),
|
2018-12-07 19:01:28 -08:00
|
|
|
)
|
|
|
|
.arg(
|
2018-09-14 01:59:09 -07:00
|
|
|
Arg::with_name("tokens")
|
2018-09-19 12:44:16 -07:00
|
|
|
.index(2)
|
|
|
|
.value_name("NUM")
|
2018-09-14 01:59:09 -07:00
|
|
|
.takes_value(true)
|
|
|
|
.required(true)
|
|
|
|
.help("The number of tokens to send"),
|
2018-12-07 19:01:28 -08:00
|
|
|
)
|
|
|
|
.arg(
|
2018-09-19 12:44:16 -07:00
|
|
|
Arg::with_name("timestamp")
|
|
|
|
.long("after")
|
|
|
|
.value_name("DATETIME")
|
|
|
|
.takes_value(true)
|
|
|
|
.help("A timestamp after which transaction will execute"),
|
2018-12-07 19:01:28 -08:00
|
|
|
)
|
|
|
|
.arg(
|
2018-09-19 12:44:16 -07:00
|
|
|
Arg::with_name("timestamp-pubkey")
|
|
|
|
.long("require-timestamp-from")
|
2018-09-14 01:59:09 -07:00
|
|
|
.value_name("PUBKEY")
|
|
|
|
.takes_value(true)
|
2018-09-19 12:44:16 -07:00
|
|
|
.requires("timestamp")
|
|
|
|
.help("Require timestamp from this third party"),
|
2018-12-07 19:01:28 -08:00
|
|
|
)
|
|
|
|
.arg(
|
2018-09-19 12:44:16 -07:00
|
|
|
Arg::with_name("witness")
|
|
|
|
.long("require-signature-from")
|
|
|
|
.value_name("PUBKEY")
|
|
|
|
.takes_value(true)
|
|
|
|
.multiple(true)
|
|
|
|
.use_delimiter(true)
|
|
|
|
.help("Any third party signatures required to unlock the tokens"),
|
2018-12-07 19:01:28 -08:00
|
|
|
)
|
|
|
|
.arg(
|
2018-09-25 11:45:25 -07:00
|
|
|
Arg::with_name("cancelable")
|
|
|
|
.long("cancelable")
|
|
|
|
.takes_value(false),
|
2018-09-14 01:59:09 -07:00
|
|
|
),
|
2018-12-07 19:01:28 -08:00
|
|
|
)
|
|
|
|
.subcommand(
|
2018-09-19 12:44:16 -07:00
|
|
|
SubCommand::with_name("send-signature")
|
|
|
|
.about("Send a signature to authorize a transfer")
|
2018-09-14 01:59:09 -07:00
|
|
|
.arg(
|
2018-09-24 09:23:16 -07:00
|
|
|
Arg::with_name("to")
|
2018-09-14 01:59:09 -07:00
|
|
|
.index(1)
|
2018-09-24 09:23:16 -07:00
|
|
|
.value_name("PUBKEY")
|
|
|
|
.takes_value(true)
|
|
|
|
.required(true)
|
|
|
|
.help("The pubkey of recipient"),
|
2018-12-07 19:01:28 -08:00
|
|
|
)
|
|
|
|
.arg(
|
2018-09-24 09:23:16 -07:00
|
|
|
Arg::with_name("process-id")
|
|
|
|
.index(2)
|
2018-09-19 12:44:16 -07:00
|
|
|
.value_name("PROCESS_ID")
|
|
|
|
.takes_value(true)
|
2018-09-14 01:59:09 -07:00
|
|
|
.required(true)
|
2018-09-19 12:44:16 -07:00
|
|
|
.help("The process id of the transfer to authorize"),
|
2018-09-14 01:59:09 -07:00
|
|
|
),
|
2018-12-07 19:01:28 -08:00
|
|
|
)
|
|
|
|
.subcommand(
|
2018-09-19 12:44:16 -07:00
|
|
|
SubCommand::with_name("send-timestamp")
|
|
|
|
.about("Send a timestamp to unlock a transfer")
|
|
|
|
.arg(
|
2018-09-24 09:23:16 -07:00
|
|
|
Arg::with_name("to")
|
2018-09-19 12:44:16 -07:00
|
|
|
.index(1)
|
2018-09-24 09:23:16 -07:00
|
|
|
.value_name("PUBKEY")
|
|
|
|
.takes_value(true)
|
|
|
|
.required(true)
|
|
|
|
.help("The pubkey of recipient"),
|
2018-12-07 19:01:28 -08:00
|
|
|
)
|
|
|
|
.arg(
|
2018-09-24 09:23:16 -07:00
|
|
|
Arg::with_name("process-id")
|
|
|
|
.index(2)
|
2018-09-19 12:44:16 -07:00
|
|
|
.value_name("PROCESS_ID")
|
|
|
|
.takes_value(true)
|
|
|
|
.required(true)
|
|
|
|
.help("The process id of the transfer to unlock"),
|
2018-12-07 19:01:28 -08:00
|
|
|
)
|
|
|
|
.arg(
|
2018-09-19 12:44:16 -07:00
|
|
|
Arg::with_name("datetime")
|
|
|
|
.long("date")
|
|
|
|
.value_name("DATETIME")
|
|
|
|
.takes_value(true)
|
|
|
|
.help("Optional arbitrary timestamp to apply"),
|
|
|
|
),
|
|
|
|
);
|
|
|
|
let pubkey = Keypair::new().pubkey();
|
|
|
|
let pubkey_string = format!("{}", pubkey);
|
|
|
|
let witness0 = Keypair::new().pubkey();
|
|
|
|
let witness0_string = format!("{}", witness0);
|
|
|
|
let witness1 = Keypair::new().pubkey();
|
|
|
|
let witness1_string = format!("{}", witness1);
|
|
|
|
let dt = Utc.ymd(2018, 9, 19).and_hms(17, 30, 59);
|
2018-09-14 01:59:09 -07:00
|
|
|
|
2018-09-19 12:44:16 -07:00
|
|
|
// Test Airdrop Subcommand
|
2018-09-14 01:59:09 -07:00
|
|
|
let test_airdrop = test_commands
|
|
|
|
.clone()
|
2018-09-19 12:44:16 -07:00
|
|
|
.get_matches_from(vec!["test", "airdrop", "50"]);
|
2018-09-14 01:59:09 -07:00
|
|
|
assert_eq!(
|
|
|
|
parse_command(pubkey, &test_airdrop).unwrap(),
|
2019-01-13 23:10:03 -08:00
|
|
|
WalletCommand::Airdrop(50)
|
2018-09-14 01:59:09 -07:00
|
|
|
);
|
|
|
|
let test_bad_airdrop = test_commands
|
|
|
|
.clone()
|
2018-09-19 12:44:16 -07:00
|
|
|
.get_matches_from(vec!["test", "airdrop", "notint"]);
|
2018-09-14 01:59:09 -07:00
|
|
|
assert!(parse_command(pubkey, &test_bad_airdrop).is_err());
|
|
|
|
|
2018-09-19 12:44:16 -07:00
|
|
|
// Test Cancel Subcommand
|
|
|
|
let test_cancel =
|
|
|
|
test_commands
|
|
|
|
.clone()
|
|
|
|
.get_matches_from(vec!["test", "cancel", &pubkey_string]);
|
2018-09-14 01:59:09 -07:00
|
|
|
assert_eq!(
|
2018-09-19 12:44:16 -07:00
|
|
|
parse_command(pubkey, &test_cancel).unwrap(),
|
|
|
|
WalletCommand::Cancel(pubkey)
|
2018-09-14 01:59:09 -07:00
|
|
|
);
|
|
|
|
|
2018-09-19 12:44:16 -07:00
|
|
|
// Test Confirm Subcommand
|
2018-09-14 01:59:09 -07:00
|
|
|
let signature = Signature::new(&vec![1; 64]);
|
|
|
|
let signature_string = format!("{:?}", signature);
|
|
|
|
let test_confirm =
|
|
|
|
test_commands
|
|
|
|
.clone()
|
|
|
|
.get_matches_from(vec!["test", "confirm", &signature_string]);
|
|
|
|
assert_eq!(
|
|
|
|
parse_command(pubkey, &test_confirm).unwrap(),
|
|
|
|
WalletCommand::Confirm(signature)
|
|
|
|
);
|
2018-09-19 12:44:16 -07:00
|
|
|
let test_bad_signature = test_commands
|
|
|
|
.clone()
|
|
|
|
.get_matches_from(vec!["test", "confirm", "deadbeef"]);
|
2018-09-14 01:59:09 -07:00
|
|
|
assert!(parse_command(pubkey, &test_bad_signature).is_err());
|
2018-09-19 12:44:16 -07:00
|
|
|
|
2018-11-12 12:48:59 -08:00
|
|
|
// Test Deploy Subcommand
|
|
|
|
let test_deploy =
|
|
|
|
test_commands
|
|
|
|
.clone()
|
|
|
|
.get_matches_from(vec!["test", "deploy", "/Users/test/program.o"]);
|
|
|
|
assert_eq!(
|
|
|
|
parse_command(pubkey, &test_deploy).unwrap(),
|
|
|
|
WalletCommand::Deploy("/Users/test/program.o".to_string())
|
|
|
|
);
|
|
|
|
|
2018-09-19 12:44:16 -07:00
|
|
|
// Test Simple Pay Subcommand
|
|
|
|
let test_pay =
|
|
|
|
test_commands
|
|
|
|
.clone()
|
|
|
|
.get_matches_from(vec!["test", "pay", &pubkey_string, "50"]);
|
|
|
|
assert_eq!(
|
|
|
|
parse_command(pubkey, &test_pay).unwrap(),
|
|
|
|
WalletCommand::Pay(50, pubkey, None, None, None, None)
|
|
|
|
);
|
|
|
|
let test_bad_pubkey = test_commands
|
|
|
|
.clone()
|
|
|
|
.get_matches_from(vec!["test", "pay", "deadbeef", "50"]);
|
|
|
|
assert!(parse_command(pubkey, &test_bad_pubkey).is_err());
|
|
|
|
|
|
|
|
// Test Pay Subcommand w/ Witness
|
|
|
|
let test_pay_multiple_witnesses = test_commands.clone().get_matches_from(vec![
|
|
|
|
"test",
|
|
|
|
"pay",
|
|
|
|
&pubkey_string,
|
|
|
|
"50",
|
|
|
|
"--require-signature-from",
|
|
|
|
&witness0_string,
|
|
|
|
"--require-signature-from",
|
|
|
|
&witness1_string,
|
|
|
|
]);
|
|
|
|
assert_eq!(
|
|
|
|
parse_command(pubkey, &test_pay_multiple_witnesses).unwrap(),
|
|
|
|
WalletCommand::Pay(50, pubkey, None, None, Some(vec![witness0, witness1]), None)
|
|
|
|
);
|
|
|
|
let test_pay_single_witness = test_commands.clone().get_matches_from(vec![
|
|
|
|
"test",
|
|
|
|
"pay",
|
|
|
|
&pubkey_string,
|
|
|
|
"50",
|
|
|
|
"--require-signature-from",
|
|
|
|
&witness0_string,
|
|
|
|
]);
|
|
|
|
assert_eq!(
|
|
|
|
parse_command(pubkey, &test_pay_single_witness).unwrap(),
|
|
|
|
WalletCommand::Pay(50, pubkey, None, None, Some(vec![witness0]), None)
|
|
|
|
);
|
|
|
|
|
|
|
|
// Test Pay Subcommand w/ Timestamp
|
|
|
|
let test_pay_timestamp = test_commands.clone().get_matches_from(vec![
|
|
|
|
"test",
|
|
|
|
"pay",
|
|
|
|
&pubkey_string,
|
|
|
|
"50",
|
|
|
|
"--after",
|
|
|
|
"2018-09-19T17:30:59",
|
|
|
|
"--require-timestamp-from",
|
|
|
|
&witness0_string,
|
|
|
|
]);
|
|
|
|
assert_eq!(
|
|
|
|
parse_command(pubkey, &test_pay_timestamp).unwrap(),
|
|
|
|
WalletCommand::Pay(50, pubkey, Some(dt), Some(witness0), None, None)
|
|
|
|
);
|
|
|
|
|
|
|
|
// Test Send-Signature Subcommand
|
2018-09-24 09:23:16 -07:00
|
|
|
let test_send_signature = test_commands.clone().get_matches_from(vec![
|
|
|
|
"test",
|
|
|
|
"send-signature",
|
|
|
|
&pubkey_string,
|
|
|
|
&pubkey_string,
|
|
|
|
]);
|
2018-09-19 12:44:16 -07:00
|
|
|
assert_eq!(
|
|
|
|
parse_command(pubkey, &test_send_signature).unwrap(),
|
2018-09-24 09:23:16 -07:00
|
|
|
WalletCommand::Witness(pubkey, pubkey)
|
2018-09-19 12:44:16 -07:00
|
|
|
);
|
|
|
|
let test_pay_multiple_witnesses = test_commands.clone().get_matches_from(vec![
|
|
|
|
"test",
|
|
|
|
"pay",
|
|
|
|
&pubkey_string,
|
|
|
|
"50",
|
|
|
|
"--after",
|
|
|
|
"2018-09-19T17:30:59",
|
|
|
|
"--require-signature-from",
|
|
|
|
&witness0_string,
|
|
|
|
"--require-timestamp-from",
|
|
|
|
&witness0_string,
|
|
|
|
"--require-signature-from",
|
|
|
|
&witness1_string,
|
|
|
|
]);
|
|
|
|
assert_eq!(
|
|
|
|
parse_command(pubkey, &test_pay_multiple_witnesses).unwrap(),
|
|
|
|
WalletCommand::Pay(
|
|
|
|
50,
|
|
|
|
pubkey,
|
|
|
|
Some(dt),
|
|
|
|
Some(witness0),
|
|
|
|
Some(vec![witness0, witness1]),
|
|
|
|
None
|
|
|
|
)
|
|
|
|
);
|
|
|
|
|
|
|
|
// Test Send-Timestamp Subcommand
|
|
|
|
let test_send_timestamp = test_commands.clone().get_matches_from(vec![
|
|
|
|
"test",
|
|
|
|
"send-timestamp",
|
|
|
|
&pubkey_string,
|
2018-09-24 09:23:16 -07:00
|
|
|
&pubkey_string,
|
2018-09-19 12:44:16 -07:00
|
|
|
"--date",
|
|
|
|
"2018-09-19T17:30:59",
|
|
|
|
]);
|
|
|
|
assert_eq!(
|
|
|
|
parse_command(pubkey, &test_send_timestamp).unwrap(),
|
2018-09-24 09:23:16 -07:00
|
|
|
WalletCommand::TimeElapsed(pubkey, pubkey, dt)
|
2018-09-19 12:44:16 -07:00
|
|
|
);
|
|
|
|
let test_bad_timestamp = test_commands.clone().get_matches_from(vec![
|
|
|
|
"test",
|
|
|
|
"send-timestamp",
|
|
|
|
&pubkey_string,
|
2018-09-24 09:23:16 -07:00
|
|
|
&pubkey_string,
|
2018-09-19 12:44:16 -07:00
|
|
|
"--date",
|
|
|
|
"20180919T17:30:59",
|
|
|
|
]);
|
|
|
|
assert!(parse_command(pubkey, &test_bad_timestamp).is_err());
|
2018-09-14 01:59:09 -07:00
|
|
|
}
|
2019-01-13 23:10:03 -08:00
|
|
|
|
2018-09-14 01:59:09 -07:00
|
|
|
#[test]
|
2018-09-25 11:45:25 -07:00
|
|
|
fn test_wallet_process_command() {
|
2019-01-13 23:10:03 -08:00
|
|
|
// Success cases
|
2018-11-12 12:48:59 -08:00
|
|
|
let mut config = WalletConfig::default();
|
2019-01-13 23:10:03 -08:00
|
|
|
config.rpc_client = Some(RpcClient::new("succeeds".to_string()));
|
2018-11-12 12:48:59 -08:00
|
|
|
|
2019-01-13 23:10:03 -08:00
|
|
|
let keypair = Keypair::new();
|
|
|
|
let pubkey = keypair.pubkey().to_string();
|
|
|
|
config.id = keypair;
|
|
|
|
config.command = WalletCommand::Address;
|
|
|
|
assert_eq!(process_command(&config).unwrap(), pubkey);
|
2018-09-14 01:59:09 -07:00
|
|
|
|
|
|
|
config.command = WalletCommand::Balance;
|
2019-01-13 23:10:03 -08:00
|
|
|
assert_eq!(process_command(&config).unwrap(), "Your balance is: 50");
|
2018-09-14 01:59:09 -07:00
|
|
|
|
2019-01-13 23:10:03 -08:00
|
|
|
let process_id = Keypair::new().pubkey();
|
|
|
|
config.command = WalletCommand::Cancel(process_id);
|
|
|
|
assert_eq!(process_command(&config).unwrap(), SIGNATURE);
|
2018-09-17 12:15:26 -07:00
|
|
|
|
2019-01-13 23:10:03 -08:00
|
|
|
let good_signature = Signature::new(&bs58::decode(SIGNATURE).into_vec().unwrap());
|
|
|
|
config.command = WalletCommand::Confirm(good_signature);
|
2018-09-20 22:27:06 -07:00
|
|
|
assert_eq!(process_command(&config).unwrap(), "Confirmed");
|
2019-01-13 23:10:03 -08:00
|
|
|
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");
|
2018-09-14 01:59:09 -07:00
|
|
|
|
2019-01-13 23:10:03 -08:00
|
|
|
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_eq!(signature.unwrap(), SIGNATURE.to_string());
|
|
|
|
|
|
|
|
let date_string = "\"2018-09-19T17:30:59Z\"";
|
|
|
|
let dt: DateTime<Utc> = 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);
|
|
|
|
let json: Value = serde_json::from_str(&result.unwrap()).unwrap();
|
2018-09-17 12:15:26 -07:00
|
|
|
assert_eq!(
|
2019-01-13 23:10:03 -08:00
|
|
|
json.as_object()
|
|
|
|
.unwrap()
|
|
|
|
.get("signature")
|
|
|
|
.unwrap()
|
|
|
|
.as_str()
|
|
|
|
.unwrap(),
|
|
|
|
SIGNATURE.to_string()
|
2018-09-17 12:15:26 -07:00
|
|
|
);
|
2018-09-25 11:45:25 -07:00
|
|
|
|
2019-01-13 23:10:03 -08:00
|
|
|
let witness = Keypair::new().pubkey();
|
|
|
|
config.command = WalletCommand::Pay(
|
|
|
|
10,
|
|
|
|
bob_pubkey,
|
2019-01-03 21:29:21 -08:00
|
|
|
None,
|
2018-11-05 09:50:58 -08:00
|
|
|
None,
|
2019-01-13 23:10:03 -08:00
|
|
|
Some(vec![witness]),
|
|
|
|
Some(config.id.pubkey()),
|
|
|
|
);
|
|
|
|
let result = process_command(&config);
|
|
|
|
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()
|
2018-09-14 01:59:09 -07:00
|
|
|
);
|
|
|
|
|
2019-01-13 23:10:03 -08:00
|
|
|
let process_id = Keypair::new().pubkey();
|
|
|
|
config.command = WalletCommand::TimeElapsed(bob_pubkey, process_id, dt);
|
|
|
|
let signature = process_command(&config);
|
|
|
|
assert_eq!(signature.unwrap(), SIGNATURE.to_string());
|
2018-09-14 01:59:09 -07:00
|
|
|
|
2019-01-13 23:10:03 -08:00
|
|
|
let witness = Keypair::new().pubkey();
|
|
|
|
config.command = WalletCommand::Witness(bob_pubkey, witness);
|
|
|
|
let signature = process_command(&config);
|
|
|
|
assert_eq!(signature.unwrap(), SIGNATURE.to_string());
|
2018-09-20 22:27:06 -07:00
|
|
|
|
2019-01-13 23:10:03 -08:00
|
|
|
// Need airdrop cases
|
|
|
|
config.command = WalletCommand::Airdrop(50);
|
|
|
|
assert!(process_command(&config).is_err());
|
2018-11-14 18:57:34 -08:00
|
|
|
|
2019-01-13 23:10:03 -08:00
|
|
|
config.rpc_client = Some(RpcClient::new("airdrop".to_string()));
|
|
|
|
config.command = WalletCommand::TimeElapsed(bob_pubkey, process_id, dt);
|
|
|
|
let signature = process_command(&config);
|
|
|
|
assert_eq!(signature.unwrap(), SIGNATURE.to_string());
|
2018-11-27 21:08:14 -08:00
|
|
|
|
2019-01-13 23:10:03 -08:00
|
|
|
let witness = Keypair::new().pubkey();
|
|
|
|
config.command = WalletCommand::Witness(bob_pubkey, witness);
|
|
|
|
let signature = process_command(&config);
|
|
|
|
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());
|
2018-09-25 11:45:25 -07:00
|
|
|
|
2019-01-13 23:10:03 -08:00
|
|
|
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);
|
|
|
|
assert!(process_command(&config).is_err());
|
|
|
|
|
|
|
|
config.command = WalletCommand::Pay(
|
|
|
|
10,
|
|
|
|
bob_pubkey,
|
|
|
|
Some(dt),
|
|
|
|
Some(config.id.pubkey()),
|
|
|
|
None,
|
|
|
|
None,
|
|
|
|
);
|
|
|
|
assert!(process_command(&config).is_err());
|
|
|
|
|
|
|
|
config.command = WalletCommand::Pay(
|
|
|
|
10,
|
|
|
|
bob_pubkey,
|
|
|
|
None,
|
|
|
|
None,
|
|
|
|
Some(vec![witness]),
|
|
|
|
Some(config.id.pubkey()),
|
|
|
|
);
|
|
|
|
assert!(process_command(&config).is_err());
|
|
|
|
|
|
|
|
config.command = WalletCommand::TimeElapsed(bob_pubkey, process_id, dt);
|
|
|
|
assert!(process_command(&config).is_err());
|
2018-09-14 01:59:09 -07:00
|
|
|
}
|
2019-01-17 08:49:16 -08:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_wallet_deploy() {
|
2019-02-27 11:17:32 -08:00
|
|
|
solana_logger::setup();
|
2019-01-17 08:49:16 -08:00
|
|
|
let mut pathbuf = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
|
|
|
pathbuf.push("tests");
|
|
|
|
pathbuf.push("fixtures");
|
|
|
|
pathbuf.push("noop");
|
|
|
|
pathbuf.set_extension("so");
|
|
|
|
|
|
|
|
// Success case
|
|
|
|
let mut config = WalletConfig::default();
|
|
|
|
config.rpc_client = Some(RpcClient::new("succeeds".to_string()));
|
|
|
|
|
|
|
|
config.command = WalletCommand::Deploy(pathbuf.to_str().unwrap().to_string());
|
|
|
|
let result = process_command(&config);
|
|
|
|
let json: Value = serde_json::from_str(&result.unwrap()).unwrap();
|
|
|
|
let program_id = json
|
|
|
|
.as_object()
|
|
|
|
.unwrap()
|
|
|
|
.get("programId")
|
|
|
|
.unwrap()
|
|
|
|
.as_str()
|
|
|
|
.unwrap();
|
|
|
|
let program_id_vec = bs58::decode(program_id).into_vec().unwrap();
|
|
|
|
assert_eq!(program_id_vec.len(), mem::size_of::<Pubkey>());
|
|
|
|
|
|
|
|
// Failure cases
|
|
|
|
config.rpc_client = Some(RpcClient::new("airdrop".to_string()));
|
|
|
|
assert!(process_command(&config).is_err());
|
|
|
|
|
|
|
|
config.command = WalletCommand::Deploy("bad/file/location.so".to_string());
|
|
|
|
assert!(process_command(&config).is_err());
|
|
|
|
}
|
2018-10-17 13:42:54 -07:00
|
|
|
|
2018-10-08 16:15:17 -07:00
|
|
|
fn tmp_file_path(name: &str) -> String {
|
|
|
|
use std::env;
|
|
|
|
let out_dir = env::var("OUT_DIR").unwrap_or_else(|_| "target".to_string());
|
|
|
|
let keypair = Keypair::new();
|
|
|
|
|
|
|
|
format!("{}/tmp/{}-{}", out_dir, name, keypair.pubkey()).to_string()
|
|
|
|
}
|
|
|
|
|
2018-09-17 09:48:04 -07:00
|
|
|
#[test]
|
2018-09-25 11:45:25 -07:00
|
|
|
fn test_wallet_gen_keypair_file() {
|
2018-10-08 16:15:17 -07:00
|
|
|
let outfile = tmp_file_path("test_gen_keypair_file.json");
|
2018-09-17 09:48:04 -07:00
|
|
|
let serialized_keypair = gen_keypair_file(outfile.to_string()).unwrap();
|
|
|
|
let keypair_vec: Vec<u8> = serde_json::from_str(&serialized_keypair).unwrap();
|
2018-10-08 16:15:17 -07:00
|
|
|
assert!(Path::new(&outfile).exists());
|
2018-11-12 22:34:43 -08:00
|
|
|
assert_eq!(keypair_vec, read_pkcs8(&outfile).unwrap());
|
2019-01-28 14:52:35 -08:00
|
|
|
read_keypair(&outfile).unwrap();
|
2018-09-17 09:48:04 -07:00
|
|
|
assert_eq!(
|
|
|
|
read_keypair(&outfile).unwrap().pubkey().as_ref().len(),
|
|
|
|
mem::size_of::<Pubkey>()
|
|
|
|
);
|
2018-10-08 16:15:17 -07:00
|
|
|
fs::remove_file(&outfile).unwrap();
|
|
|
|
assert!(!Path::new(&outfile).exists());
|
2018-09-17 09:48:04 -07:00
|
|
|
}
|
2018-10-17 13:42:54 -07:00
|
|
|
|
2019-01-13 23:10:03 -08:00
|
|
|
#[test]
|
2019-03-02 10:01:13 -08:00
|
|
|
fn test_wallet_get_recent_block_hash() {
|
2019-01-13 23:10:03 -08:00
|
|
|
let rpc_client = RpcClient::new("succeeds".to_string());
|
2018-09-24 09:23:16 -07:00
|
|
|
|
2019-01-13 23:10:03 -08:00
|
|
|
let vec = bs58::decode(PUBKEY).into_vec().unwrap();
|
2019-03-02 10:09:09 -08:00
|
|
|
let expected_block_hash = Hash::new(&vec);
|
2018-09-24 09:23:16 -07:00
|
|
|
|
2019-03-02 10:09:09 -08:00
|
|
|
let block_hash = get_recent_block_hash(&rpc_client);
|
|
|
|
assert_eq!(block_hash.unwrap(), expected_block_hash);
|
2018-11-12 12:48:59 -08:00
|
|
|
|
2019-01-13 23:10:03 -08:00
|
|
|
let rpc_client = RpcClient::new("fails".to_string());
|
2018-11-12 12:48:59 -08:00
|
|
|
|
2019-03-02 10:09:09 -08:00
|
|
|
let block_hash = get_recent_block_hash(&rpc_client);
|
|
|
|
assert!(block_hash.is_err());
|
2019-01-13 23:10:03 -08:00
|
|
|
}
|
2018-09-24 09:23:16 -07:00
|
|
|
|
2019-01-13 23:10:03 -08:00
|
|
|
#[test]
|
2019-02-27 15:56:21 -08:00
|
|
|
fn test_wallet_send_transaction() {
|
2019-01-13 23:10:03 -08:00
|
|
|
let rpc_client = RpcClient::new("succeeds".to_string());
|
2018-09-24 09:23:16 -07:00
|
|
|
|
2019-01-13 23:10:03 -08:00
|
|
|
let key = Keypair::new();
|
|
|
|
let to = Keypair::new().pubkey();
|
2019-03-02 10:09:09 -08:00
|
|
|
let block_hash = Hash::default();
|
|
|
|
let tx = SystemTransaction::new_account(&key, to, 50, block_hash, 0);
|
2018-09-24 09:23:16 -07:00
|
|
|
|
2019-02-27 15:56:21 -08:00
|
|
|
let signature = send_transaction(&rpc_client, &tx);
|
2019-01-13 23:10:03 -08:00
|
|
|
assert_eq!(signature.unwrap(), SIGNATURE.to_string());
|
2018-09-24 09:23:16 -07:00
|
|
|
|
2019-01-13 23:10:03 -08:00
|
|
|
let rpc_client = RpcClient::new("fails".to_string());
|
2018-09-25 11:45:25 -07:00
|
|
|
|
2019-02-27 15:56:21 -08:00
|
|
|
let signature = send_transaction(&rpc_client, &tx);
|
2019-01-13 23:10:03 -08:00
|
|
|
assert!(signature.is_err());
|
2018-09-24 09:23:16 -07:00
|
|
|
}
|
|
|
|
|
2019-01-13 23:10:03 -08:00
|
|
|
#[test]
|
2019-02-27 15:56:21 -08:00
|
|
|
fn test_wallet_confirm_transaction() {
|
2019-01-13 23:10:03 -08:00
|
|
|
let rpc_client = RpcClient::new("succeeds".to_string());
|
|
|
|
let signature = "good_signature";
|
2019-02-27 15:56:21 -08:00
|
|
|
let status = confirm_transaction(&rpc_client, &signature);
|
2019-01-13 23:10:03 -08:00
|
|
|
assert_eq!(status.unwrap(), RpcSignatureStatus::Confirmed);
|
|
|
|
|
|
|
|
let rpc_client = RpcClient::new("bad_sig_status".to_string());
|
|
|
|
let signature = "bad_status";
|
2019-02-27 15:56:21 -08:00
|
|
|
let status = confirm_transaction(&rpc_client, &signature);
|
2019-01-13 23:10:03 -08:00
|
|
|
assert!(status.is_err());
|
|
|
|
|
|
|
|
let rpc_client = RpcClient::new("fails".to_string());
|
|
|
|
let signature = "bad_status_fmt";
|
2019-02-27 15:56:21 -08:00
|
|
|
let status = confirm_transaction(&rpc_client, &signature);
|
2019-01-13 23:10:03 -08:00
|
|
|
assert!(status.is_err());
|
|
|
|
}
|
2018-09-24 09:23:16 -07:00
|
|
|
|
2019-01-13 23:10:03 -08:00
|
|
|
#[test]
|
2019-02-27 15:56:21 -08:00
|
|
|
fn test_wallet_send_and_confirm_transaction() {
|
2019-01-13 23:10:03 -08:00
|
|
|
let rpc_client = RpcClient::new("succeeds".to_string());
|
2018-09-24 09:23:16 -07:00
|
|
|
|
2019-01-13 23:10:03 -08:00
|
|
|
let key = Keypair::new();
|
|
|
|
let to = Keypair::new().pubkey();
|
2019-03-02 10:09:09 -08:00
|
|
|
let block_hash = Hash::default();
|
|
|
|
let mut tx = SystemTransaction::new_account(&key, to, 50, block_hash, 0);
|
2018-09-24 09:23:16 -07:00
|
|
|
|
2019-01-13 23:10:03 -08:00
|
|
|
let signer = Keypair::new();
|
2018-09-24 09:23:16 -07:00
|
|
|
|
2019-02-27 15:56:21 -08:00
|
|
|
let result = send_and_confirm_transaction(&rpc_client, &mut tx, &signer);
|
2019-01-28 14:52:35 -08:00
|
|
|
result.unwrap();
|
2018-09-24 09:23:16 -07:00
|
|
|
|
2019-01-13 23:10:03 -08:00
|
|
|
let rpc_client = RpcClient::new("account_in_use".to_string());
|
2019-02-27 15:56:21 -08:00
|
|
|
let result = send_and_confirm_transaction(&rpc_client, &mut tx, &signer);
|
2019-01-13 23:10:03 -08:00
|
|
|
assert!(result.is_err());
|
2018-09-25 11:45:25 -07:00
|
|
|
|
2019-01-13 23:10:03 -08:00
|
|
|
let rpc_client = RpcClient::new("fails".to_string());
|
2019-02-27 15:56:21 -08:00
|
|
|
let result = send_and_confirm_transaction(&rpc_client, &mut tx, &signer);
|
2019-01-13 23:10:03 -08:00
|
|
|
assert!(result.is_err());
|
2018-09-24 09:23:16 -07:00
|
|
|
}
|
2018-11-12 12:48:59 -08:00
|
|
|
|
2019-01-13 23:10:03 -08:00
|
|
|
#[test]
|
2019-02-27 15:56:21 -08:00
|
|
|
fn test_wallet_resign_transaction() {
|
2019-01-13 23:10:03 -08:00
|
|
|
let rpc_client = RpcClient::new("succeeds".to_string());
|
2018-11-12 12:48:59 -08:00
|
|
|
|
2019-01-13 23:10:03 -08:00
|
|
|
let key = Keypair::new();
|
|
|
|
let to = Keypair::new().pubkey();
|
|
|
|
let vec = bs58::decode("HUu3LwEzGRsUkuJS121jzkPJW39Kq62pXCTmTa1F9jDL")
|
|
|
|
.into_vec()
|
|
|
|
.unwrap();
|
2019-03-02 10:09:09 -08:00
|
|
|
let block_hash = Hash::new(&vec);
|
|
|
|
let prev_tx = SystemTransaction::new_account(&key, to, 50, block_hash, 0);
|
|
|
|
let mut tx = SystemTransaction::new_account(&key, to, 50, block_hash, 0);
|
2018-09-24 09:23:16 -07:00
|
|
|
|
2019-02-27 15:56:21 -08:00
|
|
|
resign_transaction(&rpc_client, &mut tx, &key).unwrap();
|
2018-09-24 09:23:16 -07:00
|
|
|
|
2019-01-13 23:10:03 -08:00
|
|
|
assert_ne!(prev_tx, tx);
|
|
|
|
assert_ne!(prev_tx.signatures, tx.signatures);
|
2019-03-02 09:17:03 -08:00
|
|
|
assert_ne!(prev_tx.recent_block_hash, tx.recent_block_hash);
|
2019-01-13 23:10:03 -08:00
|
|
|
assert_eq!(prev_tx.fee, tx.fee);
|
|
|
|
assert_eq!(prev_tx.account_keys, tx.account_keys);
|
|
|
|
assert_eq!(prev_tx.instructions, tx.instructions);
|
|
|
|
}
|
2018-09-24 09:23:16 -07:00
|
|
|
|
2019-01-13 23:10:03 -08:00
|
|
|
#[test]
|
|
|
|
fn test_request_and_confirm_airdrop() {
|
|
|
|
let rpc_client = RpcClient::new("succeeds".to_string());
|
|
|
|
let drone_addr = socketaddr!(0, 0);
|
2019-01-15 16:24:09 -08:00
|
|
|
let keypair = Keypair::new();
|
2019-01-13 23:10:03 -08:00
|
|
|
let tokens = 50;
|
|
|
|
assert_eq!(
|
2019-01-15 16:24:09 -08:00
|
|
|
request_and_confirm_airdrop(&rpc_client, &drone_addr, &keypair, tokens).unwrap(),
|
2019-01-13 23:10:03 -08:00
|
|
|
()
|
2018-09-24 09:23:16 -07:00
|
|
|
);
|
|
|
|
|
2019-01-13 23:10:03 -08:00
|
|
|
let rpc_client = RpcClient::new("account_in_use".to_string());
|
2019-01-15 16:24:09 -08:00
|
|
|
assert!(request_and_confirm_airdrop(&rpc_client, &drone_addr, &keypair, tokens).is_err());
|
2018-09-25 11:45:25 -07:00
|
|
|
|
2019-01-13 23:10:03 -08:00
|
|
|
let tokens = 0;
|
2019-01-15 16:24:09 -08:00
|
|
|
assert!(request_and_confirm_airdrop(&rpc_client, &drone_addr, &keypair, tokens).is_err());
|
2018-09-24 09:23:16 -07:00
|
|
|
}
|
2018-09-14 01:59:09 -07:00
|
|
|
}
|