2019-09-10 16:16:40 -07:00
|
|
|
use crate::{
|
2019-09-18 09:29:57 -07:00
|
|
|
display::println_name_value, input_parsers::*, input_validators::*, lamports_to_sol,
|
|
|
|
sol_to_lamports, validator_info::*, vote::*,
|
2019-09-10 16:16:40 -07:00
|
|
|
};
|
2018-09-19 12:44:16 -07:00
|
|
|
use chrono::prelude::*;
|
2019-08-12 21:33:13 -07:00
|
|
|
use clap::{value_t_or_exit, App, AppSettings, Arg, ArgMatches, SubCommand};
|
|
|
|
use console::{style, Emoji};
|
2019-02-27 11:17:32 -08:00
|
|
|
use log::*;
|
2019-04-25 10:29:44 -07:00
|
|
|
use num_traits::FromPrimitive;
|
2019-09-25 13:53:49 -07:00
|
|
|
use serde_json::{self, json, Value};
|
2019-09-09 18:17:32 -07:00
|
|
|
use solana_budget_api::budget_instruction::{self, BudgetError};
|
2019-09-25 13:53:49 -07:00
|
|
|
use solana_client::{client_error::ClientError, rpc_client::RpcClient};
|
2019-01-13 23:10:03 -08:00
|
|
|
#[cfg(not(test))]
|
|
|
|
use solana_drone::drone::request_airdrop_transaction;
|
2019-03-16 21:47:16 -07:00
|
|
|
#[cfg(test)]
|
|
|
|
use solana_drone::drone_mock::request_airdrop_transaction;
|
2019-09-09 18:17:32 -07:00
|
|
|
use solana_sdk::{
|
|
|
|
account_utils::State,
|
|
|
|
bpf_loader,
|
|
|
|
fee_calculator::FeeCalculator,
|
|
|
|
hash::Hash,
|
|
|
|
instruction::InstructionError,
|
|
|
|
instruction_processor_utils::DecodeError,
|
|
|
|
loader_instruction,
|
|
|
|
message::Message,
|
|
|
|
pubkey::Pubkey,
|
2019-09-18 09:29:57 -07:00
|
|
|
signature::{Keypair, KeypairUtil, Signature},
|
2019-09-09 18:17:32 -07:00
|
|
|
system_instruction::SystemError,
|
|
|
|
system_transaction,
|
|
|
|
transaction::{Transaction, TransactionError},
|
|
|
|
};
|
2019-09-26 13:29:29 -07:00
|
|
|
use solana_stake_api::{
|
|
|
|
stake_instruction::{self, StakeError},
|
|
|
|
stake_state::{Authorized, Lockup},
|
|
|
|
};
|
2019-05-23 14:50:23 -07:00
|
|
|
use solana_storage_api::storage_instruction;
|
2019-09-25 13:53:49 -07:00
|
|
|
use solana_vote_api::vote_state::{VoteAuthorize, VoteInit, VoteState};
|
|
|
|
use std::{
|
|
|
|
collections::VecDeque,
|
|
|
|
fs::File,
|
|
|
|
io::{Read, Write},
|
|
|
|
net::{IpAddr, SocketAddr},
|
|
|
|
thread::sleep,
|
|
|
|
time::{Duration, Instant},
|
|
|
|
{error, fmt},
|
|
|
|
};
|
2018-09-14 01:58:39 -07:00
|
|
|
|
2019-05-07 15:00:54 -07:00
|
|
|
const USERDATA_CHUNK_SIZE: usize = 229; // Keep program chunks under PACKET_DATA_SIZE
|
2018-10-22 21:21:33 -07:00
|
|
|
|
2019-08-12 21:33:13 -07:00
|
|
|
static CHECK_MARK: Emoji = Emoji("✅ ", "");
|
|
|
|
static CROSS_MARK: Emoji = Emoji("❌ ", "");
|
|
|
|
|
2018-09-14 01:59:09 -07:00
|
|
|
#[derive(Debug, PartialEq)]
|
2019-05-09 19:31:42 -07:00
|
|
|
#[allow(clippy::large_enum_variant)]
|
2018-09-14 01:58:39 -07:00
|
|
|
pub enum WalletCommand {
|
|
|
|
Address,
|
2019-06-07 13:11:56 -07:00
|
|
|
Fees,
|
2019-08-29 20:45:53 -07:00
|
|
|
Airdrop {
|
|
|
|
drone_host: Option<IpAddr>,
|
|
|
|
drone_port: u16,
|
|
|
|
lamports: u64,
|
2019-09-10 16:16:40 -07:00
|
|
|
use_lamports_unit: bool,
|
2019-08-29 20:45:53 -07:00
|
|
|
},
|
2019-09-10 12:36:59 -07:00
|
|
|
Balance {
|
|
|
|
pubkey: Pubkey,
|
|
|
|
use_lamports_unit: bool,
|
|
|
|
},
|
2018-09-18 14:42:32 -07:00
|
|
|
Cancel(Pubkey),
|
2018-09-14 01:58:39 -07:00
|
|
|
Confirm(Signature),
|
2019-09-25 13:53:49 -07:00
|
|
|
VoteAuthorize(Pubkey, Keypair, Pubkey, VoteAuthorize),
|
|
|
|
CreateVoteAccount(Pubkey, VoteInit, u64),
|
2019-09-10 12:36:59 -07:00
|
|
|
ShowAccount {
|
|
|
|
pubkey: Pubkey,
|
|
|
|
output_file: Option<String>,
|
|
|
|
use_lamports_unit: bool,
|
|
|
|
},
|
2019-09-26 10:26:47 -07:00
|
|
|
ShowVoteAccount {
|
|
|
|
pubkey: Pubkey,
|
|
|
|
use_lamports_unit: bool,
|
|
|
|
},
|
2019-09-18 09:29:57 -07:00
|
|
|
Uptime {
|
|
|
|
pubkey: Pubkey,
|
|
|
|
aggregate: bool,
|
|
|
|
span: Option<u64>,
|
|
|
|
},
|
2019-09-26 13:29:29 -07:00
|
|
|
DelegateStake(Keypair, Pubkey, u64, Authorized, bool),
|
2019-06-21 22:28:34 -07:00
|
|
|
WithdrawStake(Keypair, Pubkey, u64),
|
2019-08-09 12:55:21 -07:00
|
|
|
DeactivateStake(Keypair, Pubkey),
|
2019-06-21 20:43:24 -07:00
|
|
|
RedeemVoteCredits(Pubkey, Pubkey),
|
2019-09-26 10:26:47 -07:00
|
|
|
ShowStakeAccount {
|
|
|
|
pubkey: Pubkey,
|
|
|
|
use_lamports_unit: bool,
|
|
|
|
},
|
2019-06-04 14:52:52 -07:00
|
|
|
CreateReplicatorStorageAccount(Pubkey, Pubkey),
|
|
|
|
CreateValidatorStorageAccount(Pubkey, Pubkey),
|
2019-06-22 17:18:35 -07:00
|
|
|
ClaimStorageReward(Pubkey, Pubkey),
|
2019-05-23 14:50:23 -07:00
|
|
|
ShowStorageAccount(Pubkey),
|
2018-10-22 21:21:33 -07:00
|
|
|
Deploy(String),
|
2019-08-05 13:17:03 -07:00
|
|
|
GetSlot,
|
2018-10-25 10:20:17 -07:00
|
|
|
GetTransactionCount,
|
2019-08-08 10:13:06 -07:00
|
|
|
GetVersion,
|
2019-09-10 16:16:40 -07:00
|
|
|
Pay {
|
|
|
|
lamports: u64,
|
|
|
|
to: Pubkey,
|
|
|
|
timestamp: Option<DateTime<Utc>>,
|
|
|
|
timestamp_pubkey: Option<Pubkey>,
|
|
|
|
witnesses: Option<Vec<Pubkey>>,
|
|
|
|
cancelable: Option<Pubkey>,
|
|
|
|
},
|
2019-08-12 21:33:13 -07:00
|
|
|
Ping {
|
|
|
|
interval: Duration,
|
|
|
|
count: Option<u64>,
|
|
|
|
timeout: Duration,
|
|
|
|
},
|
2019-09-03 10:38:12 -07:00
|
|
|
TimeElapsed(Pubkey, Pubkey, DateTime<Utc>), // TimeElapsed(to, process_id, timestamp)
|
|
|
|
Witness(Pubkey, Pubkey), // Witness(to, process_id)
|
|
|
|
GetValidatorInfo(Option<Pubkey>),
|
|
|
|
SetValidatorInfo(ValidatorInfo, Option<Pubkey>),
|
2018-09-14 01:58:39 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
pub enum WalletError {
|
|
|
|
BadParameter(String),
|
2019-08-09 14:52:06 -07:00
|
|
|
CommandNotRecognized(String),
|
|
|
|
InsufficientFundsForFee,
|
2018-10-22 21:21:33 -07:00
|
|
|
DynamicProgramError(String),
|
2018-09-20 22:27:06 -07:00
|
|
|
RpcRequestError(String),
|
2019-09-12 18:37:29 -07:00
|
|
|
KeypairFileNotFound(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 command: WalletCommand,
|
2019-05-06 07:38:26 -07:00
|
|
|
pub json_rpc_url: String,
|
|
|
|
pub keypair: Keypair,
|
2019-09-02 12:53:13 -07:00
|
|
|
pub keypair_path: String,
|
2019-01-13 23:10:03 -08:00
|
|
|
pub rpc_client: Option<RpcClient>,
|
2018-09-14 01:58:39 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Default for WalletConfig {
|
|
|
|
fn default() -> WalletConfig {
|
2019-09-05 10:14:23 -07:00
|
|
|
let mut keypair_path = dirs::home_dir().expect("home directory");
|
|
|
|
keypair_path.extend(&[".config", "solana", "id.json"]);
|
|
|
|
|
2018-09-14 01:58:39 -07:00
|
|
|
WalletConfig {
|
2019-09-10 12:36:59 -07:00
|
|
|
command: WalletCommand::Balance {
|
|
|
|
pubkey: Pubkey::default(),
|
|
|
|
use_lamports_unit: false,
|
|
|
|
},
|
2019-08-16 10:22:22 -07:00
|
|
|
json_rpc_url: "http://127.0.0.1:8899".to_string(),
|
2019-04-02 06:08:11 -07:00
|
|
|
keypair: Keypair::new(),
|
2019-09-05 10:14:23 -07:00
|
|
|
keypair_path: keypair_path.to_str().unwrap().to_string(),
|
2019-01-13 23:10:03 -08:00
|
|
|
rpc_client: None,
|
2018-09-14 01:58:39 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn parse_command(
|
2019-03-09 19:28:43 -08:00
|
|
|
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),
|
2019-06-07 13:11:56 -07:00
|
|
|
("fees", Some(_fees_matches)) => Ok(WalletCommand::Fees),
|
2018-09-14 01:58:39 -07:00
|
|
|
("airdrop", Some(airdrop_matches)) => {
|
2019-08-29 20:45:53 -07:00
|
|
|
let drone_port = airdrop_matches
|
|
|
|
.value_of("drone_port")
|
|
|
|
.unwrap()
|
|
|
|
.parse()
|
|
|
|
.or_else(|err| {
|
|
|
|
Err(WalletError::BadParameter(format!(
|
|
|
|
"Invalid drone port: {:?}",
|
|
|
|
err
|
|
|
|
)))
|
|
|
|
})?;
|
|
|
|
|
|
|
|
let drone_host = if let Some(drone_host) = matches.value_of("drone_host") {
|
|
|
|
Some(solana_netutil::parse_host(drone_host).or_else(|err| {
|
|
|
|
Err(WalletError::BadParameter(format!(
|
|
|
|
"Invalid drone host: {:?}",
|
|
|
|
err
|
|
|
|
)))
|
|
|
|
})?)
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
};
|
2019-09-10 16:16:40 -07:00
|
|
|
let lamports = parse_amount_lamports(
|
|
|
|
airdrop_matches.value_of("amount").unwrap(),
|
|
|
|
airdrop_matches.value_of("unit"),
|
|
|
|
)?;
|
|
|
|
let use_lamports_unit = airdrop_matches.value_of("unit").is_some()
|
|
|
|
&& airdrop_matches.value_of("unit").unwrap() == "lamports";
|
2019-08-29 20:45:53 -07:00
|
|
|
Ok(WalletCommand::Airdrop {
|
|
|
|
drone_host,
|
|
|
|
drone_port,
|
|
|
|
lamports,
|
2019-09-10 16:16:40 -07:00
|
|
|
use_lamports_unit,
|
2019-08-29 20:45:53 -07:00
|
|
|
})
|
2018-09-14 01:58:39 -07:00
|
|
|
}
|
2019-03-20 09:44:16 -07:00
|
|
|
("balance", Some(balance_matches)) => {
|
2019-08-08 17:10:09 -07:00
|
|
|
let pubkey = pubkey_of(&balance_matches, "pubkey").unwrap_or(*pubkey);
|
2019-09-10 12:36:59 -07:00
|
|
|
let use_lamports_unit = balance_matches.is_present("lamports");
|
|
|
|
Ok(WalletCommand::Balance {
|
|
|
|
pubkey,
|
|
|
|
use_lamports_unit,
|
|
|
|
})
|
2019-03-20 09:44:16 -07:00
|
|
|
}
|
2018-09-19 12:44:16 -07:00
|
|
|
("cancel", Some(cancel_matches)) => {
|
2019-06-10 12:17:29 -07:00
|
|
|
let process_id = value_of(cancel_matches, "process_id").unwrap();
|
2018-09-19 12:44:16 -07:00
|
|
|
Ok(WalletCommand::Cancel(process_id))
|
|
|
|
}
|
2018-09-20 22:27:06 -07:00
|
|
|
("confirm", Some(confirm_matches)) => {
|
2019-05-16 21:43:18 -07:00
|
|
|
match confirm_matches.value_of("signature").unwrap().parse() {
|
|
|
|
Ok(signature) => Ok(WalletCommand::Confirm(signature)),
|
|
|
|
_ => {
|
|
|
|
eprintln!("{}", confirm_matches.usage());
|
|
|
|
Err(WalletError::BadParameter("Invalid signature".to_string()))
|
|
|
|
}
|
2018-09-20 22:27:06 -07:00
|
|
|
}
|
|
|
|
}
|
2019-08-09 22:48:57 -07:00
|
|
|
("show-account", Some(matches)) => {
|
|
|
|
let account_pubkey = pubkey_of(matches, "account_pubkey").unwrap();
|
|
|
|
let output_file = matches.value_of("output_file");
|
2019-09-10 12:36:59 -07:00
|
|
|
let use_lamports_unit = matches.is_present("lamports");
|
|
|
|
Ok(WalletCommand::ShowAccount {
|
|
|
|
pubkey: account_pubkey,
|
|
|
|
output_file: output_file.map(ToString::to_string),
|
|
|
|
use_lamports_unit,
|
|
|
|
})
|
2019-08-09 22:48:57 -07:00
|
|
|
}
|
2019-09-18 09:29:57 -07:00
|
|
|
("create-vote-account", Some(matches)) => parse_vote_create_account(matches),
|
2019-09-25 13:53:49 -07:00
|
|
|
("vote-authorize-voter", Some(matches)) => {
|
|
|
|
parse_vote_authorize(matches, VoteAuthorize::Voter)
|
|
|
|
}
|
|
|
|
("vote-authorize-withdrawer", Some(matches)) => {
|
|
|
|
parse_vote_authorize(matches, VoteAuthorize::Withdrawer)
|
|
|
|
}
|
2019-09-18 09:29:57 -07:00
|
|
|
("show-vote-account", Some(matches)) => parse_vote_get_account_command(matches),
|
|
|
|
("uptime", Some(matches)) => parse_vote_uptime_command(matches),
|
2019-05-09 19:31:42 -07:00
|
|
|
("delegate-stake", Some(matches)) => {
|
2019-07-26 09:34:12 -07:00
|
|
|
let stake_account_keypair = keypair_of(matches, "stake_account_keypair_file").unwrap();
|
2019-08-08 17:10:09 -07:00
|
|
|
let vote_account_pubkey = pubkey_of(matches, "vote_account_pubkey").unwrap();
|
2019-09-10 16:16:40 -07:00
|
|
|
let lamports = parse_amount_lamports(
|
|
|
|
matches.value_of("amount").unwrap(),
|
|
|
|
matches.value_of("unit"),
|
|
|
|
)?;
|
2019-09-26 13:29:29 -07:00
|
|
|
let authorized = Authorized::auto(&stake_account_keypair.pubkey());
|
2019-08-01 21:08:24 -07:00
|
|
|
let force = matches.is_present("force");
|
2019-05-09 19:31:42 -07:00
|
|
|
Ok(WalletCommand::DelegateStake(
|
2019-07-26 09:34:12 -07:00
|
|
|
stake_account_keypair,
|
|
|
|
vote_account_pubkey,
|
2019-09-10 16:16:40 -07:00
|
|
|
lamports,
|
2019-09-26 13:29:29 -07:00
|
|
|
authorized,
|
2019-08-01 21:08:24 -07:00
|
|
|
force,
|
2019-05-09 19:31:42 -07:00
|
|
|
))
|
|
|
|
}
|
2019-06-21 22:28:34 -07:00
|
|
|
("withdraw-stake", Some(matches)) => {
|
2019-07-26 09:34:12 -07:00
|
|
|
let stake_account_keypair = keypair_of(matches, "stake_account_keypair_file").unwrap();
|
2019-06-21 22:28:34 -07:00
|
|
|
let destination_account_pubkey =
|
2019-08-08 17:10:09 -07:00
|
|
|
pubkey_of(matches, "destination_account_pubkey").unwrap();
|
2019-09-10 16:16:40 -07:00
|
|
|
let lamports = parse_amount_lamports(
|
|
|
|
matches.value_of("amount").unwrap(),
|
|
|
|
matches.value_of("unit"),
|
|
|
|
)?;
|
2019-06-21 22:28:34 -07:00
|
|
|
Ok(WalletCommand::WithdrawStake(
|
2019-07-26 09:34:12 -07:00
|
|
|
stake_account_keypair,
|
2019-06-21 22:28:34 -07:00
|
|
|
destination_account_pubkey,
|
|
|
|
lamports,
|
|
|
|
))
|
|
|
|
}
|
2019-06-21 23:45:03 -07:00
|
|
|
("deactivate-stake", Some(matches)) => {
|
2019-07-26 09:34:12 -07:00
|
|
|
let stake_account_keypair = keypair_of(matches, "stake_account_keypair_file").unwrap();
|
2019-08-16 15:06:59 -07:00
|
|
|
let vote_account_pubkey = pubkey_of(matches, "vote_account_pubkey").unwrap();
|
2019-08-09 12:55:21 -07:00
|
|
|
Ok(WalletCommand::DeactivateStake(
|
|
|
|
stake_account_keypair,
|
|
|
|
vote_account_pubkey,
|
|
|
|
))
|
2019-06-21 23:45:03 -07:00
|
|
|
}
|
2019-05-21 07:32:38 -07:00
|
|
|
("redeem-vote-credits", Some(matches)) => {
|
2019-08-08 17:10:09 -07:00
|
|
|
let stake_account_pubkey = pubkey_of(matches, "stake_account_pubkey").unwrap();
|
|
|
|
let vote_account_pubkey = pubkey_of(matches, "vote_account_pubkey").unwrap();
|
2019-05-21 07:32:38 -07:00
|
|
|
Ok(WalletCommand::RedeemVoteCredits(
|
2019-07-26 09:34:12 -07:00
|
|
|
stake_account_pubkey,
|
|
|
|
vote_account_pubkey,
|
2019-05-21 07:32:38 -07:00
|
|
|
))
|
|
|
|
}
|
2019-05-09 19:31:42 -07:00
|
|
|
("show-stake-account", Some(matches)) => {
|
2019-08-08 17:10:09 -07:00
|
|
|
let stake_account_pubkey = pubkey_of(matches, "stake_account_pubkey").unwrap();
|
2019-09-26 10:26:47 -07:00
|
|
|
let use_lamports_unit = matches.is_present("lamports");
|
|
|
|
Ok(WalletCommand::ShowStakeAccount {
|
|
|
|
pubkey: stake_account_pubkey,
|
|
|
|
use_lamports_unit,
|
|
|
|
})
|
2019-05-09 19:31:42 -07:00
|
|
|
}
|
2019-05-23 14:50:23 -07:00
|
|
|
("create-replicator-storage-account", Some(matches)) => {
|
2019-08-08 17:10:09 -07:00
|
|
|
let account_owner = pubkey_of(matches, "storage_account_owner").unwrap();
|
|
|
|
let storage_account_pubkey = pubkey_of(matches, "storage_account_pubkey").unwrap();
|
2019-05-23 14:50:23 -07:00
|
|
|
Ok(WalletCommand::CreateReplicatorStorageAccount(
|
2019-06-04 14:52:52 -07:00
|
|
|
account_owner,
|
2019-05-23 23:20:04 -07:00
|
|
|
storage_account_pubkey,
|
2019-05-23 14:50:23 -07:00
|
|
|
))
|
|
|
|
}
|
|
|
|
("create-validator-storage-account", Some(matches)) => {
|
2019-08-08 17:10:09 -07:00
|
|
|
let account_owner = pubkey_of(matches, "storage_account_owner").unwrap();
|
|
|
|
let storage_account_pubkey = pubkey_of(matches, "storage_account_pubkey").unwrap();
|
2019-05-23 14:50:23 -07:00
|
|
|
Ok(WalletCommand::CreateValidatorStorageAccount(
|
2019-06-04 14:52:52 -07:00
|
|
|
account_owner,
|
2019-05-23 23:20:04 -07:00
|
|
|
storage_account_pubkey,
|
2019-05-23 14:50:23 -07:00
|
|
|
))
|
|
|
|
}
|
|
|
|
("claim-storage-reward", Some(matches)) => {
|
2019-08-08 17:10:09 -07:00
|
|
|
let node_account_pubkey = pubkey_of(matches, "node_account_pubkey").unwrap();
|
|
|
|
let storage_account_pubkey = pubkey_of(matches, "storage_account_pubkey").unwrap();
|
2019-05-23 14:50:23 -07:00
|
|
|
Ok(WalletCommand::ClaimStorageReward(
|
2019-06-13 17:53:54 -07:00
|
|
|
node_account_pubkey,
|
2019-05-23 23:20:04 -07:00
|
|
|
storage_account_pubkey,
|
2019-05-23 14:50:23 -07:00
|
|
|
))
|
|
|
|
}
|
|
|
|
("show-storage-account", Some(matches)) => {
|
2019-08-08 17:10:09 -07:00
|
|
|
let storage_account_pubkey = pubkey_of(matches, "storage_account_pubkey").unwrap();
|
2019-05-23 23:20:04 -07:00
|
|
|
Ok(WalletCommand::ShowStorageAccount(storage_account_pubkey))
|
2019-05-23 14:50:23 -07:00
|
|
|
}
|
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(),
|
|
|
|
)),
|
2019-08-05 13:17:03 -07:00
|
|
|
("get-slot", Some(_matches)) => Ok(WalletCommand::GetSlot),
|
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)) => {
|
2019-09-10 16:16:40 -07:00
|
|
|
let lamports = parse_amount_lamports(
|
|
|
|
pay_matches.value_of("amount").unwrap(),
|
|
|
|
pay_matches.value_of("unit"),
|
|
|
|
)?;
|
2019-06-10 12:17:29 -07:00
|
|
|
let to = value_of(&pay_matches, "to").unwrap_or(*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-06-10 12:17:29 -07:00
|
|
|
let timestamp_pubkey = value_of(&pay_matches, "timestamp_pubkey");
|
2019-09-10 16:16:40 -07:00
|
|
|
let witnesses = values_of(&pay_matches, "witness");
|
2018-09-19 12:44:16 -07:00
|
|
|
let cancelable = if pay_matches.is_present("cancelable") {
|
2019-03-09 19:28:43 -08:00
|
|
|
Some(*pubkey)
|
2018-09-19 12:44:16 -07:00
|
|
|
} else {
|
|
|
|
None
|
|
|
|
};
|
|
|
|
|
2019-09-10 16:16:40 -07:00
|
|
|
Ok(WalletCommand::Pay {
|
2019-03-05 17:22:46 -08:00
|
|
|
lamports,
|
2018-09-19 12:44:16 -07:00
|
|
|
to,
|
|
|
|
timestamp,
|
|
|
|
timestamp_pubkey,
|
2019-09-10 16:16:40 -07:00
|
|
|
witnesses,
|
2018-09-19 12:44:16 -07:00
|
|
|
cancelable,
|
2019-09-10 16:16:40 -07:00
|
|
|
})
|
2018-09-19 12:44:16 -07:00
|
|
|
}
|
2019-08-12 21:33:13 -07:00
|
|
|
("ping", Some(ping_matches)) => {
|
|
|
|
let interval = Duration::from_secs(value_t_or_exit!(ping_matches, "interval", u64));
|
|
|
|
let count = if ping_matches.is_present("count") {
|
|
|
|
Some(value_t_or_exit!(ping_matches, "count", u64))
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
};
|
|
|
|
let timeout = Duration::from_secs(value_t_or_exit!(ping_matches, "timeout", u64));
|
|
|
|
Ok(WalletCommand::Ping {
|
|
|
|
interval,
|
|
|
|
count,
|
|
|
|
timeout,
|
|
|
|
})
|
|
|
|
}
|
2018-09-19 12:44:16 -07:00
|
|
|
("send-signature", Some(sig_matches)) => {
|
2019-06-10 12:17:29 -07:00
|
|
|
let to = value_of(&sig_matches, "to").unwrap();
|
|
|
|
let process_id = value_of(&sig_matches, "process_id").unwrap();
|
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)) => {
|
2019-06-10 12:17:29 -07:00
|
|
|
let to = value_of(×tamp_matches, "to").unwrap();
|
|
|
|
let process_id = value_of(×tamp_matches, "process_id").unwrap();
|
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
|
|
|
}
|
2019-08-08 10:13:06 -07:00
|
|
|
("cluster-version", Some(_matches)) => Ok(WalletCommand::GetVersion),
|
2019-09-03 10:38:12 -07:00
|
|
|
("validator-info", Some(matches)) => match matches.subcommand() {
|
|
|
|
("publish", Some(matches)) => parse_validator_info_command(matches, pubkey),
|
|
|
|
("get", Some(matches)) => parse_get_validator_info_command(matches),
|
|
|
|
("", None) => {
|
|
|
|
eprintln!("{}", matches.usage());
|
|
|
|
Err(WalletError::CommandNotRecognized(
|
|
|
|
"no validator-info subcommand given".to_string(),
|
|
|
|
))
|
|
|
|
}
|
|
|
|
_ => unreachable!(),
|
|
|
|
},
|
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-09-03 10:38:12 -07:00
|
|
|
pub type ProcessResult = Result<String, Box<dyn error::Error>>;
|
2019-02-09 13:15:44 -08:00
|
|
|
|
2019-09-03 10:38:12 -07:00
|
|
|
pub fn check_account_for_fee(
|
2019-08-09 14:52:06 -07:00
|
|
|
rpc_client: &RpcClient,
|
|
|
|
config: &WalletConfig,
|
|
|
|
fee_calculator: &FeeCalculator,
|
|
|
|
message: &Message,
|
|
|
|
) -> Result<(), Box<dyn error::Error>> {
|
|
|
|
check_account_for_multiple_fees(rpc_client, config, fee_calculator, &[message])
|
|
|
|
}
|
|
|
|
|
|
|
|
fn check_account_for_multiple_fees(
|
|
|
|
rpc_client: &RpcClient,
|
|
|
|
config: &WalletConfig,
|
|
|
|
fee_calculator: &FeeCalculator,
|
|
|
|
messages: &[&Message],
|
|
|
|
) -> Result<(), Box<dyn error::Error>> {
|
|
|
|
let balance = rpc_client.retry_get_balance(&config.keypair.pubkey(), 5)?;
|
|
|
|
if let Some(lamports) = balance {
|
|
|
|
if lamports
|
|
|
|
>= messages
|
|
|
|
.iter()
|
|
|
|
.map(|message| fee_calculator.calculate_fee(message))
|
|
|
|
.sum()
|
|
|
|
{
|
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Err(WalletError::InsufficientFundsForFee)?
|
|
|
|
}
|
|
|
|
|
2019-09-18 09:29:57 -07:00
|
|
|
pub fn check_unique_pubkeys(
|
2019-08-12 13:01:55 -07:00
|
|
|
pubkey0: (&Pubkey, String),
|
|
|
|
pubkey1: (&Pubkey, String),
|
|
|
|
) -> Result<(), WalletError> {
|
|
|
|
if pubkey0.0 == pubkey1.0 {
|
|
|
|
Err(WalletError::BadParameter(format!(
|
|
|
|
"Identical pubkeys found: `{}` and `{}` must be unique",
|
|
|
|
pubkey0.1, pubkey1.1
|
|
|
|
)))
|
|
|
|
} else {
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-06-07 13:11:56 -07:00
|
|
|
fn process_fees(rpc_client: &RpcClient) -> ProcessResult {
|
|
|
|
let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?;
|
|
|
|
|
|
|
|
Ok(format!(
|
|
|
|
"blockhash: {}\nlamports per signature: {}",
|
|
|
|
recent_blockhash, fee_calculator.lamports_per_signature
|
|
|
|
))
|
|
|
|
}
|
2019-02-09 13:15:44 -08:00
|
|
|
fn process_airdrop(
|
|
|
|
rpc_client: &RpcClient,
|
|
|
|
config: &WalletConfig,
|
2019-08-29 20:45:53 -07:00
|
|
|
drone_addr: &SocketAddr,
|
2019-03-05 17:22:46 -08:00
|
|
|
lamports: u64,
|
2019-09-10 16:16:40 -07:00
|
|
|
use_lamports_unit: bool,
|
2019-02-09 13:15:44 -08:00
|
|
|
) -> ProcessResult {
|
|
|
|
println!(
|
2019-09-17 22:59:35 -07:00
|
|
|
"Requesting airdrop of {} from {}",
|
|
|
|
build_balance_message(lamports, use_lamports_unit),
|
|
|
|
drone_addr
|
2019-02-09 13:15:44 -08:00
|
|
|
);
|
2019-04-02 06:08:11 -07:00
|
|
|
let previous_balance = match rpc_client.retry_get_balance(&config.keypair.pubkey(), 5)? {
|
2019-03-05 17:22:46 -08:00
|
|
|
Some(lamports) => lamports,
|
2019-02-09 13:15:44 -08:00
|
|
|
None => Err(WalletError::RpcRequestError(
|
|
|
|
"Received result of an unexpected type".to_string(),
|
|
|
|
))?,
|
|
|
|
};
|
|
|
|
|
2019-08-29 20:45:53 -07:00
|
|
|
request_and_confirm_airdrop(&rpc_client, drone_addr, &config.keypair.pubkey(), lamports)?;
|
2019-02-09 13:15:44 -08:00
|
|
|
|
|
|
|
let current_balance = rpc_client
|
2019-04-02 06:08:11 -07:00
|
|
|
.retry_get_balance(&config.keypair.pubkey(), 5)?
|
2019-02-09 13:15:44 -08:00
|
|
|
.unwrap_or(previous_balance);
|
|
|
|
|
2019-09-10 16:16:40 -07:00
|
|
|
Ok(build_balance_message(current_balance, use_lamports_unit))
|
2019-02-09 13:15:44 -08:00
|
|
|
}
|
|
|
|
|
2019-09-10 12:36:59 -07:00
|
|
|
fn process_balance(
|
|
|
|
pubkey: &Pubkey,
|
|
|
|
rpc_client: &RpcClient,
|
|
|
|
use_lamports_unit: bool,
|
|
|
|
) -> ProcessResult {
|
2019-03-20 09:44:16 -07:00
|
|
|
let balance = rpc_client.retry_get_balance(pubkey, 5)?;
|
2019-02-09 13:15:44 -08:00
|
|
|
match balance {
|
2019-09-10 12:36:59 -07:00
|
|
|
Some(lamports) => Ok(build_balance_message(lamports, use_lamports_unit)),
|
2019-02-09 13:15:44 -08:00
|
|
|
None => Err(WalletError::RpcRequestError(
|
|
|
|
"Received result of an unexpected type".to_string(),
|
|
|
|
))?,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-05-09 19:31:42 -07:00
|
|
|
fn process_confirm(rpc_client: &RpcClient, signature: &Signature) -> ProcessResult {
|
2019-03-17 00:40:45 -07:00
|
|
|
match rpc_client.get_signature_status(&signature.to_string()) {
|
|
|
|
Ok(status) => {
|
2019-04-05 19:56:17 -07:00
|
|
|
if let Some(result) = status {
|
|
|
|
match result {
|
|
|
|
Ok(_) => Ok("Confirmed".to_string()),
|
|
|
|
Err(err) => Ok(format!("Transaction failed with error {:?}", err)),
|
|
|
|
}
|
2019-02-09 13:15:44 -08:00
|
|
|
} else {
|
|
|
|
Ok("Not found".to_string())
|
|
|
|
}
|
|
|
|
}
|
2019-03-17 00:40:45 -07:00
|
|
|
Err(err) => Err(WalletError::RpcRequestError(format!(
|
|
|
|
"Unable to confirm: {:?}",
|
|
|
|
err
|
|
|
|
)))?,
|
2019-02-09 13:15:44 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-09 22:48:57 -07:00
|
|
|
fn process_show_account(
|
|
|
|
rpc_client: &RpcClient,
|
|
|
|
_config: &WalletConfig,
|
|
|
|
account_pubkey: &Pubkey,
|
|
|
|
output_file: &Option<String>,
|
2019-09-10 12:36:59 -07:00
|
|
|
use_lamports_unit: bool,
|
2019-08-09 22:48:57 -07:00
|
|
|
) -> ProcessResult {
|
|
|
|
let account = rpc_client.get_account(account_pubkey)?;
|
|
|
|
|
|
|
|
println!();
|
|
|
|
println!("Public Key: {}", account_pubkey);
|
2019-09-10 12:36:59 -07:00
|
|
|
println!(
|
|
|
|
"{:?}",
|
|
|
|
build_balance_message(account.lamports, use_lamports_unit)
|
|
|
|
);
|
2019-08-09 22:48:57 -07:00
|
|
|
println!("Owner: {}", account.owner);
|
|
|
|
println!("Executable: {}", account.executable);
|
|
|
|
|
|
|
|
if let Some(output_file) = output_file {
|
|
|
|
let mut f = File::create(output_file)?;
|
|
|
|
f.write_all(&account.data)?;
|
|
|
|
println!();
|
|
|
|
println!("Wrote account data to {}", output_file);
|
|
|
|
} else {
|
|
|
|
use pretty_hex::*;
|
|
|
|
println!("{:?}", account.data.hex_dump());
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok("".to_string())
|
|
|
|
}
|
|
|
|
|
2019-06-21 23:45:03 -07:00
|
|
|
fn process_deactivate_stake_account(
|
|
|
|
rpc_client: &RpcClient,
|
|
|
|
config: &WalletConfig,
|
2019-07-26 09:34:12 -07:00
|
|
|
stake_account_keypair: &Keypair,
|
2019-08-09 12:55:21 -07:00
|
|
|
vote_account_pubkey: &Pubkey,
|
2019-06-21 23:45:03 -07:00
|
|
|
) -> ProcessResult {
|
2019-08-09 14:52:06 -07:00
|
|
|
let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?;
|
2019-08-09 12:55:21 -07:00
|
|
|
let ixs =
|
|
|
|
stake_instruction::deactivate_stake(&stake_account_keypair.pubkey(), vote_account_pubkey);
|
2019-06-21 23:45:03 -07:00
|
|
|
let mut tx = Transaction::new_signed_with_payer(
|
|
|
|
vec![ixs],
|
|
|
|
Some(&config.keypair.pubkey()),
|
2019-07-26 09:34:12 -07:00
|
|
|
&[&config.keypair, &stake_account_keypair],
|
2019-06-21 23:45:03 -07:00
|
|
|
recent_blockhash,
|
|
|
|
);
|
2019-08-09 14:52:06 -07:00
|
|
|
check_account_for_fee(rpc_client, config, &fee_calculator, &tx.message)?;
|
2019-09-06 10:55:03 -07:00
|
|
|
let result = rpc_client
|
|
|
|
.send_and_confirm_transaction(&mut tx, &[&config.keypair, &stake_account_keypair]);
|
|
|
|
log_instruction_custom_error::<StakeError>(result)
|
2019-06-21 23:45:03 -07:00
|
|
|
}
|
|
|
|
|
2019-05-09 19:31:42 -07:00
|
|
|
fn process_delegate_stake(
|
|
|
|
rpc_client: &RpcClient,
|
|
|
|
config: &WalletConfig,
|
2019-07-26 09:34:12 -07:00
|
|
|
stake_account_keypair: &Keypair,
|
|
|
|
vote_account_pubkey: &Pubkey,
|
2019-07-25 16:53:43 -07:00
|
|
|
lamports: u64,
|
2019-09-26 13:29:29 -07:00
|
|
|
authorized: &Authorized,
|
2019-08-01 21:08:24 -07:00
|
|
|
force: bool,
|
2019-05-09 19:31:42 -07:00
|
|
|
) -> ProcessResult {
|
2019-08-15 13:16:05 -07:00
|
|
|
check_unique_pubkeys(
|
|
|
|
(&config.keypair.pubkey(), "wallet keypair".to_string()),
|
|
|
|
(
|
|
|
|
&stake_account_keypair.pubkey(),
|
|
|
|
"stake_account_keypair".to_string(),
|
|
|
|
),
|
|
|
|
)?;
|
2019-08-09 14:52:06 -07:00
|
|
|
let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?;
|
2019-07-25 16:53:43 -07:00
|
|
|
|
|
|
|
let ixs = stake_instruction::create_stake_account_and_delegate_stake(
|
|
|
|
&config.keypair.pubkey(),
|
2019-07-26 09:34:12 -07:00
|
|
|
&stake_account_keypair.pubkey(),
|
|
|
|
vote_account_pubkey,
|
2019-07-25 16:53:43 -07:00
|
|
|
lamports,
|
2019-09-26 13:29:29 -07:00
|
|
|
authorized,
|
2019-07-25 16:53:43 -07:00
|
|
|
);
|
2019-06-03 09:04:51 -07:00
|
|
|
|
2019-08-01 21:08:24 -07:00
|
|
|
// Sanity check the vote account to ensure it is attached to a validator that has recently
|
|
|
|
// voted at the tip of the ledger
|
2019-08-02 10:09:09 -07:00
|
|
|
let vote_account_data = rpc_client
|
|
|
|
.get_account_data(vote_account_pubkey)
|
|
|
|
.map_err(|_| {
|
|
|
|
WalletError::RpcRequestError(format!("Vote account not found: {}", vote_account_pubkey))
|
|
|
|
})?;
|
|
|
|
|
2019-08-01 21:08:24 -07:00
|
|
|
let vote_state = VoteState::deserialize(&vote_account_data).map_err(|_| {
|
|
|
|
WalletError::RpcRequestError(
|
|
|
|
"Account data could not be deserialized to vote state".to_string(),
|
|
|
|
)
|
|
|
|
})?;
|
|
|
|
|
|
|
|
let sanity_check_result = match vote_state.root_slot {
|
2019-08-02 10:09:09 -07:00
|
|
|
None => Err(WalletError::BadParameter(
|
|
|
|
"Unable to delegate. Vote account has no root slot".to_string(),
|
2019-08-01 21:08:24 -07:00
|
|
|
)),
|
|
|
|
Some(root_slot) => {
|
|
|
|
let slot = rpc_client.get_slot()?;
|
2019-09-06 14:30:56 -07:00
|
|
|
if root_slot + solana_sdk::clock::DEFAULT_SLOTS_PER_TURN < slot {
|
2019-08-02 10:09:09 -07:00
|
|
|
Err(WalletError::BadParameter(
|
|
|
|
format!(
|
|
|
|
"Unable to delegate. Vote account root slot ({}) is too old, the current slot is {}", root_slot, slot
|
|
|
|
)
|
2019-08-01 21:08:24 -07:00
|
|
|
))
|
|
|
|
} else {
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
if sanity_check_result.is_err() {
|
|
|
|
if !force {
|
|
|
|
sanity_check_result?;
|
|
|
|
} else {
|
|
|
|
println!("--force supplied, ignoring: {:?}", sanity_check_result);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-06-03 09:04:51 -07:00
|
|
|
let mut tx = Transaction::new_signed_with_payer(
|
2019-05-09 19:31:42 -07:00
|
|
|
ixs,
|
2019-06-03 09:04:51 -07:00
|
|
|
Some(&config.keypair.pubkey()),
|
2019-07-26 09:34:12 -07:00
|
|
|
&[&config.keypair, &stake_account_keypair],
|
2019-05-09 19:31:42 -07:00
|
|
|
recent_blockhash,
|
|
|
|
);
|
2019-08-09 14:52:06 -07:00
|
|
|
check_account_for_fee(rpc_client, config, &fee_calculator, &tx.message)?;
|
2019-06-03 09:04:51 -07:00
|
|
|
|
2019-08-12 13:00:55 -07:00
|
|
|
let result = rpc_client
|
|
|
|
.send_and_confirm_transaction(&mut tx, &[&config.keypair, &stake_account_keypair]);
|
2019-09-06 10:55:03 -07:00
|
|
|
log_instruction_custom_error::<StakeError>(result)
|
2019-05-09 19:31:42 -07:00
|
|
|
}
|
|
|
|
|
2019-06-21 22:28:34 -07:00
|
|
|
fn process_withdraw_stake(
|
|
|
|
rpc_client: &RpcClient,
|
|
|
|
config: &WalletConfig,
|
2019-07-26 09:34:12 -07:00
|
|
|
stake_account_keypair: &Keypair,
|
2019-06-21 22:28:34 -07:00
|
|
|
destination_account_pubkey: &Pubkey,
|
|
|
|
lamports: u64,
|
|
|
|
) -> ProcessResult {
|
2019-08-09 14:52:06 -07:00
|
|
|
let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?;
|
2019-06-21 22:28:34 -07:00
|
|
|
let ixs = vec![stake_instruction::withdraw(
|
2019-07-26 09:34:12 -07:00
|
|
|
&stake_account_keypair.pubkey(),
|
2019-06-21 22:28:34 -07:00
|
|
|
destination_account_pubkey,
|
|
|
|
lamports,
|
|
|
|
)];
|
|
|
|
|
|
|
|
let mut tx = Transaction::new_signed_with_payer(
|
|
|
|
ixs,
|
|
|
|
Some(&config.keypair.pubkey()),
|
2019-07-26 09:34:12 -07:00
|
|
|
&[&config.keypair, &stake_account_keypair],
|
2019-06-21 22:28:34 -07:00
|
|
|
recent_blockhash,
|
|
|
|
);
|
2019-08-09 14:52:06 -07:00
|
|
|
check_account_for_fee(rpc_client, config, &fee_calculator, &tx.message)?;
|
2019-06-21 22:28:34 -07:00
|
|
|
|
2019-09-06 10:55:03 -07:00
|
|
|
let result = rpc_client
|
|
|
|
.send_and_confirm_transaction(&mut tx, &[&config.keypair, &stake_account_keypair]);
|
|
|
|
log_instruction_custom_error::<StakeError>(result)
|
2019-06-21 22:28:34 -07:00
|
|
|
}
|
|
|
|
|
2019-05-21 07:32:38 -07:00
|
|
|
fn process_redeem_vote_credits(
|
|
|
|
rpc_client: &RpcClient,
|
|
|
|
config: &WalletConfig,
|
2019-07-26 09:34:12 -07:00
|
|
|
stake_account_pubkey: &Pubkey,
|
|
|
|
vote_account_pubkey: &Pubkey,
|
2019-05-21 07:32:38 -07:00
|
|
|
) -> ProcessResult {
|
2019-08-09 14:52:06 -07:00
|
|
|
let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?;
|
2019-05-21 07:32:38 -07:00
|
|
|
let ixs = vec![stake_instruction::redeem_vote_credits(
|
2019-07-26 09:34:12 -07:00
|
|
|
stake_account_pubkey,
|
|
|
|
vote_account_pubkey,
|
2019-05-21 07:32:38 -07:00
|
|
|
)];
|
2019-06-03 09:04:51 -07:00
|
|
|
let mut tx = Transaction::new_signed_with_payer(
|
|
|
|
ixs,
|
|
|
|
Some(&config.keypair.pubkey()),
|
|
|
|
&[&config.keypair],
|
|
|
|
recent_blockhash,
|
|
|
|
);
|
2019-08-09 14:52:06 -07:00
|
|
|
check_account_for_fee(rpc_client, config, &fee_calculator, &tx.message)?;
|
2019-09-06 10:55:03 -07:00
|
|
|
let result = rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair]);
|
|
|
|
log_instruction_custom_error::<StakeError>(result)
|
2019-05-21 07:32:38 -07:00
|
|
|
}
|
|
|
|
|
2019-05-09 19:31:42 -07:00
|
|
|
fn process_show_stake_account(
|
|
|
|
rpc_client: &RpcClient,
|
|
|
|
_config: &WalletConfig,
|
2019-07-26 09:34:12 -07:00
|
|
|
stake_account_pubkey: &Pubkey,
|
2019-09-26 10:26:47 -07:00
|
|
|
use_lamports_unit: bool,
|
2019-05-09 19:31:42 -07:00
|
|
|
) -> ProcessResult {
|
|
|
|
use solana_stake_api::stake_state::StakeState;
|
2019-07-26 09:34:12 -07:00
|
|
|
let stake_account = rpc_client.get_account(stake_account_pubkey)?;
|
|
|
|
if stake_account.owner != solana_stake_api::id() {
|
|
|
|
Err(WalletError::RpcRequestError(
|
|
|
|
format!("{:?} is not a stake account", stake_account_pubkey).to_string(),
|
|
|
|
))?;
|
|
|
|
}
|
2019-09-26 13:29:29 -07:00
|
|
|
fn show_authorized(authorized: &Authorized) {
|
|
|
|
println!("authorized staker: {}", authorized.staker);
|
|
|
|
println!("authorized withdrawer: {}", authorized.staker);
|
|
|
|
}
|
|
|
|
fn show_lockup(lockup: &Lockup) {
|
|
|
|
println!("lockup slot: {}", lockup.slot);
|
|
|
|
println!("lockup custodian: {}", lockup.custodian);
|
|
|
|
}
|
2019-05-09 19:31:42 -07:00
|
|
|
match stake_account.state() {
|
2019-09-26 13:29:29 -07:00
|
|
|
Ok(StakeState::Stake(authorized, lockup, stake)) => {
|
2019-09-26 10:26:47 -07:00
|
|
|
println!(
|
|
|
|
"total stake: {}",
|
|
|
|
build_balance_message(stake_account.lamports, use_lamports_unit)
|
|
|
|
);
|
2019-06-17 19:34:21 -07:00
|
|
|
println!("credits observed: {}", stake.credits_observed);
|
2019-09-26 10:26:47 -07:00
|
|
|
println!(
|
|
|
|
"delegated stake: {}",
|
|
|
|
build_balance_message(stake.stake, use_lamports_unit)
|
|
|
|
);
|
2019-07-25 16:53:43 -07:00
|
|
|
if stake.voter_pubkey != Pubkey::default() {
|
|
|
|
println!("delegated voter pubkey: {}", stake.voter_pubkey);
|
|
|
|
}
|
2019-08-17 18:12:30 -07:00
|
|
|
println!(
|
|
|
|
"stake activates starting from epoch: {}",
|
|
|
|
stake.activation_epoch
|
|
|
|
);
|
|
|
|
if stake.deactivation_epoch < std::u64::MAX {
|
|
|
|
println!(
|
|
|
|
"stake deactivates starting from epoch: {}",
|
|
|
|
stake.deactivation_epoch
|
|
|
|
);
|
2019-07-25 16:53:43 -07:00
|
|
|
}
|
2019-09-26 13:29:29 -07:00
|
|
|
show_authorized(&authorized);
|
|
|
|
show_lockup(&lockup);
|
2019-05-21 07:32:38 -07:00
|
|
|
Ok("".to_string())
|
|
|
|
}
|
2019-08-02 17:15:26 -07:00
|
|
|
Ok(StakeState::RewardsPool) => Ok("Stake account is a rewards pool".to_string()),
|
2019-09-26 13:29:29 -07:00
|
|
|
Ok(StakeState::Uninitialized) => Ok("Stake account is uninitialized".to_string()),
|
|
|
|
Ok(StakeState::Initialized(authorized, lockup)) => {
|
|
|
|
println!("Stake account is undelegated");
|
|
|
|
show_authorized(&authorized);
|
|
|
|
show_lockup(&lockup);
|
|
|
|
Ok("".to_string())
|
2019-09-04 13:34:09 -07:00
|
|
|
}
|
2019-08-02 17:15:26 -07:00
|
|
|
Err(err) => Err(WalletError::RpcRequestError(format!(
|
|
|
|
"Account data could not be deserialized to stake state: {:?}",
|
|
|
|
err
|
|
|
|
)))?,
|
2019-05-09 19:31:42 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-05-23 14:50:23 -07:00
|
|
|
fn process_create_replicator_storage_account(
|
|
|
|
rpc_client: &RpcClient,
|
|
|
|
config: &WalletConfig,
|
2019-06-04 14:52:52 -07:00
|
|
|
account_owner: &Pubkey,
|
2019-05-23 23:20:04 -07:00
|
|
|
storage_account_pubkey: &Pubkey,
|
2019-05-23 14:50:23 -07:00
|
|
|
) -> ProcessResult {
|
2019-08-15 13:16:05 -07:00
|
|
|
check_unique_pubkeys(
|
|
|
|
(&config.keypair.pubkey(), "wallet keypair".to_string()),
|
|
|
|
(
|
|
|
|
&storage_account_pubkey,
|
|
|
|
"storage_account_pubkey".to_string(),
|
|
|
|
),
|
|
|
|
)?;
|
2019-08-09 14:52:06 -07:00
|
|
|
let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?;
|
2019-05-23 14:50:23 -07:00
|
|
|
let ixs = storage_instruction::create_replicator_storage_account(
|
|
|
|
&config.keypair.pubkey(),
|
2019-06-04 14:52:52 -07:00
|
|
|
&account_owner,
|
2019-05-23 23:20:04 -07:00
|
|
|
storage_account_pubkey,
|
2019-05-23 14:50:23 -07:00
|
|
|
1,
|
|
|
|
);
|
|
|
|
let mut tx = Transaction::new_signed_instructions(&[&config.keypair], ixs, recent_blockhash);
|
2019-08-09 14:52:06 -07:00
|
|
|
check_account_for_fee(rpc_client, config, &fee_calculator, &tx.message)?;
|
2019-08-12 13:00:55 -07:00
|
|
|
let result = rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair]);
|
2019-09-06 10:55:03 -07:00
|
|
|
log_instruction_custom_error::<SystemError>(result)
|
2019-05-23 14:50:23 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
fn process_create_validator_storage_account(
|
|
|
|
rpc_client: &RpcClient,
|
|
|
|
config: &WalletConfig,
|
2019-06-04 14:52:52 -07:00
|
|
|
account_owner: &Pubkey,
|
2019-05-23 23:20:04 -07:00
|
|
|
storage_account_pubkey: &Pubkey,
|
2019-05-23 14:50:23 -07:00
|
|
|
) -> ProcessResult {
|
2019-08-15 13:16:05 -07:00
|
|
|
check_unique_pubkeys(
|
|
|
|
(&config.keypair.pubkey(), "wallet keypair".to_string()),
|
|
|
|
(
|
|
|
|
&storage_account_pubkey,
|
|
|
|
"storage_account_pubkey".to_string(),
|
|
|
|
),
|
|
|
|
)?;
|
2019-08-09 14:52:06 -07:00
|
|
|
let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?;
|
2019-05-23 14:50:23 -07:00
|
|
|
let ixs = storage_instruction::create_validator_storage_account(
|
|
|
|
&config.keypair.pubkey(),
|
2019-06-04 14:52:52 -07:00
|
|
|
account_owner,
|
2019-05-23 23:20:04 -07:00
|
|
|
storage_account_pubkey,
|
2019-05-23 14:50:23 -07:00
|
|
|
1,
|
|
|
|
);
|
|
|
|
let mut tx = Transaction::new_signed_instructions(&[&config.keypair], ixs, recent_blockhash);
|
2019-08-09 14:52:06 -07:00
|
|
|
check_account_for_fee(rpc_client, config, &fee_calculator, &tx.message)?;
|
2019-08-12 13:00:55 -07:00
|
|
|
let result = rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair]);
|
2019-09-06 10:55:03 -07:00
|
|
|
log_instruction_custom_error::<SystemError>(result)
|
2019-05-23 14:50:23 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
fn process_claim_storage_reward(
|
|
|
|
rpc_client: &RpcClient,
|
|
|
|
config: &WalletConfig,
|
2019-06-13 17:53:54 -07:00
|
|
|
node_account_pubkey: &Pubkey,
|
2019-05-23 23:20:04 -07:00
|
|
|
storage_account_pubkey: &Pubkey,
|
2019-05-23 14:50:23 -07:00
|
|
|
) -> ProcessResult {
|
2019-08-09 14:52:06 -07:00
|
|
|
let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?;
|
2019-05-23 14:50:23 -07:00
|
|
|
|
2019-06-22 17:18:35 -07:00
|
|
|
let instruction =
|
|
|
|
storage_instruction::claim_reward(node_account_pubkey, storage_account_pubkey);
|
2019-05-23 14:50:23 -07:00
|
|
|
let signers = [&config.keypair];
|
|
|
|
let message = Message::new_with_payer(vec![instruction], Some(&signers[0].pubkey()));
|
|
|
|
|
2019-08-09 14:52:06 -07:00
|
|
|
let mut tx = Transaction::new(&signers, message, recent_blockhash);
|
|
|
|
check_account_for_fee(rpc_client, config, &fee_calculator, &tx.message)?;
|
|
|
|
let signature_str = rpc_client.send_and_confirm_transaction(&mut tx, &signers)?;
|
2019-05-23 14:50:23 -07:00
|
|
|
Ok(signature_str.to_string())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn process_show_storage_account(
|
|
|
|
rpc_client: &RpcClient,
|
|
|
|
_config: &WalletConfig,
|
2019-05-23 23:20:04 -07:00
|
|
|
storage_account_pubkey: &Pubkey,
|
2019-05-23 14:50:23 -07:00
|
|
|
) -> ProcessResult {
|
2019-05-23 23:20:04 -07:00
|
|
|
let account = rpc_client.get_account(storage_account_pubkey)?;
|
2019-07-26 09:34:12 -07:00
|
|
|
|
|
|
|
if account.owner != solana_storage_api::id() {
|
|
|
|
Err(WalletError::RpcRequestError(
|
|
|
|
format!("{:?} is not a storage account", storage_account_pubkey).to_string(),
|
|
|
|
))?;
|
|
|
|
}
|
|
|
|
|
|
|
|
use solana_storage_api::storage_contract::StorageContract;
|
2019-05-23 14:50:23 -07:00
|
|
|
let storage_contract: StorageContract = account.state().map_err(|err| {
|
|
|
|
WalletError::RpcRequestError(
|
|
|
|
format!("Unable to deserialize storage account: {:?}", err).to_string(),
|
|
|
|
)
|
|
|
|
})?;
|
2019-06-13 17:53:54 -07:00
|
|
|
println!("{:#?}", storage_contract);
|
2019-05-23 14:50:23 -07:00
|
|
|
println!("account lamports: {}", account.lamports);
|
|
|
|
Ok("".to_string())
|
|
|
|
}
|
|
|
|
|
2019-02-09 13:15:44 -08:00
|
|
|
fn process_deploy(
|
|
|
|
rpc_client: &RpcClient,
|
|
|
|
config: &WalletConfig,
|
|
|
|
program_location: &str,
|
|
|
|
) -> ProcessResult {
|
|
|
|
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(),
|
|
|
|
)
|
|
|
|
})?;
|
2019-03-14 09:48:27 -07:00
|
|
|
let mut program_data = Vec::new();
|
|
|
|
file.read_to_end(&mut program_data).map_err(|err| {
|
2019-02-09 13:15:44 -08:00
|
|
|
WalletError::DynamicProgramError(
|
|
|
|
format!("Unable to read program file: {}", err).to_string(),
|
|
|
|
)
|
|
|
|
})?;
|
|
|
|
|
2019-08-09 14:52:06 -07:00
|
|
|
// Build transactions to calculate fees
|
|
|
|
let mut messages: Vec<&Message> = Vec::new();
|
|
|
|
let (blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?;
|
2019-09-20 14:10:39 -07:00
|
|
|
let mut create_account_tx = system_transaction::create_account(
|
2019-04-02 06:08:11 -07:00
|
|
|
&config.keypair,
|
2019-03-09 19:28:43 -08:00
|
|
|
&program_id.pubkey(),
|
2019-03-02 10:25:16 -08:00
|
|
|
blockhash,
|
2019-02-09 13:15:44 -08:00
|
|
|
1,
|
2019-03-14 09:48:27 -07:00
|
|
|
program_data.len() as u64,
|
2019-03-09 19:28:43 -08:00
|
|
|
&bpf_loader::id(),
|
2019-02-09 13:15:44 -08:00
|
|
|
);
|
2019-08-09 14:52:06 -07:00
|
|
|
messages.push(&create_account_tx.message);
|
2019-05-07 15:00:54 -07:00
|
|
|
let signers = [&config.keypair, &program_id];
|
2019-03-14 09:48:27 -07:00
|
|
|
let write_transactions: Vec<_> = program_data
|
2019-02-27 11:17:32 -08:00
|
|
|
.chunks(USERDATA_CHUNK_SIZE)
|
|
|
|
.zip(0..)
|
|
|
|
.map(|(chunk, i)| {
|
2019-04-03 08:45:57 -07:00
|
|
|
let instruction = loader_instruction::write(
|
2019-03-21 07:14:14 -07:00
|
|
|
&program_id.pubkey(),
|
2019-03-09 19:28:43 -08:00
|
|
|
&bpf_loader::id(),
|
2019-02-27 11:17:32 -08:00
|
|
|
(i * USERDATA_CHUNK_SIZE) as u32,
|
|
|
|
chunk.to_vec(),
|
2019-03-21 07:14:14 -07:00
|
|
|
);
|
2019-05-07 15:00:54 -07:00
|
|
|
let message = Message::new_with_payer(vec![instruction], Some(&signers[0].pubkey()));
|
|
|
|
Transaction::new(&signers, message, blockhash)
|
2019-02-27 11:17:32 -08:00
|
|
|
})
|
|
|
|
.collect();
|
2019-08-09 14:52:06 -07:00
|
|
|
for transaction in write_transactions.iter() {
|
|
|
|
messages.push(&transaction.message);
|
|
|
|
}
|
2019-02-09 13:15:44 -08:00
|
|
|
|
2019-04-03 08:45:57 -07:00
|
|
|
let instruction = loader_instruction::finalize(&program_id.pubkey(), &bpf_loader::id());
|
2019-05-07 15:00:54 -07:00
|
|
|
let message = Message::new_with_payer(vec![instruction], Some(&signers[0].pubkey()));
|
2019-08-09 14:52:06 -07:00
|
|
|
let mut finalize_tx = Transaction::new(&signers, message, blockhash);
|
|
|
|
messages.push(&finalize_tx.message);
|
|
|
|
|
|
|
|
check_account_for_multiple_fees(rpc_client, config, &fee_calculator, &messages)?;
|
|
|
|
|
|
|
|
trace!("Creating program account");
|
|
|
|
let result =
|
|
|
|
rpc_client.send_and_confirm_transaction(&mut create_account_tx, &[&config.keypair]);
|
|
|
|
log_instruction_custom_error::<SystemError>(result).map_err(|_| {
|
|
|
|
WalletError::DynamicProgramError("Program allocate space failed".to_string())
|
|
|
|
})?;
|
|
|
|
|
|
|
|
trace!("Writing program data");
|
|
|
|
rpc_client.send_and_confirm_transactions(write_transactions, &signers)?;
|
|
|
|
|
|
|
|
trace!("Finalizing program account");
|
2019-03-16 17:17:44 -07:00
|
|
|
rpc_client
|
2019-08-09 14:52:06 -07:00
|
|
|
.send_and_confirm_transaction(&mut finalize_tx, &signers)
|
2019-03-16 17:17:44 -07:00
|
|
|
.map_err(|_| {
|
|
|
|
WalletError::DynamicProgramError("Program finalize transaction failed".to_string())
|
|
|
|
})?;
|
2019-02-09 13:15:44 -08:00
|
|
|
|
|
|
|
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,
|
2019-03-09 19:28:43 -08:00
|
|
|
to: &Pubkey,
|
2019-02-09 13:15:44 -08:00
|
|
|
timestamp: Option<DateTime<Utc>>,
|
|
|
|
timestamp_pubkey: Option<Pubkey>,
|
|
|
|
witnesses: &Option<Vec<Pubkey>>,
|
|
|
|
cancelable: Option<Pubkey>,
|
|
|
|
) -> ProcessResult {
|
2019-08-15 13:16:05 -07:00
|
|
|
check_unique_pubkeys(
|
|
|
|
(&config.keypair.pubkey(), "wallet keypair".to_string()),
|
|
|
|
(to, "to".to_string()),
|
|
|
|
)?;
|
2019-08-09 14:52:06 -07:00
|
|
|
let (blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?;
|
2019-02-09 13:15:44 -08:00
|
|
|
|
|
|
|
if timestamp == None && *witnesses == None {
|
2019-05-20 10:03:19 -07:00
|
|
|
let mut tx = system_transaction::transfer(&config.keypair, to, lamports, blockhash);
|
2019-08-09 14:52:06 -07:00
|
|
|
check_account_for_fee(rpc_client, config, &fee_calculator, &tx.message)?;
|
2019-05-07 15:00:54 -07:00
|
|
|
let result = rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair]);
|
2019-09-06 10:55:03 -07:00
|
|
|
log_instruction_custom_error::<SystemError>(result)
|
2019-02-09 13:15:44 -08:00
|
|
|
} else if *witnesses == None {
|
|
|
|
let dt = timestamp.unwrap();
|
|
|
|
let dt_pubkey = match timestamp_pubkey {
|
|
|
|
Some(pubkey) => pubkey,
|
2019-04-02 06:08:11 -07:00
|
|
|
None => config.keypair.pubkey(),
|
2019-02-09 13:15:44 -08:00
|
|
|
};
|
|
|
|
|
|
|
|
let contract_state = Keypair::new();
|
|
|
|
|
|
|
|
// Initializing contract
|
2019-04-03 08:45:57 -07:00
|
|
|
let ixs = budget_instruction::on_date(
|
2019-04-02 06:08:11 -07:00
|
|
|
&config.keypair.pubkey(),
|
2019-02-09 13:15:44 -08:00
|
|
|
to,
|
2019-03-09 19:28:43 -08:00
|
|
|
&contract_state.pubkey(),
|
2019-02-09 13:15:44 -08:00
|
|
|
dt,
|
2019-03-09 19:28:43 -08:00
|
|
|
&dt_pubkey,
|
2019-02-09 13:15:44 -08:00
|
|
|
cancelable,
|
2019-03-05 17:22:46 -08:00
|
|
|
lamports,
|
2019-02-09 13:15:44 -08:00
|
|
|
);
|
2019-04-02 06:08:11 -07:00
|
|
|
let mut tx = Transaction::new_signed_instructions(&[&config.keypair], ixs, blockhash);
|
2019-08-09 14:52:06 -07:00
|
|
|
check_account_for_fee(rpc_client, config, &fee_calculator, &tx.message)?;
|
2019-05-07 15:00:54 -07:00
|
|
|
let result = rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair]);
|
2019-04-25 10:29:44 -07:00
|
|
|
let signature_str = log_instruction_custom_error::<BudgetError>(result)?;
|
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-05-13 12:49:37 -07:00
|
|
|
let (blockhash, _fee_calculator) = rpc_client.get_recent_blockhash()?;
|
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_state = Keypair::new();
|
|
|
|
|
|
|
|
// Initializing contract
|
2019-04-03 08:45:57 -07:00
|
|
|
let ixs = budget_instruction::when_signed(
|
2019-04-02 06:08:11 -07:00
|
|
|
&config.keypair.pubkey(),
|
2019-02-09 13:15:44 -08:00
|
|
|
to,
|
2019-03-09 19:28:43 -08:00
|
|
|
&contract_state.pubkey(),
|
|
|
|
&witness,
|
2019-02-09 13:15:44 -08:00
|
|
|
cancelable,
|
2019-03-05 17:22:46 -08:00
|
|
|
lamports,
|
2019-02-09 13:15:44 -08:00
|
|
|
);
|
2019-04-02 06:08:11 -07:00
|
|
|
let mut tx = Transaction::new_signed_instructions(&[&config.keypair], ixs, blockhash);
|
2019-05-07 15:00:54 -07:00
|
|
|
let result = rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair]);
|
2019-08-09 14:52:06 -07:00
|
|
|
check_account_for_fee(rpc_client, config, &fee_calculator, &tx.message)?;
|
2019-04-25 10:29:44 -07:00
|
|
|
let signature_str = log_instruction_custom_error::<BudgetError>(result)?;
|
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())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-09 19:28:43 -08:00
|
|
|
fn process_cancel(rpc_client: &RpcClient, config: &WalletConfig, pubkey: &Pubkey) -> ProcessResult {
|
2019-08-09 14:52:06 -07:00
|
|
|
let (blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?;
|
2019-04-03 08:45:57 -07:00
|
|
|
let ix = budget_instruction::apply_signature(
|
2019-04-02 06:08:11 -07:00
|
|
|
&config.keypair.pubkey(),
|
|
|
|
pubkey,
|
|
|
|
&config.keypair.pubkey(),
|
|
|
|
);
|
|
|
|
let mut tx = Transaction::new_signed_instructions(&[&config.keypair], vec![ix], blockhash);
|
2019-08-09 14:52:06 -07:00
|
|
|
check_account_for_fee(rpc_client, config, &fee_calculator, &tx.message)?;
|
2019-05-07 15:00:54 -07:00
|
|
|
let result = rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair]);
|
2019-09-06 10:55:03 -07:00
|
|
|
log_instruction_custom_error::<BudgetError>(result)
|
2019-02-09 13:15:44 -08:00
|
|
|
}
|
|
|
|
|
2019-08-05 13:17:03 -07:00
|
|
|
fn process_get_slot(rpc_client: &RpcClient) -> ProcessResult {
|
|
|
|
let slot = rpc_client.get_slot()?;
|
|
|
|
Ok(slot.to_string())
|
|
|
|
}
|
|
|
|
|
2019-02-09 13:15:44 -08:00
|
|
|
fn process_get_transaction_count(rpc_client: &RpcClient) -> ProcessResult {
|
2019-03-17 00:40:45 -07:00
|
|
|
let transaction_count = rpc_client.get_transaction_count()?;
|
|
|
|
Ok(transaction_count.to_string())
|
2019-02-09 13:15:44 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
fn process_time_elapsed(
|
|
|
|
rpc_client: &RpcClient,
|
|
|
|
config: &WalletConfig,
|
2019-03-09 19:28:43 -08:00
|
|
|
to: &Pubkey,
|
|
|
|
pubkey: &Pubkey,
|
2019-02-09 13:15:44 -08:00
|
|
|
dt: DateTime<Utc>,
|
|
|
|
) -> ProcessResult {
|
2019-08-09 14:52:06 -07:00
|
|
|
let (blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?;
|
2019-02-09 13:15:44 -08:00
|
|
|
|
2019-04-03 08:45:57 -07:00
|
|
|
let ix = budget_instruction::apply_timestamp(&config.keypair.pubkey(), pubkey, to, dt);
|
2019-04-02 06:08:11 -07:00
|
|
|
let mut tx = Transaction::new_signed_instructions(&[&config.keypair], vec![ix], blockhash);
|
2019-08-09 14:52:06 -07:00
|
|
|
check_account_for_fee(rpc_client, config, &fee_calculator, &tx.message)?;
|
2019-05-07 15:00:54 -07:00
|
|
|
let result = rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair]);
|
2019-09-06 10:55:03 -07:00
|
|
|
log_instruction_custom_error::<BudgetError>(result)
|
2019-02-09 13:15:44 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
fn process_witness(
|
|
|
|
rpc_client: &RpcClient,
|
|
|
|
config: &WalletConfig,
|
2019-03-09 19:28:43 -08:00
|
|
|
to: &Pubkey,
|
|
|
|
pubkey: &Pubkey,
|
2019-02-09 13:15:44 -08:00
|
|
|
) -> ProcessResult {
|
2019-08-09 14:52:06 -07:00
|
|
|
let (blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?;
|
2019-02-09 13:15:44 -08:00
|
|
|
|
2019-04-03 08:45:57 -07:00
|
|
|
let ix = budget_instruction::apply_signature(&config.keypair.pubkey(), pubkey, to);
|
2019-04-02 06:08:11 -07:00
|
|
|
let mut tx = Transaction::new_signed_instructions(&[&config.keypair], vec![ix], blockhash);
|
2019-08-09 14:52:06 -07:00
|
|
|
check_account_for_fee(rpc_client, config, &fee_calculator, &tx.message)?;
|
2019-05-07 15:00:54 -07:00
|
|
|
let result = rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair]);
|
2019-09-06 10:55:03 -07:00
|
|
|
log_instruction_custom_error::<BudgetError>(result)
|
2019-02-09 13:15:44 -08:00
|
|
|
}
|
|
|
|
|
2019-08-08 10:13:06 -07:00
|
|
|
fn process_get_version(rpc_client: &RpcClient, config: &WalletConfig) -> ProcessResult {
|
|
|
|
let remote_version: Value = serde_json::from_str(&rpc_client.get_version()?)?;
|
|
|
|
println!(
|
|
|
|
"{} {}",
|
|
|
|
style("Cluster versions from:").bold(),
|
|
|
|
config.json_rpc_url
|
|
|
|
);
|
|
|
|
if let Some(versions) = remote_version.as_object() {
|
|
|
|
for (key, value) in versions.iter() {
|
|
|
|
if let Some(value_string) = value.as_str() {
|
|
|
|
println_name_value(&format!("* {}:", key), &value_string);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Ok("".to_string())
|
|
|
|
}
|
|
|
|
|
2019-08-12 21:33:13 -07:00
|
|
|
fn process_ping(
|
|
|
|
rpc_client: &RpcClient,
|
|
|
|
config: &WalletConfig,
|
|
|
|
interval: &Duration,
|
|
|
|
count: &Option<u64>,
|
|
|
|
timeout: &Duration,
|
|
|
|
) -> ProcessResult {
|
|
|
|
let to = Keypair::new().pubkey();
|
|
|
|
|
|
|
|
println_name_value("Source account:", &config.keypair.pubkey().to_string());
|
|
|
|
println_name_value("Destination account:", &to.to_string());
|
|
|
|
println!();
|
|
|
|
|
|
|
|
let (signal_sender, signal_receiver) = std::sync::mpsc::channel();
|
|
|
|
ctrlc::set_handler(move || {
|
|
|
|
let _ = signal_sender.send(());
|
|
|
|
})
|
|
|
|
.expect("Error setting Ctrl-C handler");
|
|
|
|
|
|
|
|
let mut last_blockhash = Hash::default();
|
|
|
|
let mut submit_count = 0;
|
|
|
|
let mut confirmed_count = 0;
|
|
|
|
let mut confirmation_time: VecDeque<u64> = VecDeque::with_capacity(1024);
|
|
|
|
|
|
|
|
'mainloop: for seq in 0..count.unwrap_or(std::u64::MAX) {
|
|
|
|
let (recent_blockhash, fee_calculator) = rpc_client.get_new_blockhash(&last_blockhash)?;
|
|
|
|
last_blockhash = recent_blockhash;
|
|
|
|
|
|
|
|
let transaction = system_transaction::transfer(&config.keypair, &to, 1, recent_blockhash);
|
|
|
|
check_account_for_fee(rpc_client, config, &fee_calculator, &transaction.message)?;
|
|
|
|
|
|
|
|
match rpc_client.send_transaction(&transaction) {
|
|
|
|
Ok(signature) => {
|
|
|
|
let transaction_sent = Instant::now();
|
|
|
|
loop {
|
|
|
|
let signature_status = rpc_client.get_signature_status(&signature)?;
|
|
|
|
let elapsed_time = Instant::now().duration_since(transaction_sent);
|
|
|
|
if let Some(transaction_status) = signature_status {
|
|
|
|
match transaction_status {
|
|
|
|
Ok(()) => {
|
|
|
|
let elapsed_time_millis = elapsed_time.as_millis() as u64;
|
|
|
|
confirmation_time.push_back(elapsed_time_millis);
|
|
|
|
println!(
|
|
|
|
"{}1 lamport transferred: seq={:<3} time={:>4}ms signature={}",
|
|
|
|
CHECK_MARK, seq, elapsed_time_millis, signature
|
|
|
|
);
|
|
|
|
confirmed_count += 1;
|
|
|
|
}
|
|
|
|
Err(err) => {
|
|
|
|
println!(
|
|
|
|
"{}Transaction failed: seq={:<3} error={:?} signature={}",
|
|
|
|
CROSS_MARK, seq, err, signature
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if elapsed_time >= *timeout {
|
|
|
|
println!(
|
|
|
|
"{}Confirmation timeout: seq={:<3} signature={}",
|
|
|
|
CROSS_MARK, seq, signature
|
|
|
|
);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Sleep for half a slot
|
|
|
|
if signal_receiver
|
|
|
|
.recv_timeout(Duration::from_millis(
|
2019-09-06 14:30:56 -07:00
|
|
|
500 * solana_sdk::clock::DEFAULT_TICKS_PER_SLOT
|
|
|
|
/ solana_sdk::clock::DEFAULT_TICKS_PER_SECOND,
|
2019-08-12 21:33:13 -07:00
|
|
|
))
|
|
|
|
.is_ok()
|
|
|
|
{
|
|
|
|
break 'mainloop;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Err(err) => {
|
|
|
|
println!(
|
|
|
|
"{}Submit failed: seq={:<3} error={:?}",
|
|
|
|
CROSS_MARK, seq, err
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
submit_count += 1;
|
|
|
|
|
|
|
|
if signal_receiver.recv_timeout(*interval).is_ok() {
|
|
|
|
break 'mainloop;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
println!();
|
|
|
|
println!("--- transaction statistics ---");
|
|
|
|
println!(
|
|
|
|
"{} transactions submitted, {} transactions confirmed, {:.1}% transaction loss",
|
|
|
|
submit_count,
|
|
|
|
confirmed_count,
|
|
|
|
(100. - f64::from(confirmed_count) / f64::from(submit_count) * 100.)
|
|
|
|
);
|
|
|
|
if !confirmation_time.is_empty() {
|
|
|
|
let samples: Vec<f64> = confirmation_time.iter().map(|t| *t as f64).collect();
|
|
|
|
let dist = criterion_stats::Distribution::from(samples.into_boxed_slice());
|
|
|
|
let mean = dist.mean();
|
|
|
|
println!(
|
|
|
|
"confirmation min/mean/max/stddev = {:.0}/{:.0}/{:.0}/{:.0} ms",
|
|
|
|
dist.min(),
|
|
|
|
mean,
|
|
|
|
dist.max(),
|
|
|
|
dist.std_dev(Some(mean))
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok("".to_string())
|
|
|
|
}
|
|
|
|
|
2019-02-09 13:15:44 -08:00
|
|
|
pub fn process_command(config: &WalletConfig) -> ProcessResult {
|
2019-09-02 12:53:13 -07:00
|
|
|
println_name_value("Keypair:", &config.keypair_path);
|
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
|
2019-04-02 06:08:11 -07:00
|
|
|
return Ok(format!("{}", config.keypair.pubkey()));
|
2018-11-09 14:42:11 -08:00
|
|
|
}
|
2019-09-02 12:53:13 -07:00
|
|
|
println_name_value("RPC Endpoint:", &config.json_rpc_url);
|
2018-11-09 14:42:11 -08:00
|
|
|
|
2019-03-16 21:51:41 -07:00
|
|
|
let mut _rpc_client;
|
2019-01-13 23:10:03 -08:00
|
|
|
let rpc_client = if config.rpc_client.is_none() {
|
2019-05-06 07:38:26 -07:00
|
|
|
_rpc_client = RpcClient::new(config.json_rpc_url.to_string());
|
2019-03-16 21:51:41 -07:00
|
|
|
&_rpc_client
|
2019-01-13 23:10:03 -08:00
|
|
|
} else {
|
|
|
|
// Primarily for testing
|
2019-03-16 21:51:41 -07:00
|
|
|
config.rpc_client.as_ref().unwrap()
|
2019-01-13 23:10:03 -08:00
|
|
|
};
|
2018-11-09 14:36:08 -08:00
|
|
|
|
2019-05-09 19:31:42 -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-06-07 13:11:56 -07:00
|
|
|
WalletCommand::Fees => process_fees(&rpc_client),
|
|
|
|
|
2019-02-09 13:15:44 -08:00
|
|
|
// Request an airdrop from Solana Drone;
|
2019-08-29 20:45:53 -07:00
|
|
|
WalletCommand::Airdrop {
|
|
|
|
drone_host,
|
|
|
|
drone_port,
|
|
|
|
lamports,
|
2019-09-10 16:16:40 -07:00
|
|
|
use_lamports_unit,
|
2019-08-29 20:45:53 -07:00
|
|
|
} => {
|
|
|
|
let drone_addr = SocketAddr::new(
|
|
|
|
drone_host.unwrap_or_else(|| {
|
|
|
|
let drone_host = url::Url::parse(&config.json_rpc_url)
|
|
|
|
.unwrap()
|
|
|
|
.host()
|
|
|
|
.unwrap()
|
|
|
|
.to_string();
|
|
|
|
solana_netutil::parse_host(&drone_host).unwrap_or_else(|err| {
|
|
|
|
panic!("Unable to resolve {}: {}", drone_host, err);
|
|
|
|
})
|
|
|
|
}),
|
|
|
|
*drone_port,
|
|
|
|
);
|
|
|
|
|
2019-09-10 16:16:40 -07:00
|
|
|
process_airdrop(
|
|
|
|
&rpc_client,
|
|
|
|
config,
|
|
|
|
&drone_addr,
|
|
|
|
*lamports,
|
|
|
|
*use_lamports_unit,
|
|
|
|
)
|
2019-03-05 17:22:46 -08:00
|
|
|
}
|
2018-11-14 18:57:34 -08:00
|
|
|
|
2018-09-18 14:42:32 -07:00
|
|
|
// Check client balance
|
2019-09-10 12:36:59 -07:00
|
|
|
WalletCommand::Balance {
|
|
|
|
pubkey,
|
|
|
|
use_lamports_unit,
|
|
|
|
} => process_balance(&pubkey, &rpc_client, *use_lamports_unit),
|
2019-02-09 13:15:44 -08:00
|
|
|
|
2018-09-18 14:42:32 -07:00
|
|
|
// Cancel a contract by contract Pubkey
|
2019-03-09 19:28:43 -08:00
|
|
|
WalletCommand::Cancel(pubkey) => process_cancel(&rpc_client, config, &pubkey),
|
2019-02-09 13:15:44 -08:00
|
|
|
|
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),
|
|
|
|
|
2019-05-09 19:31:42 -07:00
|
|
|
// Create vote account
|
2019-09-25 13:53:49 -07:00
|
|
|
WalletCommand::CreateVoteAccount(vote_account_pubkey, vote_init, lamports) => {
|
|
|
|
process_create_vote_account(
|
|
|
|
&rpc_client,
|
|
|
|
config,
|
|
|
|
&vote_account_pubkey,
|
|
|
|
&vote_init,
|
|
|
|
*lamports,
|
|
|
|
)
|
|
|
|
}
|
2019-07-26 09:34:12 -07:00
|
|
|
|
2019-09-25 13:53:49 -07:00
|
|
|
WalletCommand::VoteAuthorize(
|
2019-07-26 09:34:12 -07:00
|
|
|
vote_account_pubkey,
|
2019-09-25 13:53:49 -07:00
|
|
|
authorized_keypair,
|
|
|
|
new_authorized_pubkey,
|
|
|
|
vote_authorize,
|
|
|
|
) => process_vote_authorize(
|
2019-05-20 13:32:32 -07:00
|
|
|
&rpc_client,
|
|
|
|
config,
|
2019-07-26 09:34:12 -07:00
|
|
|
&vote_account_pubkey,
|
2019-09-25 13:53:49 -07:00
|
|
|
&authorized_keypair,
|
|
|
|
&new_authorized_pubkey,
|
|
|
|
*vote_authorize,
|
2019-05-20 13:32:32 -07:00
|
|
|
),
|
2019-08-09 22:48:57 -07:00
|
|
|
|
2019-09-10 12:36:59 -07:00
|
|
|
WalletCommand::ShowAccount {
|
|
|
|
pubkey,
|
|
|
|
output_file,
|
|
|
|
use_lamports_unit,
|
|
|
|
} => process_show_account(
|
|
|
|
&rpc_client,
|
|
|
|
config,
|
|
|
|
&pubkey,
|
|
|
|
&output_file,
|
|
|
|
*use_lamports_unit,
|
|
|
|
),
|
2019-08-09 22:48:57 -07:00
|
|
|
|
2019-09-26 10:26:47 -07:00
|
|
|
WalletCommand::ShowVoteAccount {
|
|
|
|
pubkey: vote_account_pubkey,
|
|
|
|
use_lamports_unit,
|
|
|
|
} => process_show_vote_account(
|
|
|
|
&rpc_client,
|
|
|
|
config,
|
|
|
|
&vote_account_pubkey,
|
|
|
|
*use_lamports_unit,
|
|
|
|
),
|
2019-03-06 17:02:20 -08:00
|
|
|
|
2019-09-18 09:29:57 -07:00
|
|
|
WalletCommand::Uptime {
|
|
|
|
pubkey: vote_account_pubkey,
|
|
|
|
aggregate,
|
|
|
|
span,
|
|
|
|
} => process_uptime(&rpc_client, config, &vote_account_pubkey, *aggregate, *span),
|
|
|
|
|
2019-08-01 21:08:24 -07:00
|
|
|
WalletCommand::DelegateStake(
|
|
|
|
stake_account_keypair,
|
|
|
|
vote_account_pubkey,
|
|
|
|
lamports,
|
2019-09-26 13:29:29 -07:00
|
|
|
authorized,
|
2019-08-01 21:08:24 -07:00
|
|
|
force,
|
|
|
|
) => process_delegate_stake(
|
|
|
|
&rpc_client,
|
|
|
|
config,
|
|
|
|
&stake_account_keypair,
|
|
|
|
&vote_account_pubkey,
|
|
|
|
*lamports,
|
2019-09-26 13:29:29 -07:00
|
|
|
&authorized,
|
2019-08-01 21:08:24 -07:00
|
|
|
*force,
|
|
|
|
),
|
2019-05-21 07:32:38 -07:00
|
|
|
|
2019-06-21 22:28:34 -07:00
|
|
|
WalletCommand::WithdrawStake(
|
2019-07-26 09:34:12 -07:00
|
|
|
stake_account_keypair,
|
2019-06-21 22:28:34 -07:00
|
|
|
destination_account_pubkey,
|
|
|
|
lamports,
|
|
|
|
) => process_withdraw_stake(
|
|
|
|
&rpc_client,
|
|
|
|
config,
|
2019-07-26 09:34:12 -07:00
|
|
|
&stake_account_keypair,
|
2019-06-21 22:28:34 -07:00
|
|
|
&destination_account_pubkey,
|
|
|
|
*lamports,
|
|
|
|
),
|
|
|
|
|
2019-06-21 23:45:03 -07:00
|
|
|
// Deactivate stake account
|
2019-08-09 12:55:21 -07:00
|
|
|
WalletCommand::DeactivateStake(stake_account_keypair, vote_account_pubkey) => {
|
|
|
|
process_deactivate_stake_account(
|
|
|
|
&rpc_client,
|
|
|
|
config,
|
|
|
|
&stake_account_keypair,
|
|
|
|
&vote_account_pubkey,
|
|
|
|
)
|
2019-06-21 23:45:03 -07:00
|
|
|
}
|
|
|
|
|
2019-07-26 09:34:12 -07:00
|
|
|
WalletCommand::RedeemVoteCredits(stake_account_pubkey, vote_account_pubkey) => {
|
2019-06-21 20:43:24 -07:00
|
|
|
process_redeem_vote_credits(
|
2019-04-10 17:52:47 -07:00
|
|
|
&rpc_client,
|
|
|
|
config,
|
2019-07-26 09:34:12 -07:00
|
|
|
&stake_account_pubkey,
|
|
|
|
&vote_account_pubkey,
|
2019-04-10 17:52:47 -07:00
|
|
|
)
|
2019-03-06 17:02:20 -08:00
|
|
|
}
|
2019-05-21 07:32:38 -07:00
|
|
|
|
2019-09-26 10:26:47 -07:00
|
|
|
WalletCommand::ShowStakeAccount {
|
|
|
|
pubkey: stake_account_pubkey,
|
|
|
|
use_lamports_unit,
|
|
|
|
} => process_show_stake_account(
|
|
|
|
&rpc_client,
|
|
|
|
config,
|
|
|
|
&stake_account_pubkey,
|
|
|
|
*use_lamports_unit,
|
|
|
|
),
|
2019-04-17 07:45:07 -07:00
|
|
|
|
2019-06-04 14:52:52 -07:00
|
|
|
WalletCommand::CreateReplicatorStorageAccount(
|
|
|
|
storage_account_owner,
|
|
|
|
storage_account_pubkey,
|
|
|
|
) => process_create_replicator_storage_account(
|
|
|
|
&rpc_client,
|
|
|
|
config,
|
|
|
|
&storage_account_owner,
|
|
|
|
&storage_account_pubkey,
|
|
|
|
),
|
2019-05-23 14:50:23 -07:00
|
|
|
|
2019-06-04 14:52:52 -07:00
|
|
|
WalletCommand::CreateValidatorStorageAccount(account_owner, storage_account_pubkey) => {
|
|
|
|
process_create_validator_storage_account(
|
|
|
|
&rpc_client,
|
|
|
|
config,
|
|
|
|
&account_owner,
|
|
|
|
&storage_account_pubkey,
|
|
|
|
)
|
2019-05-23 14:50:23 -07:00
|
|
|
}
|
|
|
|
|
2019-06-22 17:18:35 -07:00
|
|
|
WalletCommand::ClaimStorageReward(node_account_pubkey, storage_account_pubkey) => {
|
|
|
|
process_claim_storage_reward(
|
|
|
|
&rpc_client,
|
|
|
|
config,
|
|
|
|
node_account_pubkey,
|
|
|
|
&storage_account_pubkey,
|
|
|
|
)
|
|
|
|
}
|
2019-05-23 14:50:23 -07:00
|
|
|
|
2019-05-23 23:20:04 -07:00
|
|
|
WalletCommand::ShowStorageAccount(storage_account_pubkey) => {
|
|
|
|
process_show_storage_account(&rpc_client, config, &storage_account_pubkey)
|
2019-05-23 14:50:23 -07:00
|
|
|
}
|
|
|
|
|
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-08-05 13:17:03 -07:00
|
|
|
WalletCommand::GetSlot => process_get_slot(&rpc_client),
|
2019-02-09 13:15:44 -08: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
|
2019-09-10 16:16:40 -07:00
|
|
|
WalletCommand::Pay {
|
2019-03-05 17:22:46 -08:00
|
|
|
lamports,
|
|
|
|
to,
|
|
|
|
timestamp,
|
|
|
|
timestamp_pubkey,
|
|
|
|
ref witnesses,
|
|
|
|
cancelable,
|
2019-09-10 16:16:40 -07:00
|
|
|
} => process_pay(
|
2019-03-05 17:22:46 -08:00
|
|
|
&rpc_client,
|
|
|
|
config,
|
2019-05-09 19:31:42 -07:00
|
|
|
*lamports,
|
2019-03-09 19:28:43 -08:00
|
|
|
&to,
|
2019-05-09 19:31:42 -07:00
|
|
|
*timestamp,
|
|
|
|
*timestamp_pubkey,
|
2019-03-05 17:22:46 -08:00
|
|
|
witnesses,
|
2019-05-09 19:31:42 -07:00
|
|
|
*cancelable,
|
2019-03-05 17:22:46 -08:00
|
|
|
),
|
2019-02-09 13:15:44 -08:00
|
|
|
|
2019-08-12 21:33:13 -07:00
|
|
|
WalletCommand::Ping {
|
|
|
|
interval,
|
|
|
|
count,
|
|
|
|
timeout,
|
|
|
|
} => process_ping(&rpc_client, config, interval, count, timeout),
|
|
|
|
|
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-08-09 14:52:06 -07:00
|
|
|
process_time_elapsed(&rpc_client, config, &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
|
2019-08-09 14:52:06 -07:00
|
|
|
WalletCommand::Witness(to, pubkey) => process_witness(&rpc_client, config, &to, &pubkey),
|
2019-08-08 10:13:06 -07:00
|
|
|
|
|
|
|
// Return software version of wallet and cluster entrypoint node
|
|
|
|
WalletCommand::GetVersion => process_get_version(&rpc_client, config),
|
2019-09-03 10:38:12 -07:00
|
|
|
|
|
|
|
// Return all or single validator info
|
|
|
|
WalletCommand::GetValidatorInfo(info_pubkey) => {
|
|
|
|
process_get_validator_info(&rpc_client, *info_pubkey)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Publish validator info
|
|
|
|
WalletCommand::SetValidatorInfo(validator_info, info_pubkey) => {
|
|
|
|
process_set_validator_info(&rpc_client, config, &validator_info, *info_pubkey)
|
|
|
|
}
|
2018-09-14 01:58:39 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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 {
|
2019-03-29 09:05:06 -07:00
|
|
|
self.transaction.message().account_keys[0]
|
2019-03-15 06:39:42 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
fn sign_message(&self, _msg: &[u8]) -> Signature {
|
|
|
|
self.transaction.signatures[0]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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-03-14 19:18:28 -07:00
|
|
|
to_pubkey: &Pubkey,
|
2019-03-05 17:22:46 -08:00
|
|
|
lamports: u64,
|
2019-09-06 10:55:03 -07:00
|
|
|
) -> ProcessResult {
|
2019-05-13 12:49:37 -07:00
|
|
|
let (blockhash, _fee_calculator) = rpc_client.get_recent_blockhash()?;
|
2019-05-09 09:48:27 -07:00
|
|
|
let keypair = {
|
|
|
|
let mut retries = 5;
|
|
|
|
loop {
|
|
|
|
let result = DroneKeypair::new_keypair(drone_addr, to_pubkey, lamports, blockhash);
|
|
|
|
if result.is_ok() || retries == 0 {
|
|
|
|
break result;
|
|
|
|
}
|
|
|
|
retries -= 1;
|
|
|
|
sleep(Duration::from_secs(1));
|
|
|
|
}
|
|
|
|
}?;
|
2019-03-15 06:39:42 -07:00
|
|
|
let mut tx = keypair.airdrop_transaction();
|
2019-05-07 15:00:54 -07:00
|
|
|
let result = rpc_client.send_and_confirm_transaction(&mut tx, &[&keypair]);
|
2019-09-06 10:55:03 -07:00
|
|
|
log_instruction_custom_error::<SystemError>(result)
|
2018-10-22 21:21:33 -07:00
|
|
|
}
|
|
|
|
|
2019-09-18 09:29:57 -07:00
|
|
|
pub fn log_instruction_custom_error<E>(result: Result<String, ClientError>) -> ProcessResult
|
2019-04-25 10:29:44 -07:00
|
|
|
where
|
|
|
|
E: 'static + std::error::Error + DecodeError<E> + FromPrimitive,
|
|
|
|
{
|
|
|
|
if result.is_err() {
|
|
|
|
let err = result.unwrap_err();
|
|
|
|
if let ClientError::TransactionError(TransactionError::InstructionError(
|
|
|
|
_,
|
|
|
|
InstructionError::CustomError(code),
|
|
|
|
)) = err
|
|
|
|
{
|
|
|
|
if let Some(specific_error) = E::decode_custom_error_to_enum(code) {
|
2019-09-06 10:55:03 -07:00
|
|
|
error!("{}::{:?}", E::type_of(), specific_error);
|
2019-04-25 10:29:44 -07:00
|
|
|
Err(specific_error)?
|
|
|
|
}
|
|
|
|
}
|
|
|
|
error!("{:?}", err);
|
|
|
|
Err(err)?
|
|
|
|
} else {
|
|
|
|
Ok(result.unwrap())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-26 10:26:47 -07:00
|
|
|
pub(crate) fn build_balance_message(lamports: u64, use_lamports_unit: bool) -> String {
|
2019-09-10 12:36:59 -07:00
|
|
|
if use_lamports_unit {
|
|
|
|
let ess = if lamports == 1 { "" } else { "s" };
|
|
|
|
format!("{:?} lamport{}", lamports, ess)
|
|
|
|
} else {
|
|
|
|
let sol = lamports_to_sol(lamports);
|
2019-09-17 22:59:35 -07:00
|
|
|
let sol_str = format!("{:.8}", sol);
|
|
|
|
let pretty_sol = sol_str.trim_end_matches('0').trim_end_matches('.');
|
|
|
|
format!("{} SOL", pretty_sol)
|
2019-09-10 12:36:59 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-26 10:26:47 -07:00
|
|
|
pub(crate) fn parse_amount_lamports(
|
2019-09-10 16:16:40 -07:00
|
|
|
amount: &str,
|
|
|
|
use_lamports_unit: Option<&str>,
|
|
|
|
) -> Result<u64, Box<dyn error::Error>> {
|
|
|
|
if use_lamports_unit.is_some() && use_lamports_unit.unwrap() == "lamports" {
|
|
|
|
Ok(amount.parse()?)
|
|
|
|
} else {
|
|
|
|
Ok(sol_to_lamports(amount.parse()?))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-05-09 19:31:42 -07:00
|
|
|
pub fn app<'ab, 'v>(name: &str, about: &'ab str, version: &'v str) -> App<'ab, 'v> {
|
|
|
|
App::new(name)
|
|
|
|
.about(about)
|
|
|
|
.version(version)
|
|
|
|
.setting(AppSettings::SubcommandRequiredElseHelp)
|
|
|
|
.subcommand(SubCommand::with_name("address").about("Get your public key"))
|
2019-06-07 13:11:56 -07:00
|
|
|
.subcommand(SubCommand::with_name("fees").about("Display current cluster fees"))
|
2019-05-09 19:31:42 -07:00
|
|
|
.subcommand(
|
|
|
|
SubCommand::with_name("airdrop")
|
2019-08-29 20:45:53 -07:00
|
|
|
.about("Request lamports")
|
2019-08-30 11:13:23 -07:00
|
|
|
.arg(
|
|
|
|
Arg::with_name("drone_host")
|
|
|
|
.long("drone-host")
|
|
|
|
.value_name("HOST")
|
|
|
|
.takes_value(true)
|
|
|
|
.help("Drone host to use [default: the --url host]"),
|
|
|
|
)
|
|
|
|
.arg(
|
|
|
|
Arg::with_name("drone_port")
|
|
|
|
.long("drone-port")
|
|
|
|
.value_name("PORT")
|
|
|
|
.takes_value(true)
|
|
|
|
.default_value(solana_drone::drone::DRONE_PORT_STR)
|
|
|
|
.help("Drone port to use"),
|
|
|
|
)
|
2019-05-09 19:31:42 -07:00
|
|
|
.arg(
|
2019-09-10 16:16:40 -07:00
|
|
|
Arg::with_name("amount")
|
2019-05-09 19:31:42 -07:00
|
|
|
.index(1)
|
2019-09-10 16:16:40 -07:00
|
|
|
.value_name("AMOUNT")
|
2019-05-09 19:31:42 -07:00
|
|
|
.takes_value(true)
|
|
|
|
.required(true)
|
2019-09-10 16:16:40 -07:00
|
|
|
.help("The airdrop amount to request (default unit SOL)"),
|
|
|
|
)
|
|
|
|
.arg(
|
|
|
|
Arg::with_name("unit")
|
|
|
|
.index(2)
|
|
|
|
.takes_value(true)
|
|
|
|
.possible_values(&["SOL", "lamports"])
|
|
|
|
.help("Specify unit to use for request and balance display"),
|
2019-05-09 19:31:42 -07:00
|
|
|
),
|
|
|
|
)
|
|
|
|
.subcommand(
|
|
|
|
SubCommand::with_name("balance")
|
|
|
|
.about("Get your balance")
|
|
|
|
.arg(
|
|
|
|
Arg::with_name("pubkey")
|
|
|
|
.index(1)
|
|
|
|
.value_name("PUBKEY")
|
|
|
|
.takes_value(true)
|
2019-08-08 17:10:09 -07:00
|
|
|
.validator(is_pubkey_or_keypair)
|
2019-05-09 19:31:42 -07:00
|
|
|
.help("The public key of the balance to check"),
|
2019-09-10 12:36:59 -07:00
|
|
|
)
|
|
|
|
.arg(
|
|
|
|
Arg::with_name("lamports")
|
|
|
|
.long("lamports")
|
|
|
|
.takes_value(false)
|
|
|
|
.help("Display balance in lamports instead of SOL"),
|
2019-05-09 19:31:42 -07:00
|
|
|
),
|
|
|
|
)
|
|
|
|
.subcommand(
|
|
|
|
SubCommand::with_name("cancel")
|
|
|
|
.about("Cancel a transfer")
|
|
|
|
.arg(
|
|
|
|
Arg::with_name("process_id")
|
|
|
|
.index(1)
|
2019-08-08 17:10:09 -07:00
|
|
|
.value_name("PROCESS ID")
|
2019-05-09 19:31:42 -07:00
|
|
|
.takes_value(true)
|
|
|
|
.required(true)
|
|
|
|
.validator(is_pubkey)
|
|
|
|
.help("The process id of the transfer to cancel"),
|
|
|
|
),
|
|
|
|
)
|
|
|
|
.subcommand(
|
|
|
|
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"),
|
|
|
|
),
|
|
|
|
)
|
|
|
|
.subcommand(
|
2019-09-25 13:53:49 -07:00
|
|
|
SubCommand::with_name("vote-authorize-voter")
|
2019-05-20 13:32:32 -07:00
|
|
|
.about("Authorize a new vote signing keypair for the given vote account")
|
2019-05-09 19:31:42 -07:00
|
|
|
.arg(
|
2019-07-26 09:34:12 -07:00
|
|
|
Arg::with_name("vote_account_pubkey")
|
2019-05-09 19:31:42 -07:00
|
|
|
.index(1)
|
2019-08-08 17:10:09 -07:00
|
|
|
.value_name("VOTE ACCOUNT PUBKEY")
|
2019-05-09 19:31:42 -07:00
|
|
|
.takes_value(true)
|
|
|
|
.required(true)
|
2019-08-08 17:10:09 -07:00
|
|
|
.validator(is_pubkey_or_keypair)
|
2019-05-20 13:32:32 -07:00
|
|
|
.help("Vote account in which to set the authorized voter"),
|
|
|
|
)
|
|
|
|
.arg(
|
2019-09-25 13:53:49 -07:00
|
|
|
Arg::with_name("authorized_keypair_file")
|
2019-05-20 13:32:32 -07:00
|
|
|
.index(2)
|
2019-08-08 17:10:09 -07:00
|
|
|
.value_name("CURRENT VOTER KEYPAIR FILE")
|
2019-05-20 13:32:32 -07:00
|
|
|
.takes_value(true)
|
|
|
|
.required(true)
|
2019-08-20 13:59:31 -07:00
|
|
|
.validator(is_keypair)
|
2019-05-20 13:32:32 -07:00
|
|
|
.help("Keypair file for the currently authorized vote signer"),
|
|
|
|
)
|
|
|
|
.arg(
|
2019-09-25 13:53:49 -07:00
|
|
|
Arg::with_name("new_authorized_pubkey")
|
2019-05-20 13:32:32 -07:00
|
|
|
.index(3)
|
2019-08-08 17:10:09 -07:00
|
|
|
.value_name("NEW VOTER PUBKEY")
|
2019-05-20 13:32:32 -07:00
|
|
|
.takes_value(true)
|
|
|
|
.required(true)
|
2019-08-08 17:10:09 -07:00
|
|
|
.validator(is_pubkey_or_keypair)
|
2019-05-20 13:32:32 -07:00
|
|
|
.help("New vote signer to authorize"),
|
2019-05-09 19:31:42 -07:00
|
|
|
),
|
|
|
|
)
|
2019-09-25 13:53:49 -07:00
|
|
|
.subcommand(
|
|
|
|
SubCommand::with_name("vote-authorize-withdrawer")
|
|
|
|
.about("Authorize a new withdraw signing keypair for the given vote account")
|
|
|
|
.arg(
|
|
|
|
Arg::with_name("vote_account_pubkey")
|
|
|
|
.index(1)
|
|
|
|
.value_name("VOTE ACCOUNT PUBKEY")
|
|
|
|
.takes_value(true)
|
|
|
|
.required(true)
|
|
|
|
.validator(is_pubkey_or_keypair)
|
|
|
|
.help("Vote account in which to set the authorized withdrawer"),
|
|
|
|
)
|
|
|
|
.arg(
|
|
|
|
Arg::with_name("authorized_keypair_file")
|
|
|
|
.index(2)
|
|
|
|
.value_name("CURRENT WITHDRAWER KEYPAIR FILE")
|
|
|
|
.takes_value(true)
|
|
|
|
.required(true)
|
|
|
|
.validator(is_keypair)
|
|
|
|
.help("Keypair file for the currently authorized withdrawer"),
|
|
|
|
)
|
|
|
|
.arg(
|
|
|
|
Arg::with_name("new_authorized_pubkey")
|
|
|
|
.index(3)
|
|
|
|
.value_name("NEW WITHDRAWER PUBKEY")
|
|
|
|
.takes_value(true)
|
|
|
|
.required(true)
|
|
|
|
.validator(is_pubkey_or_keypair)
|
|
|
|
.help("New withdrawer to authorize"),
|
|
|
|
),
|
|
|
|
)
|
2019-05-09 19:31:42 -07:00
|
|
|
.subcommand(
|
|
|
|
SubCommand::with_name("create-vote-account")
|
2019-07-26 09:34:12 -07:00
|
|
|
.about("Create a vote account")
|
2019-05-09 19:31:42 -07:00
|
|
|
.arg(
|
2019-07-26 09:34:12 -07:00
|
|
|
Arg::with_name("vote_account_pubkey")
|
2019-05-09 19:31:42 -07:00
|
|
|
.index(1)
|
2019-08-08 17:10:09 -07:00
|
|
|
.value_name("VOTE ACCOUNT PUBKEY")
|
2019-05-09 19:31:42 -07:00
|
|
|
.takes_value(true)
|
|
|
|
.required(true)
|
2019-08-08 17:10:09 -07:00
|
|
|
.validator(is_pubkey_or_keypair)
|
2019-05-09 19:31:42 -07:00
|
|
|
.help("Vote account address to fund"),
|
|
|
|
)
|
|
|
|
.arg(
|
2019-05-23 23:20:04 -07:00
|
|
|
Arg::with_name("node_pubkey")
|
2019-05-09 19:31:42 -07:00
|
|
|
.index(2)
|
2019-08-08 17:10:09 -07:00
|
|
|
.value_name("VALIDATOR PUBKEY")
|
2019-05-09 19:31:42 -07:00
|
|
|
.takes_value(true)
|
|
|
|
.required(true)
|
2019-08-08 17:10:09 -07:00
|
|
|
.validator(is_pubkey_or_keypair)
|
2019-07-26 09:34:12 -07:00
|
|
|
.help("Validator that will vote with this account"),
|
2019-05-09 19:31:42 -07:00
|
|
|
)
|
|
|
|
.arg(
|
2019-09-26 10:26:47 -07:00
|
|
|
Arg::with_name("amount")
|
2019-05-09 19:31:42 -07:00
|
|
|
.index(3)
|
2019-09-26 10:26:47 -07:00
|
|
|
.value_name("AMOUNT")
|
2019-05-09 19:31:42 -07:00
|
|
|
.takes_value(true)
|
|
|
|
.required(true)
|
2019-09-26 10:26:47 -07:00
|
|
|
.help("The amount of send to the vote account (default unit SOL)"),
|
|
|
|
)
|
|
|
|
.arg(
|
|
|
|
Arg::with_name("unit")
|
|
|
|
.index(4)
|
|
|
|
.takes_value(true)
|
|
|
|
.possible_values(&["SOL", "lamports"])
|
|
|
|
.help("Specify unit to use for request"),
|
2019-05-09 19:31:42 -07:00
|
|
|
)
|
|
|
|
.arg(
|
|
|
|
Arg::with_name("commission")
|
|
|
|
.long("commission")
|
|
|
|
.value_name("NUM")
|
|
|
|
.takes_value(true)
|
2019-07-02 14:18:11 -07:00
|
|
|
.help("The commission taken on reward redemption (0-255), default: 0"),
|
2019-09-25 13:53:49 -07:00
|
|
|
)
|
|
|
|
.arg(
|
|
|
|
Arg::with_name("authorized_voter")
|
|
|
|
.long("authorized-voter")
|
|
|
|
.value_name("PUBKEY")
|
|
|
|
.takes_value(true)
|
|
|
|
.validator(is_pubkey_or_keypair)
|
|
|
|
.help("Public key of the authorized voter (defaults to vote account pubkey)"),
|
|
|
|
)
|
|
|
|
.arg(
|
|
|
|
Arg::with_name("authorized_withdrawer")
|
|
|
|
.long("authorized-withdrawer")
|
|
|
|
.value_name("PUBKEY")
|
|
|
|
.takes_value(true)
|
|
|
|
.validator(is_pubkey_or_keypair)
|
|
|
|
.help("Public key of the authorized withdrawer (defaults to vote account pubkey)"),
|
|
|
|
)
|
|
|
|
|
|
|
|
,
|
2019-05-09 19:31:42 -07:00
|
|
|
)
|
2019-08-09 22:48:57 -07:00
|
|
|
.subcommand(
|
|
|
|
SubCommand::with_name("show-account")
|
|
|
|
.about("Show the contents of an account")
|
|
|
|
.arg(
|
|
|
|
Arg::with_name("account_pubkey")
|
|
|
|
.index(1)
|
|
|
|
.value_name("ACCOUNT PUBKEY")
|
|
|
|
.takes_value(true)
|
|
|
|
.required(true)
|
|
|
|
.validator(is_pubkey_or_keypair)
|
|
|
|
.help("Account pubkey"),
|
|
|
|
)
|
|
|
|
.arg(
|
|
|
|
Arg::with_name("output_file")
|
|
|
|
.long("output")
|
|
|
|
.short("o")
|
|
|
|
.value_name("FILE")
|
|
|
|
.takes_value(true)
|
|
|
|
.help("Write the account data to this file"),
|
2019-09-10 12:36:59 -07:00
|
|
|
)
|
|
|
|
.arg(
|
|
|
|
Arg::with_name("lamports")
|
|
|
|
.long("lamports")
|
|
|
|
.takes_value(false)
|
|
|
|
.help("Display balance in lamports instead of SOL"),
|
2019-08-30 11:13:23 -07:00
|
|
|
),
|
2019-08-09 22:48:57 -07:00
|
|
|
)
|
2019-05-09 19:31:42 -07:00
|
|
|
.subcommand(
|
|
|
|
SubCommand::with_name("show-vote-account")
|
|
|
|
.about("Show the contents of a vote account")
|
|
|
|
.arg(
|
2019-07-26 09:34:12 -07:00
|
|
|
Arg::with_name("vote_account_pubkey")
|
2019-05-09 19:31:42 -07:00
|
|
|
.index(1)
|
2019-08-08 17:10:09 -07:00
|
|
|
.value_name("VOTE ACCOUNT PUBKEY")
|
2019-05-09 19:31:42 -07:00
|
|
|
.takes_value(true)
|
|
|
|
.required(true)
|
2019-08-08 17:10:09 -07:00
|
|
|
.validator(is_pubkey_or_keypair)
|
2019-05-09 19:31:42 -07:00
|
|
|
.help("Vote account pubkey"),
|
2019-09-26 10:26:47 -07:00
|
|
|
)
|
|
|
|
.arg(
|
|
|
|
Arg::with_name("lamports")
|
|
|
|
.long("lamports")
|
|
|
|
.takes_value(false)
|
|
|
|
.help("Display balance in lamports instead of SOL"),
|
2019-08-30 11:13:23 -07:00
|
|
|
),
|
2019-05-09 19:31:42 -07:00
|
|
|
)
|
2019-09-18 09:29:57 -07:00
|
|
|
.subcommand(
|
|
|
|
SubCommand::with_name("uptime")
|
|
|
|
.about("Show the uptime of a validator, based on epoch voting history")
|
|
|
|
.arg(
|
|
|
|
Arg::with_name("vote_account_pubkey")
|
|
|
|
.index(1)
|
|
|
|
.value_name("VOTE ACCOUNT PUBKEY")
|
|
|
|
.takes_value(true)
|
|
|
|
.required(true)
|
|
|
|
.validator(is_pubkey_or_keypair)
|
|
|
|
.help("Vote account pubkey"),
|
|
|
|
)
|
|
|
|
.arg(
|
|
|
|
Arg::with_name("span")
|
|
|
|
.long("span")
|
|
|
|
.value_name("NUM OF EPOCHS")
|
|
|
|
.takes_value(true)
|
|
|
|
.help("Number of recent epochs to examine")
|
|
|
|
)
|
|
|
|
.arg(
|
|
|
|
Arg::with_name("aggregate")
|
|
|
|
.long("aggregate")
|
|
|
|
.help("Aggregate uptime data across span")
|
|
|
|
),
|
|
|
|
)
|
2019-05-09 19:31:42 -07:00
|
|
|
.subcommand(
|
|
|
|
SubCommand::with_name("delegate-stake")
|
2019-07-25 16:53:43 -07:00
|
|
|
.about("Delegate stake to a vote account")
|
2019-08-01 21:08:24 -07:00
|
|
|
.arg(
|
|
|
|
Arg::with_name("force")
|
|
|
|
.long("force")
|
|
|
|
.takes_value(false)
|
|
|
|
.hidden(true) // Don't document this argument to discourage its use
|
|
|
|
.help("Override vote account sanity checks (use carefully!)"),
|
|
|
|
)
|
2019-05-09 19:31:42 -07:00
|
|
|
.arg(
|
2019-07-26 09:34:12 -07:00
|
|
|
Arg::with_name("stake_account_keypair_file")
|
2019-05-09 19:31:42 -07:00
|
|
|
.index(1)
|
2019-08-08 17:10:09 -07:00
|
|
|
.value_name("STAKE ACCOUNT KEYPAIR FILE")
|
2019-05-09 19:31:42 -07:00
|
|
|
.takes_value(true)
|
|
|
|
.required(true)
|
2019-08-20 13:59:31 -07:00
|
|
|
.validator(is_keypair)
|
2019-07-25 16:53:43 -07:00
|
|
|
.help("Keypair file for the new stake account"),
|
2019-05-09 19:31:42 -07:00
|
|
|
)
|
|
|
|
.arg(
|
2019-07-26 09:34:12 -07:00
|
|
|
Arg::with_name("vote_account_pubkey")
|
2019-05-09 19:31:42 -07:00
|
|
|
.index(2)
|
2019-08-08 17:10:09 -07:00
|
|
|
.value_name("VOTE ACCOUNT PUBKEY")
|
2019-05-09 19:31:42 -07:00
|
|
|
.takes_value(true)
|
|
|
|
.required(true)
|
2019-08-08 17:10:09 -07:00
|
|
|
.validator(is_pubkey_or_keypair)
|
2019-07-26 09:34:12 -07:00
|
|
|
.help("The vote account to which the stake will be delegated"),
|
2019-06-10 12:17:29 -07:00
|
|
|
)
|
|
|
|
.arg(
|
2019-09-10 16:16:40 -07:00
|
|
|
Arg::with_name("amount")
|
2019-06-10 12:17:29 -07:00
|
|
|
.index(3)
|
2019-09-10 16:16:40 -07:00
|
|
|
.value_name("AMOUNT")
|
2019-06-10 12:17:29 -07:00
|
|
|
.takes_value(true)
|
|
|
|
.required(true)
|
2019-09-10 16:16:40 -07:00
|
|
|
.help("The amount to delegate (default unit SOL)"),
|
|
|
|
)
|
|
|
|
.arg(
|
|
|
|
Arg::with_name("unit")
|
|
|
|
.index(4)
|
|
|
|
.takes_value(true)
|
|
|
|
.possible_values(&["SOL", "lamports"])
|
|
|
|
.help("Specify unit to use for request"),
|
2019-05-09 19:31:42 -07:00
|
|
|
),
|
|
|
|
)
|
2019-06-21 23:45:03 -07:00
|
|
|
.subcommand(
|
|
|
|
SubCommand::with_name("deactivate-stake")
|
2019-07-26 09:34:12 -07:00
|
|
|
.about("Deactivate the delegated stake from the stake account")
|
2019-06-21 23:45:03 -07:00
|
|
|
.arg(
|
2019-07-26 09:34:12 -07:00
|
|
|
Arg::with_name("stake_account_keypair_file")
|
2019-06-21 23:45:03 -07:00
|
|
|
.index(1)
|
2019-08-08 17:10:09 -07:00
|
|
|
.value_name("STAKE ACCOUNT KEYPAIR FILE")
|
2019-06-21 23:45:03 -07:00
|
|
|
.takes_value(true)
|
|
|
|
.required(true)
|
2019-07-26 09:34:12 -07:00
|
|
|
.help("Keypair file for the stake account, for signing the delegate transaction."),
|
2019-06-21 23:45:03 -07:00
|
|
|
)
|
2019-08-09 12:55:21 -07:00
|
|
|
.arg(
|
|
|
|
Arg::with_name("vote_account_pubkey")
|
|
|
|
.index(2)
|
|
|
|
.value_name("PUBKEY")
|
|
|
|
.takes_value(true)
|
|
|
|
.required(true)
|
2019-08-16 15:06:59 -07:00
|
|
|
.validator(is_pubkey_or_keypair)
|
2019-08-09 12:55:21 -07:00
|
|
|
.help("The vote account to which the stake is currently delegated"),
|
|
|
|
)
|
2019-06-21 23:45:03 -07:00
|
|
|
)
|
2019-06-21 22:28:34 -07:00
|
|
|
.subcommand(
|
|
|
|
SubCommand::with_name("withdraw-stake")
|
|
|
|
.about("Withdraw the unstaked lamports from the stake account")
|
|
|
|
.arg(
|
2019-07-26 09:34:12 -07:00
|
|
|
Arg::with_name("stake_account_keypair_file")
|
2019-06-21 22:28:34 -07:00
|
|
|
.index(1)
|
2019-08-08 17:10:09 -07:00
|
|
|
.value_name("STAKE ACCOUNT KEYPAIR FILE")
|
2019-06-21 22:28:34 -07:00
|
|
|
.takes_value(true)
|
|
|
|
.required(true)
|
2019-08-20 13:59:31 -07:00
|
|
|
.validator(is_keypair)
|
2019-07-26 09:34:12 -07:00
|
|
|
.help("Keypair file for the stake account, for signing the withdraw transaction."),
|
2019-06-21 22:28:34 -07:00
|
|
|
)
|
|
|
|
.arg(
|
|
|
|
Arg::with_name("destination_account_pubkey")
|
|
|
|
.index(2)
|
2019-08-08 17:10:09 -07:00
|
|
|
.value_name("DESTINATION PUBKEY")
|
2019-06-21 22:28:34 -07:00
|
|
|
.takes_value(true)
|
|
|
|
.required(true)
|
2019-08-08 17:10:09 -07:00
|
|
|
.validator(is_pubkey_or_keypair)
|
2019-06-21 22:28:34 -07:00
|
|
|
.help("The account where the lamports should be transfered"),
|
|
|
|
)
|
|
|
|
.arg(
|
2019-09-10 16:16:40 -07:00
|
|
|
Arg::with_name("amount")
|
2019-06-21 22:28:34 -07:00
|
|
|
.index(3)
|
2019-09-10 16:16:40 -07:00
|
|
|
.value_name("AMOUNT")
|
2019-06-21 22:28:34 -07:00
|
|
|
.takes_value(true)
|
|
|
|
.required(true)
|
2019-09-10 16:16:40 -07:00
|
|
|
.help("The amount to withdraw from the stake account (default unit SOL)"),
|
|
|
|
)
|
|
|
|
.arg(
|
|
|
|
Arg::with_name("unit")
|
|
|
|
.index(4)
|
|
|
|
.takes_value(true)
|
|
|
|
.possible_values(&["SOL", "lamports"])
|
|
|
|
.help("Specify unit to use for request"),
|
2019-06-21 22:28:34 -07:00
|
|
|
),
|
|
|
|
)
|
2019-05-09 19:31:42 -07:00
|
|
|
.subcommand(
|
2019-05-21 07:32:38 -07:00
|
|
|
SubCommand::with_name("redeem-vote-credits")
|
2019-07-26 09:34:12 -07:00
|
|
|
.about("Redeem credits in the stake account")
|
2019-05-21 07:32:38 -07:00
|
|
|
.arg(
|
2019-07-26 09:34:12 -07:00
|
|
|
Arg::with_name("stake_account_pubkey")
|
2019-08-09 15:02:27 -07:00
|
|
|
.index(1)
|
2019-05-23 14:50:23 -07:00
|
|
|
.value_name("STAKING ACCOUNT PUBKEY")
|
2019-05-21 07:32:38 -07:00
|
|
|
.takes_value(true)
|
|
|
|
.required(true)
|
2019-08-08 17:10:09 -07:00
|
|
|
.validator(is_pubkey_or_keypair)
|
2019-05-21 07:32:38 -07:00
|
|
|
.help("Staking account address to redeem credits for"),
|
|
|
|
)
|
|
|
|
.arg(
|
2019-07-26 09:34:12 -07:00
|
|
|
Arg::with_name("vote_account_pubkey")
|
2019-08-09 15:02:27 -07:00
|
|
|
.index(2)
|
2019-08-08 17:10:09 -07:00
|
|
|
.value_name("VOTE ACCOUNT PUBKEY")
|
2019-05-21 07:32:38 -07:00
|
|
|
.takes_value(true)
|
|
|
|
.required(true)
|
2019-08-08 17:10:09 -07:00
|
|
|
.validator(is_pubkey_or_keypair)
|
2019-07-26 09:34:12 -07:00
|
|
|
.help("The vote account to which the stake was previously delegated."),
|
2019-05-21 07:32:38 -07:00
|
|
|
),
|
|
|
|
)
|
2019-05-23 14:50:23 -07:00
|
|
|
.subcommand(
|
2019-05-09 19:31:42 -07:00
|
|
|
SubCommand::with_name("show-stake-account")
|
|
|
|
.about("Show the contents of a stake account")
|
|
|
|
.arg(
|
2019-07-26 09:34:12 -07:00
|
|
|
Arg::with_name("stake_account_pubkey")
|
2019-05-09 19:31:42 -07:00
|
|
|
.index(1)
|
2019-08-08 17:10:09 -07:00
|
|
|
.value_name("STAKE ACCOUNT PUBKEY")
|
2019-05-09 19:31:42 -07:00
|
|
|
.takes_value(true)
|
|
|
|
.required(true)
|
2019-08-08 17:10:09 -07:00
|
|
|
.validator(is_pubkey_or_keypair)
|
2019-05-09 19:31:42 -07:00
|
|
|
.help("Stake account pubkey"),
|
|
|
|
)
|
2019-09-26 10:26:47 -07:00
|
|
|
.arg(
|
|
|
|
Arg::with_name("lamports")
|
|
|
|
.long("lamports")
|
|
|
|
.takes_value(false)
|
|
|
|
.help("Display balance in lamports instead of SOL"),
|
|
|
|
),
|
2019-05-09 19:31:42 -07:00
|
|
|
)
|
2019-05-23 14:50:23 -07:00
|
|
|
.subcommand(
|
|
|
|
SubCommand::with_name("create-storage-mining-pool-account")
|
|
|
|
.about("Create mining pool account")
|
|
|
|
.arg(
|
2019-05-23 23:20:04 -07:00
|
|
|
Arg::with_name("storage_account_pubkey")
|
2019-05-23 14:50:23 -07:00
|
|
|
.index(1)
|
2019-08-08 17:10:09 -07:00
|
|
|
.value_name("STORAGE ACCOUNT PUBKEY")
|
2019-05-23 14:50:23 -07:00
|
|
|
.takes_value(true)
|
|
|
|
.required(true)
|
2019-08-08 17:10:09 -07:00
|
|
|
.validator(is_pubkey_or_keypair)
|
2019-05-23 14:50:23 -07:00
|
|
|
.help("Storage mining pool account address to fund"),
|
|
|
|
)
|
|
|
|
.arg(
|
2019-09-10 16:16:40 -07:00
|
|
|
Arg::with_name("amount")
|
2019-05-23 14:50:23 -07:00
|
|
|
.index(2)
|
2019-09-10 16:16:40 -07:00
|
|
|
.value_name("AMOUNT")
|
2019-05-23 14:50:23 -07:00
|
|
|
.takes_value(true)
|
|
|
|
.required(true)
|
2019-09-10 16:16:40 -07:00
|
|
|
.help("The amount to assign to the storage mining pool account (default unit SOL)"),
|
|
|
|
)
|
|
|
|
.arg(
|
|
|
|
Arg::with_name("unit")
|
|
|
|
.index(3)
|
|
|
|
.takes_value(true)
|
|
|
|
.possible_values(&["SOL", "lamports"])
|
|
|
|
.help("Specify unit to use for request"),
|
2019-05-23 14:50:23 -07:00
|
|
|
),
|
|
|
|
)
|
|
|
|
.subcommand(
|
|
|
|
SubCommand::with_name("create-replicator-storage-account")
|
|
|
|
.about("Create a replicator storage account")
|
|
|
|
.arg(
|
2019-06-04 14:52:52 -07:00
|
|
|
Arg::with_name("storage_account_owner")
|
2019-05-23 14:50:23 -07:00
|
|
|
.index(1)
|
2019-08-08 17:10:09 -07:00
|
|
|
.value_name("STORAGE ACCOUNT OWNER PUBKEY")
|
2019-05-23 14:50:23 -07:00
|
|
|
.takes_value(true)
|
|
|
|
.required(true)
|
2019-08-08 17:10:09 -07:00
|
|
|
.validator(is_pubkey_or_keypair)
|
2019-05-23 14:50:23 -07:00
|
|
|
)
|
2019-06-04 14:52:52 -07:00
|
|
|
.arg(
|
|
|
|
Arg::with_name("storage_account_pubkey")
|
|
|
|
.index(2)
|
2019-08-08 17:10:09 -07:00
|
|
|
.value_name("STORAGE ACCOUNT PUBKEY")
|
2019-06-04 14:52:52 -07:00
|
|
|
.takes_value(true)
|
|
|
|
.required(true)
|
2019-08-08 17:10:09 -07:00
|
|
|
.validator(is_pubkey_or_keypair)
|
2019-06-04 14:52:52 -07:00
|
|
|
)
|
2019-05-23 14:50:23 -07:00
|
|
|
)
|
|
|
|
.subcommand(
|
|
|
|
SubCommand::with_name("create-validator-storage-account")
|
|
|
|
.about("Create a validator storage account")
|
|
|
|
.arg(
|
2019-06-04 14:52:52 -07:00
|
|
|
Arg::with_name("storage_account_owner")
|
2019-05-23 14:50:23 -07:00
|
|
|
.index(1)
|
2019-08-08 17:10:09 -07:00
|
|
|
.value_name("STORAGE ACCOUNT OWNER PUBKEY")
|
2019-05-23 14:50:23 -07:00
|
|
|
.takes_value(true)
|
|
|
|
.required(true)
|
2019-08-10 13:15:29 -07:00
|
|
|
.validator(is_pubkey_or_keypair)
|
2019-05-23 14:50:23 -07:00
|
|
|
)
|
2019-06-04 14:52:52 -07:00
|
|
|
.arg(
|
|
|
|
Arg::with_name("storage_account_pubkey")
|
|
|
|
.index(2)
|
2019-08-08 17:10:09 -07:00
|
|
|
.value_name("STORAGE ACCOUNT PUBKEY")
|
2019-06-04 14:52:52 -07:00
|
|
|
.takes_value(true)
|
|
|
|
.required(true)
|
2019-08-10 13:15:29 -07:00
|
|
|
.validator(is_pubkey_or_keypair)
|
2019-06-04 14:52:52 -07:00
|
|
|
)
|
2019-05-23 14:50:23 -07:00
|
|
|
)
|
|
|
|
.subcommand(
|
|
|
|
SubCommand::with_name("claim-storage-reward")
|
|
|
|
.about("Redeem storage reward credits")
|
|
|
|
.arg(
|
2019-06-13 17:53:54 -07:00
|
|
|
Arg::with_name("node_account_pubkey")
|
2019-05-23 14:50:23 -07:00
|
|
|
.index(1)
|
2019-06-13 17:53:54 -07:00
|
|
|
.value_name("NODE PUBKEY")
|
2019-05-23 14:50:23 -07:00
|
|
|
.takes_value(true)
|
|
|
|
.required(true)
|
2019-08-08 17:10:09 -07:00
|
|
|
.validator(is_pubkey_or_keypair)
|
2019-06-13 17:53:54 -07:00
|
|
|
.help("The node account to credit the rewards to"),
|
2019-05-23 14:50:23 -07:00
|
|
|
)
|
|
|
|
.arg(
|
2019-06-13 17:53:54 -07:00
|
|
|
Arg::with_name("storage_account_pubkey")
|
2019-08-30 11:13:23 -07:00
|
|
|
.index(2)
|
2019-06-13 17:53:54 -07:00
|
|
|
.value_name("STORAGE ACCOUNT PUBKEY")
|
2019-05-23 14:50:23 -07:00
|
|
|
.takes_value(true)
|
|
|
|
.required(true)
|
2019-08-08 17:10:09 -07:00
|
|
|
.validator(is_pubkey_or_keypair)
|
2019-06-13 17:53:54 -07:00
|
|
|
.help("Storage account address to redeem credits for"),
|
|
|
|
))
|
2019-05-23 14:50:23 -07:00
|
|
|
.subcommand(
|
|
|
|
SubCommand::with_name("show-storage-account")
|
|
|
|
.about("Show the contents of a storage account")
|
|
|
|
.arg(
|
2019-05-23 23:20:04 -07:00
|
|
|
Arg::with_name("storage_account_pubkey")
|
2019-05-23 14:50:23 -07:00
|
|
|
.index(1)
|
2019-08-08 17:10:09 -07:00
|
|
|
.value_name("STORAGE ACCOUNT PUBKEY")
|
2019-05-23 14:50:23 -07:00
|
|
|
.takes_value(true)
|
|
|
|
.required(true)
|
2019-08-08 17:10:09 -07:00
|
|
|
.validator(is_pubkey_or_keypair)
|
2019-05-23 14:50:23 -07:00
|
|
|
.help("Storage account pubkey"),
|
|
|
|
)
|
|
|
|
)
|
2019-05-09 19:31:42 -07:00
|
|
|
.subcommand(
|
|
|
|
SubCommand::with_name("deploy")
|
|
|
|
.about("Deploy a program")
|
|
|
|
.arg(
|
|
|
|
Arg::with_name("program_location")
|
|
|
|
.index(1)
|
2019-08-08 17:10:09 -07:00
|
|
|
.value_name("PATH TO PROGRAM")
|
2019-05-09 19:31:42 -07:00
|
|
|
.takes_value(true)
|
|
|
|
.required(true)
|
|
|
|
.help("/path/to/program.o"),
|
|
|
|
), // TODO: Add "loader" argument; current default is bpf_loader
|
|
|
|
)
|
2019-08-05 13:17:03 -07:00
|
|
|
.subcommand(
|
|
|
|
SubCommand::with_name("get-slot")
|
|
|
|
.about("Get current slot"),
|
|
|
|
)
|
2019-05-09 19:31:42 -07:00
|
|
|
.subcommand(
|
|
|
|
SubCommand::with_name("get-transaction-count")
|
|
|
|
.about("Get current transaction count"),
|
|
|
|
)
|
|
|
|
.subcommand(
|
|
|
|
SubCommand::with_name("pay")
|
|
|
|
.about("Send a payment")
|
|
|
|
.arg(
|
|
|
|
Arg::with_name("to")
|
|
|
|
.index(1)
|
|
|
|
.value_name("PUBKEY")
|
|
|
|
.takes_value(true)
|
|
|
|
.required(true)
|
|
|
|
.validator(is_pubkey)
|
|
|
|
.help("The pubkey of recipient"),
|
|
|
|
)
|
|
|
|
.arg(
|
2019-09-10 16:16:40 -07:00
|
|
|
Arg::with_name("amount")
|
2019-05-09 19:31:42 -07:00
|
|
|
.index(2)
|
2019-09-10 16:16:40 -07:00
|
|
|
.value_name("AMOUNT")
|
2019-05-09 19:31:42 -07:00
|
|
|
.takes_value(true)
|
|
|
|
.required(true)
|
2019-09-10 16:16:40 -07:00
|
|
|
.help("The amount to send (default unit SOL)"),
|
|
|
|
)
|
|
|
|
.arg(
|
|
|
|
Arg::with_name("unit")
|
|
|
|
.index(3)
|
|
|
|
.takes_value(true)
|
|
|
|
.possible_values(&["SOL", "lamports"])
|
|
|
|
.help("Specify unit to use for request"),
|
2019-05-09 19:31:42 -07:00
|
|
|
)
|
|
|
|
.arg(
|
|
|
|
Arg::with_name("timestamp")
|
|
|
|
.long("after")
|
|
|
|
.value_name("DATETIME")
|
|
|
|
.takes_value(true)
|
|
|
|
.help("A timestamp after which transaction will execute"),
|
|
|
|
)
|
|
|
|
.arg(
|
|
|
|
Arg::with_name("timestamp_pubkey")
|
|
|
|
.long("require-timestamp-from")
|
|
|
|
.value_name("PUBKEY")
|
|
|
|
.takes_value(true)
|
|
|
|
.requires("timestamp")
|
|
|
|
.validator(is_pubkey)
|
|
|
|
.help("Require timestamp from this third party"),
|
|
|
|
)
|
|
|
|
.arg(
|
|
|
|
Arg::with_name("witness")
|
|
|
|
.long("require-signature-from")
|
|
|
|
.value_name("PUBKEY")
|
|
|
|
.takes_value(true)
|
|
|
|
.multiple(true)
|
|
|
|
.use_delimiter(true)
|
|
|
|
.validator(is_pubkey)
|
|
|
|
.help("Any third party signatures required to unlock the lamports"),
|
|
|
|
)
|
|
|
|
.arg(
|
|
|
|
Arg::with_name("cancelable")
|
|
|
|
.long("cancelable")
|
|
|
|
.takes_value(false),
|
|
|
|
),
|
|
|
|
)
|
2019-08-12 21:33:13 -07:00
|
|
|
.subcommand(
|
|
|
|
SubCommand::with_name("ping")
|
|
|
|
.about("Submit transactions sequentially")
|
|
|
|
.arg(
|
|
|
|
Arg::with_name("interval")
|
|
|
|
.short("i")
|
|
|
|
.long("interval")
|
|
|
|
.value_name("SECONDS")
|
|
|
|
.takes_value(true)
|
|
|
|
.default_value("2")
|
|
|
|
.help("Wait interval seconds between submitting the next transaction"),
|
|
|
|
)
|
|
|
|
.arg(
|
|
|
|
Arg::with_name("count")
|
|
|
|
.short("c")
|
|
|
|
.long("count")
|
|
|
|
.value_name("NUMBER")
|
|
|
|
.takes_value(true)
|
|
|
|
.help("Stop after submitting count transactions"),
|
|
|
|
)
|
|
|
|
.arg(
|
|
|
|
Arg::with_name("timeout")
|
|
|
|
.short("t")
|
|
|
|
.long("timeout")
|
|
|
|
.value_name("SECONDS")
|
|
|
|
.takes_value(true)
|
|
|
|
.default_value("10")
|
|
|
|
.help("Wait up to timeout seconds for transaction confirmation"),
|
2019-08-30 11:13:23 -07:00
|
|
|
),
|
2019-08-12 21:33:13 -07:00
|
|
|
)
|
2019-05-09 19:31:42 -07:00
|
|
|
.subcommand(
|
|
|
|
SubCommand::with_name("send-signature")
|
|
|
|
.about("Send a signature to authorize a transfer")
|
|
|
|
.arg(
|
|
|
|
Arg::with_name("to")
|
|
|
|
.index(1)
|
|
|
|
.value_name("PUBKEY")
|
|
|
|
.takes_value(true)
|
|
|
|
.required(true)
|
|
|
|
.validator(is_pubkey)
|
|
|
|
.help("The pubkey of recipient"),
|
|
|
|
)
|
|
|
|
.arg(
|
|
|
|
Arg::with_name("process_id")
|
|
|
|
.index(2)
|
2019-08-08 17:10:09 -07:00
|
|
|
.value_name("PROCESS ID")
|
2019-05-09 19:31:42 -07:00
|
|
|
.takes_value(true)
|
|
|
|
.required(true)
|
|
|
|
.help("The process id of the transfer to authorize"),
|
|
|
|
),
|
|
|
|
)
|
|
|
|
.subcommand(
|
|
|
|
SubCommand::with_name("send-timestamp")
|
|
|
|
.about("Send a timestamp to unlock a transfer")
|
|
|
|
.arg(
|
|
|
|
Arg::with_name("to")
|
|
|
|
.index(1)
|
|
|
|
.value_name("PUBKEY")
|
|
|
|
.takes_value(true)
|
|
|
|
.required(true)
|
|
|
|
.validator(is_pubkey)
|
|
|
|
.help("The pubkey of recipient"),
|
|
|
|
)
|
|
|
|
.arg(
|
|
|
|
Arg::with_name("process_id")
|
|
|
|
.index(2)
|
2019-08-08 17:10:09 -07:00
|
|
|
.value_name("PROCESS ID")
|
2019-05-09 19:31:42 -07:00
|
|
|
.takes_value(true)
|
|
|
|
.required(true)
|
|
|
|
.help("The process id of the transfer to unlock"),
|
|
|
|
)
|
|
|
|
.arg(
|
|
|
|
Arg::with_name("datetime")
|
|
|
|
.long("date")
|
|
|
|
.value_name("DATETIME")
|
|
|
|
.takes_value(true)
|
|
|
|
.help("Optional arbitrary timestamp to apply"),
|
|
|
|
),
|
|
|
|
)
|
2019-08-08 10:13:06 -07:00
|
|
|
.subcommand(
|
|
|
|
SubCommand::with_name("cluster-version")
|
|
|
|
.about("Get the version of the cluster entrypoint"),
|
|
|
|
)
|
2019-09-03 10:38:12 -07:00
|
|
|
.subcommand(
|
|
|
|
SubCommand::with_name("validator-info")
|
|
|
|
.about("Publish/get Validator info on Solana")
|
|
|
|
.subcommand(
|
|
|
|
SubCommand::with_name("publish")
|
|
|
|
.about("Publish Validator info on Solana")
|
|
|
|
.arg(
|
|
|
|
Arg::with_name("info_pubkey")
|
|
|
|
.short("p")
|
|
|
|
.long("info-pubkey")
|
|
|
|
.value_name("PUBKEY")
|
|
|
|
.takes_value(true)
|
|
|
|
.validator(is_pubkey)
|
|
|
|
.help("The pubkey of the Validator info account to update"),
|
|
|
|
)
|
|
|
|
.arg(
|
|
|
|
Arg::with_name("name")
|
|
|
|
.index(1)
|
|
|
|
.value_name("NAME")
|
|
|
|
.takes_value(true)
|
|
|
|
.required(true)
|
|
|
|
.validator(is_short_field)
|
|
|
|
.help("Validator name"),
|
|
|
|
)
|
|
|
|
.arg(
|
|
|
|
Arg::with_name("website")
|
|
|
|
.short("w")
|
|
|
|
.long("website")
|
|
|
|
.value_name("URL")
|
|
|
|
.takes_value(true)
|
|
|
|
.validator(check_url)
|
|
|
|
.help("Validator website url"),
|
|
|
|
)
|
|
|
|
.arg(
|
|
|
|
Arg::with_name("keybase_username")
|
|
|
|
.short("n")
|
|
|
|
.long("keybase")
|
|
|
|
.value_name("USERNAME")
|
|
|
|
.takes_value(true)
|
|
|
|
.validator(is_short_field)
|
|
|
|
.help("Validator Keybase username"),
|
|
|
|
)
|
|
|
|
.arg(
|
|
|
|
Arg::with_name("details")
|
|
|
|
.short("d")
|
|
|
|
.long("details")
|
|
|
|
.value_name("DETAILS")
|
|
|
|
.takes_value(true)
|
|
|
|
.validator(check_details_length)
|
|
|
|
.help("Validator description")
|
|
|
|
)
|
|
|
|
.arg(
|
|
|
|
Arg::with_name("force")
|
|
|
|
.long("force")
|
|
|
|
.takes_value(false)
|
|
|
|
.hidden(true) // Don't document this argument to discourage its use
|
|
|
|
.help("Override keybase username validity check"),
|
|
|
|
),
|
|
|
|
)
|
|
|
|
.subcommand(
|
|
|
|
SubCommand::with_name("get")
|
|
|
|
.about("Get and parse Solana Validator info")
|
|
|
|
.arg(
|
|
|
|
Arg::with_name("info_pubkey")
|
|
|
|
.index(1)
|
|
|
|
.value_name("PUBKEY")
|
|
|
|
.takes_value(true)
|
|
|
|
.validator(is_pubkey)
|
|
|
|
.help("The pubkey of the Validator info account; without this argument, returns all"),
|
|
|
|
),
|
|
|
|
)
|
|
|
|
)
|
2019-05-09 19:31:42 -07:00
|
|
|
}
|
|
|
|
|
2018-09-14 01:58:39 -07:00
|
|
|
#[cfg(test)]
|
2018-09-14 01:59:09 -07:00
|
|
|
mod tests {
|
|
|
|
use super::*;
|
2018-10-08 11:10:47 -07:00
|
|
|
use serde_json::Value;
|
2019-03-16 17:17:44 -07:00
|
|
|
use solana_client::mock_rpc_client_request::SIGNATURE;
|
2019-09-18 09:29:57 -07:00
|
|
|
use solana_sdk::{
|
|
|
|
signature::{gen_keypair_file, read_keypair},
|
|
|
|
transaction::TransactionError,
|
|
|
|
};
|
2019-04-18 13:37:20 -07:00
|
|
|
use std::path::PathBuf;
|
2018-09-14 01:59:09 -07:00
|
|
|
|
|
|
|
#[test]
|
2018-09-26 10:17:45 -07:00
|
|
|
fn test_wallet_parse_command() {
|
2019-05-09 19:31:42 -07:00
|
|
|
let test_commands = app("test", "desc", "version");
|
|
|
|
|
2019-03-30 20:37:33 -07:00
|
|
|
let pubkey = Pubkey::new_rand();
|
2018-09-19 12:44:16 -07:00
|
|
|
let pubkey_string = format!("{}", pubkey);
|
2019-03-30 20:37:33 -07:00
|
|
|
let witness0 = Pubkey::new_rand();
|
2018-09-19 12:44:16 -07:00
|
|
|
let witness0_string = format!("{}", witness0);
|
2019-03-30 20:37:33 -07:00
|
|
|
let witness1 = Pubkey::new_rand();
|
2018-09-19 12:44:16 -07:00
|
|
|
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()
|
2019-09-10 16:16:40 -07:00
|
|
|
.get_matches_from(vec!["test", "airdrop", "50", "lamports"]);
|
2018-09-14 01:59:09 -07:00
|
|
|
assert_eq!(
|
2019-03-09 19:28:43 -08:00
|
|
|
parse_command(&pubkey, &test_airdrop).unwrap(),
|
2019-08-29 20:45:53 -07:00
|
|
|
WalletCommand::Airdrop {
|
|
|
|
drone_host: None,
|
|
|
|
drone_port: solana_drone::drone::DRONE_PORT,
|
2019-09-10 16:16:40 -07:00
|
|
|
lamports: 50,
|
|
|
|
use_lamports_unit: true,
|
2019-08-29 20:45:53 -07:00
|
|
|
}
|
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"]);
|
2019-03-09 19:28:43 -08:00
|
|
|
assert!(parse_command(&pubkey, &test_bad_airdrop).is_err());
|
2018-09-14 01:59:09 -07:00
|
|
|
|
2019-08-08 17:10:09 -07:00
|
|
|
// Test Balance Subcommand, incl pubkey and keypair-file inputs
|
|
|
|
let keypair_file = make_tmp_path("keypair_file");
|
|
|
|
gen_keypair_file(&keypair_file).unwrap();
|
|
|
|
let keypair = read_keypair(&keypair_file).unwrap();
|
|
|
|
let test_balance = test_commands.clone().get_matches_from(vec![
|
|
|
|
"test",
|
|
|
|
"balance",
|
|
|
|
&keypair.pubkey().to_string(),
|
|
|
|
]);
|
|
|
|
assert_eq!(
|
|
|
|
parse_command(&pubkey, &test_balance).unwrap(),
|
2019-09-10 12:36:59 -07:00
|
|
|
WalletCommand::Balance {
|
|
|
|
pubkey: keypair.pubkey(),
|
|
|
|
use_lamports_unit: false
|
|
|
|
}
|
2019-08-08 17:10:09 -07:00
|
|
|
);
|
2019-09-10 12:36:59 -07:00
|
|
|
let test_balance = test_commands.clone().get_matches_from(vec![
|
|
|
|
"test",
|
|
|
|
"balance",
|
|
|
|
&keypair_file,
|
|
|
|
"--lamports",
|
|
|
|
]);
|
2019-08-08 17:10:09 -07:00
|
|
|
assert_eq!(
|
|
|
|
parse_command(&pubkey, &test_balance).unwrap(),
|
2019-09-10 12:36:59 -07:00
|
|
|
WalletCommand::Balance {
|
|
|
|
pubkey: keypair.pubkey(),
|
|
|
|
use_lamports_unit: true
|
|
|
|
}
|
2019-08-08 17:10: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!(
|
2019-03-09 19:28:43 -08:00
|
|
|
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!(
|
2019-03-09 19:28:43 -08:00
|
|
|
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"]);
|
2019-03-09 19:28:43 -08:00
|
|
|
assert!(parse_command(&pubkey, &test_bad_signature).is_err());
|
2018-09-19 12:44:16 -07:00
|
|
|
|
2019-07-25 16:53:43 -07:00
|
|
|
// Test DelegateStake Subcommand
|
2019-05-09 19:31:42 -07:00
|
|
|
fn make_tmp_path(name: &str) -> String {
|
2019-07-17 14:27:58 -07:00
|
|
|
let out_dir = std::env::var("FARF_DIR").unwrap_or_else(|_| "farf".to_string());
|
2019-05-09 19:31:42 -07:00
|
|
|
let keypair = Keypair::new();
|
|
|
|
|
|
|
|
let path = format!("{}/tmp/{}-{}", out_dir, name, keypair.pubkey());
|
|
|
|
|
|
|
|
// whack any possible collision
|
|
|
|
let _ignored = std::fs::remove_dir_all(&path);
|
|
|
|
// whack any possible collision
|
|
|
|
let _ignored = std::fs::remove_file(&path);
|
|
|
|
|
|
|
|
path
|
|
|
|
}
|
|
|
|
|
|
|
|
let keypair_file = make_tmp_path("keypair_file");
|
|
|
|
gen_keypair_file(&keypair_file).unwrap();
|
|
|
|
let keypair = read_keypair(&keypair_file).unwrap();
|
2019-07-25 16:53:43 -07:00
|
|
|
|
2019-05-09 19:31:42 -07:00
|
|
|
let test_delegate_stake = test_commands.clone().get_matches_from(vec![
|
|
|
|
"test",
|
|
|
|
"delegate-stake",
|
|
|
|
&keypair_file,
|
|
|
|
&pubkey_string,
|
2019-06-10 12:17:29 -07:00
|
|
|
"42",
|
2019-09-10 16:16:40 -07:00
|
|
|
"lamports",
|
2019-05-09 19:31:42 -07:00
|
|
|
]);
|
2019-09-26 13:29:29 -07:00
|
|
|
let stake_pubkey = keypair.pubkey();
|
2019-05-09 19:31:42 -07:00
|
|
|
assert_eq!(
|
|
|
|
parse_command(&pubkey, &test_delegate_stake).unwrap(),
|
2019-09-26 13:29:29 -07:00
|
|
|
WalletCommand::DelegateStake(
|
|
|
|
keypair,
|
|
|
|
pubkey,
|
|
|
|
42,
|
|
|
|
Authorized::auto(&stake_pubkey),
|
|
|
|
false,
|
|
|
|
)
|
2019-08-01 21:08:24 -07:00
|
|
|
);
|
|
|
|
|
|
|
|
let keypair = read_keypair(&keypair_file).unwrap();
|
|
|
|
let test_delegate_stake = test_commands.clone().get_matches_from(vec![
|
|
|
|
"test",
|
|
|
|
"delegate-stake",
|
|
|
|
"--force",
|
|
|
|
&keypair_file,
|
|
|
|
&pubkey_string,
|
|
|
|
"42",
|
2019-09-10 16:16:40 -07:00
|
|
|
"lamports",
|
2019-08-01 21:08:24 -07:00
|
|
|
]);
|
2019-09-26 13:29:29 -07:00
|
|
|
let stake_pubkey = keypair.pubkey();
|
2019-08-01 21:08:24 -07:00
|
|
|
assert_eq!(
|
|
|
|
parse_command(&pubkey, &test_delegate_stake).unwrap(),
|
2019-09-26 13:29:29 -07:00
|
|
|
WalletCommand::DelegateStake(
|
|
|
|
keypair,
|
|
|
|
pubkey,
|
|
|
|
42,
|
|
|
|
Authorized::auto(&stake_pubkey),
|
|
|
|
true
|
|
|
|
)
|
2019-05-09 19:31:42 -07:00
|
|
|
);
|
|
|
|
|
2019-07-25 16:53:43 -07:00
|
|
|
// Test WithdrawStake Subcommand
|
2019-06-21 22:28:34 -07:00
|
|
|
let test_withdraw_stake = test_commands.clone().get_matches_from(vec![
|
|
|
|
"test",
|
|
|
|
"withdraw-stake",
|
|
|
|
&keypair_file,
|
|
|
|
&pubkey_string,
|
|
|
|
"42",
|
2019-09-10 16:16:40 -07:00
|
|
|
"lamports",
|
2019-06-21 22:28:34 -07:00
|
|
|
]);
|
2019-07-25 16:53:43 -07:00
|
|
|
let keypair = read_keypair(&keypair_file).unwrap();
|
2019-06-21 22:28:34 -07:00
|
|
|
assert_eq!(
|
|
|
|
parse_command(&pubkey, &test_withdraw_stake).unwrap(),
|
|
|
|
WalletCommand::WithdrawStake(keypair, pubkey, 42)
|
|
|
|
);
|
|
|
|
|
2019-07-25 16:53:43 -07:00
|
|
|
// Test DeactivateStake Subcommand
|
2019-06-21 23:45:03 -07:00
|
|
|
let keypair_file = make_tmp_path("keypair_file");
|
|
|
|
gen_keypair_file(&keypair_file).unwrap();
|
|
|
|
let keypair = read_keypair(&keypair_file).unwrap();
|
2019-08-09 12:55:21 -07:00
|
|
|
let test_deactivate_stake = test_commands.clone().get_matches_from(vec![
|
|
|
|
"test",
|
|
|
|
"deactivate-stake",
|
|
|
|
&keypair_file,
|
|
|
|
&pubkey_string,
|
|
|
|
]);
|
2019-06-21 23:45:03 -07:00
|
|
|
assert_eq!(
|
|
|
|
parse_command(&pubkey, &test_deactivate_stake).unwrap(),
|
2019-08-09 12:55:21 -07:00
|
|
|
WalletCommand::DeactivateStake(keypair, pubkey)
|
2019-06-21 23:45:03 -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!(
|
2019-03-09 19:28:43 -08:00
|
|
|
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
|
2019-09-10 16:16:40 -07:00
|
|
|
let test_pay = test_commands.clone().get_matches_from(vec![
|
|
|
|
"test",
|
|
|
|
"pay",
|
|
|
|
&pubkey_string,
|
|
|
|
"50",
|
|
|
|
"lamports",
|
|
|
|
]);
|
2018-09-19 12:44:16 -07:00
|
|
|
assert_eq!(
|
2019-03-09 19:28:43 -08:00
|
|
|
parse_command(&pubkey, &test_pay).unwrap(),
|
2019-09-10 16:16:40 -07:00
|
|
|
WalletCommand::Pay {
|
|
|
|
lamports: 50,
|
|
|
|
to: pubkey,
|
|
|
|
timestamp: None,
|
|
|
|
timestamp_pubkey: None,
|
|
|
|
witnesses: None,
|
|
|
|
cancelable: None
|
|
|
|
}
|
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",
|
2019-09-10 16:16:40 -07:00
|
|
|
"lamports",
|
2018-09-19 12:44:16 -07:00
|
|
|
"--require-signature-from",
|
|
|
|
&witness0_string,
|
|
|
|
"--require-signature-from",
|
|
|
|
&witness1_string,
|
|
|
|
]);
|
|
|
|
assert_eq!(
|
2019-03-09 19:28:43 -08:00
|
|
|
parse_command(&pubkey, &test_pay_multiple_witnesses).unwrap(),
|
2019-09-10 16:16:40 -07:00
|
|
|
WalletCommand::Pay {
|
|
|
|
lamports: 50,
|
|
|
|
to: pubkey,
|
|
|
|
timestamp: None,
|
|
|
|
timestamp_pubkey: None,
|
|
|
|
witnesses: Some(vec![witness0, witness1]),
|
|
|
|
cancelable: None
|
|
|
|
}
|
2018-09-19 12:44:16 -07:00
|
|
|
);
|
|
|
|
let test_pay_single_witness = test_commands.clone().get_matches_from(vec![
|
|
|
|
"test",
|
|
|
|
"pay",
|
|
|
|
&pubkey_string,
|
|
|
|
"50",
|
2019-09-10 16:16:40 -07:00
|
|
|
"lamports",
|
2018-09-19 12:44:16 -07:00
|
|
|
"--require-signature-from",
|
|
|
|
&witness0_string,
|
|
|
|
]);
|
|
|
|
assert_eq!(
|
2019-03-09 19:28:43 -08:00
|
|
|
parse_command(&pubkey, &test_pay_single_witness).unwrap(),
|
2019-09-10 16:16:40 -07:00
|
|
|
WalletCommand::Pay {
|
|
|
|
lamports: 50,
|
|
|
|
to: pubkey,
|
|
|
|
timestamp: None,
|
|
|
|
timestamp_pubkey: None,
|
|
|
|
witnesses: Some(vec![witness0]),
|
|
|
|
cancelable: None
|
|
|
|
}
|
2018-09-19 12:44:16 -07:00
|
|
|
);
|
|
|
|
|
|
|
|
// Test Pay Subcommand w/ Timestamp
|
|
|
|
let test_pay_timestamp = test_commands.clone().get_matches_from(vec![
|
|
|
|
"test",
|
|
|
|
"pay",
|
|
|
|
&pubkey_string,
|
|
|
|
"50",
|
2019-09-10 16:16:40 -07:00
|
|
|
"lamports",
|
2018-09-19 12:44:16 -07:00
|
|
|
"--after",
|
|
|
|
"2018-09-19T17:30:59",
|
|
|
|
"--require-timestamp-from",
|
|
|
|
&witness0_string,
|
|
|
|
]);
|
|
|
|
assert_eq!(
|
2019-03-09 19:28:43 -08:00
|
|
|
parse_command(&pubkey, &test_pay_timestamp).unwrap(),
|
2019-09-10 16:16:40 -07:00
|
|
|
WalletCommand::Pay {
|
|
|
|
lamports: 50,
|
|
|
|
to: pubkey,
|
|
|
|
timestamp: Some(dt),
|
|
|
|
timestamp_pubkey: Some(witness0),
|
|
|
|
witnesses: None,
|
|
|
|
cancelable: None
|
|
|
|
}
|
2018-09-19 12:44:16 -07:00
|
|
|
);
|
|
|
|
|
|
|
|
// 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!(
|
2019-03-09 19:28:43 -08:00
|
|
|
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",
|
2019-09-10 16:16:40 -07:00
|
|
|
"lamports",
|
2018-09-19 12:44:16 -07:00
|
|
|
"--after",
|
|
|
|
"2018-09-19T17:30:59",
|
|
|
|
"--require-signature-from",
|
|
|
|
&witness0_string,
|
|
|
|
"--require-timestamp-from",
|
|
|
|
&witness0_string,
|
|
|
|
"--require-signature-from",
|
|
|
|
&witness1_string,
|
|
|
|
]);
|
|
|
|
assert_eq!(
|
2019-03-09 19:28:43 -08:00
|
|
|
parse_command(&pubkey, &test_pay_multiple_witnesses).unwrap(),
|
2019-09-10 16:16:40 -07:00
|
|
|
WalletCommand::Pay {
|
|
|
|
lamports: 50,
|
|
|
|
to: pubkey,
|
|
|
|
timestamp: Some(dt),
|
|
|
|
timestamp_pubkey: Some(witness0),
|
|
|
|
witnesses: Some(vec![witness0, witness1]),
|
|
|
|
cancelable: None
|
|
|
|
}
|
2018-09-19 12:44:16 -07:00
|
|
|
);
|
|
|
|
|
|
|
|
// 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!(
|
2019-03-09 19:28:43 -08:00
|
|
|
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",
|
|
|
|
]);
|
2019-03-09 19:28:43 -08:00
|
|
|
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-03-16 21:51:41 -07:00
|
|
|
config.rpc_client = Some(RpcClient::new_mock("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();
|
2019-04-02 06:08:11 -07:00
|
|
|
config.keypair = keypair;
|
2019-01-13 23:10:03 -08:00
|
|
|
config.command = WalletCommand::Address;
|
|
|
|
assert_eq!(process_command(&config).unwrap(), pubkey);
|
2018-09-14 01:59:09 -07:00
|
|
|
|
2019-09-10 12:36:59 -07:00
|
|
|
config.command = WalletCommand::Balance {
|
|
|
|
pubkey: config.keypair.pubkey(),
|
|
|
|
use_lamports_unit: true,
|
|
|
|
};
|
2019-03-20 09:44:16 -07:00
|
|
|
assert_eq!(process_command(&config).unwrap(), "50 lamports");
|
2018-09-14 01:59:09 -07:00
|
|
|
|
2019-09-10 12:36:59 -07:00
|
|
|
config.command = WalletCommand::Balance {
|
|
|
|
pubkey: config.keypair.pubkey(),
|
|
|
|
use_lamports_unit: false,
|
|
|
|
};
|
2019-09-17 22:59:35 -07:00
|
|
|
assert_eq!(process_command(&config).unwrap(), "0 SOL");
|
2019-09-10 12:36:59 -07:00
|
|
|
|
2019-03-30 20:37:33 -07:00
|
|
|
let process_id = Pubkey::new_rand();
|
2019-01-13 23:10:03 -08:00
|
|
|
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");
|
2018-09-14 01:59:09 -07:00
|
|
|
|
2019-03-30 20:37:33 -07:00
|
|
|
let bob_pubkey = Pubkey::new_rand();
|
2019-05-23 23:20:04 -07:00
|
|
|
let node_pubkey = Pubkey::new_rand();
|
2019-09-25 13:53:49 -07:00
|
|
|
config.command = WalletCommand::CreateVoteAccount(
|
|
|
|
bob_pubkey,
|
|
|
|
VoteInit {
|
|
|
|
node_pubkey,
|
|
|
|
authorized_voter: bob_pubkey,
|
|
|
|
authorized_withdrawer: bob_pubkey,
|
|
|
|
commission: 0,
|
|
|
|
},
|
|
|
|
10,
|
|
|
|
);
|
2019-05-09 19:31:42 -07:00
|
|
|
let signature = process_command(&config);
|
|
|
|
assert_eq!(signature.unwrap(), SIGNATURE.to_string());
|
|
|
|
|
2019-05-20 13:32:32 -07:00
|
|
|
let bob_keypair = Keypair::new();
|
2019-09-25 13:53:49 -07:00
|
|
|
let new_authorized_pubkey = Pubkey::new_rand();
|
|
|
|
config.command = WalletCommand::VoteAuthorize(
|
|
|
|
bob_pubkey,
|
|
|
|
bob_keypair,
|
|
|
|
new_authorized_pubkey,
|
|
|
|
VoteAuthorize::Voter,
|
|
|
|
);
|
2019-03-06 20:29:08 -08:00
|
|
|
let signature = process_command(&config);
|
|
|
|
assert_eq!(signature.unwrap(), SIGNATURE.to_string());
|
2019-05-09 19:31:42 -07:00
|
|
|
|
2019-08-01 21:08:24 -07:00
|
|
|
// TODO: Need to add mock GetAccountInfo to mock_rpc_client_request.rs to re-enable the
|
|
|
|
// DeactivateStake test.
|
|
|
|
/*
|
2019-05-09 19:31:42 -07:00
|
|
|
let bob_keypair = Keypair::new();
|
2019-08-09 12:55:21 -07:00
|
|
|
let vote_pubkey = Pubkey::new_rand();
|
|
|
|
config.command = WalletCommand::DelegateStake(bob_keypair.into(), vote_pubkey, 100, true);
|
2019-03-06 20:29:08 -08:00
|
|
|
let signature = process_command(&config);
|
|
|
|
assert_eq!(signature.unwrap(), SIGNATURE.to_string());
|
2019-08-01 21:08:24 -07:00
|
|
|
*/
|
2019-03-06 20:29:08 -08:00
|
|
|
|
2019-06-21 22:28:34 -07:00
|
|
|
let bob_keypair = Keypair::new();
|
|
|
|
let to_pubkey = Pubkey::new_rand();
|
|
|
|
config.command = WalletCommand::WithdrawStake(bob_keypair.into(), to_pubkey, 100);
|
|
|
|
let signature = process_command(&config);
|
|
|
|
assert_eq!(signature.unwrap(), SIGNATURE.to_string());
|
|
|
|
|
2019-06-21 23:45:03 -07:00
|
|
|
let bob_keypair = Keypair::new();
|
2019-08-09 12:55:21 -07:00
|
|
|
let vote_pubkey = Pubkey::new_rand();
|
|
|
|
config.command = WalletCommand::DeactivateStake(bob_keypair.into(), vote_pubkey);
|
2019-06-21 23:45:03 -07:00
|
|
|
let signature = process_command(&config);
|
|
|
|
assert_eq!(signature.unwrap(), SIGNATURE.to_string());
|
|
|
|
|
2019-08-05 13:17:03 -07:00
|
|
|
config.command = WalletCommand::GetSlot;
|
|
|
|
assert_eq!(process_command(&config).unwrap(), "0");
|
|
|
|
|
2019-01-13 23:10:03 -08:00
|
|
|
config.command = WalletCommand::GetTransactionCount;
|
|
|
|
assert_eq!(process_command(&config).unwrap(), "1234");
|
|
|
|
|
2019-09-10 16:16:40 -07:00
|
|
|
config.command = WalletCommand::Pay {
|
|
|
|
lamports: 10,
|
|
|
|
to: bob_pubkey,
|
|
|
|
timestamp: None,
|
|
|
|
timestamp_pubkey: None,
|
|
|
|
witnesses: None,
|
|
|
|
cancelable: None,
|
|
|
|
};
|
2019-01-13 23:10:03 -08:00
|
|
|
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();
|
2019-09-10 16:16:40 -07:00
|
|
|
config.command = WalletCommand::Pay {
|
|
|
|
lamports: 10,
|
|
|
|
to: bob_pubkey,
|
|
|
|
timestamp: Some(dt),
|
|
|
|
timestamp_pubkey: Some(config.keypair.pubkey()),
|
|
|
|
witnesses: None,
|
|
|
|
cancelable: None,
|
|
|
|
};
|
2019-01-13 23:10:03 -08:00
|
|
|
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-03-30 20:37:33 -07:00
|
|
|
let witness = Pubkey::new_rand();
|
2019-09-10 16:16:40 -07:00
|
|
|
config.command = WalletCommand::Pay {
|
|
|
|
lamports: 10,
|
|
|
|
to: bob_pubkey,
|
|
|
|
timestamp: None,
|
|
|
|
timestamp_pubkey: None,
|
|
|
|
witnesses: Some(vec![witness]),
|
|
|
|
cancelable: Some(config.keypair.pubkey()),
|
|
|
|
};
|
2019-01-13 23:10:03 -08:00
|
|
|
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-03-30 20:37:33 -07:00
|
|
|
let process_id = Pubkey::new_rand();
|
2019-01-13 23:10:03 -08:00
|
|
|
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-03-30 20:37:33 -07:00
|
|
|
let witness = Pubkey::new_rand();
|
2019-01-13 23:10:03 -08:00
|
|
|
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
|
2019-08-29 20:45:53 -07:00
|
|
|
config.command = WalletCommand::Airdrop {
|
|
|
|
drone_host: None,
|
|
|
|
drone_port: 1234,
|
|
|
|
lamports: 50,
|
2019-09-10 16:16:40 -07:00
|
|
|
use_lamports_unit: true,
|
2019-08-29 20:45:53 -07:00
|
|
|
};
|
2019-08-16 15:23:59 -07:00
|
|
|
assert!(process_command(&config).is_ok());
|
2018-11-14 18:57:34 -08:00
|
|
|
|
2019-03-16 21:51:41 -07:00
|
|
|
config.rpc_client = Some(RpcClient::new_mock("airdrop".to_string()));
|
2019-01-13 23:10:03 -08:00
|
|
|
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-03-30 20:37:33 -07:00
|
|
|
let witness = Pubkey::new_rand();
|
2019-01-13 23:10:03 -08:00
|
|
|
config.command = WalletCommand::Witness(bob_pubkey, witness);
|
|
|
|
let signature = process_command(&config);
|
|
|
|
assert_eq!(signature.unwrap(), SIGNATURE.to_string());
|
|
|
|
|
2019-04-05 19:56:17 -07:00
|
|
|
// sig_not_found case
|
|
|
|
config.rpc_client = Some(RpcClient::new_mock("sig_not_found".to_string()));
|
2019-03-17 00:40:45 -07: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");
|
|
|
|
|
2019-04-05 19:56:17 -07:00
|
|
|
// Tx error case
|
|
|
|
config.rpc_client = Some(RpcClient::new_mock("account_in_use".to_string()));
|
|
|
|
let any_signature = Signature::new(&bs58::decode(SIGNATURE).into_vec().unwrap());
|
|
|
|
config.command = WalletCommand::Confirm(any_signature);
|
|
|
|
assert_eq!(
|
|
|
|
process_command(&config).unwrap(),
|
|
|
|
format!(
|
|
|
|
"Transaction failed with error {:?}",
|
|
|
|
TransactionError::AccountInUse
|
|
|
|
)
|
|
|
|
);
|
|
|
|
|
2019-03-17 00:40:45 -07:00
|
|
|
// Failure cases
|
2019-03-16 21:51:41 -07:00
|
|
|
config.rpc_client = Some(RpcClient::new_mock("fails".to_string()));
|
2019-01-13 23:10:03 -08:00
|
|
|
|
2019-08-29 20:45:53 -07:00
|
|
|
config.command = WalletCommand::Airdrop {
|
|
|
|
drone_host: None,
|
|
|
|
drone_port: 1234,
|
|
|
|
lamports: 50,
|
2019-09-10 16:16:40 -07:00
|
|
|
use_lamports_unit: true,
|
2019-08-29 20:45:53 -07:00
|
|
|
};
|
2019-01-13 23:10:03 -08:00
|
|
|
assert!(process_command(&config).is_err());
|
2018-09-25 11:45:25 -07:00
|
|
|
|
2019-09-10 12:36:59 -07:00
|
|
|
config.command = WalletCommand::Balance {
|
|
|
|
pubkey: config.keypair.pubkey(),
|
|
|
|
use_lamports_unit: false,
|
|
|
|
};
|
2019-01-13 23:10:03 -08:00
|
|
|
assert!(process_command(&config).is_err());
|
|
|
|
|
2019-09-25 13:53:49 -07:00
|
|
|
config.command = WalletCommand::CreateVoteAccount(
|
|
|
|
bob_pubkey,
|
|
|
|
VoteInit {
|
|
|
|
node_pubkey,
|
|
|
|
authorized_voter: bob_pubkey,
|
|
|
|
authorized_withdrawer: bob_pubkey,
|
|
|
|
commission: 0,
|
|
|
|
},
|
|
|
|
10,
|
|
|
|
);
|
2019-03-06 20:29:08 -08:00
|
|
|
assert!(process_command(&config).is_err());
|
|
|
|
|
2019-09-25 13:53:49 -07:00
|
|
|
config.command = WalletCommand::VoteAuthorize(
|
|
|
|
bob_pubkey,
|
|
|
|
Keypair::new(),
|
|
|
|
bob_pubkey,
|
|
|
|
VoteAuthorize::Voter,
|
|
|
|
);
|
2019-03-06 20:29:08 -08:00
|
|
|
assert!(process_command(&config).is_err());
|
|
|
|
|
2019-08-05 13:17:03 -07:00
|
|
|
config.command = WalletCommand::GetSlot;
|
|
|
|
assert!(process_command(&config).is_err());
|
|
|
|
|
2019-01-13 23:10:03 -08:00
|
|
|
config.command = WalletCommand::GetTransactionCount;
|
|
|
|
assert!(process_command(&config).is_err());
|
|
|
|
|
2019-09-10 16:16:40 -07:00
|
|
|
config.command = WalletCommand::Pay {
|
|
|
|
lamports: 10,
|
|
|
|
to: bob_pubkey,
|
|
|
|
timestamp: None,
|
|
|
|
timestamp_pubkey: None,
|
|
|
|
witnesses: None,
|
|
|
|
cancelable: None,
|
|
|
|
};
|
2019-01-13 23:10:03 -08:00
|
|
|
assert!(process_command(&config).is_err());
|
|
|
|
|
2019-09-10 16:16:40 -07:00
|
|
|
config.command = WalletCommand::Pay {
|
|
|
|
lamports: 10,
|
|
|
|
to: bob_pubkey,
|
|
|
|
timestamp: Some(dt),
|
|
|
|
timestamp_pubkey: Some(config.keypair.pubkey()),
|
|
|
|
witnesses: None,
|
|
|
|
cancelable: None,
|
|
|
|
};
|
2019-01-13 23:10:03 -08:00
|
|
|
assert!(process_command(&config).is_err());
|
|
|
|
|
2019-09-10 16:16:40 -07:00
|
|
|
config.command = WalletCommand::Pay {
|
|
|
|
lamports: 10,
|
|
|
|
to: bob_pubkey,
|
|
|
|
timestamp: None,
|
|
|
|
timestamp_pubkey: None,
|
|
|
|
witnesses: Some(vec![witness]),
|
|
|
|
cancelable: Some(config.keypair.pubkey()),
|
|
|
|
};
|
2019-01-13 23:10:03 -08:00
|
|
|
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();
|
2019-08-09 14:52:06 -07:00
|
|
|
config.rpc_client = Some(RpcClient::new_mock("deploy_succeeds".to_string()));
|
2019-01-17 08:49:16 -08:00
|
|
|
|
|
|
|
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();
|
2019-05-16 21:43:18 -07:00
|
|
|
|
|
|
|
assert!(program_id.parse::<Pubkey>().is_ok());
|
2019-01-17 08:49:16 -08:00
|
|
|
|
2019-08-09 14:52:06 -07:00
|
|
|
// Failure case
|
2019-01-17 08:49:16 -08:00
|
|
|
config.command = WalletCommand::Deploy("bad/file/location.so".to_string());
|
|
|
|
assert!(process_command(&config).is_err());
|
|
|
|
}
|
2018-09-14 01:59:09 -07:00
|
|
|
}
|