2019-12-11 22:04:54 -08:00
|
|
|
use crate::{
|
|
|
|
cli::{
|
|
|
|
build_balance_message, check_account_for_fee, check_unique_pubkeys,
|
|
|
|
log_instruction_custom_error, CliCommand, CliCommandInfo, CliConfig, CliError,
|
|
|
|
ProcessResult,
|
|
|
|
},
|
|
|
|
cluster_query::aggregate_epoch_credits,
|
2019-09-18 09:29:57 -07:00
|
|
|
};
|
2019-10-04 14:18:19 -07:00
|
|
|
use clap::{value_t_or_exit, App, Arg, ArgMatches, SubCommand};
|
2019-11-11 16:42:08 -08:00
|
|
|
use solana_clap_utils::{input_parsers::*, input_validators::*};
|
2019-09-18 09:29:57 -07:00
|
|
|
use solana_client::rpc_client::RpcClient;
|
|
|
|
use solana_sdk::{
|
2020-01-09 15:22:48 -08:00
|
|
|
account::Account,
|
|
|
|
pubkey::Pubkey,
|
2020-01-09 20:25:07 -08:00
|
|
|
signature::Keypair,
|
2020-01-09 15:22:48 -08:00
|
|
|
signature::KeypairUtil,
|
|
|
|
system_instruction::{create_address_with_seed, SystemError},
|
2019-10-22 13:41:18 -07:00
|
|
|
transaction::Transaction,
|
2019-09-18 09:29:57 -07:00
|
|
|
};
|
2019-11-20 10:12:43 -08:00
|
|
|
use solana_vote_program::{
|
2019-09-18 09:29:57 -07:00
|
|
|
vote_instruction::{self, VoteError},
|
2019-09-25 13:53:49 -07:00
|
|
|
vote_state::{VoteAuthorize, VoteInit, VoteState},
|
2019-09-18 09:29:57 -07:00
|
|
|
};
|
|
|
|
|
2019-10-04 14:18:19 -07:00
|
|
|
pub trait VoteSubCommands {
|
|
|
|
fn vote_subcommands(self) -> Self;
|
|
|
|
}
|
|
|
|
|
|
|
|
impl VoteSubCommands for App<'_, '_> {
|
|
|
|
fn vote_subcommands(self) -> Self {
|
|
|
|
self.subcommand(
|
|
|
|
SubCommand::with_name("create-vote-account")
|
|
|
|
.about("Create a vote account")
|
|
|
|
.arg(
|
2019-11-06 06:47:34 -08:00
|
|
|
Arg::with_name("vote_account")
|
2019-10-04 14:18:19 -07:00
|
|
|
.index(1)
|
2019-11-06 06:47:34 -08:00
|
|
|
.value_name("VOTE ACCOUNT KEYPAIR")
|
2019-10-04 14:18:19 -07:00
|
|
|
.takes_value(true)
|
|
|
|
.required(true)
|
2019-11-23 08:55:43 -08:00
|
|
|
.validator(is_keypair_or_ask_keyword)
|
2019-11-06 06:47:34 -08:00
|
|
|
.help("Vote account keypair to fund"),
|
2019-10-04 14:18:19 -07:00
|
|
|
)
|
|
|
|
.arg(
|
2019-12-12 09:17:18 -08:00
|
|
|
Arg::with_name("identity_pubkey")
|
2019-10-04 14:18:19 -07:00
|
|
|
.index(2)
|
2019-12-12 09:17:18 -08:00
|
|
|
.value_name("VALIDATOR IDENTITY PUBKEY")
|
2019-10-04 14:18:19 -07:00
|
|
|
.takes_value(true)
|
|
|
|
.required(true)
|
|
|
|
.validator(is_pubkey_or_keypair)
|
|
|
|
.help("Validator that will vote with this account"),
|
|
|
|
)
|
|
|
|
.arg(
|
|
|
|
Arg::with_name("commission")
|
|
|
|
.long("commission")
|
|
|
|
.value_name("NUM")
|
|
|
|
.takes_value(true)
|
2020-01-04 08:20:44 -08:00
|
|
|
.default_value("100")
|
|
|
|
.help("The commission taken on reward redemption (0-100)"),
|
2019-10-04 14:18:19 -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)"),
|
|
|
|
)
|
|
|
|
.arg(
|
|
|
|
Arg::with_name("authorized_withdrawer")
|
|
|
|
.long("authorized-withdrawer")
|
|
|
|
.value_name("PUBKEY")
|
|
|
|
.takes_value(true)
|
|
|
|
.validator(is_pubkey_or_keypair)
|
2019-10-04 15:13:21 -07:00
|
|
|
.help("Public key of the authorized withdrawer (defaults to cli config pubkey)"),
|
2020-01-09 15:22:48 -08:00
|
|
|
)
|
|
|
|
.arg(
|
|
|
|
Arg::with_name("seed")
|
|
|
|
.long("seed")
|
|
|
|
.value_name("SEED STRING")
|
|
|
|
.takes_value(true)
|
|
|
|
.help("Seed for address generation; if specified, the resulting account will be at a derived address of the VOTE ACCOUNT pubkey")
|
2019-10-04 14:18:19 -07:00
|
|
|
),
|
|
|
|
)
|
2019-12-12 15:04:03 -08:00
|
|
|
.subcommand(
|
|
|
|
SubCommand::with_name("vote-update-validator")
|
|
|
|
.about("Update the vote account's validator identity")
|
|
|
|
.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 to update"),
|
|
|
|
)
|
|
|
|
.arg(
|
|
|
|
Arg::with_name("new_identity_pubkey")
|
|
|
|
.index(2)
|
|
|
|
.value_name("NEW VALIDATOR IDENTITY PUBKEY")
|
|
|
|
.takes_value(true)
|
|
|
|
.required(true)
|
|
|
|
.validator(is_pubkey_or_keypair)
|
|
|
|
.help("New validator that will vote with this account"),
|
|
|
|
)
|
|
|
|
.arg(
|
|
|
|
Arg::with_name("authorized_voter")
|
|
|
|
.index(3)
|
|
|
|
.value_name("AUTHORIZED VOTER KEYPAIR")
|
|
|
|
.takes_value(true)
|
|
|
|
.required(true)
|
|
|
|
.validator(is_keypair)
|
|
|
|
.help("Authorized voter keypair"),
|
|
|
|
)
|
|
|
|
)
|
2019-10-04 14:18:19 -07:00
|
|
|
.subcommand(
|
|
|
|
SubCommand::with_name("vote-authorize-voter")
|
|
|
|
.about("Authorize a new vote 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 voter"),
|
|
|
|
)
|
|
|
|
.arg(
|
|
|
|
Arg::with_name("new_authorized_pubkey")
|
|
|
|
.index(2)
|
|
|
|
.value_name("NEW VOTER PUBKEY")
|
|
|
|
.takes_value(true)
|
|
|
|
.required(true)
|
|
|
|
.validator(is_pubkey_or_keypair)
|
|
|
|
.help("New vote signer to authorize"),
|
|
|
|
),
|
|
|
|
)
|
|
|
|
.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("new_authorized_pubkey")
|
|
|
|
.index(2)
|
|
|
|
.value_name("NEW WITHDRAWER PUBKEY")
|
|
|
|
.takes_value(true)
|
|
|
|
.required(true)
|
|
|
|
.validator(is_pubkey_or_keypair)
|
|
|
|
.help("New withdrawer to authorize"),
|
|
|
|
),
|
|
|
|
)
|
|
|
|
.subcommand(
|
2020-01-20 22:06:47 -08:00
|
|
|
SubCommand::with_name("vote-account")
|
2019-10-04 14:18:19 -07:00
|
|
|
.about("Show the contents of a vote account")
|
2020-01-20 22:06:47 -08:00
|
|
|
.alias("show-vote-account")
|
2019-10-04 14:18:19 -07:00
|
|
|
.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("lamports")
|
|
|
|
.long("lamports")
|
|
|
|
.takes_value(false)
|
|
|
|
.help("Display balance in lamports instead of SOL"),
|
|
|
|
),
|
|
|
|
)
|
|
|
|
.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-10-21 16:08:09 -07:00
|
|
|
pub fn parse_vote_create_account(matches: &ArgMatches<'_>) -> Result<CliCommandInfo, CliError> {
|
2019-11-06 06:47:34 -08:00
|
|
|
let vote_account = keypair_of(matches, "vote_account").unwrap();
|
2020-01-09 15:22:48 -08:00
|
|
|
let seed = matches.value_of("seed").map(|s| s.to_string());
|
2019-12-12 09:17:18 -08:00
|
|
|
let identity_pubkey = pubkey_of(matches, "identity_pubkey").unwrap();
|
2020-01-04 08:20:44 -08:00
|
|
|
let commission = value_t_or_exit!(matches, "commission", u8);
|
2019-10-21 16:08:09 -07:00
|
|
|
let authorized_voter = pubkey_of(matches, "authorized_voter");
|
|
|
|
let authorized_withdrawer = pubkey_of(matches, "authorized_withdrawer");
|
2019-09-26 10:26:47 -07:00
|
|
|
|
2019-10-21 16:08:09 -07:00
|
|
|
Ok(CliCommandInfo {
|
|
|
|
command: CliCommand::CreateVoteAccount {
|
2019-11-07 17:08:10 -08:00
|
|
|
vote_account: vote_account.into(),
|
2020-01-09 15:22:48 -08:00
|
|
|
seed,
|
2019-12-12 09:17:18 -08:00
|
|
|
node_pubkey: identity_pubkey,
|
2019-09-25 13:53:49 -07:00
|
|
|
authorized_voter,
|
|
|
|
authorized_withdrawer,
|
|
|
|
commission,
|
|
|
|
},
|
2019-10-21 16:08:09 -07:00
|
|
|
require_keypair: true,
|
|
|
|
})
|
2019-09-18 09:29:57 -07:00
|
|
|
}
|
|
|
|
|
2019-09-25 13:53:49 -07:00
|
|
|
pub fn parse_vote_authorize(
|
|
|
|
matches: &ArgMatches<'_>,
|
|
|
|
vote_authorize: VoteAuthorize,
|
2019-10-21 16:08:09 -07:00
|
|
|
) -> Result<CliCommandInfo, CliError> {
|
2019-09-18 09:29:57 -07:00
|
|
|
let vote_account_pubkey = pubkey_of(matches, "vote_account_pubkey").unwrap();
|
2019-09-25 13:53:49 -07:00
|
|
|
let new_authorized_pubkey = pubkey_of(matches, "new_authorized_pubkey").unwrap();
|
2019-09-18 09:29:57 -07:00
|
|
|
|
2019-10-21 16:08:09 -07:00
|
|
|
Ok(CliCommandInfo {
|
2019-12-12 15:04:03 -08:00
|
|
|
command: CliCommand::VoteAuthorize {
|
2019-10-21 16:08:09 -07:00
|
|
|
vote_account_pubkey,
|
|
|
|
new_authorized_pubkey,
|
|
|
|
vote_authorize,
|
2019-12-12 15:04:03 -08:00
|
|
|
},
|
|
|
|
require_keypair: true,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn parse_vote_update_validator(matches: &ArgMatches<'_>) -> Result<CliCommandInfo, CliError> {
|
|
|
|
let vote_account_pubkey = pubkey_of(matches, "vote_account_pubkey").unwrap();
|
|
|
|
let new_identity_pubkey = pubkey_of(matches, "new_identity_pubkey").unwrap();
|
|
|
|
let authorized_voter = keypair_of(matches, "authorized_voter").unwrap();
|
|
|
|
|
|
|
|
Ok(CliCommandInfo {
|
|
|
|
command: CliCommand::VoteUpdateValidator {
|
|
|
|
vote_account_pubkey,
|
|
|
|
new_identity_pubkey,
|
|
|
|
authorized_voter: authorized_voter.into(),
|
|
|
|
},
|
2019-10-21 16:08:09 -07:00
|
|
|
require_keypair: true,
|
|
|
|
})
|
2019-09-18 09:29:57 -07:00
|
|
|
}
|
|
|
|
|
2019-10-21 16:08:09 -07:00
|
|
|
pub fn parse_vote_get_account_command(
|
|
|
|
matches: &ArgMatches<'_>,
|
|
|
|
) -> Result<CliCommandInfo, CliError> {
|
2019-09-18 09:29:57 -07:00
|
|
|
let vote_account_pubkey = pubkey_of(matches, "vote_account_pubkey").unwrap();
|
2019-09-26 10:26:47 -07:00
|
|
|
let use_lamports_unit = matches.is_present("lamports");
|
2019-10-21 16:08:09 -07:00
|
|
|
Ok(CliCommandInfo {
|
|
|
|
command: CliCommand::ShowVoteAccount {
|
|
|
|
pubkey: vote_account_pubkey,
|
|
|
|
use_lamports_unit,
|
|
|
|
},
|
|
|
|
require_keypair: false,
|
2019-09-26 10:26:47 -07:00
|
|
|
})
|
2019-09-18 09:29:57 -07:00
|
|
|
}
|
|
|
|
|
2019-10-21 16:08:09 -07:00
|
|
|
pub fn parse_vote_uptime_command(matches: &ArgMatches<'_>) -> Result<CliCommandInfo, CliError> {
|
2019-10-04 14:18:19 -07:00
|
|
|
let vote_account_pubkey = pubkey_of(matches, "vote_account_pubkey").unwrap();
|
|
|
|
let aggregate = matches.is_present("aggregate");
|
|
|
|
let span = if matches.is_present("span") {
|
|
|
|
Some(value_t_or_exit!(matches, "span", u64))
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
};
|
2019-10-21 16:08:09 -07:00
|
|
|
Ok(CliCommandInfo {
|
|
|
|
command: CliCommand::Uptime {
|
|
|
|
pubkey: vote_account_pubkey,
|
|
|
|
aggregate,
|
|
|
|
span,
|
|
|
|
},
|
|
|
|
require_keypair: false,
|
2019-10-04 14:18:19 -07:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2019-09-18 09:29:57 -07:00
|
|
|
pub fn process_create_vote_account(
|
|
|
|
rpc_client: &RpcClient,
|
2019-10-04 15:13:21 -07:00
|
|
|
config: &CliConfig,
|
2019-11-06 06:47:34 -08:00
|
|
|
vote_account: &Keypair,
|
2020-01-09 15:22:48 -08:00
|
|
|
seed: &Option<String>,
|
2019-12-12 09:17:18 -08:00
|
|
|
identity_pubkey: &Pubkey,
|
2019-10-21 16:08:09 -07:00
|
|
|
authorized_voter: &Option<Pubkey>,
|
|
|
|
authorized_withdrawer: &Option<Pubkey>,
|
|
|
|
commission: u8,
|
2019-09-18 09:29:57 -07:00
|
|
|
) -> ProcessResult {
|
2019-11-06 06:47:34 -08:00
|
|
|
let vote_account_pubkey = vote_account.pubkey();
|
2020-01-09 15:22:48 -08:00
|
|
|
let vote_account_address = if let Some(seed) = seed {
|
|
|
|
create_address_with_seed(&vote_account_pubkey, &seed, &solana_vote_program::id())?
|
|
|
|
} else {
|
|
|
|
vote_account_pubkey
|
|
|
|
};
|
2019-09-18 09:29:57 -07:00
|
|
|
check_unique_pubkeys(
|
2020-01-09 15:22:48 -08:00
|
|
|
(&config.keypair.pubkey(), "cli keypair".to_string()),
|
|
|
|
(&vote_account_address, "vote_account".to_string()),
|
2019-09-18 09:29:57 -07:00
|
|
|
)?;
|
2020-01-09 15:22:48 -08:00
|
|
|
|
2019-09-18 09:29:57 -07:00
|
|
|
check_unique_pubkeys(
|
2020-01-09 15:22:48 -08:00
|
|
|
(&vote_account_address, "vote_account".to_string()),
|
|
|
|
(&identity_pubkey, "identity_pubkey".to_string()),
|
2019-09-18 09:29:57 -07:00
|
|
|
)?;
|
2019-12-09 21:56:43 -08:00
|
|
|
|
2020-01-09 20:25:07 -08:00
|
|
|
if let Ok(vote_account) = rpc_client.get_account(&vote_account_address) {
|
|
|
|
let err_msg = if vote_account.owner == solana_vote_program::id() {
|
|
|
|
format!("Vote account {} already exists", vote_account_address)
|
|
|
|
} else {
|
|
|
|
format!(
|
|
|
|
"Account {} already exists and is not a vote account",
|
|
|
|
vote_account_address
|
|
|
|
)
|
|
|
|
};
|
|
|
|
return Err(CliError::BadParameter(err_msg).into());
|
2020-01-09 15:22:48 -08:00
|
|
|
}
|
|
|
|
|
2019-12-12 17:24:30 -08:00
|
|
|
let required_balance = rpc_client
|
2019-12-09 21:56:43 -08:00
|
|
|
.get_minimum_balance_for_rent_exemption(VoteState::size_of())?
|
2019-12-12 17:24:30 -08:00
|
|
|
.max(1);
|
2019-12-09 21:56:43 -08:00
|
|
|
|
2019-10-21 16:08:09 -07:00
|
|
|
let vote_init = VoteInit {
|
2019-12-12 09:17:18 -08:00
|
|
|
node_pubkey: *identity_pubkey,
|
2019-11-06 06:47:34 -08:00
|
|
|
authorized_voter: authorized_voter.unwrap_or(vote_account_pubkey),
|
2020-01-09 15:22:48 -08:00
|
|
|
authorized_withdrawer: authorized_withdrawer.unwrap_or(vote_account_pubkey),
|
2019-10-21 16:08:09 -07:00
|
|
|
commission,
|
|
|
|
};
|
2020-01-09 15:22:48 -08:00
|
|
|
|
|
|
|
let ixs = if let Some(seed) = seed {
|
|
|
|
vote_instruction::create_account_with_seed(
|
|
|
|
&config.keypair.pubkey(), // from
|
|
|
|
&vote_account_address, // to
|
|
|
|
&vote_account_pubkey, // base
|
|
|
|
seed, // seed
|
|
|
|
&vote_init,
|
|
|
|
required_balance,
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
vote_instruction::create_account(
|
|
|
|
&config.keypair.pubkey(),
|
|
|
|
&vote_account_pubkey,
|
|
|
|
&vote_init,
|
|
|
|
required_balance,
|
|
|
|
)
|
|
|
|
};
|
2019-09-18 09:29:57 -07:00
|
|
|
let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?;
|
2020-01-09 15:22:48 -08:00
|
|
|
|
|
|
|
let signers = if vote_account_pubkey != config.keypair.pubkey() {
|
|
|
|
vec![&config.keypair, vote_account] // both must sign if `from` and `to` differ
|
|
|
|
} else {
|
|
|
|
vec![&config.keypair] // when stake_account == config.keypair and there's a seed, we only need one signature
|
|
|
|
};
|
|
|
|
|
|
|
|
let mut tx = Transaction::new_signed_instructions(&signers, ixs, recent_blockhash);
|
2019-12-09 23:11:04 -08:00
|
|
|
check_account_for_fee(
|
|
|
|
rpc_client,
|
|
|
|
&config.keypair.pubkey(),
|
|
|
|
&fee_calculator,
|
|
|
|
&tx.message,
|
|
|
|
)?;
|
2020-01-09 15:22:48 -08:00
|
|
|
let result = rpc_client.send_and_confirm_transaction(&mut tx, &signers);
|
2019-09-18 09:29:57 -07:00
|
|
|
log_instruction_custom_error::<SystemError>(result)
|
|
|
|
}
|
|
|
|
|
2019-09-25 13:53:49 -07:00
|
|
|
pub fn process_vote_authorize(
|
2019-09-18 09:29:57 -07:00
|
|
|
rpc_client: &RpcClient,
|
2019-10-04 15:13:21 -07:00
|
|
|
config: &CliConfig,
|
2019-09-18 09:29:57 -07:00
|
|
|
vote_account_pubkey: &Pubkey,
|
2019-09-25 13:53:49 -07:00
|
|
|
new_authorized_pubkey: &Pubkey,
|
|
|
|
vote_authorize: VoteAuthorize,
|
2019-09-18 09:29:57 -07:00
|
|
|
) -> ProcessResult {
|
|
|
|
check_unique_pubkeys(
|
|
|
|
(vote_account_pubkey, "vote_account_pubkey".to_string()),
|
2019-09-25 13:53:49 -07:00
|
|
|
(new_authorized_pubkey, "new_authorized_pubkey".to_string()),
|
2019-09-18 09:29:57 -07:00
|
|
|
)?;
|
|
|
|
let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?;
|
2019-09-25 13:53:49 -07:00
|
|
|
let ixs = vec![vote_instruction::authorize(
|
2019-09-29 21:18:15 -07:00
|
|
|
vote_account_pubkey, // vote account to update
|
|
|
|
&config.keypair.pubkey(), // current authorized voter
|
|
|
|
new_authorized_pubkey, // new vote signer/withdrawer
|
|
|
|
vote_authorize, // vote or withdraw
|
2019-09-18 09:29:57 -07:00
|
|
|
)];
|
|
|
|
|
2019-10-18 20:39:05 -07:00
|
|
|
let mut tx = Transaction::new_signed_with_payer(
|
|
|
|
ixs,
|
|
|
|
Some(&config.keypair.pubkey()),
|
|
|
|
&[&config.keypair],
|
|
|
|
recent_blockhash,
|
|
|
|
);
|
2019-12-09 23:11:04 -08:00
|
|
|
check_account_for_fee(
|
|
|
|
rpc_client,
|
|
|
|
&config.keypair.pubkey(),
|
|
|
|
&fee_calculator,
|
|
|
|
&tx.message,
|
|
|
|
)?;
|
2019-09-29 21:18:15 -07:00
|
|
|
let result = rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair]);
|
2019-09-18 09:29:57 -07:00
|
|
|
log_instruction_custom_error::<VoteError>(result)
|
|
|
|
}
|
|
|
|
|
2019-12-12 15:04:03 -08:00
|
|
|
pub fn process_vote_update_validator(
|
|
|
|
rpc_client: &RpcClient,
|
|
|
|
config: &CliConfig,
|
|
|
|
vote_account_pubkey: &Pubkey,
|
|
|
|
new_identity_pubkey: &Pubkey,
|
|
|
|
authorized_voter: &Keypair,
|
|
|
|
) -> ProcessResult {
|
|
|
|
check_unique_pubkeys(
|
|
|
|
(vote_account_pubkey, "vote_account_pubkey".to_string()),
|
|
|
|
(new_identity_pubkey, "new_identity_pubkey".to_string()),
|
|
|
|
)?;
|
|
|
|
let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?;
|
|
|
|
let ixs = vec![vote_instruction::update_node(
|
|
|
|
vote_account_pubkey,
|
|
|
|
&authorized_voter.pubkey(),
|
|
|
|
new_identity_pubkey,
|
|
|
|
)];
|
|
|
|
|
|
|
|
let mut tx = Transaction::new_signed_with_payer(
|
|
|
|
ixs,
|
|
|
|
Some(&config.keypair.pubkey()),
|
|
|
|
&[&config.keypair, authorized_voter],
|
|
|
|
recent_blockhash,
|
|
|
|
);
|
|
|
|
check_account_for_fee(
|
|
|
|
rpc_client,
|
|
|
|
&config.keypair.pubkey(),
|
|
|
|
&fee_calculator,
|
|
|
|
&tx.message,
|
|
|
|
)?;
|
|
|
|
let result = rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair]);
|
|
|
|
log_instruction_custom_error::<VoteError>(result)
|
|
|
|
}
|
|
|
|
|
2019-10-08 22:34:26 -07:00
|
|
|
fn get_vote_account(
|
2019-09-18 09:29:57 -07:00
|
|
|
rpc_client: &RpcClient,
|
|
|
|
vote_account_pubkey: &Pubkey,
|
2019-10-08 22:34:26 -07:00
|
|
|
) -> Result<(Account, VoteState), Box<dyn std::error::Error>> {
|
2019-09-18 09:29:57 -07:00
|
|
|
let vote_account = rpc_client.get_account(vote_account_pubkey)?;
|
|
|
|
|
2019-11-20 10:12:43 -08:00
|
|
|
if vote_account.owner != solana_vote_program::id() {
|
2019-12-19 23:27:54 -08:00
|
|
|
return Err(CliError::RpcRequestError(format!(
|
|
|
|
"{:?} is not a vote account",
|
|
|
|
vote_account_pubkey
|
|
|
|
))
|
2019-10-02 18:33:01 -07:00
|
|
|
.into());
|
2019-09-18 09:29:57 -07:00
|
|
|
}
|
|
|
|
let vote_state = VoteState::deserialize(&vote_account.data).map_err(|_| {
|
2019-10-04 15:13:21 -07:00
|
|
|
CliError::RpcRequestError(
|
2019-09-18 09:29:57 -07:00
|
|
|
"Account data could not be deserialized to vote state".to_string(),
|
|
|
|
)
|
|
|
|
})?;
|
|
|
|
|
2019-10-08 22:34:26 -07:00
|
|
|
Ok((vote_account, vote_state))
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn process_show_vote_account(
|
|
|
|
rpc_client: &RpcClient,
|
|
|
|
_config: &CliConfig,
|
|
|
|
vote_account_pubkey: &Pubkey,
|
|
|
|
use_lamports_unit: bool,
|
|
|
|
) -> ProcessResult {
|
|
|
|
let (vote_account, vote_state) = get_vote_account(rpc_client, vote_account_pubkey)?;
|
|
|
|
|
2019-10-22 13:41:18 -07:00
|
|
|
let epoch_schedule = rpc_client.get_epoch_schedule()?;
|
2019-10-08 22:34:26 -07:00
|
|
|
|
2019-09-26 10:26:47 -07:00
|
|
|
println!(
|
|
|
|
"account balance: {}",
|
2019-10-25 09:20:08 -07:00
|
|
|
build_balance_message(vote_account.lamports, use_lamports_unit, true)
|
2019-09-26 10:26:47 -07:00
|
|
|
);
|
2019-12-12 09:17:18 -08:00
|
|
|
println!("validator identity: {}", vote_state.node_pubkey);
|
2019-09-25 13:53:49 -07:00
|
|
|
println!("authorized voter: {}", vote_state.authorized_voter);
|
2019-09-18 09:29:57 -07:00
|
|
|
println!(
|
2019-09-25 13:53:49 -07:00
|
|
|
"authorized withdrawer: {}",
|
|
|
|
vote_state.authorized_withdrawer
|
2019-09-18 09:29:57 -07:00
|
|
|
);
|
|
|
|
println!("credits: {}", vote_state.credits());
|
2019-12-03 20:55:04 -08:00
|
|
|
println!("commission: {}%", vote_state.commission);
|
2019-09-18 09:29:57 -07:00
|
|
|
println!(
|
|
|
|
"root slot: {}",
|
|
|
|
match vote_state.root_slot {
|
|
|
|
Some(slot) => slot.to_string(),
|
|
|
|
None => "~".to_string(),
|
|
|
|
}
|
|
|
|
);
|
2019-12-06 13:38:49 -08:00
|
|
|
println!("recent timestamp: {:?}", vote_state.last_timestamp);
|
2019-09-18 09:29:57 -07:00
|
|
|
if !vote_state.votes.is_empty() {
|
|
|
|
println!("recent votes:");
|
|
|
|
for vote in &vote_state.votes {
|
|
|
|
println!(
|
|
|
|
"- slot: {}\n confirmation count: {}",
|
|
|
|
vote.slot, vote.confirmation_count
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
println!("epoch voting history:");
|
|
|
|
for (epoch, credits, prev_credits) in vote_state.epoch_credits() {
|
|
|
|
let credits_earned = credits - prev_credits;
|
|
|
|
let slots_in_epoch = epoch_schedule.get_slots_in_epoch(*epoch);
|
|
|
|
println!(
|
|
|
|
"- epoch: {}\n slots in epoch: {}\n credits earned: {}",
|
|
|
|
epoch, slots_in_epoch, credits_earned,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Ok("".to_string())
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn process_uptime(
|
|
|
|
rpc_client: &RpcClient,
|
2019-10-04 15:13:21 -07:00
|
|
|
_config: &CliConfig,
|
2019-09-18 09:29:57 -07:00
|
|
|
vote_account_pubkey: &Pubkey,
|
|
|
|
aggregate: bool,
|
|
|
|
span: Option<u64>,
|
|
|
|
) -> ProcessResult {
|
2019-10-08 22:34:26 -07:00
|
|
|
let (_vote_account, vote_state) = get_vote_account(rpc_client, vote_account_pubkey)?;
|
2019-09-18 09:29:57 -07:00
|
|
|
|
2019-10-22 13:41:18 -07:00
|
|
|
let epoch_schedule = rpc_client.get_epoch_schedule()?;
|
2019-09-18 09:29:57 -07:00
|
|
|
|
2019-12-12 09:17:18 -08:00
|
|
|
println!("validator identity: {}", vote_state.node_pubkey);
|
|
|
|
println!("authorized voter: {}", vote_state.authorized_voter);
|
2019-09-18 09:29:57 -07:00
|
|
|
if !vote_state.votes.is_empty() {
|
2019-12-12 09:17:18 -08:00
|
|
|
println!("uptime:");
|
2019-09-18 09:29:57 -07:00
|
|
|
|
2019-12-11 22:04:54 -08:00
|
|
|
let epoch_credits: Vec<(u64, u64, u64)> = if let Some(x) = span {
|
|
|
|
vote_state
|
|
|
|
.epoch_credits()
|
|
|
|
.iter()
|
|
|
|
.rev()
|
|
|
|
.take(x as usize)
|
|
|
|
.cloned()
|
|
|
|
.collect()
|
2019-09-18 09:29:57 -07:00
|
|
|
} else {
|
2019-12-11 22:04:54 -08:00
|
|
|
vote_state.epoch_credits().iter().rev().cloned().collect()
|
2019-09-18 09:29:57 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
if aggregate {
|
2019-12-11 22:04:54 -08:00
|
|
|
let (total_credits, total_slots, epochs) =
|
|
|
|
aggregate_epoch_credits(&epoch_credits, &epoch_schedule);
|
|
|
|
if total_slots > 0 {
|
|
|
|
let total_uptime = 100_f64 * total_credits as f64 / total_slots as f64;
|
|
|
|
println!("{:.2}% over {} epochs", total_uptime, epochs);
|
|
|
|
} else {
|
|
|
|
println!("Insufficient voting history available");
|
|
|
|
}
|
2019-09-18 09:29:57 -07:00
|
|
|
} else {
|
|
|
|
for (epoch, credits, prev_credits) in epoch_credits {
|
|
|
|
let credits_earned = credits - prev_credits;
|
2019-12-11 22:04:54 -08:00
|
|
|
let slots_in_epoch = epoch_schedule.get_slots_in_epoch(epoch);
|
2019-09-18 09:29:57 -07:00
|
|
|
let uptime = credits_earned as f64 / slots_in_epoch as f64;
|
|
|
|
println!("- epoch: {} {:.2}% uptime", epoch, uptime * 100_f64,);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if let Some(x) = span {
|
2019-12-11 22:04:54 -08:00
|
|
|
if x > vote_state.epoch_credits().len() as u64 {
|
2019-09-18 09:29:57 -07:00
|
|
|
println!("(span longer than available epochs)");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Ok("".to_string())
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
2019-10-04 15:13:21 -07:00
|
|
|
use crate::cli::{app, parse_command};
|
2019-11-06 06:47:34 -08:00
|
|
|
use solana_sdk::signature::write_keypair;
|
|
|
|
use tempfile::NamedTempFile;
|
|
|
|
|
|
|
|
fn make_tmp_file() -> (String, NamedTempFile) {
|
|
|
|
let tmp_file = NamedTempFile::new().unwrap();
|
|
|
|
(String::from(tmp_file.path().to_str().unwrap()), tmp_file)
|
|
|
|
}
|
2019-09-18 09:29:57 -07:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_parse_command() {
|
|
|
|
let test_commands = app("test", "desc", "version");
|
2019-11-06 06:47:34 -08:00
|
|
|
let keypair = Keypair::new();
|
|
|
|
let pubkey = keypair.pubkey();
|
2019-09-29 21:18:15 -07:00
|
|
|
let pubkey_string = pubkey.to_string();
|
2019-12-12 15:04:03 -08:00
|
|
|
let keypair2 = Keypair::new();
|
|
|
|
let pubkey2 = keypair2.pubkey();
|
|
|
|
let pubkey2_string = pubkey2.to_string();
|
2019-09-18 09:29:57 -07:00
|
|
|
|
|
|
|
let test_authorize_voter = test_commands.clone().get_matches_from(vec![
|
|
|
|
"test",
|
2019-09-25 13:53:49 -07:00
|
|
|
"vote-authorize-voter",
|
2019-09-18 09:29:57 -07:00
|
|
|
&pubkey_string,
|
2019-12-12 15:04:03 -08:00
|
|
|
&pubkey2_string,
|
2019-09-18 09:29:57 -07:00
|
|
|
]);
|
|
|
|
assert_eq!(
|
2019-10-21 16:08:09 -07:00
|
|
|
parse_command(&test_authorize_voter).unwrap(),
|
|
|
|
CliCommandInfo {
|
2019-12-12 15:04:03 -08:00
|
|
|
command: CliCommand::VoteAuthorize {
|
|
|
|
vote_account_pubkey: pubkey,
|
|
|
|
new_authorized_pubkey: pubkey2,
|
|
|
|
vote_authorize: VoteAuthorize::Voter
|
|
|
|
},
|
2019-10-21 16:08:09 -07:00
|
|
|
require_keypair: true
|
|
|
|
}
|
2019-09-18 09:29:57 -07:00
|
|
|
);
|
|
|
|
|
2019-11-06 06:47:34 -08:00
|
|
|
let (keypair_file, mut tmp_file) = make_tmp_file();
|
|
|
|
let keypair = Keypair::new();
|
|
|
|
write_keypair(&keypair, tmp_file.as_file_mut()).unwrap();
|
2019-09-18 09:29:57 -07:00
|
|
|
// Test CreateVoteAccount SubCommand
|
|
|
|
let node_pubkey = Pubkey::new_rand();
|
|
|
|
let node_pubkey_string = format!("{}", node_pubkey);
|
|
|
|
let test_create_vote_account = test_commands.clone().get_matches_from(vec![
|
|
|
|
"test",
|
|
|
|
"create-vote-account",
|
2019-11-06 06:47:34 -08:00
|
|
|
&keypair_file,
|
2019-09-18 09:29:57 -07:00
|
|
|
&node_pubkey_string,
|
|
|
|
"--commission",
|
|
|
|
"10",
|
|
|
|
]);
|
|
|
|
assert_eq!(
|
2019-10-21 16:08:09 -07:00
|
|
|
parse_command(&test_create_vote_account).unwrap(),
|
|
|
|
CliCommandInfo {
|
|
|
|
command: CliCommand::CreateVoteAccount {
|
2019-11-07 17:08:10 -08:00
|
|
|
vote_account: keypair.into(),
|
2020-01-09 15:22:48 -08:00
|
|
|
seed: None,
|
2019-09-25 13:53:49 -07:00
|
|
|
node_pubkey,
|
2019-10-21 16:08:09 -07:00
|
|
|
authorized_voter: None,
|
|
|
|
authorized_withdrawer: None,
|
|
|
|
commission: 10,
|
|
|
|
},
|
|
|
|
require_keypair: true
|
|
|
|
}
|
2019-09-18 09:29:57 -07:00
|
|
|
);
|
2019-11-06 06:47:34 -08:00
|
|
|
|
|
|
|
let (keypair_file, mut tmp_file) = make_tmp_file();
|
|
|
|
let keypair = Keypair::new();
|
|
|
|
write_keypair(&keypair, tmp_file.as_file_mut()).unwrap();
|
|
|
|
|
2019-09-18 09:29:57 -07:00
|
|
|
let test_create_vote_account2 = test_commands.clone().get_matches_from(vec![
|
|
|
|
"test",
|
|
|
|
"create-vote-account",
|
2019-11-06 06:47:34 -08:00
|
|
|
&keypair_file,
|
2019-09-18 09:29:57 -07:00
|
|
|
&node_pubkey_string,
|
|
|
|
]);
|
|
|
|
assert_eq!(
|
2019-10-21 16:08:09 -07:00
|
|
|
parse_command(&test_create_vote_account2).unwrap(),
|
|
|
|
CliCommandInfo {
|
|
|
|
command: CliCommand::CreateVoteAccount {
|
2019-11-07 17:08:10 -08:00
|
|
|
vote_account: keypair.into(),
|
2020-01-09 15:22:48 -08:00
|
|
|
seed: None,
|
2019-09-25 13:53:49 -07:00
|
|
|
node_pubkey,
|
2019-10-21 16:08:09 -07:00
|
|
|
authorized_voter: None,
|
|
|
|
authorized_withdrawer: None,
|
2020-01-04 08:20:44 -08:00
|
|
|
commission: 100,
|
2019-10-21 16:08:09 -07:00
|
|
|
},
|
|
|
|
require_keypair: true
|
|
|
|
}
|
2019-09-25 13:53:49 -07:00
|
|
|
);
|
2019-11-06 06:47:34 -08:00
|
|
|
|
2019-09-25 13:53:49 -07:00
|
|
|
// test init with an authed voter
|
|
|
|
let authed = Pubkey::new_rand();
|
2019-11-06 06:47:34 -08:00
|
|
|
let (keypair_file, mut tmp_file) = make_tmp_file();
|
|
|
|
let keypair = Keypair::new();
|
|
|
|
write_keypair(&keypair, tmp_file.as_file_mut()).unwrap();
|
|
|
|
|
2019-09-25 13:53:49 -07:00
|
|
|
let test_create_vote_account3 = test_commands.clone().get_matches_from(vec![
|
|
|
|
"test",
|
|
|
|
"create-vote-account",
|
2019-11-06 06:47:34 -08:00
|
|
|
&keypair_file,
|
2019-09-25 13:53:49 -07:00
|
|
|
&node_pubkey_string,
|
|
|
|
"--authorized-voter",
|
|
|
|
&authed.to_string(),
|
|
|
|
]);
|
|
|
|
assert_eq!(
|
2019-10-21 16:08:09 -07:00
|
|
|
parse_command(&test_create_vote_account3).unwrap(),
|
|
|
|
CliCommandInfo {
|
|
|
|
command: CliCommand::CreateVoteAccount {
|
2019-11-07 17:08:10 -08:00
|
|
|
vote_account: keypair.into(),
|
2020-01-09 15:22:48 -08:00
|
|
|
seed: None,
|
2019-09-25 13:53:49 -07:00
|
|
|
node_pubkey,
|
2019-10-21 16:08:09 -07:00
|
|
|
authorized_voter: Some(authed),
|
|
|
|
authorized_withdrawer: None,
|
2020-01-04 08:20:44 -08:00
|
|
|
commission: 100
|
2019-10-21 16:08:09 -07:00
|
|
|
},
|
|
|
|
require_keypair: true
|
|
|
|
}
|
2019-09-25 13:53:49 -07:00
|
|
|
);
|
2019-11-06 06:47:34 -08:00
|
|
|
|
|
|
|
let (keypair_file, mut tmp_file) = make_tmp_file();
|
|
|
|
let keypair = Keypair::new();
|
|
|
|
write_keypair(&keypair, tmp_file.as_file_mut()).unwrap();
|
2019-09-25 13:53:49 -07:00
|
|
|
// test init with an authed withdrawer
|
|
|
|
let test_create_vote_account4 = test_commands.clone().get_matches_from(vec![
|
|
|
|
"test",
|
|
|
|
"create-vote-account",
|
2019-11-06 06:47:34 -08:00
|
|
|
&keypair_file,
|
2019-09-25 13:53:49 -07:00
|
|
|
&node_pubkey_string,
|
|
|
|
"--authorized-withdrawer",
|
|
|
|
&authed.to_string(),
|
|
|
|
]);
|
|
|
|
assert_eq!(
|
2019-10-21 16:08:09 -07:00
|
|
|
parse_command(&test_create_vote_account4).unwrap(),
|
|
|
|
CliCommandInfo {
|
|
|
|
command: CliCommand::CreateVoteAccount {
|
2019-11-07 17:08:10 -08:00
|
|
|
vote_account: keypair.into(),
|
2020-01-09 15:22:48 -08:00
|
|
|
seed: None,
|
2019-09-25 13:53:49 -07:00
|
|
|
node_pubkey,
|
2019-10-21 16:08:09 -07:00
|
|
|
authorized_voter: None,
|
|
|
|
authorized_withdrawer: Some(authed),
|
2020-01-04 08:20:44 -08:00
|
|
|
commission: 100
|
2019-10-21 16:08:09 -07:00
|
|
|
},
|
|
|
|
require_keypair: true
|
|
|
|
}
|
2019-09-18 09:29:57 -07:00
|
|
|
);
|
|
|
|
|
2019-12-12 15:04:03 -08:00
|
|
|
let test_update_validator = test_commands.clone().get_matches_from(vec![
|
|
|
|
"test",
|
|
|
|
"vote-update-validator",
|
|
|
|
&pubkey_string,
|
|
|
|
&pubkey2_string,
|
|
|
|
&keypair_file,
|
|
|
|
]);
|
|
|
|
assert_eq!(
|
|
|
|
parse_command(&test_update_validator).unwrap(),
|
|
|
|
CliCommandInfo {
|
|
|
|
command: CliCommand::VoteUpdateValidator {
|
|
|
|
vote_account_pubkey: pubkey,
|
|
|
|
new_identity_pubkey: pubkey2,
|
|
|
|
authorized_voter: solana_sdk::signature::read_keypair_file(&keypair_file)
|
|
|
|
.unwrap()
|
|
|
|
.into(),
|
|
|
|
},
|
|
|
|
require_keypair: true
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
2019-09-18 09:29:57 -07:00
|
|
|
// Test Uptime Subcommand
|
|
|
|
let pubkey = Pubkey::new_rand();
|
|
|
|
let matches = test_commands.clone().get_matches_from(vec![
|
|
|
|
"test",
|
|
|
|
"uptime",
|
|
|
|
&pubkey.to_string(),
|
|
|
|
"--span",
|
|
|
|
"4",
|
|
|
|
"--aggregate",
|
|
|
|
]);
|
|
|
|
assert_eq!(
|
2019-10-21 16:08:09 -07:00
|
|
|
parse_command(&matches).unwrap(),
|
|
|
|
CliCommandInfo {
|
|
|
|
command: CliCommand::Uptime {
|
|
|
|
pubkey,
|
|
|
|
aggregate: true,
|
|
|
|
span: Some(4)
|
|
|
|
},
|
|
|
|
require_keypair: false
|
2019-09-18 09:29:57 -07:00
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|