solana/wallet/src/wallet.rs

1820 lines
64 KiB
Rust
Raw Normal View History

use bincode::serialize;
use bs58;
2018-09-19 12:44:16 -07:00
use chrono::prelude::*;
use clap::ArgMatches;
use log::*;
use serde_json;
2018-12-14 20:39:10 -08:00
use serde_json::json;
2019-03-02 13:23:22 -08:00
use solana_budget_api;
use solana_budget_api::budget_transaction::BudgetTransaction;
2019-03-12 17:27:52 -07:00
#[cfg(test)]
use solana_client::rpc_mock::MockRpcClient as RpcClient;
2019-03-12 17:27:52 -07:00
#[cfg(not(test))]
use solana_client::rpc_request::RpcClient;
use solana_client::rpc_request::{get_rpc_request_str, RpcRequest};
2019-03-16 17:43:39 -07:00
use solana_client::rpc_signature_status::RpcSignatureStatus;
#[cfg(not(test))]
use solana_drone::drone::request_airdrop_transaction;
use solana_drone::drone::DRONE_PORT;
#[cfg(test)]
use solana_drone::drone_mock::request_airdrop_transaction;
2018-12-03 13:32:31 -08:00
use solana_sdk::bpf_loader;
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;
use solana_sdk::pubkey::Pubkey;
2019-03-16 17:43:39 -07:00
use solana_sdk::rpc_port::DEFAULT_RPC_PORT;
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;
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;
2019-03-06 17:02:20 -08:00
use solana_vote_api::vote_instruction::VoteInstruction;
use solana_vote_api::vote_transaction::VoteTransaction;
use std::fs::File;
use std::io::Read;
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
2018-10-24 09:01:19 -07:00
use std::str::FromStr;
use std::thread::sleep;
use std::time::Duration;
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)]
pub enum WalletCommand {
Address,
Airdrop(u64),
Balance,
Cancel(Pubkey),
Confirm(Signature),
2019-03-06 17:02:20 -08:00
// ConfigureStakingAccount(delegate_id, authorized_voter_id)
ConfigureStakingAccount(Option<Pubkey>, Option<Pubkey>),
CreateStakingAccount(Pubkey, u64),
2018-10-22 21:21:33 -07:00
Deploy(String),
GetTransactionCount,
2019-03-05 17:22:46 -08:00
// Pay(lamports, to, timestamp, timestamp_pubkey, witness(es), cancelable)
Pay(
u64,
Pubkey,
Option<DateTime<Utc>>,
Option<Pubkey>,
2018-09-19 12:44:16 -07:00
Option<Vec<Pubkey>>,
Option<Pubkey>,
),
// 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),
}
#[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),
}
impl fmt::Display for WalletError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "invalid")
}
}
impl error::Error for WalletError {
fn description(&self) -> &str {
"invalid"
}
fn cause(&self) -> Option<&dyn error::Error> {
// Generic error, underlying cause isn't tracked.
None
}
}
pub struct WalletConfig {
pub id: Keypair,
pub command: WalletCommand,
pub drone_host: Option<IpAddr>,
pub drone_port: u16,
pub host: IpAddr,
pub rpc_client: Option<RpcClient>,
pub rpc_host: Option<IpAddr>,
pub rpc_port: u16,
pub rpc_tls: bool,
}
impl Default for WalletConfig {
fn default() -> WalletConfig {
WalletConfig {
command: WalletCommand::Balance,
drone_host: None,
drone_port: DRONE_PORT,
host: IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)),
id: Keypair::new(),
rpc_client: None,
rpc_host: None,
2019-03-16 17:43:39 -07:00
rpc_port: DEFAULT_RPC_PORT,
rpc_tls: false,
}
}
}
2018-11-09 14:10:44 -08:00
impl WalletConfig {
pub fn drone_addr(&self) -> SocketAddr {
SocketAddr::new(self.drone_host.unwrap_or(self.host), self.drone_port)
}
pub fn rpc_addr(&self) -> String {
get_rpc_request_str(
SocketAddr::new(self.rpc_host.unwrap_or(self.host), self.rpc_port),
self.rpc_tls,
)
}
2018-11-09 14:10:44 -08:00
}
pub fn parse_command(
pubkey: &Pubkey,
matches: &ArgMatches<'_>,
) -> Result<WalletCommand, Box<dyn error::Error>> {
let response = match matches.subcommand() {
2018-09-20 22:27:06 -07:00
("address", Some(_address_matches)) => Ok(WalletCommand::Address),
("airdrop", Some(airdrop_matches)) => {
2019-03-05 17:22:46 -08:00
let lamports = airdrop_matches.value_of("lamports").unwrap().parse()?;
Ok(WalletCommand::Airdrop(lamports))
}
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)) => {
2019-03-07 16:00:12 -08:00
let pubkey_vec = bs58::decode(cancel_matches.value_of("process_id").unwrap())
2018-09-19 12:44:16 -07:00
.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()))
}
}
2019-03-06 17:02:20 -08:00
("configure-staking-account", Some(staking_config_matches)) => {
let delegate_id = staking_config_matches
.value_of("delegate")
.map(|pubkey_string| {
let pubkey_vec = bs58::decode(pubkey_string)
.into_vec()
.expect("base58-encoded public key");
// TODO: Add valid pubkey check
Pubkey::new(&pubkey_vec)
});
let authorized_voter_id =
staking_config_matches
2019-03-06 20:29:08 -08:00
.value_of("authorize")
2019-03-06 17:02:20 -08:00
.map(|pubkey_string| {
let pubkey_vec = bs58::decode(pubkey_string)
.into_vec()
.expect("base58-encoded public key");
// TODO: Add valid pubkey check
Pubkey::new(&pubkey_vec)
});
Ok(WalletCommand::ConfigureStakingAccount(
delegate_id,
authorized_voter_id,
))
}
("create-staking-account", Some(staking_matches)) => {
2019-03-07 16:00:12 -08:00
let voting_account_string = staking_matches.value_of("voting_account_id").unwrap();
2019-03-06 17:02:20 -08:00
let voting_account_vec = bs58::decode(voting_account_string)
.into_vec()
.expect("base58-encoded public key");
if voting_account_vec.len() != mem::size_of::<Pubkey>() {
eprintln!("{}", staking_matches.usage());
Err(WalletError::BadParameter("Invalid public key".to_string()))?;
}
let voting_account_id = Pubkey::new(&voting_account_vec);
let lamports = staking_matches.value_of("lamports").unwrap().parse()?;
Ok(WalletCommand::CreateStakingAccount(
voting_account_id,
lamports,
))
}
2018-10-22 21:21:33 -07:00
("deploy", Some(deploy_matches)) => Ok(WalletCommand::Deploy(
deploy_matches
2019-03-07 16:00:12 -08:00
.value_of("program_location")
2018-10-22 21:21:33 -07:00
.unwrap()
.to_string(),
)),
("get-transaction-count", Some(_matches)) => Ok(WalletCommand::GetTransactionCount),
("pay", Some(pay_matches)) => {
2019-03-05 17:22:46 -08:00
let lamports = pay_matches.value_of("lamports").unwrap().parse()?;
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(),
))?;
}
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
};
2019-03-07 16:00:12 -08:00
let timestamp_pubkey = if pay_matches.is_present("timestamp_pubkey") {
let pubkey_vec = bs58::decode(pay_matches.value_of("timestamp_pubkey").unwrap())
2018-09-19 12:44:16 -07:00
.into_vec()
.expect("base58-encoded public key");
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") {
Some(*pubkey)
2018-09-19 12:44:16 -07:00
} else {
None
};
Ok(WalletCommand::Pay(
2019-03-05 17:22:46 -08:00
lamports,
2018-09-19 12:44:16 -07:00
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);
2019-03-07 16:00:12 -08:00
let pubkey_vec = bs58::decode(sig_matches.value_of("process_id").unwrap())
2018-09-19 12:44:16 -07:00
.into_vec()
.expect("base58-encoded public key");
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)) => {
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);
2019-03-07 16:00:12 -08:00
let pubkey_vec = bs58::decode(timestamp_matches.value_of("process_id").unwrap())
2018-09-19 12:44:16 -07:00
.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);
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()
};
Ok(WalletCommand::TimeElapsed(to, process_id, dt))
}
("", None) => {
2018-09-24 09:23:16 -07:00
eprintln!("{}", matches.usage());
Err(WalletError::CommandNotRecognized(
"no subcommand given".to_string(),
))
}
_ => unreachable!(),
}?;
Ok(response)
}
type ProcessResult = Result<String, Box<dyn error::Error>>;
fn process_airdrop(
rpc_client: &RpcClient,
config: &WalletConfig,
drone_addr: SocketAddr,
2019-03-05 17:22:46 -08:00
lamports: u64,
) -> ProcessResult {
println!(
2019-03-05 17:22:46 -08:00
"Requesting airdrop of {:?} lamports from {}",
lamports, drone_addr
);
let previous_balance = match rpc_client.retry_get_balance(&config.id.pubkey(), 5)? {
2019-03-05 17:22:46 -08:00
Some(lamports) => lamports,
None => Err(WalletError::RpcRequestError(
"Received result of an unexpected type".to_string(),
))?,
};
request_and_confirm_airdrop(&rpc_client, &drone_addr, &config.id.pubkey(), lamports)?;
let current_balance = rpc_client
.retry_get_balance(&config.id.pubkey(), 5)?
.unwrap_or(previous_balance);
if current_balance < previous_balance {
Err(format!(
"Airdrop failed: current_balance({}) < previous_balance({})",
current_balance, previous_balance
))?;
}
2019-03-05 17:22:46 -08:00
if current_balance - previous_balance < lamports {
Err(format!(
"Airdrop failed: Account balance increased by {} instead of {}",
current_balance - previous_balance,
2019-03-05 17:22:46 -08:00
lamports
))?;
}
Ok(format!("Your balance is: {:?}", current_balance))
}
fn process_balance(config: &WalletConfig, rpc_client: &RpcClient) -> ProcessResult {
let balance = rpc_client.retry_get_balance(&config.id.pubkey(), 5)?;
match balance {
Some(0) => Ok("No account found! Request an airdrop to get started.".to_string()),
2019-03-05 17:22:46 -08:00
Some(lamports) => Ok(format!("Your balance is: {:?}", lamports)),
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(&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(),
))?,
}
}
2019-03-06 17:02:20 -08:00
fn process_configure_staking(
rpc_client: &RpcClient,
config: &WalletConfig,
delegate_option: Option<Pubkey>,
authorized_voter_option: Option<Pubkey>,
) -> ProcessResult {
let recent_blockhash = get_recent_blockhash(&rpc_client)?;
2019-03-15 09:54:56 -07:00
let mut ixs = vec![];
2019-03-06 17:02:20 -08:00
if let Some(delegate_id) = delegate_option {
2019-03-15 09:54:56 -07:00
ixs.push(VoteInstruction::new_delegate_stake(
&config.id.pubkey(),
&delegate_id,
2019-03-06 17:02:20 -08:00
));
}
if let Some(authorized_voter_id) = authorized_voter_option {
2019-03-15 09:54:56 -07:00
ixs.push(VoteInstruction::new_authorize_voter(
&config.id.pubkey(),
&authorized_voter_id,
2019-03-06 17:02:20 -08:00
));
}
2019-03-15 09:54:56 -07:00
let mut tx = Transaction::new(ixs);
tx.sign(&[&config.id], recent_blockhash);
2019-03-06 17:02:20 -08:00
let signature_str = send_and_confirm_transaction(&rpc_client, &mut tx, &config.id)?;
Ok(signature_str.to_string())
}
fn process_create_staking(
rpc_client: &RpcClient,
config: &WalletConfig,
voting_account_id: &Pubkey,
2019-03-06 17:02:20 -08:00
lamports: u64,
) -> ProcessResult {
let recent_blockhash = get_recent_blockhash(&rpc_client)?;
let mut tx =
VoteTransaction::new_account(&config.id, voting_account_id, recent_blockhash, lamports, 0);
let signature_str = send_and_confirm_transaction(&rpc_client, &mut tx, &config.id)?;
Ok(signature_str.to_string())
}
fn process_deploy(
rpc_client: &RpcClient,
config: &WalletConfig,
program_location: &str,
) -> ProcessResult {
let balance = rpc_client.retry_get_balance(&config.id.pubkey(), 5)?;
2019-03-05 17:22:46 -08:00
if let Some(lamports) = balance {
if lamports < 1 {
Err(WalletError::DynamicProgramError(
"Insufficient funds".to_string(),
))?
}
}
2019-03-02 10:25:16 -08:00
let blockhash = get_recent_blockhash(&rpc_client)?;
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_data = Vec::new();
file.read_to_end(&mut program_data).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:25:16 -08:00
blockhash,
1,
program_data.len() as u64,
&bpf_loader::id(),
0,
);
trace!("Creating program account");
2019-02-27 15:56:21 -08:00
send_and_confirm_transaction(&rpc_client, &mut tx, &config.id).map_err(|_| {
WalletError::DynamicProgramError("Program allocate space failed".to_string())
})?;
trace!("Writing program data");
let write_transactions: Vec<_> = program_data
.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:25:16 -08:00
blockhash,
0,
)
})
.collect();
send_and_confirm_transactions(&rpc_client, write_transactions, &program_id)?;
trace!("Finalizing program account");
let mut tx = LoaderTransaction::new_finalize(&program_id, &bpf_loader::id(), blockhash, 0);
2019-02-27 15:56:21 -08:00
send_and_confirm_transaction(&rpc_client, &mut tx, &program_id).map_err(|_| {
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,
2019-03-05 17:22:46 -08:00
lamports: u64,
to: &Pubkey,
timestamp: Option<DateTime<Utc>>,
timestamp_pubkey: Option<Pubkey>,
witnesses: &Option<Vec<Pubkey>>,
cancelable: Option<Pubkey>,
) -> ProcessResult {
2019-03-02 10:25:16 -08:00
let blockhash = get_recent_blockhash(&rpc_client)?;
if timestamp == None && *witnesses == None {
2019-03-05 17:22:46 -08:00
let mut tx = SystemTransaction::new_move(&config.id, to, lamports, blockhash, 0);
2019-02-27 15:56:21 -08:00
let signature_str = send_and_confirm_transaction(&rpc_client, &mut tx, &config.id)?;
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_state = Keypair::new();
// Initializing contract
let mut tx = BudgetTransaction::new_on_date(
2019-03-07 17:25:34 -08:00
&config.id,
to,
&contract_state.pubkey(),
dt,
&dt_pubkey,
cancelable,
2019-03-05 17:22:46 -08:00
lamports,
2019-03-02 10:25:16 -08:00
blockhash,
);
2019-02-27 15:56:21 -08:00
let signature_str = send_and_confirm_transaction(&rpc_client, &mut tx, &config.id)?;
Ok(json!({
"signature": signature_str,
"processId": format!("{}", contract_state.pubkey()),
})
.to_string())
} else if timestamp == None {
2019-03-02 10:25:16 -08:00
let blockhash = get_recent_blockhash(&rpc_client)?;
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_state = Keypair::new();
// Initializing contract
let mut tx = BudgetTransaction::new_when_signed(
2019-03-07 17:25:34 -08:00
&config.id,
to,
&contract_state.pubkey(),
&witness,
cancelable,
2019-03-05 17:22:46 -08:00
lamports,
2019-03-02 10:25:16 -08:00
blockhash,
);
2019-02-27 15:56:21 -08:00
let signature_str = send_and_confirm_transaction(&rpc_client, &mut tx, &config.id)?;
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:25:16 -08:00
let blockhash = get_recent_blockhash(&rpc_client)?;
2019-03-02 10:20:10 -08:00
let mut tx =
BudgetTransaction::new_signature(&config.id, pubkey, &config.id.pubkey(), blockhash);
2019-02-27 15:56:21 -08:00
let signature_str = send_and_confirm_transaction(&rpc_client, &mut tx, &config.id)?;
Ok(signature_str.to_string())
}
fn process_get_transaction_count(rpc_client: &RpcClient) -> ProcessResult {
let transaction_count = rpc_client
.retry_make_rpc_request(&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 {
let balance = rpc_client.retry_get_balance(&config.id.pubkey(), 5)?;
if let Some(0) = balance {
request_and_confirm_airdrop(&rpc_client, &drone_addr, &config.id.pubkey(), 1)?;
}
2019-03-02 10:25:16 -08:00
let blockhash = get_recent_blockhash(&rpc_client)?;
2019-03-02 10:25:16 -08:00
let mut tx = BudgetTransaction::new_timestamp(&config.id, pubkey, to, dt, blockhash);
2019-02-27 15:56:21 -08:00
let signature_str = send_and_confirm_transaction(&rpc_client, &mut tx, &config.id)?;
Ok(signature_str.to_string())
}
fn process_witness(
rpc_client: &RpcClient,
config: &WalletConfig,
drone_addr: SocketAddr,
to: &Pubkey,
pubkey: &Pubkey,
) -> ProcessResult {
let balance = rpc_client.retry_get_balance(&config.id.pubkey(), 5)?;
if let Some(0) = balance {
request_and_confirm_airdrop(&rpc_client, &drone_addr, &config.id.pubkey(), 1)?;
}
2019-03-02 10:25:16 -08:00
let blockhash = get_recent_blockhash(&rpc_client)?;
let mut tx = BudgetTransaction::new_signature(&config.id, pubkey, to, blockhash);
2019-02-27 15:56:21 -08:00
let signature_str = send_and_confirm_transaction(&rpc_client, &mut tx, &config.id)?;
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 {
// Get address of this client
2018-11-09 15:37:34 -08:00
return Ok(format!("{}", config.id.pubkey()));
}
let drone_addr = config.drone_addr();
let rpc_client = if config.rpc_client.is_none() {
let rpc_addr = config.rpc_addr();
RpcClient::new(rpc_addr)
} else {
// Primarily for testing
config.rpc_client.clone().unwrap()
};
match config.command {
2018-09-20 22:27:06 -07:00
// Get address of this client
WalletCommand::Address => unreachable!(),
// Request an airdrop from Solana Drone;
2019-03-05 17:22:46 -08:00
WalletCommand::Airdrop(lamports) => {
process_airdrop(&rpc_client, config, drone_addr, lamports)
}
// Check client balance
WalletCommand::Balance => process_balance(config, &rpc_client),
// Cancel a contract by contract Pubkey
WalletCommand::Cancel(pubkey) => process_cancel(&rpc_client, config, &pubkey),
// Confirm the last client transaction by signature
WalletCommand::Confirm(signature) => process_confirm(&rpc_client, signature),
2019-03-06 17:02:20 -08:00
// Configure staking account already created
WalletCommand::ConfigureStakingAccount(delegate_option, authorized_voter_option) => {
process_configure_staking(
&rpc_client,
config,
delegate_option,
authorized_voter_option,
)
}
// Create staking account
WalletCommand::CreateStakingAccount(voting_account_id, lamports) => {
process_create_staking(&rpc_client, config, &voting_account_id, lamports)
2019-03-06 17:02:20 -08:00
}
2018-10-22 21:21:33 -07:00
// Deploy a custom program to the chain
WalletCommand::Deploy(ref program_location) => {
process_deploy(&rpc_client, config, program_location)
}
2018-10-22 21:21:33 -07:00
WalletCommand::GetTransactionCount => process_get_transaction_count(&rpc_client),
2018-10-24 09:01:19 -07:00
2019-03-05 17:22:46 -08:00
// If client has positive balance, pay lamports to another address
WalletCommand::Pay(
lamports,
to,
timestamp,
timestamp_pubkey,
ref witnesses,
cancelable,
) => process_pay(
&rpc_client,
config,
lamports,
&to,
2019-03-05 17:22:46 -08:00
timestamp,
timestamp_pubkey,
witnesses,
cancelable,
),
// Apply time elapsed to contract
WalletCommand::TimeElapsed(to, pubkey, dt) => {
process_time_elapsed(&rpc_client, config, drone_addr, &to, &pubkey, dt)
}
// Apply witness signature to contract
2018-09-24 09:23:16 -07:00
WalletCommand::Witness(to, pubkey) => {
process_witness(&rpc_client, config, drone_addr, &to, &pubkey)
}
}
}
2019-03-02 10:25:16 -08:00
fn get_recent_blockhash(rpc_client: &RpcClient) -> Result<Hash, Box<dyn error::Error>> {
let result = rpc_client.retry_make_rpc_request(&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:25:16 -08:00
"Received bad blockhash".to_string(),
2018-09-22 10:33:06 -07:00
))?
}
2019-03-02 10:25:16 -08:00
let blockhash_str = result.as_str().unwrap();
let blockhash_vec = bs58::decode(blockhash_str)
2018-09-22 10:33:06 -07:00
.into_vec()
2019-03-02 10:25:16 -08:00
.map_err(|_| WalletError::RpcRequestError("Received bad blockhash".to_string()))?;
Ok(Hash::new(&blockhash_vec))
2018-09-22 10:33:06 -07:00
}
2019-03-02 10:25:16 -08:00
fn get_next_blockhash(
rpc_client: &RpcClient,
2019-03-02 10:25:16 -08:00
previous_blockhash: &Hash,
) -> Result<Hash, Box<dyn error::Error>> {
2019-03-02 10:25:16 -08:00
let mut next_blockhash_retries = 3;
loop {
2019-03-02 10:25:16 -08:00
let next_blockhash = get_recent_blockhash(rpc_client)?;
if cfg!(not(test)) {
2019-03-02 10:25:16 -08:00
if next_blockhash != *previous_blockhash {
return Ok(next_blockhash);
}
} else {
2019-03-02 10:25:16 -08:00
// When using MockRpcClient, get_recent_blockhash() returns a constant value
return Ok(next_blockhash);
}
2019-03-02 10:25:16 -08:00
if next_blockhash_retries == 0 {
Err(WalletError::RpcRequestError(
format!(
2019-03-02 10:25:16 -08:00
"Unable to fetch new blockhash, blockhash stuck at {:?}",
next_blockhash
)
.to_string(),
))?;
}
2019-03-02 10:25:16 -08:00
next_blockhash_retries -= 1;
// 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 15:56:21 -08:00
fn send_transaction(
rpc_client: &RpcClient,
transaction: &Transaction,
) -> Result<String, Box<dyn error::Error>> {
let serialized = serialize(transaction).unwrap();
let params = json!([serialized]);
let signature =
rpc_client.retry_make_rpc_request(&RpcRequest::SendTransaction, Some(params), 5)?;
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(
rpc_client: &RpcClient,
signature: &str,
) -> Result<RpcSignatureStatus, Box<dyn error::Error>> {
let params = json!([signature.to_string()]);
2018-10-24 09:01:19 -07:00
let signature_status =
rpc_client.retry_make_rpc_request(&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-03-15 06:39:42 -07:00
fn send_and_confirm_transaction<T: KeypairUtil>(
2018-12-08 21:52:29 -08:00
rpc_client: &RpcClient,
2019-02-27 15:56:21 -08:00
transaction: &mut Transaction,
2019-03-15 06:39:42 -07:00
signer: &T,
) -> Result<String, Box<dyn error::Error>> {
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;
}
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,
));
}
2018-10-24 09:01:19 -07:00
};
match status {
RpcSignatureStatus::AccountInUse | RpcSignatureStatus::SignatureNotFound => {
2019-03-02 10:25:16 -08:00
// Fetch a new blockhash and re-sign the transaction before sending it again
2019-02-27 15:56:21 -08:00
resign_transaction(rpc_client, transaction, signer)?;
send_retries -= 1;
}
RpcSignatureStatus::Confirmed => {
return Ok(signature_str);
}
_ => {
send_retries = 0;
}
}
if send_retries == 0 {
Err(WalletError::RpcRequestError(format!(
"Transaction {:?} failed: {:?}",
signature_str, status
)))?;
}
}
}
fn send_and_confirm_transactions(
rpc_client: &RpcClient,
mut transactions: Vec<Transaction>,
signer: &Keypair,
) -> Result<(), Box<dyn error::Error>> {
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 15:56:21 -08:00
let signature = send_transaction(&rpc_client, &transaction).ok();
transactions_signatures.push((transaction, signature))
}
// 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,
));
}
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 {
Err(WalletError::RpcRequestError(
"Transactions failed".to_string(),
))?;
}
send_retries -= 1;
2019-03-02 10:25:16 -08:00
// Re-sign any failed transactions with a new blockhash and retry
let blockhash =
get_next_blockhash(rpc_client, &transactions_signatures[0].0.recent_blockhash)?;
transactions = transactions_signatures
.into_iter()
.map(|(mut transaction, _)| {
2019-03-02 10:25:16 -08:00
transaction.sign(&[signer], blockhash);
transaction
})
.collect();
}
}
2019-03-15 06:39:42 -07:00
fn resign_transaction<T: KeypairUtil>(
rpc_client: &RpcClient,
tx: &mut Transaction,
2019-03-15 06:39:42 -07:00
signer_key: &T,
) -> Result<(), Box<dyn error::Error>> {
2019-03-02 10:25:16 -08:00
let blockhash = get_next_blockhash(rpc_client, &tx.recent_blockhash)?;
tx.sign(&[signer_key], blockhash);
Ok(())
}
2019-03-15 06:39:42 -07:00
// Quick and dirty Keypair that assumes the client will do retries but not update the
// blockhash. If the client updates the blockhash, the signature will be invalid.
// TODO: Parse `msg` and use that data to make a new airdrop request.
struct DroneKeypair {
transaction: Transaction,
}
impl DroneKeypair {
fn new_keypair(
drone_addr: &SocketAddr,
to_pubkey: &Pubkey,
lamports: u64,
blockhash: Hash,
) -> Result<Self, Box<dyn error::Error>> {
let transaction = request_airdrop_transaction(drone_addr, to_pubkey, lamports, blockhash)?;
Ok(Self { transaction })
}
fn airdrop_transaction(&self) -> Transaction {
self.transaction.clone()
}
}
impl KeypairUtil for DroneKeypair {
fn new() -> Self {
unimplemented!();
}
/// Return the public key of the keypair used to sign votes
fn pubkey(&self) -> Pubkey {
self.transaction.account_keys[0]
}
fn sign_message(&self, _msg: &[u8]) -> Signature {
self.transaction.signatures[0]
}
}
pub fn request_and_confirm_airdrop(
rpc_client: &RpcClient,
drone_addr: &SocketAddr,
to_pubkey: &Pubkey,
2019-03-05 17:22:46 -08:00
lamports: u64,
) -> Result<(), Box<dyn error::Error>> {
2019-03-02 10:25:16 -08:00
let blockhash = get_recent_blockhash(rpc_client)?;
2019-03-15 06:39:42 -07:00
let keypair = DroneKeypair::new_keypair(drone_addr, to_pubkey, lamports, blockhash)?;
let mut tx = keypair.airdrop_transaction();
send_and_confirm_transaction(rpc_client, &mut tx, &keypair)?;
Ok(())
2018-10-22 21:21:33 -07:00
}
#[cfg(test)]
2018-09-14 01:59:09 -07:00
mod tests {
use super::*;
2019-03-06 20:29:08 -08:00
use clap::{App, Arg, ArgGroup, SubCommand};
use serde_json::Value;
use solana::socketaddr;
2019-03-12 17:27:52 -07:00
use solana_client::rpc_mock::{PUBKEY, SIGNATURE};
use solana_sdk::signature::{gen_keypair_file, read_keypair, read_pkcs8, Keypair, KeypairUtil};
use std::fs;
use std::net::{Ipv4Addr, SocketAddr};
use std::path::{Path, PathBuf};
2018-09-14 01:59:09 -07:00
#[test]
fn test_wallet_config_drone_addr() {
let mut config = WalletConfig::default();
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)
);
}
#[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-09-14 01:59:09 -07:00
#[test]
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")
2019-03-05 17:22:46 -08:00
.about("Request a batch of lamports")
2018-09-14 01:59:09 -07:00
.arg(
2019-03-05 17:22:46 -08:00
Arg::with_name("lamports")
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)
2019-03-05 17:22:46 -08:00
.help("The number of lamports to request"),
2018-09-14 01:59:09 -07: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(
2019-03-07 16:00:12 -08:00
Arg::with_name("process_id")
2018-09-19 12:44:16 -07:00
.index(1)
.value_name("PROCESS_ID")
.takes_value(true)
.required(true)
.help("The process id of the transfer to cancel"),
),
)
.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"),
),
)
2019-03-06 20:29:08 -08:00
.subcommand(
SubCommand::with_name("configure-staking-account")
.about("Configure staking account for node")
.group(
ArgGroup::with_name("options")
.args(&["delegate", "authorize"])
.multiple(true)
.required(true),
)
.arg(
Arg::with_name("delegate")
.long("delegate-account")
.value_name("PUBKEY")
.takes_value(true)
.help("Address to delegate this vote account to"),
)
.arg(
Arg::with_name("authorize")
.long("authorize-voter")
.value_name("PUBKEY")
.takes_value(true)
.help("Vote signer to authorize"),
),
)
.subcommand(
SubCommand::with_name("create-staking-account")
.about("Create staking account for node")
.arg(
2019-03-07 16:00:12 -08:00
Arg::with_name("voting_account_id")
2019-03-06 20:29:08 -08:00
.index(1)
.value_name("PUBKEY")
.takes_value(true)
.required(true)
.help("Staking account address to fund"),
)
.arg(
Arg::with_name("lamports")
.index(2)
.value_name("NUM")
.takes_value(true)
.required(true)
.help("The number of lamports to send to staking account"),
),
)
.subcommand(
2018-11-12 12:48:59 -08:00
SubCommand::with_name("deploy")
.about("Deploy a program")
.arg(
2019-03-07 16:00:12 -08:00
Arg::with_name("program_location")
2018-11-12 12:48:59 -08:00
.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
)
.subcommand(
2018-11-12 12:48:59 -08:00
SubCommand::with_name("get-transaction-count")
.about("Get current transaction count"),
)
.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"),
)
.arg(
2019-03-05 17:22:46 -08:00
Arg::with_name("lamports")
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)
2019-03-05 17:22:46 -08:00
.help("The number of lamports to send"),
)
.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"),
)
.arg(
2019-03-07 16:00:12 -08:00
Arg::with_name("timestamp_pubkey")
2018-09-19 12:44:16 -07:00
.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"),
)
.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)
2019-03-05 17:22:46 -08:00
.help("Any third party signatures required to unlock the lamports"),
)
.arg(
Arg::with_name("cancelable")
.long("cancelable")
.takes_value(false),
2018-09-14 01:59:09 -07: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"),
)
.arg(
2019-03-07 16:00:12 -08:00
Arg::with_name("process_id")
2018-09-24 09:23:16 -07:00
.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
),
)
.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"),
)
.arg(
2019-03-07 16:00:12 -08:00
Arg::with_name("process_id")
2018-09-24 09:23:16 -07:00
.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"),
)
.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(),
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"]);
assert!(parse_command(&pubkey, &test_bad_airdrop).is_err());
2018-09-14 01:59:09 -07:00
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!(
parse_command(&pubkey, &test_cancel).unwrap(),
2018-09-19 12:44:16 -07:00
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(),
2018-09-14 01:59:09 -07:00
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"]);
assert!(parse_command(&pubkey, &test_bad_signature).is_err());
2018-09-19 12:44:16 -07:00
2019-03-06 20:29:08 -08:00
// Test ConfigureStakingAccount Subcommand
let second_pubkey = Keypair::new().pubkey();
let second_pubkey_string = format!("{}", second_pubkey);
let test_configure_staking_account = test_commands.clone().get_matches_from(vec![
"test",
"configure-staking-account",
"--delegate-account",
&pubkey_string,
"--authorize-voter",
&second_pubkey_string,
]);
assert_eq!(
parse_command(&pubkey, &test_configure_staking_account).unwrap(),
2019-03-06 20:29:08 -08:00
WalletCommand::ConfigureStakingAccount(Some(pubkey), Some(second_pubkey))
);
let test_configure_staking_account = test_commands.clone().get_matches_from(vec![
"test",
"configure-staking-account",
"--delegate-account",
&pubkey_string,
]);
assert_eq!(
parse_command(&pubkey, &test_configure_staking_account).unwrap(),
2019-03-06 20:29:08 -08:00
WalletCommand::ConfigureStakingAccount(Some(pubkey), None)
);
// Test CreateStakingAccount SubCommand
let test_create_staking_account = test_commands.clone().get_matches_from(vec![
"test",
"create-staking-account",
&pubkey_string,
"50",
]);
assert_eq!(
parse_command(&pubkey, &test_create_staking_account).unwrap(),
2019-03-06 20:29:08 -08:00
WalletCommand::CreateStakingAccount(pubkey, 50)
);
let test_bad_pubkey = test_commands.clone().get_matches_from(vec![
"test",
"create-staking-account",
"deadbeef",
"50",
]);
assert!(parse_command(&pubkey, &test_bad_pubkey).is_err());
2019-03-06 20:29:08 -08: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(),
2018-11-12 12:48:59 -08:00
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(),
2018-09-19 12:44:16 -07:00
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());
2018-09-19 12:44:16 -07:00
// 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(),
2018-09-19 12:44:16 -07:00
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(),
2018-09-19 12:44:16 -07:00
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(),
2018-09-19 12:44:16 -07:00
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(),
2018-09-19 12:44:16 -07:00
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
}
2018-09-14 01:59:09 -07:00
#[test]
fn test_wallet_process_command() {
// Success cases
2018-11-12 12:48:59 -08:00
let mut config = WalletConfig::default();
config.rpc_client = Some(RpcClient::new("succeeds".to_string()));
2018-11-12 12:48:59 -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;
assert_eq!(process_command(&config).unwrap(), "Your balance is: 50");
2018-09-14 01:59:09 -07:00
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);
2018-09-20 22:27:06 -07:00
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");
2018-09-14 01:59:09 -07:00
2019-03-06 20:29:08 -08:00
let bob_pubkey = Keypair::new().pubkey();
config.command = WalletCommand::ConfigureStakingAccount(None, Some(bob_pubkey));
let signature = process_command(&config);
assert_eq!(signature.unwrap(), SIGNATURE.to_string());
config.command = WalletCommand::CreateStakingAccount(bob_pubkey, 10);
let signature = process_command(&config);
assert_eq!(signature.unwrap(), SIGNATURE.to_string());
config.command = WalletCommand::GetTransactionCount;
assert_eq!(process_command(&config).unwrap(), "1234");
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();
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,
2018-11-05 09:50:58 -08:00
None,
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
);
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
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
// 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_eq!(signature.unwrap(), SIGNATURE.to_string());
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());
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());
2019-03-06 20:29:08 -08:00
config.command = WalletCommand::ConfigureStakingAccount(None, Some(bob_pubkey));
assert!(process_command(&config).is_err());
config.command = WalletCommand::CreateStakingAccount(bob_pubkey, 10);
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
}
#[test]
fn test_wallet_deploy() {
solana_logger::setup();
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
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()
}
#[test]
fn test_wallet_gen_keypair_file() {
let outfile = tmp_file_path("test_gen_keypair_file.json");
let serialized_keypair = gen_keypair_file(outfile.to_string()).unwrap();
let keypair_vec: Vec<u8> = serde_json::from_str(&serialized_keypair).unwrap();
assert!(Path::new(&outfile).exists());
assert_eq!(keypair_vec, read_pkcs8(&outfile).unwrap());
read_keypair(&outfile).unwrap();
assert_eq!(
read_keypair(&outfile).unwrap().pubkey().as_ref().len(),
mem::size_of::<Pubkey>()
);
fs::remove_file(&outfile).unwrap();
assert!(!Path::new(&outfile).exists());
}
2018-10-17 13:42:54 -07:00
#[test]
2019-03-02 10:25:16 -08:00
fn test_wallet_get_recent_blockhash() {
let rpc_client = RpcClient::new("succeeds".to_string());
2018-09-24 09:23:16 -07:00
let vec = bs58::decode(PUBKEY).into_vec().unwrap();
2019-03-02 10:25:16 -08:00
let expected_blockhash = Hash::new(&vec);
2018-09-24 09:23:16 -07:00
2019-03-02 10:25:16 -08:00
let blockhash = get_recent_blockhash(&rpc_client);
assert_eq!(blockhash.unwrap(), expected_blockhash);
2018-11-12 12:48:59 -08:00
let rpc_client = RpcClient::new("fails".to_string());
2018-11-12 12:48:59 -08:00
2019-03-02 10:25:16 -08:00
let blockhash = get_recent_blockhash(&rpc_client);
assert!(blockhash.is_err());
}
2018-09-24 09:23:16 -07:00
#[test]
2019-02-27 15:56:21 -08:00
fn test_wallet_send_transaction() {
let rpc_client = RpcClient::new("succeeds".to_string());
2018-09-24 09:23:16 -07:00
let key = Keypair::new();
let to = Keypair::new().pubkey();
2019-03-02 10:25:16 -08:00
let blockhash = Hash::default();
let tx = SystemTransaction::new_account(&key, &to, 50, blockhash, 0);
2018-09-24 09:23:16 -07:00
2019-02-27 15:56:21 -08:00
let signature = send_transaction(&rpc_client, &tx);
assert_eq!(signature.unwrap(), SIGNATURE.to_string());
2018-09-24 09:23:16 -07:00
let rpc_client = RpcClient::new("fails".to_string());
2019-02-27 15:56:21 -08:00
let signature = send_transaction(&rpc_client, &tx);
assert!(signature.is_err());
2018-09-24 09:23:16 -07:00
}
#[test]
2019-02-27 15:56:21 -08:00
fn test_wallet_confirm_transaction() {
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);
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);
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);
assert!(status.is_err());
}
2018-09-24 09:23:16 -07:00
#[test]
2019-02-27 15:56:21 -08:00
fn test_wallet_send_and_confirm_transaction() {
let rpc_client = RpcClient::new("succeeds".to_string());
2018-09-24 09:23:16 -07:00
let key = Keypair::new();
let to = Keypair::new().pubkey();
2019-03-02 10:25:16 -08:00
let blockhash = Hash::default();
let mut tx = SystemTransaction::new_account(&key, &to, 50, blockhash, 0);
2018-09-24 09:23:16 -07:00
let result = send_and_confirm_transaction(&rpc_client, &mut tx, &key);
result.unwrap();
2018-09-24 09:23:16 -07:00
let rpc_client = RpcClient::new("account_in_use".to_string());
let result = send_and_confirm_transaction(&rpc_client, &mut tx, &key);
assert!(result.is_err());
let rpc_client = RpcClient::new("fails".to_string());
let result = send_and_confirm_transaction(&rpc_client, &mut tx, &key);
assert!(result.is_err());
2018-09-24 09:23:16 -07:00
}
2018-11-12 12:48:59 -08:00
#[test]
2019-02-27 15:56:21 -08:00
fn test_wallet_resign_transaction() {
let rpc_client = RpcClient::new("succeeds".to_string());
2018-11-12 12:48:59 -08:00
let key = Keypair::new();
let to = Keypair::new().pubkey();
let vec = bs58::decode("HUu3LwEzGRsUkuJS121jzkPJW39Kq62pXCTmTa1F9jDL")
.into_vec()
.unwrap();
2019-03-02 10:25:16 -08:00
let blockhash = Hash::new(&vec);
let prev_tx = SystemTransaction::new_account(&key, &to, 50, blockhash, 0);
let mut tx = SystemTransaction::new_account(&key, &to, 50, blockhash, 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
assert_ne!(prev_tx, tx);
assert_ne!(prev_tx.signatures, tx.signatures);
2019-03-02 10:25:16 -08:00
assert_ne!(prev_tx.recent_blockhash, tx.recent_blockhash);
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
#[test]
fn test_request_and_confirm_airdrop() {
let rpc_client = RpcClient::new("succeeds".to_string());
let drone_addr = socketaddr!(0, 0);
let pubkey = Keypair::new().pubkey();
2019-03-05 17:22:46 -08:00
let lamports = 50;
assert_eq!(
request_and_confirm_airdrop(&rpc_client, &drone_addr, &pubkey, lamports).unwrap(),
()
2018-09-24 09:23:16 -07:00
);
2019-03-05 17:22:46 -08:00
let lamports = 0;
assert!(request_and_confirm_airdrop(&rpc_client, &drone_addr, &pubkey, lamports).is_err());
2018-09-24 09:23:16 -07:00
}
2018-09-14 01:59:09 -07:00
}