From 8319fa05d042efaafd170c113696483a9a03f4be Mon Sep 17 00:00:00 2001 From: Tyera Eulberg Date: Mon, 21 Oct 2019 17:08:09 -0600 Subject: [PATCH] solana-cli: selectively require keypair (#6477) * Make parse_command consistent * Strip pubkey out of parse_stake_create_account * Move validator-info args into module * Strip pubkey out of parse_validator_info_command * Strip pubkey out of parse_vote_create_account * Strip pubkey out of balance parsing * Strip pubkey out of parse pay * Only verify keypair existence if command requires it * Use struct instead of tuple --- cli/src/cli.rs | 654 +++++++++++++++++++++----------------- cli/src/cluster_query.rs | 81 +++-- cli/src/main.rs | 58 ++-- cli/src/stake.rs | 200 +++++++----- cli/src/storage.rs | 85 +++-- cli/src/validator_info.rs | 152 +++++++-- cli/src/vote.rs | 177 ++++++----- cli/tests/pay.rs | 6 +- 8 files changed, 842 insertions(+), 571 deletions(-) diff --git a/cli/src/cli.rs b/cli/src/cli.rs index ac2c79064..88220993d 100644 --- a/cli/src/cli.rs +++ b/cli/src/cli.rs @@ -6,7 +6,7 @@ use chrono::prelude::*; use clap::{App, AppSettings, Arg, ArgMatches, SubCommand}; use log::*; use num_traits::FromPrimitive; -use serde_json::{self, json}; +use serde_json::{self, json, Value}; use solana_budget_api::budget_instruction::{self, BudgetError}; use solana_client::{client_error::ClientError, rpc_client::RpcClient}; #[cfg(not(test))] @@ -28,9 +28,9 @@ use solana_sdk::{ system_transaction, transaction::{Transaction, TransactionError}, }; -use solana_stake_api::stake_state::{Authorized, Lockup, StakeAuthorize}; +use solana_stake_api::stake_state::{Lockup, StakeAuthorize}; use solana_storage_api::storage_instruction::StorageAccountType; -use solana_vote_api::vote_state::{VoteAuthorize, VoteInit}; +use solana_vote_api::vote_state::VoteAuthorize; use std::{ fs::File, io::{Read, Write}, @@ -63,7 +63,13 @@ pub enum CliCommand { // Program Deployment Deploy(String), // Stake Commands - CreateStakeAccount(Pubkey, Authorized, Lockup, u64), + CreateStakeAccount { + stake_account_pubkey: Pubkey, + staker: Option, + withdrawer: Option, + lockup: Lockup, + lamports: u64, + }, DeactivateStake(Pubkey), DelegateStake(Pubkey, Pubkey, bool), RedeemVoteCredits(Pubkey, Pubkey), @@ -86,9 +92,19 @@ pub enum CliCommand { ShowStorageAccount(Pubkey), // Validator Info Commands GetValidatorInfo(Option), - SetValidatorInfo(ValidatorInfo, Option), + SetValidatorInfo { + validator_info: Value, + force_keybase: bool, + info_pubkey: Option, + }, // Vote Commands - CreateVoteAccount(Pubkey, VoteInit), + CreateVoteAccount { + vote_account_pubkey: Pubkey, + node_pubkey: Pubkey, + authorized_voter: Option, + authorized_withdrawer: Option, + commission: u8, + }, ShowVoteAccount { pubkey: Pubkey, use_lamports_unit: bool, @@ -108,7 +124,7 @@ pub enum CliCommand { use_lamports_unit: bool, }, Balance { - pubkey: Pubkey, + pubkey: Option, use_lamports_unit: bool, }, Cancel(Pubkey), @@ -119,7 +135,7 @@ pub enum CliCommand { timestamp: Option>, timestamp_pubkey: Option, witnesses: Option>, - cancelable: Option, + cancelable: bool, }, ShowAccount { pubkey: Pubkey, @@ -130,6 +146,12 @@ pub enum CliCommand { Witness(Pubkey, Pubkey), // Witness(to, process_id) } +#[derive(Debug, PartialEq)] +pub struct CliCommandInfo { + pub command: CliCommand, + pub require_keypair: bool, +} + #[derive(Debug, Clone)] pub enum CliError { BadParameter(String), @@ -161,7 +183,7 @@ pub struct CliConfig { pub command: CliCommand, pub json_rpc_url: String, pub keypair: Keypair, - pub keypair_path: String, + pub keypair_path: Option, pub rpc_client: Option, } @@ -172,40 +194,53 @@ impl Default for CliConfig { CliConfig { command: CliCommand::Balance { - pubkey: Pubkey::default(), + pubkey: Some(Pubkey::default()), use_lamports_unit: false, }, json_rpc_url: "http://127.0.0.1:8899".to_string(), keypair: Keypair::new(), - keypair_path: keypair_path.to_str().unwrap().to_string(), + keypair_path: Some(keypair_path.to_str().unwrap().to_string()), rpc_client: None, } } } -pub fn parse_command( - pubkey: &Pubkey, - matches: &ArgMatches<'_>, -) -> Result> { +pub fn parse_command(matches: &ArgMatches<'_>) -> Result> { let response = match matches.subcommand() { // Cluster Query Commands - ("cluster-version", Some(_matches)) => Ok(CliCommand::ClusterVersion), - ("fees", Some(_fees_matches)) => Ok(CliCommand::Fees), - ("get-epoch-info", Some(_matches)) => Ok(CliCommand::GetEpochInfo), - ("get-genesis-blockhash", Some(_matches)) => Ok(CliCommand::GetGenesisBlockhash), - ("get-slot", Some(_matches)) => Ok(CliCommand::GetSlot), - ("get-transaction-count", Some(_matches)) => Ok(CliCommand::GetTransactionCount), + ("cluster-version", Some(_matches)) => Ok(CliCommandInfo { + command: CliCommand::ClusterVersion, + require_keypair: false, + }), + ("fees", Some(_matches)) => Ok(CliCommandInfo { + command: CliCommand::Fees, + require_keypair: false, + }), + ("get-epoch-info", Some(_matches)) => Ok(CliCommandInfo { + command: CliCommand::GetEpochInfo, + require_keypair: false, + }), + ("get-genesis-blockhash", Some(_matches)) => Ok(CliCommandInfo { + command: CliCommand::GetGenesisBlockhash, + require_keypair: false, + }), + ("get-slot", Some(_matches)) => Ok(CliCommandInfo { + command: CliCommand::GetSlot, + require_keypair: false, + }), + ("get-transaction-count", Some(_matches)) => Ok(CliCommandInfo { + command: CliCommand::GetTransactionCount, + require_keypair: false, + }), ("ping", Some(matches)) => parse_cluster_ping(matches), ("show-validators", Some(matches)) => parse_show_validators(matches), // Program Deployment - ("deploy", Some(deploy_matches)) => Ok(CliCommand::Deploy( - deploy_matches - .value_of("program_location") - .unwrap() - .to_string(), - )), + ("deploy", Some(matches)) => Ok(CliCommandInfo { + command: CliCommand::Deploy(matches.value_of("program_location").unwrap().to_string()), + require_keypair: true, + }), // Stake Commands - ("create-stake-account", Some(matches)) => parse_stake_create_account(pubkey, matches), + ("create-stake-account", Some(matches)) => parse_stake_create_account(matches), ("delegate-stake", Some(matches)) => parse_stake_delegate_stake(matches), ("withdraw-stake", Some(matches)) => parse_stake_withdraw_stake(matches), ("deactivate-stake", Some(matches)) => parse_stake_deactivate_stake(matches), @@ -228,7 +263,7 @@ pub fn parse_command( ("show-storage-account", Some(matches)) => parse_storage_get_account_command(matches), // Validator Info Commands ("validator-info", Some(matches)) => match matches.subcommand() { - ("publish", Some(matches)) => parse_validator_info_command(matches, pubkey), + ("publish", Some(matches)) => parse_validator_info_command(matches), ("get", Some(matches)) => parse_get_validator_info_command(matches), ("", None) => { eprintln!("{}", matches.usage()); @@ -239,7 +274,7 @@ pub fn parse_command( _ => unreachable!(), }, // Vote Commands - ("create-vote-account", Some(matches)) => parse_vote_create_account(pubkey, matches), + ("create-vote-account", Some(matches)) => parse_vote_create_account(matches), ("vote-authorize-voter", Some(matches)) => { parse_vote_authorize(matches, VoteAuthorize::Voter) } @@ -249,9 +284,12 @@ pub fn parse_command( ("show-vote-account", Some(matches)) => parse_vote_get_account_command(matches), ("uptime", Some(matches)) => parse_vote_uptime_command(matches), // Wallet Commands - ("address", Some(_address_matches)) => Ok(CliCommand::Address), - ("airdrop", Some(airdrop_matches)) => { - let drone_port = airdrop_matches + ("address", Some(_matches)) => Ok(CliCommandInfo { + command: CliCommand::Address, + require_keypair: true, + }), + ("airdrop", Some(matches)) => { + let drone_port = matches .value_of("drone_port") .unwrap() .parse() @@ -272,102 +310,116 @@ pub fn parse_command( } else { None }; - let lamports = amount_of(airdrop_matches, "amount", "unit").expect("Invalid amount"); - let use_lamports_unit = airdrop_matches.value_of("unit").is_some() - && airdrop_matches.value_of("unit").unwrap() == "lamports"; - Ok(CliCommand::Airdrop { - drone_host, - drone_port, - lamports, - use_lamports_unit, + let lamports = amount_of(matches, "amount", "unit").expect("Invalid amount"); + let use_lamports_unit = matches.value_of("unit").is_some() + && matches.value_of("unit").unwrap() == "lamports"; + Ok(CliCommandInfo { + command: CliCommand::Airdrop { + drone_host, + drone_port, + lamports, + use_lamports_unit, + }, + require_keypair: true, }) } - ("balance", Some(balance_matches)) => { - let pubkey = pubkey_of(&balance_matches, "pubkey").unwrap_or(*pubkey); - let use_lamports_unit = balance_matches.is_present("lamports"); - Ok(CliCommand::Balance { - pubkey, - use_lamports_unit, + ("balance", Some(matches)) => { + let pubkey = pubkey_of(&matches, "pubkey"); + println!("{:?}", pubkey); + Ok(CliCommandInfo { + command: CliCommand::Balance { + pubkey, + use_lamports_unit: matches.is_present("lamports"), + }, + require_keypair: pubkey.is_none(), }) } - ("cancel", Some(cancel_matches)) => { - let process_id = value_of(cancel_matches, "process_id").unwrap(); - Ok(CliCommand::Cancel(process_id)) + ("cancel", Some(matches)) => { + let process_id = value_of(matches, "process_id").unwrap(); + Ok(CliCommandInfo { + command: CliCommand::Cancel(process_id), + require_keypair: true, + }) } - ("confirm", Some(confirm_matches)) => { - match confirm_matches.value_of("signature").unwrap().parse() { - Ok(signature) => Ok(CliCommand::Confirm(signature)), - _ => { - eprintln!("{}", confirm_matches.usage()); - Err(CliError::BadParameter("Invalid signature".to_string())) - } + ("confirm", Some(matches)) => match matches.value_of("signature").unwrap().parse() { + Ok(signature) => Ok(CliCommandInfo { + command: CliCommand::Confirm(signature), + require_keypair: false, + }), + _ => { + eprintln!("{}", matches.usage()); + Err(CliError::BadParameter("Invalid signature".to_string())) } - } - ("pay", Some(pay_matches)) => { - let lamports = amount_of(pay_matches, "amount", "unit").expect("Invalid amount"); - let to = value_of(&pay_matches, "to").unwrap_or(*pubkey); - let timestamp = if pay_matches.is_present("timestamp") { + }, + ("pay", Some(matches)) => { + let lamports = amount_of(matches, "amount", "unit").expect("Invalid amount"); + let to = value_of(&matches, "to").unwrap(); + let timestamp = if matches.is_present("timestamp") { // Parse input for serde_json - let date_string = if !pay_matches.value_of("timestamp").unwrap().contains('Z') { - format!("\"{}Z\"", pay_matches.value_of("timestamp").unwrap()) + let date_string = if !matches.value_of("timestamp").unwrap().contains('Z') { + format!("\"{}Z\"", matches.value_of("timestamp").unwrap()) } else { - format!("\"{}\"", pay_matches.value_of("timestamp").unwrap()) + format!("\"{}\"", matches.value_of("timestamp").unwrap()) }; Some(serde_json::from_str(&date_string)?) } else { None }; - let timestamp_pubkey = value_of(&pay_matches, "timestamp_pubkey"); - let witnesses = values_of(&pay_matches, "witness"); - let cancelable = if pay_matches.is_present("cancelable") { - Some(*pubkey) - } else { - None - }; + let timestamp_pubkey = value_of(&matches, "timestamp_pubkey"); + let witnesses = values_of(&matches, "witness"); + let cancelable = matches.is_present("cancelable"); - Ok(CliCommand::Pay { - lamports, - to, - timestamp, - timestamp_pubkey, - witnesses, - cancelable, + Ok(CliCommandInfo { + command: CliCommand::Pay { + lamports, + to, + timestamp, + timestamp_pubkey, + witnesses, + cancelable, + }, + require_keypair: true, }) } ("show-account", Some(matches)) => { let account_pubkey = pubkey_of(matches, "account_pubkey").unwrap(); let output_file = matches.value_of("output_file"); let use_lamports_unit = matches.is_present("lamports"); - Ok(CliCommand::ShowAccount { - pubkey: account_pubkey, - output_file: output_file.map(ToString::to_string), - use_lamports_unit, + Ok(CliCommandInfo { + command: CliCommand::ShowAccount { + pubkey: account_pubkey, + output_file: output_file.map(ToString::to_string), + use_lamports_unit, + }, + require_keypair: false, }) } - ("send-signature", Some(sig_matches)) => { - let to = value_of(&sig_matches, "to").unwrap(); - let process_id = value_of(&sig_matches, "process_id").unwrap(); - Ok(CliCommand::Witness(to, process_id)) + ("send-signature", Some(matches)) => { + let to = value_of(&matches, "to").unwrap(); + let process_id = value_of(&matches, "process_id").unwrap(); + Ok(CliCommandInfo { + command: CliCommand::Witness(to, process_id), + require_keypair: true, + }) } - ("send-timestamp", Some(timestamp_matches)) => { - let to = value_of(×tamp_matches, "to").unwrap(); - let process_id = value_of(×tamp_matches, "process_id").unwrap(); - let dt = if timestamp_matches.is_present("datetime") { + ("send-timestamp", Some(matches)) => { + let to = value_of(&matches, "to").unwrap(); + let process_id = value_of(&matches, "process_id").unwrap(); + let dt = if matches.is_present("datetime") { // Parse input for serde_json - let date_string = if !timestamp_matches - .value_of("datetime") - .unwrap() - .contains('Z') - { - format!("\"{}Z\"", timestamp_matches.value_of("datetime").unwrap()) + let date_string = if !matches.value_of("datetime").unwrap().contains('Z') { + format!("\"{}Z\"", matches.value_of("datetime").unwrap()) } else { - format!("\"{}\"", timestamp_matches.value_of("datetime").unwrap()) + format!("\"{}\"", matches.value_of("datetime").unwrap()) }; serde_json::from_str(&date_string)? } else { Utc::now() }; - Ok(CliCommand::TimeElapsed(to, process_id, dt)) + Ok(CliCommandInfo { + command: CliCommand::TimeElapsed(to, process_id, dt), + require_keypair: true, + }) } ("", None) => { eprintln!("{}", matches.usage()); @@ -457,11 +509,13 @@ fn process_airdrop( } fn process_balance( - pubkey: &Pubkey, rpc_client: &RpcClient, + config: &CliConfig, + pubkey: &Option, use_lamports_unit: bool, ) -> ProcessResult { - let balance = rpc_client.retry_get_balance(pubkey, 5)?; + let pubkey = pubkey.unwrap_or(config.keypair.pubkey()); + let balance = rpc_client.retry_get_balance(&pubkey, 5)?; match balance { Some(lamports) => Ok(build_balance_message(lamports, use_lamports_unit)), None => Err( @@ -600,7 +654,7 @@ fn process_pay( timestamp: Option>, timestamp_pubkey: Option, witnesses: &Option>, - cancelable: Option, + cancelable: bool, ) -> ProcessResult { check_unique_pubkeys( (&config.keypair.pubkey(), "cli keypair".to_string()), @@ -608,6 +662,12 @@ fn process_pay( )?; let (blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?; + let cancelable = if cancelable { + Some(config.keypair.pubkey()) + } else { + None + }; + if timestamp == None && *witnesses == None { let mut tx = system_transaction::transfer(&config.keypair, to, lamports, blockhash); check_account_for_fee(rpc_client, config, &fee_calculator, &tx.message)?; @@ -725,7 +785,9 @@ fn process_witness( } pub fn process_command(config: &CliConfig) -> ProcessResult { - println_name_value("Keypair:", &config.keypair_path); + if let Some(keypair_path) = &config.keypair_path { + println_name_value("Keypair:", keypair_path); + } if let CliCommand::Address = config.command { // Get address of this client return Ok(format!("{}", config.keypair.pubkey())); @@ -770,16 +832,21 @@ pub fn process_command(config: &CliConfig) -> ProcessResult { // Stake Commands // Create stake account - CliCommand::CreateStakeAccount(stake_account_pubkey, authorized, lockup, lamports) => { - process_create_stake_account( - &rpc_client, - config, - &stake_account_pubkey, - &authorized, - lockup, - *lamports, - ) - } + CliCommand::CreateStakeAccount { + stake_account_pubkey, + staker, + withdrawer, + lockup, + lamports, + } => process_create_stake_account( + &rpc_client, + config, + &stake_account_pubkey, + staker, + withdrawer, + lockup, + *lamports, + ), // Deactivate stake account CliCommand::DeactivateStake(stake_account_pubkey) => { process_deactivate_stake_account(&rpc_client, config, &stake_account_pubkey) @@ -866,16 +933,36 @@ pub fn process_command(config: &CliConfig) -> ProcessResult { process_get_validator_info(&rpc_client, *info_pubkey) } // Publish validator info - CliCommand::SetValidatorInfo(validator_info, info_pubkey) => { - process_set_validator_info(&rpc_client, config, &validator_info, *info_pubkey) - } + CliCommand::SetValidatorInfo { + validator_info, + force_keybase, + info_pubkey, + } => process_set_validator_info( + &rpc_client, + config, + &validator_info, + *force_keybase, + *info_pubkey, + ), // Vote Commands // Create vote account - CliCommand::CreateVoteAccount(vote_account_pubkey, vote_init) => { - process_create_vote_account(&rpc_client, config, &vote_account_pubkey, &vote_init) - } + CliCommand::CreateVoteAccount { + vote_account_pubkey, + node_pubkey, + authorized_voter, + authorized_withdrawer, + commission, + } => process_create_vote_account( + &rpc_client, + config, + &vote_account_pubkey, + &node_pubkey, + authorized_voter, + authorized_withdrawer, + *commission, + ), CliCommand::ShowVoteAccount { pubkey: vote_account_pubkey, use_lamports_unit, @@ -937,7 +1024,7 @@ pub fn process_command(config: &CliConfig) -> ProcessResult { CliCommand::Balance { pubkey, use_lamports_unit, - } => process_balance(&pubkey, &rpc_client, *use_lamports_unit), + } => process_balance(&rpc_client, config, &pubkey, *use_lamports_unit), // Cancel a contract by contract Pubkey CliCommand::Cancel(pubkey) => process_cancel(&rpc_client, config, &pubkey), // Confirm the last client transaction by signature @@ -1311,78 +1398,7 @@ pub fn app<'ab, 'v>(name: &str, about: &'ab str, version: &'v str) -> App<'ab, ' .help("Display balance in lamports instead of SOL"), ), ) - .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"), - ), - ) - ) + .validator_info_subcommands() .vote_subcommands() } @@ -1416,8 +1432,7 @@ mod tests { fn test_bad_amount() { let test_commands = app("test", "desc", "version"); let test_bad_airdrop = test_commands.get_matches_from(vec!["test", "airdrop", "notint"]); - let pubkey = Pubkey::new_rand(); - let _ignored = parse_command(&pubkey, &test_bad_airdrop).unwrap(); + let _ignored = parse_command(&test_bad_airdrop).unwrap(); } #[test] @@ -1437,12 +1452,15 @@ mod tests { .clone() .get_matches_from(vec!["test", "airdrop", "50", "lamports"]); assert_eq!( - parse_command(&pubkey, &test_airdrop).unwrap(), - CliCommand::Airdrop { - drone_host: None, - drone_port: solana_drone::drone::DRONE_PORT, - lamports: 50, - use_lamports_unit: true, + parse_command(&test_airdrop).unwrap(), + CliCommandInfo { + command: CliCommand::Airdrop { + drone_host: None, + drone_port: solana_drone::drone::DRONE_PORT, + lamports: 50, + use_lamports_unit: true, + }, + require_keypair: true, } ); @@ -1456,10 +1474,13 @@ mod tests { &keypair.pubkey().to_string(), ]); assert_eq!( - parse_command(&pubkey, &test_balance).unwrap(), - CliCommand::Balance { - pubkey: keypair.pubkey(), - use_lamports_unit: false + parse_command(&test_balance).unwrap(), + CliCommandInfo { + command: CliCommand::Balance { + pubkey: Some(keypair.pubkey()), + use_lamports_unit: false + }, + require_keypair: false } ); let test_balance = test_commands.clone().get_matches_from(vec![ @@ -1469,10 +1490,27 @@ mod tests { "--lamports", ]); assert_eq!( - parse_command(&pubkey, &test_balance).unwrap(), - CliCommand::Balance { - pubkey: keypair.pubkey(), - use_lamports_unit: true + parse_command(&test_balance).unwrap(), + CliCommandInfo { + command: CliCommand::Balance { + pubkey: Some(keypair.pubkey()), + use_lamports_unit: true + }, + require_keypair: false + } + ); + let test_balance = + test_commands + .clone() + .get_matches_from(vec!["test", "balance", "--lamports"]); + assert_eq!( + parse_command(&test_balance).unwrap(), + CliCommandInfo { + command: CliCommand::Balance { + pubkey: None, + use_lamports_unit: true + }, + require_keypair: true } ); @@ -1482,8 +1520,11 @@ mod tests { .clone() .get_matches_from(vec!["test", "cancel", &pubkey_string]); assert_eq!( - parse_command(&pubkey, &test_cancel).unwrap(), - CliCommand::Cancel(pubkey) + parse_command(&test_cancel).unwrap(), + CliCommandInfo { + command: CliCommand::Cancel(pubkey), + require_keypair: true + } ); // Test Confirm Subcommand @@ -1494,13 +1535,16 @@ mod tests { .clone() .get_matches_from(vec!["test", "confirm", &signature_string]); assert_eq!( - parse_command(&pubkey, &test_confirm).unwrap(), - CliCommand::Confirm(signature) + parse_command(&test_confirm).unwrap(), + CliCommandInfo { + command: CliCommand::Confirm(signature), + require_keypair: false + } ); let test_bad_signature = test_commands .clone() .get_matches_from(vec!["test", "confirm", "deadbeef"]); - assert!(parse_command(&pubkey, &test_bad_signature).is_err()); + assert!(parse_command(&test_bad_signature).is_err()); // Test Deploy Subcommand let test_deploy = @@ -1508,8 +1552,11 @@ mod tests { .clone() .get_matches_from(vec!["test", "deploy", "/Users/test/program.o"]); assert_eq!( - parse_command(&pubkey, &test_deploy).unwrap(), - CliCommand::Deploy("/Users/test/program.o".to_string()) + parse_command(&test_deploy).unwrap(), + CliCommandInfo { + command: CliCommand::Deploy("/Users/test/program.o".to_string()), + require_keypair: true + } ); // Test Simple Pay Subcommand @@ -1521,14 +1568,17 @@ mod tests { "lamports", ]); assert_eq!( - parse_command(&pubkey, &test_pay).unwrap(), - CliCommand::Pay { - lamports: 50, - to: pubkey, - timestamp: None, - timestamp_pubkey: None, - witnesses: None, - cancelable: None + parse_command(&test_pay).unwrap(), + CliCommandInfo { + command: CliCommand::Pay { + lamports: 50, + to: pubkey, + timestamp: None, + timestamp_pubkey: None, + witnesses: None, + cancelable: false, + }, + require_keypair: true } ); @@ -1545,14 +1595,17 @@ mod tests { &witness1_string, ]); assert_eq!( - parse_command(&pubkey, &test_pay_multiple_witnesses).unwrap(), - CliCommand::Pay { - lamports: 50, - to: pubkey, - timestamp: None, - timestamp_pubkey: None, - witnesses: Some(vec![witness0, witness1]), - cancelable: None + parse_command(&test_pay_multiple_witnesses).unwrap(), + CliCommandInfo { + command: CliCommand::Pay { + lamports: 50, + to: pubkey, + timestamp: None, + timestamp_pubkey: None, + witnesses: Some(vec![witness0, witness1]), + cancelable: false, + }, + require_keypair: true } ); let test_pay_single_witness = test_commands.clone().get_matches_from(vec![ @@ -1565,14 +1618,17 @@ mod tests { &witness0_string, ]); assert_eq!( - parse_command(&pubkey, &test_pay_single_witness).unwrap(), - CliCommand::Pay { - lamports: 50, - to: pubkey, - timestamp: None, - timestamp_pubkey: None, - witnesses: Some(vec![witness0]), - cancelable: None + parse_command(&test_pay_single_witness).unwrap(), + CliCommandInfo { + command: CliCommand::Pay { + lamports: 50, + to: pubkey, + timestamp: None, + timestamp_pubkey: None, + witnesses: Some(vec![witness0]), + cancelable: false, + }, + require_keypair: true } ); @@ -1589,14 +1645,17 @@ mod tests { &witness0_string, ]); assert_eq!( - parse_command(&pubkey, &test_pay_timestamp).unwrap(), - CliCommand::Pay { - lamports: 50, - to: pubkey, - timestamp: Some(dt), - timestamp_pubkey: Some(witness0), - witnesses: None, - cancelable: None + parse_command(&test_pay_timestamp).unwrap(), + CliCommandInfo { + command: CliCommand::Pay { + lamports: 50, + to: pubkey, + timestamp: Some(dt), + timestamp_pubkey: Some(witness0), + witnesses: None, + cancelable: false, + }, + require_keypair: true } ); @@ -1608,8 +1667,11 @@ mod tests { &pubkey_string, ]); assert_eq!( - parse_command(&pubkey, &test_send_signature).unwrap(), - CliCommand::Witness(pubkey, pubkey) + parse_command(&test_send_signature).unwrap(), + CliCommandInfo { + command: CliCommand::Witness(pubkey, pubkey), + require_keypair: true + } ); let test_pay_multiple_witnesses = test_commands.clone().get_matches_from(vec![ "test", @@ -1627,14 +1689,17 @@ mod tests { &witness1_string, ]); assert_eq!( - parse_command(&pubkey, &test_pay_multiple_witnesses).unwrap(), - CliCommand::Pay { - lamports: 50, - to: pubkey, - timestamp: Some(dt), - timestamp_pubkey: Some(witness0), - witnesses: Some(vec![witness0, witness1]), - cancelable: None + parse_command(&test_pay_multiple_witnesses).unwrap(), + CliCommandInfo { + command: CliCommand::Pay { + lamports: 50, + to: pubkey, + timestamp: Some(dt), + timestamp_pubkey: Some(witness0), + witnesses: Some(vec![witness0, witness1]), + cancelable: false, + }, + require_keypair: true } ); @@ -1648,8 +1713,11 @@ mod tests { "2018-09-19T17:30:59", ]); assert_eq!( - parse_command(&pubkey, &test_send_timestamp).unwrap(), - CliCommand::TimeElapsed(pubkey, pubkey, dt) + parse_command(&test_send_timestamp).unwrap(), + CliCommandInfo { + command: CliCommand::TimeElapsed(pubkey, pubkey, dt), + require_keypair: true + } ); let test_bad_timestamp = test_commands.clone().get_matches_from(vec![ "test", @@ -1659,7 +1727,7 @@ mod tests { "--date", "20180919T17:30:59", ]); - assert!(parse_command(&pubkey, &test_bad_timestamp).is_err()); + assert!(parse_command(&test_bad_timestamp).is_err()); } #[test] @@ -1675,13 +1743,13 @@ mod tests { assert_eq!(process_command(&config).unwrap(), pubkey); config.command = CliCommand::Balance { - pubkey: config.keypair.pubkey(), + pubkey: None, use_lamports_unit: true, }; assert_eq!(process_command(&config).unwrap(), "50 lamports"); config.command = CliCommand::Balance { - pubkey: config.keypair.pubkey(), + pubkey: None, use_lamports_unit: false, }; assert_eq!(process_command(&config).unwrap(), "0 SOL"); @@ -1696,15 +1764,13 @@ mod tests { let bob_pubkey = Pubkey::new_rand(); let node_pubkey = Pubkey::new_rand(); - config.command = CliCommand::CreateVoteAccount( - bob_pubkey, - VoteInit { - node_pubkey, - authorized_voter: bob_pubkey, - authorized_withdrawer: bob_pubkey, - commission: 0, - }, - ); + config.command = CliCommand::CreateVoteAccount { + vote_account_pubkey: bob_pubkey, + node_pubkey, + authorized_voter: Some(bob_pubkey), + authorized_withdrawer: Some(bob_pubkey), + commission: 0, + }; let signature = process_command(&config); assert_eq!(signature.unwrap(), SIGNATURE.to_string()); @@ -1716,15 +1782,13 @@ mod tests { let bob_pubkey = Pubkey::new_rand(); let custodian = Pubkey::new_rand(); - config.command = CliCommand::CreateStakeAccount( - bob_pubkey, - Authorized { - staker: config.keypair.pubkey(), - withdrawer: config.keypair.pubkey(), - }, - Lockup { slot: 0, custodian }, - 1234, - ); + config.command = CliCommand::CreateStakeAccount { + stake_account_pubkey: bob_pubkey, + staker: None, + withdrawer: None, + lockup: Lockup { slot: 0, custodian }, + lamports: 1234, + }; let signature = process_command(&config); assert_eq!(signature.unwrap(), SIGNATURE.to_string()); @@ -1751,7 +1815,7 @@ mod tests { timestamp: None, timestamp_pubkey: None, witnesses: None, - cancelable: None, + cancelable: false, }; let signature = process_command(&config); assert_eq!(signature.unwrap(), SIGNATURE.to_string()); @@ -1764,7 +1828,7 @@ mod tests { timestamp: Some(dt), timestamp_pubkey: Some(config.keypair.pubkey()), witnesses: None, - cancelable: None, + cancelable: false, }; let result = process_command(&config); let json: Value = serde_json::from_str(&result.unwrap()).unwrap(); @@ -1785,7 +1849,7 @@ mod tests { timestamp: None, timestamp_pubkey: None, witnesses: Some(vec![witness]), - cancelable: Some(config.keypair.pubkey()), + cancelable: true, }; let result = process_command(&config); let json: Value = serde_json::from_str(&result.unwrap()).unwrap(); @@ -1858,20 +1922,18 @@ mod tests { assert!(process_command(&config).is_err()); config.command = CliCommand::Balance { - pubkey: config.keypair.pubkey(), + pubkey: None, use_lamports_unit: false, }; assert!(process_command(&config).is_err()); - config.command = CliCommand::CreateVoteAccount( - bob_pubkey, - VoteInit { - node_pubkey, - authorized_voter: bob_pubkey, - authorized_withdrawer: bob_pubkey, - commission: 0, - }, - ); + config.command = CliCommand::CreateVoteAccount { + vote_account_pubkey: bob_pubkey, + node_pubkey, + authorized_voter: Some(bob_pubkey), + authorized_withdrawer: Some(bob_pubkey), + commission: 0, + }; assert!(process_command(&config).is_err()); config.command = CliCommand::VoteAuthorize(bob_pubkey, bob_pubkey, VoteAuthorize::Voter); @@ -1889,7 +1951,7 @@ mod tests { timestamp: None, timestamp_pubkey: None, witnesses: None, - cancelable: None, + cancelable: false, }; assert!(process_command(&config).is_err()); @@ -1899,7 +1961,7 @@ mod tests { timestamp: Some(dt), timestamp_pubkey: Some(config.keypair.pubkey()), witnesses: None, - cancelable: None, + cancelable: false, }; assert!(process_command(&config).is_err()); @@ -1909,7 +1971,7 @@ mod tests { timestamp: None, timestamp_pubkey: None, witnesses: Some(vec![witness]), - cancelable: Some(config.keypair.pubkey()), + cancelable: true, }; assert!(process_command(&config).is_err()); diff --git a/cli/src/cluster_query.rs b/cli/src/cluster_query.rs index 853b7dde8..62e2d87dd 100644 --- a/cli/src/cluster_query.rs +++ b/cli/src/cluster_query.rs @@ -1,7 +1,7 @@ use crate::{ cli::{ - build_balance_message, check_account_for_fee, CliCommand, CliConfig, CliError, - ProcessResult, + build_balance_message, check_account_for_fee, CliCommand, CliCommandInfo, CliConfig, + CliError, ProcessResult, }, display::println_name_value, }; @@ -88,7 +88,7 @@ impl ClusterQuerySubCommands for App<'_, '_> { } } -pub fn parse_cluster_ping(matches: &ArgMatches<'_>) -> Result { +pub fn parse_cluster_ping(matches: &ArgMatches<'_>) -> Result { let interval = Duration::from_secs(value_t_or_exit!(matches, "interval", u64)); let count = if matches.is_present("count") { Some(value_t_or_exit!(matches, "count", u64)) @@ -96,17 +96,23 @@ pub fn parse_cluster_ping(matches: &ArgMatches<'_>) -> Result) -> Result { +pub fn parse_show_validators(matches: &ArgMatches<'_>) -> Result { let use_lamports_unit = matches.is_present("lamports"); - Ok(CliCommand::ShowValidators { use_lamports_unit }) + Ok(CliCommandInfo { + command: CliCommand::ShowValidators { use_lamports_unit }, + require_keypair: false, + }) } pub fn process_cluster_version(rpc_client: &RpcClient, config: &CliConfig) -> ProcessResult { @@ -358,68 +364,87 @@ pub fn process_show_validators(rpc_client: &RpcClient, use_lamports_unit: bool) mod tests { use super::*; use crate::cli::{app, parse_command}; - use solana_sdk::pubkey::Pubkey; #[test] fn test_parse_command() { let test_commands = app("test", "desc", "version"); - let pubkey = Pubkey::new_rand(); let test_cluster_version = test_commands .clone() .get_matches_from(vec!["test", "cluster-version"]); assert_eq!( - parse_command(&pubkey, &test_cluster_version).unwrap(), - CliCommand::ClusterVersion + parse_command(&test_cluster_version).unwrap(), + CliCommandInfo { + command: CliCommand::ClusterVersion, + require_keypair: false + } ); let test_fees = test_commands.clone().get_matches_from(vec!["test", "fees"]); assert_eq!( - parse_command(&pubkey, &test_fees).unwrap(), - CliCommand::Fees + parse_command(&test_fees).unwrap(), + CliCommandInfo { + command: CliCommand::Fees, + require_keypair: false + } ); let test_get_epoch_info = test_commands .clone() .get_matches_from(vec!["test", "get-epoch-info"]); assert_eq!( - parse_command(&pubkey, &test_get_epoch_info).unwrap(), - CliCommand::GetEpochInfo + parse_command(&test_get_epoch_info).unwrap(), + CliCommandInfo { + command: CliCommand::GetEpochInfo, + require_keypair: false + } ); let test_get_genesis_blockhash = test_commands .clone() .get_matches_from(vec!["test", "get-genesis-blockhash"]); assert_eq!( - parse_command(&pubkey, &test_get_genesis_blockhash).unwrap(), - CliCommand::GetGenesisBlockhash + parse_command(&test_get_genesis_blockhash).unwrap(), + CliCommandInfo { + command: CliCommand::GetGenesisBlockhash, + require_keypair: false + } ); let test_get_slot = test_commands .clone() .get_matches_from(vec!["test", "get-slot"]); assert_eq!( - parse_command(&pubkey, &test_get_slot).unwrap(), - CliCommand::GetSlot + parse_command(&test_get_slot).unwrap(), + CliCommandInfo { + command: CliCommand::GetSlot, + require_keypair: false + } ); let test_transaction_count = test_commands .clone() .get_matches_from(vec!["test", "get-transaction-count"]); assert_eq!( - parse_command(&pubkey, &test_transaction_count).unwrap(), - CliCommand::GetTransactionCount + parse_command(&test_transaction_count).unwrap(), + CliCommandInfo { + command: CliCommand::GetTransactionCount, + require_keypair: false + } ); let test_ping = test_commands .clone() .get_matches_from(vec!["test", "ping", "-i", "1", "-c", "2", "-t", "3"]); assert_eq!( - parse_command(&pubkey, &test_ping).unwrap(), - CliCommand::Ping { - interval: Duration::from_secs(1), - count: Some(2), - timeout: Duration::from_secs(3), + parse_command(&test_ping).unwrap(), + CliCommandInfo { + command: CliCommand::Ping { + interval: Duration::from_secs(1), + count: Some(2), + timeout: Duration::from_secs(3), + }, + require_keypair: true } ); } diff --git a/cli/src/main.rs b/cli/src/main.rs index 72de05cbd..bc07d2cac 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -1,12 +1,12 @@ use clap::{crate_description, crate_name, crate_version, Arg, ArgGroup, ArgMatches, SubCommand}; use console::style; use solana_cli::{ - cli::{app, parse_command, process_command, CliConfig, CliError}, + cli::{app, parse_command, process_command, CliCommandInfo, CliConfig, CliError}, config::{self, Config}, display::{println_name_value, println_name_value_or}, input_validators::is_url, }; -use solana_sdk::signature::{read_keypair_file, KeypairUtil}; +use solana_sdk::signature::read_keypair_file; use std::error; fn parse_settings(matches: &ArgMatches<'_>) -> Result> { @@ -18,7 +18,7 @@ fn parse_settings(matches: &ArgMatches<'_>) -> Result (config.url, default_cli_config.json_rpc_url), - "keypair" => (config.keypair, default_cli_config.keypair_path), + "keypair" => (config.keypair, default_cli_config.keypair_path.unwrap()), _ => unreachable!(), }; println_name_value_or(&format!("* {}:", field), &value, &default_value); @@ -28,7 +28,7 @@ fn parse_settings(matches: &ArgMatches<'_>) -> Result) -> Result { } } -pub fn parse_stake_create_account( - pubkey: &Pubkey, - matches: &ArgMatches<'_>, -) -> Result { +pub fn parse_stake_create_account(matches: &ArgMatches<'_>) -> Result { let stake_account_pubkey = pubkey_of(matches, "stake_account_pubkey").unwrap(); let slot = value_of(&matches, "lockup").unwrap_or(0); let custodian = pubkey_of(matches, "custodian").unwrap_or_default(); - let staker = pubkey_of(matches, "authorized_staker").unwrap_or(*pubkey); // defaults to config - let withdrawer = pubkey_of(matches, "authorized_withdrawer").unwrap_or(*pubkey); // defaults to config + let staker = pubkey_of(matches, "authorized_staker"); + let withdrawer = pubkey_of(matches, "authorized_withdrawer"); let lamports = amount_of(matches, "amount", "unit").expect("Invalid amount"); - Ok(CliCommand::CreateStakeAccount( - stake_account_pubkey, - Authorized { staker, withdrawer }, - Lockup { custodian, slot }, - lamports, - )) + Ok(CliCommandInfo { + command: CliCommand::CreateStakeAccount { + stake_account_pubkey, + staker, + withdrawer, + lockup: Lockup { custodian, slot }, + lamports, + }, + require_keypair: true, + }) } -pub fn parse_stake_delegate_stake(matches: &ArgMatches<'_>) -> Result { +pub fn parse_stake_delegate_stake(matches: &ArgMatches<'_>) -> Result { let stake_account_pubkey = pubkey_of(matches, "stake_account_pubkey").unwrap(); let vote_account_pubkey = pubkey_of(matches, "vote_account_pubkey").unwrap(); let force = matches.is_present("force"); - Ok(CliCommand::DelegateStake( - stake_account_pubkey, - vote_account_pubkey, - force, - )) + Ok(CliCommandInfo { + command: CliCommand::DelegateStake(stake_account_pubkey, vote_account_pubkey, force), + require_keypair: true, + }) } pub fn parse_stake_authorize( matches: &ArgMatches<'_>, stake_authorize: StakeAuthorize, -) -> Result { +) -> Result { let stake_account_pubkey = pubkey_of(matches, "stake_account_pubkey").unwrap(); let authorized_pubkey = pubkey_of(matches, "authorized_pubkey").unwrap(); - Ok(CliCommand::StakeAuthorize( - stake_account_pubkey, - authorized_pubkey, - stake_authorize, - )) + Ok(CliCommandInfo { + command: CliCommand::StakeAuthorize( + stake_account_pubkey, + authorized_pubkey, + stake_authorize, + ), + require_keypair: true, + }) } -pub fn parse_redeem_vote_credits(matches: &ArgMatches<'_>) -> Result { +pub fn parse_redeem_vote_credits(matches: &ArgMatches<'_>) -> Result { let stake_account_pubkey = pubkey_of(matches, "stake_account_pubkey").unwrap(); let vote_account_pubkey = pubkey_of(matches, "vote_account_pubkey").unwrap(); - Ok(CliCommand::RedeemVoteCredits( - stake_account_pubkey, - vote_account_pubkey, - )) + Ok(CliCommandInfo { + command: CliCommand::RedeemVoteCredits(stake_account_pubkey, vote_account_pubkey), + require_keypair: true, + }) } -pub fn parse_stake_deactivate_stake(matches: &ArgMatches<'_>) -> Result { +pub fn parse_stake_deactivate_stake(matches: &ArgMatches<'_>) -> Result { let stake_account_pubkey = pubkey_of(matches, "stake_account_pubkey").unwrap(); - Ok(CliCommand::DeactivateStake(stake_account_pubkey)) + Ok(CliCommandInfo { + command: CliCommand::DeactivateStake(stake_account_pubkey), + require_keypair: true, + }) } -pub fn parse_stake_withdraw_stake(matches: &ArgMatches<'_>) -> Result { +pub fn parse_stake_withdraw_stake(matches: &ArgMatches<'_>) -> Result { let stake_account_pubkey = pubkey_of(matches, "stake_account_pubkey").unwrap(); let destination_account_pubkey = pubkey_of(matches, "destination_account_pubkey").unwrap(); let lamports = amount_of(matches, "amount", "unit").expect("Invalid amount"); - Ok(CliCommand::WithdrawStake( - stake_account_pubkey, - destination_account_pubkey, - lamports, - )) + Ok(CliCommandInfo { + command: CliCommand::WithdrawStake( + stake_account_pubkey, + destination_account_pubkey, + lamports, + ), + require_keypair: true, + }) } -pub fn parse_show_stake_account(matches: &ArgMatches<'_>) -> Result { +pub fn parse_show_stake_account(matches: &ArgMatches<'_>) -> Result { let stake_account_pubkey = pubkey_of(matches, "stake_account_pubkey").unwrap(); let use_lamports_unit = matches.is_present("lamports"); - Ok(CliCommand::ShowStakeAccount { - pubkey: stake_account_pubkey, - use_lamports_unit, + Ok(CliCommandInfo { + command: CliCommand::ShowStakeAccount { + pubkey: stake_account_pubkey, + use_lamports_unit, + }, + require_keypair: false, }) } @@ -335,7 +348,8 @@ pub fn process_create_stake_account( rpc_client: &RpcClient, config: &CliConfig, stake_account_pubkey: &Pubkey, - authorized: &Authorized, + staker: &Option, + withdrawer: &Option, lockup: &Lockup, lamports: u64, ) -> ProcessResult { @@ -363,10 +377,16 @@ pub fn process_create_stake_account( .into()); } + let authorized = Authorized { + staker: staker.unwrap_or(config.keypair.pubkey()), + withdrawer: withdrawer.unwrap_or(config.keypair.pubkey()), + }; + println!("{:?}", authorized); + let ixs = stake_instruction::create_stake_account_with_lockup( &config.keypair.pubkey(), stake_account_pubkey, - authorized, + &authorized, lockup, lamports, ); @@ -636,8 +656,11 @@ mod tests { &pubkey_string, ]); assert_eq!( - parse_command(&pubkey, &test_authorize_staker).unwrap(), - CliCommand::StakeAuthorize(pubkey, pubkey, StakeAuthorize::Staker) + parse_command(&test_authorize_staker).unwrap(), + CliCommandInfo { + command: CliCommand::StakeAuthorize(pubkey, pubkey, StakeAuthorize::Staker), + require_keypair: true + } ); let test_authorize_withdrawer = test_commands.clone().get_matches_from(vec![ "test", @@ -646,8 +669,11 @@ mod tests { &pubkey_string, ]); assert_eq!( - parse_command(&pubkey, &test_authorize_withdrawer).unwrap(), - CliCommand::StakeAuthorize(pubkey, pubkey, StakeAuthorize::Withdrawer) + parse_command(&test_authorize_withdrawer).unwrap(), + CliCommandInfo { + command: CliCommand::StakeAuthorize(pubkey, pubkey, StakeAuthorize::Withdrawer), + require_keypair: true + } ); // Test CreateStakeAccount SubCommand @@ -671,19 +697,20 @@ mod tests { "lamports", ]); assert_eq!( - parse_command(&pubkey, &test_create_stake_account).unwrap(), - CliCommand::CreateStakeAccount( - pubkey, - Authorized { - staker: authorized, - withdrawer: authorized, + parse_command(&test_create_stake_account).unwrap(), + CliCommandInfo { + command: CliCommand::CreateStakeAccount { + stake_account_pubkey: pubkey, + staker: Some(authorized), + withdrawer: Some(authorized), + lockup: Lockup { + slot: 43, + custodian, + }, + lamports: 50 }, - Lockup { - slot: 43, - custodian, - }, - 50 - ) + require_keypair: true + } ); let test_create_stake_account2 = test_commands.clone().get_matches_from(vec![ "test", @@ -693,19 +720,20 @@ mod tests { "lamports", ]); assert_eq!( - parse_command(&pubkey, &test_create_stake_account2).unwrap(), - CliCommand::CreateStakeAccount( - pubkey, - Authorized { - staker: pubkey, - withdrawer: pubkey, + parse_command(&test_create_stake_account2).unwrap(), + CliCommandInfo { + command: CliCommand::CreateStakeAccount { + stake_account_pubkey: pubkey, + staker: None, + withdrawer: None, + lockup: Lockup { + slot: 0, + custodian: Pubkey::default(), + }, + lamports: 50 }, - Lockup { - slot: 0, - custodian: Pubkey::default(), - }, - 50 - ) + require_keypair: true + } ); // Test DelegateStake Subcommand @@ -718,8 +746,11 @@ mod tests { &pubkey_string, ]); assert_eq!( - parse_command(&pubkey, &test_delegate_stake).unwrap(), - CliCommand::DelegateStake(stake_pubkey, pubkey, false,) + parse_command(&test_delegate_stake).unwrap(), + CliCommandInfo { + command: CliCommand::DelegateStake(stake_pubkey, pubkey, false), + require_keypair: true + } ); let test_delegate_stake = test_commands.clone().get_matches_from(vec![ @@ -730,8 +761,11 @@ mod tests { &pubkey_string, ]); assert_eq!( - parse_command(&pubkey, &test_delegate_stake).unwrap(), - CliCommand::DelegateStake(stake_pubkey, pubkey, true) + parse_command(&test_delegate_stake).unwrap(), + CliCommandInfo { + command: CliCommand::DelegateStake(stake_pubkey, pubkey, true), + require_keypair: true + } ); // Test WithdrawStake Subcommand @@ -745,8 +779,11 @@ mod tests { ]); assert_eq!( - parse_command(&pubkey, &test_withdraw_stake).unwrap(), - CliCommand::WithdrawStake(stake_pubkey, pubkey, 42) + parse_command(&test_withdraw_stake).unwrap(), + CliCommandInfo { + command: CliCommand::WithdrawStake(stake_pubkey, pubkey, 42), + require_keypair: true + } ); // Test DeactivateStake Subcommand @@ -756,8 +793,11 @@ mod tests { &stake_pubkey_string, ]); assert_eq!( - parse_command(&pubkey, &test_deactivate_stake).unwrap(), - CliCommand::DeactivateStake(stake_pubkey) + parse_command(&test_deactivate_stake).unwrap(), + CliCommandInfo { + command: CliCommand::DeactivateStake(stake_pubkey), + require_keypair: true + } ); } // TODO: Add process tests diff --git a/cli/src/storage.rs b/cli/src/storage.rs index b68a62e8e..ae3055984 100644 --- a/cli/src/storage.rs +++ b/cli/src/storage.rs @@ -1,7 +1,7 @@ use crate::{ cli::{ check_account_for_fee, check_unique_pubkeys, log_instruction_custom_error, CliCommand, - CliConfig, CliError, ProcessResult, + CliCommandInfo, CliConfig, CliError, ProcessResult, }, input_parsers::*, input_validators::*, @@ -100,40 +100,54 @@ impl StorageSubCommands for App<'_, '_> { pub fn parse_storage_create_archiver_account( matches: &ArgMatches<'_>, -) -> Result { +) -> Result { let account_owner = pubkey_of(matches, "storage_account_owner").unwrap(); let storage_account_pubkey = pubkey_of(matches, "storage_account_pubkey").unwrap(); - Ok(CliCommand::CreateStorageAccount { - account_owner, - storage_account_pubkey, - account_type: StorageAccountType::Archiver, + Ok(CliCommandInfo { + command: CliCommand::CreateStorageAccount { + account_owner, + storage_account_pubkey, + account_type: StorageAccountType::Archiver, + }, + require_keypair: true, }) } pub fn parse_storage_create_validator_account( matches: &ArgMatches<'_>, -) -> Result { +) -> Result { let account_owner = pubkey_of(matches, "storage_account_owner").unwrap(); let storage_account_pubkey = pubkey_of(matches, "storage_account_pubkey").unwrap(); - Ok(CliCommand::CreateStorageAccount { - account_owner, - storage_account_pubkey, - account_type: StorageAccountType::Validator, + Ok(CliCommandInfo { + command: CliCommand::CreateStorageAccount { + account_owner, + storage_account_pubkey, + account_type: StorageAccountType::Validator, + }, + require_keypair: true, }) } -pub fn parse_storage_claim_reward(matches: &ArgMatches<'_>) -> Result { +pub fn parse_storage_claim_reward(matches: &ArgMatches<'_>) -> Result { let node_account_pubkey = pubkey_of(matches, "node_account_pubkey").unwrap(); let storage_account_pubkey = pubkey_of(matches, "storage_account_pubkey").unwrap(); - Ok(CliCommand::ClaimStorageReward { - node_account_pubkey, - storage_account_pubkey, + Ok(CliCommandInfo { + command: CliCommand::ClaimStorageReward { + node_account_pubkey, + storage_account_pubkey, + }, + require_keypair: true, }) } -pub fn parse_storage_get_account_command(matches: &ArgMatches<'_>) -> Result { +pub fn parse_storage_get_account_command( + matches: &ArgMatches<'_>, +) -> Result { let storage_account_pubkey = pubkey_of(matches, "storage_account_pubkey").unwrap(); - Ok(CliCommand::ShowStorageAccount(storage_account_pubkey)) + Ok(CliCommandInfo { + command: CliCommand::ShowStorageAccount(storage_account_pubkey), + require_keypair: false, + }) } pub fn process_create_storage_account( @@ -228,11 +242,14 @@ mod tests { &storage_account_string, ]); assert_eq!( - parse_command(&pubkey, &test_create_archiver_storage_account).unwrap(), - CliCommand::CreateStorageAccount { - account_owner: pubkey, - storage_account_pubkey, - account_type: StorageAccountType::Archiver, + parse_command(&test_create_archiver_storage_account).unwrap(), + CliCommandInfo { + command: CliCommand::CreateStorageAccount { + account_owner: pubkey, + storage_account_pubkey, + account_type: StorageAccountType::Archiver, + }, + require_keypair: true } ); @@ -243,11 +260,14 @@ mod tests { &storage_account_string, ]); assert_eq!( - parse_command(&pubkey, &test_create_validator_storage_account).unwrap(), - CliCommand::CreateStorageAccount { - account_owner: pubkey, - storage_account_pubkey, - account_type: StorageAccountType::Validator, + parse_command(&test_create_validator_storage_account).unwrap(), + CliCommandInfo { + command: CliCommand::CreateStorageAccount { + account_owner: pubkey, + storage_account_pubkey, + account_type: StorageAccountType::Validator, + }, + require_keypair: true } ); @@ -258,10 +278,13 @@ mod tests { &storage_account_string, ]); assert_eq!( - parse_command(&pubkey, &test_claim_storage_reward).unwrap(), - CliCommand::ClaimStorageReward { - node_account_pubkey: pubkey, - storage_account_pubkey, + parse_command(&test_claim_storage_reward).unwrap(), + CliCommandInfo { + command: CliCommand::ClaimStorageReward { + node_account_pubkey: pubkey, + storage_account_pubkey, + }, + require_keypair: true } ); } diff --git a/cli/src/validator_info.rs b/cli/src/validator_info.rs index a607da779..aa96d286a 100644 --- a/cli/src/validator_info.rs +++ b/cli/src/validator_info.rs @@ -1,9 +1,10 @@ use crate::{ - cli::{check_account_for_fee, CliCommand, CliConfig, CliError, ProcessResult}, - input_validators::is_url, + cli::{check_account_for_fee, CliCommand, CliCommandInfo, CliConfig, CliError, ProcessResult}, + input_parsers::pubkey_of, + input_validators::{is_pubkey, is_url}, }; use bincode::deserialize; -use clap::ArgMatches; +use clap::{App, Arg, ArgMatches, SubCommand}; use reqwest::Client; use serde_derive::{Deserialize, Serialize}; use serde_json::{Map, Value}; @@ -142,28 +143,123 @@ fn parse_validator_info( } } -fn parse_info_pubkey(matches: &ArgMatches<'_>) -> Result, CliError> { - let info_pubkey = if let Some(pubkey) = matches.value_of("info_pubkey") { - Some(pubkey.parse::().map_err(|err| { - CliError::BadParameter(format!("Invalid validator info pubkey: {:?}", err)) - })?) - } else { - None - }; - Ok(info_pubkey) +pub trait ValidatorInfoSubCommands { + fn validator_info_subcommands(self) -> Self; } -pub fn parse_validator_info_command( - matches: &ArgMatches<'_>, - validator_pubkey: &Pubkey, -) -> Result { - let info_pubkey = parse_info_pubkey(matches)?; +impl ValidatorInfoSubCommands for App<'_, '_> { + fn validator_info_subcommands(self) -> Self { + self.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"), + ), + ) + ) + } +} + +pub fn parse_validator_info_command(matches: &ArgMatches<'_>) -> Result { + let info_pubkey = pubkey_of(matches, "info_pubkey"); // Prepare validator info let validator_info = parse_args(&matches); + Ok(CliCommandInfo { + command: CliCommand::SetValidatorInfo { + validator_info, + force_keybase: matches.is_present("force"), + info_pubkey, + }, + require_keypair: true, + }) +} + +pub fn parse_get_validator_info_command( + matches: &ArgMatches<'_>, +) -> Result { + let info_pubkey = pubkey_of(matches, "info_pubkey"); + Ok(CliCommandInfo { + command: CliCommand::GetValidatorInfo(info_pubkey), + require_keypair: false, + }) +} + +pub fn process_set_validator_info( + rpc_client: &RpcClient, + config: &CliConfig, + validator_info: &Value, + force_keybase: bool, + info_pubkey: Option, +) -> ProcessResult { + // Validate keybase username if let Some(string) = validator_info.get("keybaseUsername") { - let result = verify_keybase(&validator_pubkey, &string); + let result = verify_keybase(&config.keypair.pubkey(), &string); if result.is_err() { - if matches.is_present("force") { + if force_keybase { println!("--force supplied, ignoring: {:?}", result); } else { result.map_err(|err| { @@ -176,20 +272,6 @@ pub fn parse_validator_info_command( let validator_info = ValidatorInfo { info: validator_string, }; - Ok(CliCommand::SetValidatorInfo(validator_info, info_pubkey)) -} - -pub fn parse_get_validator_info_command(matches: &ArgMatches<'_>) -> Result { - let info_pubkey = parse_info_pubkey(matches)?; - Ok(CliCommand::GetValidatorInfo(info_pubkey)) -} - -pub fn process_set_validator_info( - rpc_client: &RpcClient, - config: &CliConfig, - validator_info: &ValidatorInfo, - info_pubkey: Option, -) -> ProcessResult { // Check for existing validator-info account let all_config = rpc_client.get_program_accounts(&solana_config_api::id())?; let existing_account = all_config @@ -239,7 +321,7 @@ pub fn process_set_validator_info( &info_keypair.pubkey(), true, keys, - validator_info, + &validator_info, )]); let signers = vec![&config.keypair, &info_keypair]; let message = Message::new(instructions); @@ -254,7 +336,7 @@ pub fn process_set_validator_info( &info_pubkey, false, keys, - validator_info, + &validator_info, )]; let message = Message::new_with_payer(instructions, Some(&config.keypair.pubkey())); let signers = vec![&config.keypair]; diff --git a/cli/src/vote.rs b/cli/src/vote.rs index 6b2d91732..5f742ff40 100644 --- a/cli/src/vote.rs +++ b/cli/src/vote.rs @@ -1,7 +1,8 @@ use crate::{ cli::{ build_balance_message, check_account_for_fee, check_unique_pubkeys, - log_instruction_custom_error, CliCommand, CliConfig, CliError, ProcessResult, + log_instruction_custom_error, CliCommand, CliCommandInfo, CliConfig, CliError, + ProcessResult, }, input_parsers::*, input_validators::*, @@ -159,51 +160,57 @@ impl VoteSubCommands for App<'_, '_> { } } -pub fn parse_vote_create_account( - pubkey: &Pubkey, - matches: &ArgMatches<'_>, -) -> Result { +pub fn parse_vote_create_account(matches: &ArgMatches<'_>) -> Result { let vote_account_pubkey = pubkey_of(matches, "vote_account_pubkey").unwrap(); let node_pubkey = pubkey_of(matches, "node_pubkey").unwrap(); let commission = value_of(&matches, "commission").unwrap_or(0); - let authorized_voter = pubkey_of(matches, "authorized_voter").unwrap_or(vote_account_pubkey); - let authorized_withdrawer = pubkey_of(matches, "authorized_withdrawer").unwrap_or(*pubkey); + let authorized_voter = pubkey_of(matches, "authorized_voter"); + let authorized_withdrawer = pubkey_of(matches, "authorized_withdrawer"); - Ok(CliCommand::CreateVoteAccount( - vote_account_pubkey, - VoteInit { + Ok(CliCommandInfo { + command: CliCommand::CreateVoteAccount { + vote_account_pubkey, node_pubkey, authorized_voter, authorized_withdrawer, commission, }, - )) + require_keypair: true, + }) } pub fn parse_vote_authorize( matches: &ArgMatches<'_>, vote_authorize: VoteAuthorize, -) -> Result { +) -> Result { let vote_account_pubkey = pubkey_of(matches, "vote_account_pubkey").unwrap(); let new_authorized_pubkey = pubkey_of(matches, "new_authorized_pubkey").unwrap(); - Ok(CliCommand::VoteAuthorize( - vote_account_pubkey, - new_authorized_pubkey, - vote_authorize, - )) -} - -pub fn parse_vote_get_account_command(matches: &ArgMatches<'_>) -> Result { - let vote_account_pubkey = pubkey_of(matches, "vote_account_pubkey").unwrap(); - let use_lamports_unit = matches.is_present("lamports"); - Ok(CliCommand::ShowVoteAccount { - pubkey: vote_account_pubkey, - use_lamports_unit, + Ok(CliCommandInfo { + command: CliCommand::VoteAuthorize( + vote_account_pubkey, + new_authorized_pubkey, + vote_authorize, + ), + require_keypair: true, }) } -pub fn parse_vote_uptime_command(matches: &ArgMatches<'_>) -> Result { +pub fn parse_vote_get_account_command( + matches: &ArgMatches<'_>, +) -> Result { + let vote_account_pubkey = pubkey_of(matches, "vote_account_pubkey").unwrap(); + let use_lamports_unit = matches.is_present("lamports"); + Ok(CliCommandInfo { + command: CliCommand::ShowVoteAccount { + pubkey: vote_account_pubkey, + use_lamports_unit, + }, + require_keypair: false, + }) +} + +pub fn parse_vote_uptime_command(matches: &ArgMatches<'_>) -> Result { let vote_account_pubkey = pubkey_of(matches, "vote_account_pubkey").unwrap(); let aggregate = matches.is_present("aggregate"); let span = if matches.is_present("span") { @@ -211,10 +218,13 @@ pub fn parse_vote_uptime_command(matches: &ArgMatches<'_>) -> Result, + authorized_withdrawer: &Option, + commission: u8, ) -> ProcessResult { check_unique_pubkeys( (vote_account_pubkey, "vote_account_pubkey".to_string()), - (&vote_init.node_pubkey, "node_pubkey".to_string()), + (&node_pubkey, "node_pubkey".to_string()), )?; check_unique_pubkeys( (&config.keypair.pubkey(), "cli keypair".to_string()), @@ -239,10 +252,16 @@ pub fn process_create_vote_account( } else { 1 }; + let vote_init = VoteInit { + node_pubkey: *node_pubkey, + authorized_voter: authorized_voter.unwrap_or(*vote_account_pubkey), + authorized_withdrawer: authorized_withdrawer.unwrap_or(config.keypair.pubkey()), + commission, + }; let ixs = vote_instruction::create_account( &config.keypair.pubkey(), vote_account_pubkey, - vote_init, + &vote_init, lamports, ); let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?; @@ -441,8 +460,11 @@ mod tests { &pubkey_string, ]); assert_eq!( - parse_command(&pubkey, &test_authorize_voter).unwrap(), - CliCommand::VoteAuthorize(pubkey, pubkey, VoteAuthorize::Voter) + parse_command(&test_authorize_voter).unwrap(), + CliCommandInfo { + command: CliCommand::VoteAuthorize(pubkey, pubkey, VoteAuthorize::Voter), + require_keypair: true + } ); // Test CreateVoteAccount SubCommand @@ -457,16 +479,17 @@ mod tests { "10", ]); assert_eq!( - parse_command(&pubkey, &test_create_vote_account).unwrap(), - CliCommand::CreateVoteAccount( - pubkey, - VoteInit { + parse_command(&test_create_vote_account).unwrap(), + CliCommandInfo { + command: CliCommand::CreateVoteAccount { + vote_account_pubkey: pubkey, node_pubkey, - authorized_voter: pubkey, - authorized_withdrawer: pubkey, - commission: 10 - } - ) + authorized_voter: None, + authorized_withdrawer: None, + commission: 10, + }, + require_keypair: true + } ); let test_create_vote_account2 = test_commands.clone().get_matches_from(vec![ "test", @@ -475,16 +498,17 @@ mod tests { &node_pubkey_string, ]); assert_eq!( - parse_command(&pubkey, &test_create_vote_account2).unwrap(), - CliCommand::CreateVoteAccount( - pubkey, - VoteInit { + parse_command(&test_create_vote_account2).unwrap(), + CliCommandInfo { + command: CliCommand::CreateVoteAccount { + vote_account_pubkey: pubkey, node_pubkey, - authorized_voter: pubkey, - authorized_withdrawer: pubkey, - commission: 0 - } - ) + authorized_voter: None, + authorized_withdrawer: None, + commission: 0, + }, + require_keypair: true + } ); // test init with an authed voter let authed = Pubkey::new_rand(); @@ -497,16 +521,17 @@ mod tests { &authed.to_string(), ]); assert_eq!( - parse_command(&pubkey, &test_create_vote_account3).unwrap(), - CliCommand::CreateVoteAccount( - pubkey, - VoteInit { + parse_command(&test_create_vote_account3).unwrap(), + CliCommandInfo { + command: CliCommand::CreateVoteAccount { + vote_account_pubkey: pubkey, node_pubkey, - authorized_voter: authed, - authorized_withdrawer: pubkey, + authorized_voter: Some(authed), + authorized_withdrawer: None, commission: 0 - } - ) + }, + require_keypair: true + } ); // test init with an authed withdrawer let test_create_vote_account4 = test_commands.clone().get_matches_from(vec![ @@ -518,16 +543,17 @@ mod tests { &authed.to_string(), ]); assert_eq!( - parse_command(&pubkey, &test_create_vote_account4).unwrap(), - CliCommand::CreateVoteAccount( - pubkey, - VoteInit { + parse_command(&test_create_vote_account4).unwrap(), + CliCommandInfo { + command: CliCommand::CreateVoteAccount { + vote_account_pubkey: pubkey, node_pubkey, - authorized_voter: pubkey, - authorized_withdrawer: authed, + authorized_voter: None, + authorized_withdrawer: Some(authed), commission: 0 - } - ) + }, + require_keypair: true + } ); // Test Uptime Subcommand @@ -541,11 +567,14 @@ mod tests { "--aggregate", ]); assert_eq!( - parse_command(&pubkey, &matches).unwrap(), - CliCommand::Uptime { - pubkey, - aggregate: true, - span: Some(4) + parse_command(&matches).unwrap(), + CliCommandInfo { + command: CliCommand::Uptime { + pubkey, + aggregate: true, + span: Some(4) + }, + require_keypair: false } ); } diff --git a/cli/tests/pay.rs b/cli/tests/pay.rs index 94d98e9b7..e53dbeb82 100644 --- a/cli/tests/pay.rs +++ b/cli/tests/pay.rs @@ -70,7 +70,7 @@ fn test_cli_timestamp_tx() { timestamp: Some(dt), timestamp_pubkey: Some(config_witness.keypair.pubkey()), witnesses: None, - cancelable: None, + cancelable: false, }; let sig_response = process_command(&config_payer); @@ -137,7 +137,7 @@ fn test_cli_witness_tx() { timestamp: None, timestamp_pubkey: None, witnesses: Some(vec![config_witness.keypair.pubkey()]), - cancelable: None, + cancelable: false, }; let sig_response = process_command(&config_payer); @@ -197,7 +197,7 @@ fn test_cli_cancel_tx() { timestamp: None, timestamp_pubkey: None, witnesses: Some(vec![config_witness.keypair.pubkey()]), - cancelable: Some(config_payer.keypair.pubkey()), + cancelable: true, }; let sig_response = process_command(&config_payer).unwrap();