use {
crate::{
admin_rpc_service,
commands::{FromClapArgMatches, Result},
},
clap::{value_t, App, AppSettings, Arg, ArgMatches, SubCommand},
solana_clap_utils::input_validators::is_keypair,
solana_keypair::read_keypair,
solana_signer::Signer,
std::{fs, path::Path},
};
const COMMAND: &str = "authorized-voter";
#[derive(Debug, PartialEq)]
#[cfg_attr(test, derive(Default))]
pub struct AuthorizedVoterAddArgs {
pub authorized_voter_keypair: Option<String>,
}
impl FromClapArgMatches for AuthorizedVoterAddArgs {
fn from_clap_arg_match(matches: &ArgMatches) -> Result<Self> {
Ok(AuthorizedVoterAddArgs {
authorized_voter_keypair: value_t!(matches, "authorized_voter_keypair", String).ok(),
})
}
}
pub fn command<'a>() -> App<'a, 'a> {
SubCommand::with_name(COMMAND)
.about("Adjust the validator authorized voters")
.setting(AppSettings::SubcommandRequiredElseHelp)
.setting(AppSettings::InferSubcommands)
.subcommand(
SubCommand::with_name("add")
.about("Add an authorized voter")
.arg(
Arg::with_name("authorized_voter_keypair")
.index(1)
.value_name("KEYPAIR")
.required(false)
.takes_value(true)
.validator(is_keypair)
.help(
"Path to keypair of the authorized voter to add [default: read JSON \
keypair from stdin]",
),
)
.after_help(
"Note: the new authorized voter only applies to the currently running \
validator instance",
),
)
.subcommand(
SubCommand::with_name("remove-all")
.about("Remove all authorized voters")
.after_help(
"Note: the removal only applies to the currently running validator instance",
),
)
}
pub fn execute(matches: &ArgMatches, ledger_path: &Path) -> Result<()> {
match matches.subcommand() {
("add", Some(subcommand_matches)) => {
let authorized_voter_add_args =
AuthorizedVoterAddArgs::from_clap_arg_match(subcommand_matches)?;
if let Some(authorized_voter_keypair) =
authorized_voter_add_args.authorized_voter_keypair
{
let authorized_voter_keypair = fs::canonicalize(&authorized_voter_keypair)?;
println!(
"Adding authorized voter path: {}",
authorized_voter_keypair.display()
);
let admin_client = admin_rpc_service::connect(ledger_path);
admin_rpc_service::runtime().block_on(async move {
admin_client
.await?
.add_authorized_voter(authorized_voter_keypair.display().to_string())
.await
})?;
} else {
let mut stdin = std::io::stdin();
let authorized_voter_keypair = read_keypair(&mut stdin)?;
println!(
"Adding authorized voter: {}",
authorized_voter_keypair.pubkey()
);
let admin_client = admin_rpc_service::connect(ledger_path);
admin_rpc_service::runtime().block_on(async move {
admin_client
.await?
.add_authorized_voter_from_bytes(Vec::from(
authorized_voter_keypair.to_bytes(),
))
.await
})?;
}
}
("remove-all", _) => {
let admin_client = admin_rpc_service::connect(ledger_path);
admin_rpc_service::runtime().block_on(async move {
admin_client.await?.remove_all_authorized_voters().await
})?;
println!("All authorized voters removed");
}
_ => unreachable!(),
}
Ok(())
}
#[cfg(test)]
mod tests {
use {super::*, solana_keypair::Keypair};
#[test]
fn verify_args_struct_by_command_authorized_voter_add_default() {
let app = command();
let matches = app.get_matches_from(vec![COMMAND, "add"]);
let subcommand_matches = matches.subcommand_matches("add").unwrap();
let args = AuthorizedVoterAddArgs::from_clap_arg_match(subcommand_matches).unwrap();
assert_eq!(args, AuthorizedVoterAddArgs::default());
}
#[test]
fn verify_args_struct_by_command_authorized_voter_add_with_authorized_voter_keypair() {
let tmp_dir = tempfile::tempdir().unwrap();
let file = tmp_dir.path().join("id.json");
let keypair = Keypair::new();
solana_keypair::write_keypair_file(&keypair, &file).unwrap();
let app = command();
let matches = app.get_matches_from(vec![COMMAND, "add", file.to_str().unwrap()]);
let subcommand_matches = matches.subcommand_matches("add").unwrap();
let args = AuthorizedVoterAddArgs::from_clap_arg_match(subcommand_matches).unwrap();
assert_eq!(
args,
AuthorizedVoterAddArgs {
authorized_voter_keypair: Some(file.to_str().unwrap().to_string()),
}
);
}
}