From 43795193c48232e0c2ff5af948fef3ea852eb510 Mon Sep 17 00:00:00 2001 From: Rob Walker Date: Wed, 25 Sep 2019 13:53:49 -0700 Subject: [PATCH] add authorized parameters to vote api (#6072) * add authorized parameters to vote api * code review --- cli/src/vote.rs | 147 +++++++--- cli/src/wallet.rs | 167 +++++++++--- core/src/confidence.rs | 6 +- core/src/staking_utils.rs | 50 ++-- local_cluster/src/local_cluster.rs | 16 +- .../stake_tests/tests/stake_instruction.rs | 10 +- programs/vote_api/src/vote_instruction.rs | 131 ++++++--- programs/vote_api/src/vote_state.rs | 258 +++++++++++++----- runtime/src/bank.rs | 10 +- 9 files changed, 578 insertions(+), 217 deletions(-) diff --git a/cli/src/vote.rs b/cli/src/vote.rs index 0edff5196..5e23d20b5 100644 --- a/cli/src/vote.rs +++ b/cli/src/vote.rs @@ -15,13 +15,16 @@ use solana_sdk::{ }; use solana_vote_api::{ vote_instruction::{self, VoteError}, - vote_state::VoteState, + vote_state::{VoteAuthorize, VoteInit, VoteState}, }; 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(vote_account_pubkey); let lamports = matches .value_of("lamports") .unwrap() @@ -29,21 +32,29 @@ pub fn parse_vote_create_account(matches: &ArgMatches<'_>) -> Result) -> Result { +pub fn parse_vote_authorize( + matches: &ArgMatches<'_>, + vote_authorize: VoteAuthorize, +) -> Result { let vote_account_pubkey = pubkey_of(matches, "vote_account_pubkey").unwrap(); - let authorized_voter_keypair = keypair_of(matches, "authorized_voter_keypair_file").unwrap(); - let new_authorized_voter_pubkey = pubkey_of(matches, "new_authorized_voter_pubkey").unwrap(); + let authorized_keypair = keypair_of(matches, "authorized_keypair_file").unwrap(); + let new_authorized_pubkey = pubkey_of(matches, "new_authorized_pubkey").unwrap(); - Ok(WalletCommand::AuthorizeVoter( + Ok(WalletCommand::VoteAuthorize( vote_account_pubkey, - authorized_voter_keypair, - new_authorized_voter_pubkey, + authorized_keypair, + new_authorized_pubkey, + vote_authorize, )) } @@ -58,13 +69,12 @@ pub fn process_create_vote_account( rpc_client: &RpcClient, config: &WalletConfig, vote_account_pubkey: &Pubkey, - node_pubkey: &Pubkey, - commission: u8, + vote_init: &VoteInit, lamports: u64, ) -> ProcessResult { check_unique_pubkeys( (vote_account_pubkey, "vote_account_pubkey".to_string()), - (node_pubkey, "node_pubkey".to_string()), + (&vote_init.node_pubkey, "node_pubkey".to_string()), )?; check_unique_pubkeys( (&config.keypair.pubkey(), "wallet keypair".to_string()), @@ -73,8 +83,7 @@ pub fn process_create_vote_account( let ixs = vote_instruction::create_account( &config.keypair.pubkey(), vote_account_pubkey, - node_pubkey, - commission, + vote_init, lamports, ); let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?; @@ -84,36 +93,35 @@ pub fn process_create_vote_account( log_instruction_custom_error::(result) } -pub fn process_authorize_voter( +pub fn process_vote_authorize( rpc_client: &RpcClient, config: &WalletConfig, vote_account_pubkey: &Pubkey, - authorized_voter_keypair: &Keypair, - new_authorized_voter_pubkey: &Pubkey, + authorized_keypair: &Keypair, + new_authorized_pubkey: &Pubkey, + vote_authorize: VoteAuthorize, ) -> ProcessResult { check_unique_pubkeys( (vote_account_pubkey, "vote_account_pubkey".to_string()), - ( - new_authorized_voter_pubkey, - "new_authorized_voter_pubkey".to_string(), - ), + (new_authorized_pubkey, "new_authorized_pubkey".to_string()), )?; let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?; - let ixs = vec![vote_instruction::authorize_voter( - vote_account_pubkey, // vote account to update - &authorized_voter_keypair.pubkey(), // current authorized voter (often the vote account itself) - new_authorized_voter_pubkey, // new vote signer + let ixs = vec![vote_instruction::authorize( + vote_account_pubkey, // vote account to update + &authorized_keypair.pubkey(), // current authorized voter (often the vote account itself) + new_authorized_pubkey, // new vote signer + vote_authorize, // vote or withdraw )]; let mut tx = Transaction::new_signed_with_payer( ixs, Some(&config.keypair.pubkey()), - &[&config.keypair, &authorized_voter_keypair], + &[&config.keypair, &authorized_keypair], recent_blockhash, ); check_account_for_fee(rpc_client, config, &fee_calculator, &tx.message)?; - let result = rpc_client - .send_and_confirm_transaction(&mut tx, &[&config.keypair, &authorized_voter_keypair]); + let result = + rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair, &authorized_keypair]); log_instruction_custom_error::(result) } @@ -153,9 +161,10 @@ pub fn process_show_vote_account( println!("account lamports: {}", vote_account.lamports); println!("node id: {}", vote_state.node_pubkey); + println!("authorized voter: {}", vote_state.authorized_voter); println!( - "authorized voter pubkey: {}", - vote_state.authorized_voter_pubkey + "authorized withdrawer: {}", + vote_state.authorized_withdrawer ); println!("credits: {}", vote_state.credits()); println!( @@ -221,10 +230,7 @@ pub fn process_uptime( })?; println!("Node id: {}", vote_state.node_pubkey); - println!( - "Authorized voter pubkey: {}", - vote_state.authorized_voter_pubkey - ); + println!("Authorized voter: {}", vote_state.authorized_voter); if !vote_state.votes.is_empty() { println!("Uptime:"); @@ -291,14 +297,14 @@ mod tests { let test_authorize_voter = test_commands.clone().get_matches_from(vec![ "test", - "authorize-voter", + "vote-authorize-voter", &pubkey_string, &keypair_file, &pubkey_string, ]); assert_eq!( parse_command(&pubkey, &test_authorize_voter).unwrap(), - WalletCommand::AuthorizeVoter(pubkey, keypair, pubkey) + WalletCommand::VoteAuthorize(pubkey, keypair, pubkey, VoteAuthorize::Voter) ); fs::remove_file(&keypair_file).unwrap(); @@ -316,7 +322,16 @@ mod tests { ]); assert_eq!( parse_command(&pubkey, &test_create_vote_account).unwrap(), - WalletCommand::CreateVoteAccount(pubkey, node_pubkey, 10, 50) + WalletCommand::CreateVoteAccount( + pubkey, + VoteInit { + node_pubkey, + authorized_voter: pubkey, + authorized_withdrawer: pubkey, + commission: 10 + }, + 50 + ) ); let test_create_vote_account2 = test_commands.clone().get_matches_from(vec![ "test", @@ -327,7 +342,63 @@ mod tests { ]); assert_eq!( parse_command(&pubkey, &test_create_vote_account2).unwrap(), - WalletCommand::CreateVoteAccount(pubkey, node_pubkey, 0, 50) + WalletCommand::CreateVoteAccount( + pubkey, + VoteInit { + node_pubkey, + authorized_voter: pubkey, + authorized_withdrawer: pubkey, + commission: 0 + }, + 50 + ) + ); + // test init with an authed voter + let authed = Pubkey::new_rand(); + let test_create_vote_account3 = test_commands.clone().get_matches_from(vec![ + "test", + "create-vote-account", + &pubkey_string, + &node_pubkey_string, + "50", + "--authorized-voter", + &authed.to_string(), + ]); + assert_eq!( + parse_command(&pubkey, &test_create_vote_account3).unwrap(), + WalletCommand::CreateVoteAccount( + pubkey, + VoteInit { + node_pubkey, + authorized_voter: authed, + authorized_withdrawer: pubkey, + commission: 0 + }, + 50 + ) + ); + // test init with an authed withdrawer + let test_create_vote_account4 = test_commands.clone().get_matches_from(vec![ + "test", + "create-vote-account", + &pubkey_string, + &node_pubkey_string, + "50", + "--authorized-withdrawer", + &authed.to_string(), + ]); + assert_eq!( + parse_command(&pubkey, &test_create_vote_account4).unwrap(), + WalletCommand::CreateVoteAccount( + pubkey, + VoteInit { + node_pubkey, + authorized_voter: pubkey, + authorized_withdrawer: authed, + commission: 0 + }, + 50 + ) ); // Test Uptime Subcommand diff --git a/cli/src/wallet.rs b/cli/src/wallet.rs index 6a8e47fb3..ce459f9ab 100644 --- a/cli/src/wallet.rs +++ b/cli/src/wallet.rs @@ -7,11 +7,9 @@ use clap::{value_t_or_exit, App, AppSettings, Arg, ArgMatches, SubCommand}; use console::{style, Emoji}; use log::*; use num_traits::FromPrimitive; -use serde_json; -use serde_json::{json, Value}; +use serde_json::{self, json, Value}; use solana_budget_api::budget_instruction::{self, BudgetError}; -use solana_client::client_error::ClientError; -use solana_client::rpc_client::RpcClient; +use solana_client::{client_error::ClientError, rpc_client::RpcClient}; #[cfg(not(test))] use solana_drone::drone::request_airdrop_transaction; #[cfg(test)] @@ -33,14 +31,16 @@ use solana_sdk::{ }; use solana_stake_api::stake_instruction::{self, StakeError}; use solana_storage_api::storage_instruction; -use solana_vote_api::vote_state::VoteState; -use std::collections::VecDeque; -use std::fs::File; -use std::io::{Read, Write}; -use std::net::{IpAddr, SocketAddr}; -use std::thread::sleep; -use std::time::{Duration, Instant}; -use std::{error, fmt}; +use solana_vote_api::vote_state::{VoteAuthorize, VoteInit, VoteState}; +use std::{ + collections::VecDeque, + fs::File, + io::{Read, Write}, + net::{IpAddr, SocketAddr}, + thread::sleep, + time::{Duration, Instant}, + {error, fmt}, +}; const USERDATA_CHUNK_SIZE: usize = 229; // Keep program chunks under PACKET_DATA_SIZE @@ -64,8 +64,8 @@ pub enum WalletCommand { }, Cancel(Pubkey), Confirm(Signature), - AuthorizeVoter(Pubkey, Keypair, Pubkey), - CreateVoteAccount(Pubkey, Pubkey, u8, u64), + VoteAuthorize(Pubkey, Keypair, Pubkey, VoteAuthorize), + CreateVoteAccount(Pubkey, VoteInit, u64), ShowAccount { pubkey: Pubkey, output_file: Option, @@ -236,7 +236,12 @@ pub fn parse_command( }) } ("create-vote-account", Some(matches)) => parse_vote_create_account(matches), - ("authorize-voter", Some(matches)) => parse_vote_authorize_voter(matches), + ("vote-authorize-voter", Some(matches)) => { + parse_vote_authorize(matches, VoteAuthorize::Voter) + } + ("vote-authorize-withdrawer", Some(matches)) => { + parse_vote_authorize(matches, VoteAuthorize::Withdrawer) + } ("show-vote-account", Some(matches)) => parse_vote_get_account_command(matches), ("uptime", Some(matches)) => parse_vote_uptime_command(matches), ("delegate-stake", Some(matches)) => { @@ -1269,30 +1274,28 @@ pub fn process_command(config: &WalletConfig) -> ProcessResult { WalletCommand::Confirm(signature) => process_confirm(&rpc_client, signature), // Create vote account - WalletCommand::CreateVoteAccount( - vote_account_pubkey, - node_pubkey, - commission, - lamports, - ) => process_create_vote_account( - &rpc_client, - config, - &vote_account_pubkey, - &node_pubkey, - *commission, - *lamports, - ), + WalletCommand::CreateVoteAccount(vote_account_pubkey, vote_init, lamports) => { + process_create_vote_account( + &rpc_client, + config, + &vote_account_pubkey, + &vote_init, + *lamports, + ) + } - WalletCommand::AuthorizeVoter( + WalletCommand::VoteAuthorize( vote_account_pubkey, - authorized_voter_keypair, - new_authorized_voter_pubkey, - ) => process_authorize_voter( + authorized_keypair, + new_authorized_pubkey, + vote_authorize, + ) => process_vote_authorize( &rpc_client, config, &vote_account_pubkey, - &authorized_voter_keypair, - &new_authorized_voter_pubkey, + &authorized_keypair, + &new_authorized_pubkey, + *vote_authorize, ), WalletCommand::ShowAccount { @@ -1646,7 +1649,7 @@ pub fn app<'ab, 'v>(name: &str, about: &'ab str, version: &'v str) -> App<'ab, ' ), ) .subcommand( - SubCommand::with_name("authorize-voter") + 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") @@ -1658,7 +1661,7 @@ pub fn app<'ab, 'v>(name: &str, about: &'ab str, version: &'v str) -> App<'ab, ' .help("Vote account in which to set the authorized voter"), ) .arg( - Arg::with_name("authorized_voter_keypair_file") + Arg::with_name("authorized_keypair_file") .index(2) .value_name("CURRENT VOTER KEYPAIR FILE") .takes_value(true) @@ -1667,7 +1670,7 @@ pub fn app<'ab, 'v>(name: &str, about: &'ab str, version: &'v str) -> App<'ab, ' .help("Keypair file for the currently authorized vote signer"), ) .arg( - Arg::with_name("new_authorized_voter_pubkey") + Arg::with_name("new_authorized_pubkey") .index(3) .value_name("NEW VOTER PUBKEY") .takes_value(true) @@ -1676,6 +1679,37 @@ pub fn app<'ab, 'v>(name: &str, about: &'ab str, version: &'v str) -> App<'ab, ' .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("authorized_keypair_file") + .index(2) + .value_name("CURRENT WITHDRAWER KEYPAIR FILE") + .takes_value(true) + .required(true) + .validator(is_keypair) + .help("Keypair file for the currently authorized withdrawer"), + ) + .arg( + Arg::with_name("new_authorized_pubkey") + .index(3) + .value_name("NEW WITHDRAWER PUBKEY") + .takes_value(true) + .required(true) + .validator(is_pubkey_or_keypair) + .help("New withdrawer to authorize"), + ), + ) .subcommand( SubCommand::with_name("create-vote-account") .about("Create a vote account") @@ -1711,7 +1745,25 @@ pub fn app<'ab, 'v>(name: &str, about: &'ab str, version: &'v str) -> App<'ab, ' .value_name("NUM") .takes_value(true) .help("The commission taken on reward redemption (0-255), default: 0"), - ), + ) + .arg( + Arg::with_name("authorized_voter") + .long("authorized-voter") + .value_name("PUBKEY") + .takes_value(true) + .validator(is_pubkey_or_keypair) + .help("Public key of the authorized voter (defaults to vote account pubkey)"), + ) + .arg( + Arg::with_name("authorized_withdrawer") + .long("authorized-withdrawer") + .value_name("PUBKEY") + .takes_value(true) + .validator(is_pubkey_or_keypair) + .help("Public key of the authorized withdrawer (defaults to vote account pubkey)"), + ) + +, ) .subcommand( SubCommand::with_name("show-account") @@ -2621,14 +2673,27 @@ mod tests { let bob_pubkey = Pubkey::new_rand(); let node_pubkey = Pubkey::new_rand(); - config.command = WalletCommand::CreateVoteAccount(bob_pubkey, node_pubkey, 0, 10); + config.command = WalletCommand::CreateVoteAccount( + bob_pubkey, + VoteInit { + node_pubkey, + authorized_voter: bob_pubkey, + authorized_withdrawer: bob_pubkey, + commission: 0, + }, + 10, + ); let signature = process_command(&config); assert_eq!(signature.unwrap(), SIGNATURE.to_string()); let bob_keypair = Keypair::new(); - let new_authorized_voter_pubkey = Pubkey::new_rand(); - config.command = - WalletCommand::AuthorizeVoter(bob_pubkey, bob_keypair, new_authorized_voter_pubkey); + let new_authorized_pubkey = Pubkey::new_rand(); + config.command = WalletCommand::VoteAuthorize( + bob_pubkey, + bob_keypair, + new_authorized_pubkey, + VoteAuthorize::Voter, + ); let signature = process_command(&config); assert_eq!(signature.unwrap(), SIGNATURE.to_string()); @@ -2778,10 +2843,24 @@ mod tests { }; assert!(process_command(&config).is_err()); - config.command = WalletCommand::CreateVoteAccount(bob_pubkey, node_pubkey, 0, 10); + config.command = WalletCommand::CreateVoteAccount( + bob_pubkey, + VoteInit { + node_pubkey, + authorized_voter: bob_pubkey, + authorized_withdrawer: bob_pubkey, + commission: 0, + }, + 10, + ); assert!(process_command(&config).is_err()); - config.command = WalletCommand::AuthorizeVoter(bob_pubkey, Keypair::new(), bob_pubkey); + config.command = WalletCommand::VoteAuthorize( + bob_pubkey, + Keypair::new(), + bob_pubkey, + VoteAuthorize::Voter, + ); assert!(process_command(&config).is_err()); config.command = WalletCommand::GetSlot; diff --git a/core/src/confidence.rs b/core/src/confidence.rs index 341694058..8a10effa6 100644 --- a/core/src/confidence.rs +++ b/core/src/confidence.rs @@ -228,7 +228,7 @@ mod tests { let ancestors = vec![3, 4, 5, 7, 9, 11]; let mut confidence = HashMap::new(); let lamports = 5; - let mut vote_state = VoteState::new(&Pubkey::default(), &Pubkey::default(), 0); + let mut vote_state = VoteState::default(); let root = ancestors.last().unwrap(); vote_state.root_slot = Some(*root); @@ -251,7 +251,7 @@ mod tests { let ancestors = vec![3, 4, 5, 7, 9, 11]; let mut confidence = HashMap::new(); let lamports = 5; - let mut vote_state = VoteState::new(&Pubkey::default(), &Pubkey::default(), 0); + let mut vote_state = VoteState::default(); let root = ancestors[2]; vote_state.root_slot = Some(root); @@ -281,7 +281,7 @@ mod tests { let ancestors = vec![3, 4, 5, 7, 9, 10, 11]; let mut confidence = HashMap::new(); let lamports = 5; - let mut vote_state = VoteState::new(&Pubkey::default(), &Pubkey::default(), 0); + let mut vote_state = VoteState::default(); let root = ancestors[2]; vote_state.root_slot = Some(root); diff --git a/core/src/staking_utils.rs b/core/src/staking_utils.rs index d183f34db..108552d5f 100644 --- a/core/src/staking_utils.rs +++ b/core/src/staking_utils.rs @@ -1,9 +1,7 @@ use solana_runtime::bank::Bank; -use solana_sdk::account::Account; -use solana_sdk::pubkey::Pubkey; +use solana_sdk::{account::Account, pubkey::Pubkey}; use solana_vote_api::vote_state::VoteState; -use std::borrow::Borrow; -use std::collections::HashMap; +use std::{borrow::Borrow, collections::HashMap}; /// Looks through vote accounts, and finds the latest slot that has achieved /// supermajority lockout @@ -99,14 +97,15 @@ where pub(crate) mod tests { use super::*; use crate::genesis_utils::{create_genesis_block, GenesisBlockInfo, BOOTSTRAP_LEADER_LAMPORTS}; - use solana_sdk::instruction::Instruction; - use solana_sdk::pubkey::Pubkey; - use solana_sdk::signature::{Keypair, KeypairUtil}; - use solana_sdk::sysvar::stake_history::{self, StakeHistory}; - use solana_sdk::transaction::Transaction; - use solana_stake_api::stake_instruction; - use solana_stake_api::stake_state::Stake; - use solana_vote_api::vote_instruction; + use solana_sdk::{ + instruction::Instruction, + pubkey::Pubkey, + signature::{Keypair, KeypairUtil}, + sysvar::stake_history::{self, StakeHistory}, + transaction::Transaction, + }; + use solana_stake_api::{stake_instruction, stake_state::Stake}; + use solana_vote_api::{vote_instruction, vote_state::VoteInit}; use std::sync::Arc; fn new_from_parent(parent: &Arc, slot: u64) -> Bank { @@ -140,8 +139,12 @@ pub(crate) mod tests { vote_instruction::create_account( &from_account.pubkey(), vote_pubkey, - node_pubkey, - 0, + &VoteInit { + node_pubkey: *node_pubkey, + authorized_voter: *vote_pubkey, + authorized_withdrawer: *vote_pubkey, + commission: 0, + }, amount, ), ); @@ -288,15 +291,28 @@ pub(crate) mod tests { fn test_to_staked_nodes() { let mut stakes = Vec::new(); let node1 = Pubkey::new_rand(); - let node2 = Pubkey::new_rand(); // Node 1 has stake of 3 for i in 0..3 { - stakes.push((i, VoteState::new(&Pubkey::new_rand(), &node1, 0))); + stakes.push(( + i, + VoteState::new(&VoteInit { + node_pubkey: node1, + ..VoteInit::default() + }), + )); } // Node 1 has stake of 5 - stakes.push((5, VoteState::new(&Pubkey::new_rand(), &node2, 0))); + let node2 = Pubkey::new_rand(); + + stakes.push(( + 5, + VoteState::new(&VoteInit { + node_pubkey: node2, + ..VoteInit::default() + }), + )); let result = to_staked_nodes(stakes.into_iter()); assert_eq!(result.len(), 2); diff --git a/local_cluster/src/local_cluster.rs b/local_cluster/src/local_cluster.rs index fdd8ddef8..e1086d994 100644 --- a/local_cluster/src/local_cluster.rs +++ b/local_cluster/src/local_cluster.rs @@ -12,8 +12,7 @@ use solana_core::{ }; use solana_sdk::{ client::SyncClient, - clock::DEFAULT_TICKS_PER_SLOT, - clock::{DEFAULT_SLOTS_PER_EPOCH, DEFAULT_SLOTS_PER_SEGMENT}, + clock::{DEFAULT_SLOTS_PER_EPOCH, DEFAULT_SLOTS_PER_SEGMENT, DEFAULT_TICKS_PER_SLOT}, genesis_block::GenesisBlock, message::Message, poh_config::PohConfig, @@ -24,7 +23,10 @@ use solana_sdk::{ }; use solana_stake_api::{config as stake_config, stake_instruction, stake_state::StakeState}; use solana_storage_api::{storage_contract, storage_instruction}; -use solana_vote_api::{vote_instruction, vote_state::VoteState}; +use solana_vote_api::{ + vote_instruction, + vote_state::{VoteInit, VoteState}, +}; use std::{ collections::HashMap, fs::remove_dir_all, @@ -436,8 +438,12 @@ impl LocalCluster { vote_instruction::create_account( &from_account.pubkey(), &vote_account_pubkey, - &node_pubkey, - 0, + &VoteInit { + node_pubkey, + authorized_voter: vote_account_pubkey, + authorized_withdrawer: vote_account_pubkey, + commission: 0, + }, amount, ), client.get_recent_blockhash().unwrap().0, diff --git a/programs/stake_tests/tests/stake_instruction.rs b/programs/stake_tests/tests/stake_instruction.rs index 73ac528aa..bb2cbc73e 100644 --- a/programs/stake_tests/tests/stake_instruction.rs +++ b/programs/stake_tests/tests/stake_instruction.rs @@ -14,7 +14,7 @@ use solana_stake_api::stake_instruction; use solana_stake_api::stake_instruction::process_instruction; use solana_stake_api::stake_state::StakeState; use solana_vote_api::vote_instruction; -use solana_vote_api::vote_state::{Vote, VoteState}; +use solana_vote_api::vote_state::{Vote, VoteInit, VoteState}; use std::sync::Arc; fn fill_epoch_with_votes( @@ -76,8 +76,12 @@ fn test_stake_account_delegate() { let message = Message::new(vote_instruction::create_account( &mint_pubkey, &vote_pubkey, - &node_pubkey, - std::u8::MAX / 2, + &VoteInit { + node_pubkey, + authorized_voter: vote_pubkey, + authorized_withdrawer: vote_pubkey, + commission: std::u8::MAX / 2, + }, 10, )); bank_client diff --git a/programs/vote_api/src/vote_instruction.rs b/programs/vote_api/src/vote_instruction.rs index f8a38b008..a6bb8967c 100644 --- a/programs/vote_api/src/vote_instruction.rs +++ b/programs/vote_api/src/vote_instruction.rs @@ -3,7 +3,7 @@ use crate::{ id, - vote_state::{self, Vote, VoteState}, + vote_state::{self, Vote, VoteAuthorize, VoteInit, VoteState}, }; use bincode::deserialize; use log::*; @@ -51,11 +51,11 @@ impl std::error::Error for VoteError {} #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] pub enum VoteInstruction { /// Initialize the VoteState for this `vote account` - /// takes a node_pubkey and commission - InitializeAccount(Pubkey, u8), + InitializeAccount(VoteInit), - /// Authorize a voter to send signed votes. - AuthorizeVoter(Pubkey), + /// Authorize a voter to send signed votes or a withdrawer + /// to withdraw + Authorize(Pubkey, VoteAuthorize), /// A Vote instruction with recent votes Vote(Vote), @@ -64,44 +64,38 @@ pub enum VoteInstruction { Withdraw(u64), } -fn initialize_account(vote_pubkey: &Pubkey, node_pubkey: &Pubkey, commission: u8) -> Instruction { +fn initialize_account(vote_pubkey: &Pubkey, vote_init: &VoteInit) -> Instruction { let account_metas = vec![AccountMeta::new(*vote_pubkey, false)]; Instruction::new( id(), - &VoteInstruction::InitializeAccount(*node_pubkey, commission), + &VoteInstruction::InitializeAccount(*vote_init), account_metas, ) } -pub fn minimum_balance() -> u64 { - let rent_calculator = solana_sdk::rent_calculator::RentCalculator::default(); - - rent_calculator.minimum_balance(VoteState::size_of()) -} - pub fn create_account( from_pubkey: &Pubkey, vote_pubkey: &Pubkey, - node_pubkey: &Pubkey, - commission: u8, + vote_init: &VoteInit, lamports: u64, ) -> Vec { let space = VoteState::size_of() as u64; let create_ix = system_instruction::create_account(from_pubkey, vote_pubkey, lamports, space, &id()); - let init_ix = initialize_account(vote_pubkey, node_pubkey, commission); + let init_ix = initialize_account(vote_pubkey, vote_init); vec![create_ix, init_ix] } +// for instructions that whose authorized signer may differ from the account's pubkey fn metas_for_authorized_signer( - vote_pubkey: &Pubkey, - authorized_voter_pubkey: &Pubkey, // currently authorized + account_pubkey: &Pubkey, + authorized_signer: &Pubkey, // currently authorized other_params: &[AccountMeta], ) -> Vec { - let is_own_signer = authorized_voter_pubkey == vote_pubkey; + let is_own_signer = authorized_signer == account_pubkey; // vote account - let mut account_metas = vec![AccountMeta::new(*vote_pubkey, is_own_signer)]; + let mut account_metas = vec![AccountMeta::new(*account_pubkey, is_own_signer)]; for meta in other_params { account_metas.push(meta.clone()); @@ -109,22 +103,23 @@ fn metas_for_authorized_signer( // append signer at the end if !is_own_signer { - account_metas.push(AccountMeta::new_credit_only(*authorized_voter_pubkey, true)) // signer + account_metas.push(AccountMeta::new_credit_only(*authorized_signer, true)) // signer } account_metas } -pub fn authorize_voter( +pub fn authorize( vote_pubkey: &Pubkey, - authorized_voter_pubkey: &Pubkey, // currently authorized - new_authorized_voter_pubkey: &Pubkey, + authorized_pubkey: &Pubkey, // currently authorized + new_authorized_pubkey: &Pubkey, + vote_authorize: VoteAuthorize, ) -> Instruction { - let account_metas = metas_for_authorized_signer(vote_pubkey, authorized_voter_pubkey, &[]); + let account_metas = metas_for_authorized_signer(vote_pubkey, authorized_pubkey, &[]); Instruction::new( id(), - &VoteInstruction::AuthorizeVoter(*new_authorized_voter_pubkey), + &VoteInstruction::Authorize(*new_authorized_pubkey, vote_authorize), account_metas, ) } @@ -144,11 +139,17 @@ pub fn vote(vote_pubkey: &Pubkey, authorized_voter_pubkey: &Pubkey, vote: Vote) Instruction::new(id(), &VoteInstruction::Vote(vote), account_metas) } -pub fn withdraw(vote_pubkey: &Pubkey, lamports: u64, to_pubkey: &Pubkey) -> Instruction { - let account_metas = vec![ - AccountMeta::new(*vote_pubkey, true), - AccountMeta::new_credit_only(*to_pubkey, false), - ]; +pub fn withdraw( + vote_pubkey: &Pubkey, + withdrawer_pubkey: &Pubkey, + lamports: u64, + to_pubkey: &Pubkey, +) -> Instruction { + let account_metas = metas_for_authorized_signer( + vote_pubkey, + withdrawer_pubkey, + &[AccountMeta::new_credit_only(*to_pubkey, false)], + ); Instruction::new(id(), &VoteInstruction::Withdraw(lamports), account_metas) } @@ -173,11 +174,11 @@ pub fn process_instruction( // TODO: data-driven unpack and dispatch of KeyedAccounts match deserialize(data).map_err(|_| InstructionError::InvalidInstructionData)? { - VoteInstruction::InitializeAccount(node_pubkey, commission) => { - vote_state::initialize_account(me, &node_pubkey, commission) + VoteInstruction::InitializeAccount(vote_init) => { + vote_state::initialize_account(me, &vote_init) } - VoteInstruction::AuthorizeVoter(voter_pubkey) => { - vote_state::authorize_voter(me, rest, &voter_pubkey) + VoteInstruction::Authorize(voter_pubkey, vote_authorize) => { + vote_state::authorize(me, rest, &voter_pubkey, vote_authorize) } VoteInstruction::Vote(vote) => { datapoint_info!("vote-native", ("count", 1, i64)); @@ -198,7 +199,10 @@ pub fn process_instruction( if rest.is_empty() { Err(InstructionError::InvalidInstructionData)?; } - vote_state::withdraw(me, lamports, &mut rest[0]) + let (to, rest) = rest.split_at_mut(1); + let to = &mut to[0]; + + vote_state::withdraw(me, rest, lamports, to) } } } @@ -251,8 +255,7 @@ mod tests { let instructions = create_account( &Pubkey::default(), &Pubkey::default(), - &Pubkey::default(), - 0, + &VoteInit::default(), 100, ); assert_eq!( @@ -268,10 +271,20 @@ mod tests { Err(InstructionError::InvalidAccountData), ); assert_eq!( - process_instruction(&authorize_voter( + process_instruction(&authorize( &Pubkey::default(), &Pubkey::default(), &Pubkey::default(), + VoteAuthorize::Voter, + )), + Err(InstructionError::InvalidAccountData), + ); + assert_eq!( + process_instruction(&withdraw( + &Pubkey::default(), + &Pubkey::default(), + 0, + &Pubkey::default() )), Err(InstructionError::InvalidAccountData), ); @@ -285,4 +298,44 @@ mod tests { assert!(minimum_balance as f64 / 2f64.powf(34.0) < 0.02) } + #[test] + fn test_metas_for_authorized_signer() { + let account_pubkey = Pubkey::new_rand(); + let authorized_signer = Pubkey::new_rand(); + + assert_eq!( + metas_for_authorized_signer(&account_pubkey, &authorized_signer, &[]).len(), + 2 + ); + assert_eq!( + metas_for_authorized_signer(&account_pubkey, &account_pubkey, &[]).len(), + 1 + ); + } + #[test] + fn test_custom_error_decode() { + use num_traits::FromPrimitive; + fn pretty_err(err: InstructionError) -> String + where + T: 'static + std::error::Error + DecodeError + FromPrimitive, + { + if let InstructionError::CustomError(code) = err { + let specific_error: T = T::decode_custom_error_to_enum(code).unwrap(); + format!( + "{:?}: {}::{:?} - {}", + err, + T::type_of(), + specific_error, + specific_error, + ) + } else { + "".to_string() + } + } + assert_eq!( + "CustomError(0): VoteError::VoteTooOld - vote already recorded or not in slot hashes history", + pretty_err::(VoteError::VoteTooOld.into()) + ) + } + } diff --git a/programs/vote_api/src/vote_state.rs b/programs/vote_api/src/vote_state.rs index f1788157e..ce1e1aa9b 100644 --- a/programs/vote_api/src/vote_state.rs +++ b/programs/vote_api/src/vote_state.rs @@ -67,14 +67,33 @@ impl Lockout { } } +#[derive(Default, Serialize, Deserialize, Debug, PartialEq, Eq, Clone, Copy)] +pub struct VoteInit { + pub node_pubkey: Pubkey, + pub authorized_voter: Pubkey, + pub authorized_withdrawer: Pubkey, + pub commission: u8, +} + +#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone, Copy)] +pub enum VoteAuthorize { + Voter, + Withdrawer, +} + #[derive(Debug, Default, Serialize, Deserialize, PartialEq, Eq, Clone)] pub struct VoteState { - pub votes: VecDeque, + /// the node that votes in this account pub node_pubkey: Pubkey, - pub authorized_voter_pubkey: Pubkey, + /// the signer for vote transactions + pub authorized_voter: Pubkey, + /// the signer for withdrawals + pub authorized_withdrawer: Pubkey, /// fraction of std::u8::MAX that represents what part of a rewards /// payout should be given to this VoteAccount pub commission: u8, + + pub votes: VecDeque, pub root_slot: Option, /// clock epoch @@ -91,11 +110,12 @@ pub struct VoteState { } impl VoteState { - pub fn new(vote_pubkey: &Pubkey, node_pubkey: &Pubkey, commission: u8) -> Self { + pub fn new(vote_init: &VoteInit) -> Self { Self { - node_pubkey: *node_pubkey, - authorized_voter_pubkey: *vote_pubkey, - commission, + node_pubkey: vote_init.node_pubkey, + authorized_voter: vote_init.authorized_voter, + authorized_withdrawer: vote_init.authorized_withdrawer, + commission: vote_init.commission, ..VoteState::default() } } @@ -313,39 +333,69 @@ impl VoteState { } } -/// Authorize the given pubkey to sign votes. This may be called multiple times, +/// Authorize the given pubkey to withdraw or sign votes. This may be called multiple times, /// but will implicitly withdraw authorization from the previously authorized -/// voter. The default voter is the owner of the vote account's pubkey. -pub fn authorize_voter( +/// key +pub fn authorize( vote_account: &mut KeyedAccount, other_signers: &[KeyedAccount], - authorized_voter_pubkey: &Pubkey, + authorized: &Pubkey, + vote_authorize: VoteAuthorize, ) -> Result<(), InstructionError> { let mut vote_state: VoteState = vote_account.state()?; - // clock authorized signer must say "yay" - let authorized = Some(&vote_state.authorized_voter_pubkey); - if vote_account.signer_key() != authorized + // current authorized signer must say "yay" + match vote_authorize { + VoteAuthorize::Voter => { + verify_authorized_signer(&vote_state.authorized_voter, vote_account, other_signers)?; + vote_state.authorized_voter = *authorized; + } + VoteAuthorize::Withdrawer => { + verify_authorized_signer( + &vote_state.authorized_withdrawer, + vote_account, + other_signers, + )?; + vote_state.authorized_withdrawer = *authorized; + } + } + + vote_account.set_state(&vote_state) +} + +fn verify_authorized_signer( + authorized: &Pubkey, + account: &KeyedAccount, + other_signers: &[KeyedAccount], +) -> Result<(), InstructionError> { + let authorized = Some(authorized); + + // find a signer that matches authorized + if account.signer_key() != authorized && other_signers .iter() .all(|account| account.signer_key() != authorized) { return Err(InstructionError::MissingRequiredSignature); } - - vote_state.authorized_voter_pubkey = *authorized_voter_pubkey; - vote_account.set_state(&vote_state) + Ok(()) } /// Withdraw funds from the vote account pub fn withdraw( vote_account: &mut KeyedAccount, + other_signers: &[KeyedAccount], lamports: u64, to_account: &mut KeyedAccount, ) -> Result<(), InstructionError> { - if vote_account.signer_key().is_none() { - return Err(InstructionError::MissingRequiredSignature); - } + let vote_state: VoteState = vote_account.state()?; + + verify_authorized_signer( + &vote_state.authorized_withdrawer, + vote_account, + other_signers, + )?; + if vote_account.account.lamports < lamports { return Err(InstructionError::InsufficientFunds); } @@ -359,19 +409,14 @@ pub fn withdraw( /// that the transaction must be signed by the staker's keys pub fn initialize_account( vote_account: &mut KeyedAccount, - node_pubkey: &Pubkey, - commission: u8, + vote_init: &VoteInit, ) -> Result<(), InstructionError> { let vote_state: VoteState = vote_account.state()?; - if vote_state.authorized_voter_pubkey != Pubkey::default() { + if vote_state.authorized_voter != Pubkey::default() { return Err(InstructionError::AccountAlreadyInitialized); } - vote_account.set_state(&VoteState::new( - vote_account.unsigned_key(), - node_pubkey, - commission, - )) + vote_account.set_state(&VoteState::new(vote_init)) } pub fn process_vote( @@ -383,19 +428,11 @@ pub fn process_vote( ) -> Result<(), InstructionError> { let mut vote_state: VoteState = vote_account.state()?; - if vote_state.authorized_voter_pubkey == Pubkey::default() { + if vote_state.authorized_voter == Pubkey::default() { return Err(InstructionError::UninitializedAccount); } - let authorized = Some(&vote_state.authorized_voter_pubkey); - // find a signer that matches the authorized_voter_pubkey - if vote_account.signer_key() != authorized - && other_signers - .iter() - .all(|account| account.signer_key() != authorized) - { - return Err(InstructionError::MissingRequiredSignature); - } + verify_authorized_signer(&vote_state.authorized_voter, vote_account, other_signers)?; vote_state.process_vote(vote, slot_hashes, clock.epoch)?; vote_account.set_state(&vote_state) @@ -410,9 +447,14 @@ pub fn create_account( ) -> Account { let mut vote_account = Account::new(lamports, VoteState::size_of(), &id()); - VoteState::new(vote_pubkey, node_pubkey, commission) - .to(&mut vote_account) - .unwrap(); + VoteState::new(&VoteInit { + node_pubkey: *node_pubkey, + authorized_voter: *vote_pubkey, + authorized_withdrawer: *vote_pubkey, + commission, + }) + .to(&mut vote_account) + .unwrap(); vote_account } @@ -427,6 +469,17 @@ mod tests { const MAX_RECENT_VOTES: usize = 16; + impl VoteState { + pub fn new_for_test(auth_pubkey: &Pubkey) -> Self { + Self::new(&VoteInit { + node_pubkey: Pubkey::new_rand(), + authorized_voter: *auth_pubkey, + authorized_withdrawer: *auth_pubkey, + commission: 0, + }) + } + } + #[test] fn test_initialize_vote_account() { let vote_account_pubkey = Pubkey::new_rand(); @@ -436,11 +489,27 @@ mod tests { //init should pass let mut vote_account = KeyedAccount::new(&vote_account_pubkey, false, &mut vote_account); - let res = initialize_account(&mut vote_account, &node_pubkey, 0); + let res = initialize_account( + &mut vote_account, + &VoteInit { + node_pubkey, + authorized_voter: vote_account_pubkey, + authorized_withdrawer: vote_account_pubkey, + commission: 0, + }, + ); assert_eq!(res, Ok(())); // reinit should fail - let res = initialize_account(&mut vote_account, &node_pubkey, 0); + let res = initialize_account( + &mut vote_account, + &VoteInit { + node_pubkey, + authorized_voter: vote_account_pubkey, + authorized_withdrawer: vote_account_pubkey, + commission: 0, + }, + ); assert_eq!(res, Err(InstructionError::AccountAlreadyInitialized)); } @@ -504,7 +573,7 @@ mod tests { let (vote_pubkey, vote_account) = create_test_account(); let vote_state: VoteState = vote_account.state().unwrap(); - assert_eq!(vote_state.authorized_voter_pubkey, vote_pubkey); + assert_eq!(vote_state.authorized_voter, vote_pubkey); assert!(vote_state.votes.is_empty()); } @@ -582,21 +651,23 @@ mod tests { // another voter let authorized_voter_pubkey = Pubkey::new_rand(); - let res = authorize_voter( + let res = authorize( &mut KeyedAccount::new(&vote_pubkey, false, &mut vote_account), &[], &authorized_voter_pubkey, + VoteAuthorize::Voter, ); assert_eq!(res, Err(InstructionError::MissingRequiredSignature)); - let res = authorize_voter( + let res = authorize( &mut KeyedAccount::new(&vote_pubkey, true, &mut vote_account), &[], &authorized_voter_pubkey, + VoteAuthorize::Voter, ); assert_eq!(res, Ok(())); // verify authorized_voter_pubkey can authorize authorized_voter_pubkey ;) - let res = authorize_voter( + let res = authorize( &mut KeyedAccount::new(&vote_pubkey, false, &mut vote_account), &[KeyedAccount::new( &authorized_voter_pubkey, @@ -604,6 +675,31 @@ mod tests { &mut Account::default(), )], &authorized_voter_pubkey, + VoteAuthorize::Voter, + ); + assert_eq!(res, Ok(())); + + // authorize another withdrawer + // another voter + let authorized_withdrawer_pubkey = Pubkey::new_rand(); + let res = authorize( + &mut KeyedAccount::new(&vote_pubkey, true, &mut vote_account), + &[], + &authorized_withdrawer_pubkey, + VoteAuthorize::Withdrawer, + ); + assert_eq!(res, Ok(())); + + // verify authorized_withdrawer can authorize authorized_withdrawer ;) + let res = authorize( + &mut KeyedAccount::new(&vote_pubkey, false, &mut vote_account), + &[KeyedAccount::new( + &authorized_withdrawer_pubkey, + true, + &mut Account::default(), + )], + &authorized_withdrawer_pubkey, + VoteAuthorize::Withdrawer, ); assert_eq!(res, Ok(())); @@ -678,7 +774,7 @@ mod tests { #[test] fn test_vote_double_lockout_after_expiration() { let voter_pubkey = Pubkey::new_rand(); - let mut vote_state = VoteState::new(&voter_pubkey, &Pubkey::new_rand(), 0); + let mut vote_state = VoteState::new_for_test(&voter_pubkey); for i in 0..3 { vote_state.process_slot_vote_unchecked(i as u64); @@ -706,7 +802,7 @@ mod tests { #[test] fn test_expire_multiple_votes() { let voter_pubkey = Pubkey::new_rand(); - let mut vote_state = VoteState::new(&voter_pubkey, &Pubkey::new_rand(), 0); + let mut vote_state = VoteState::new_for_test(&voter_pubkey); for i in 0..3 { vote_state.process_slot_vote_unchecked(i as u64); @@ -737,7 +833,7 @@ mod tests { #[test] fn test_vote_credits() { let voter_pubkey = Pubkey::new_rand(); - let mut vote_state = VoteState::new(&voter_pubkey, &Pubkey::new_rand(), 0); + let mut vote_state = VoteState::new_for_test(&voter_pubkey); for i in 0..MAX_LOCKOUT_HISTORY { vote_state.process_slot_vote_unchecked(i as u64); @@ -756,7 +852,7 @@ mod tests { #[test] fn test_duplicate_vote() { let voter_pubkey = Pubkey::new_rand(); - let mut vote_state = VoteState::new(&voter_pubkey, &Pubkey::new_rand(), 0); + let mut vote_state = VoteState::new_for_test(&voter_pubkey); vote_state.process_slot_vote_unchecked(0); vote_state.process_slot_vote_unchecked(1); vote_state.process_slot_vote_unchecked(0); @@ -768,7 +864,7 @@ mod tests { #[test] fn test_nth_recent_vote() { let voter_pubkey = Pubkey::new_rand(); - let mut vote_state = VoteState::new(&voter_pubkey, &Pubkey::new_rand(), 0); + let mut vote_state = VoteState::new_for_test(&voter_pubkey); for i in 0..MAX_LOCKOUT_HISTORY { vote_state.process_slot_vote_unchecked(i as u64); } @@ -799,9 +895,9 @@ mod tests { #[test] fn test_process_missed_votes() { let account_a = Pubkey::new_rand(); - let mut vote_state_a = VoteState::new(&account_a, &Pubkey::new_rand(), 0); + let mut vote_state_a = VoteState::new_for_test(&account_a); let account_b = Pubkey::new_rand(); - let mut vote_state_b = VoteState::new(&account_b, &Pubkey::new_rand(), 0); + let mut vote_state_b = VoteState::new_for_test(&account_b); // process some votes on account a (0..5) @@ -821,7 +917,7 @@ mod tests { #[test] fn test_process_vote_skips_old_vote() { - let mut vote_state = VoteState::new(&Pubkey::default(), &Pubkey::default(), 0); + let mut vote_state = VoteState::default(); let vote = Vote::new(vec![0], Hash::default()); let slot_hashes: Vec<_> = vec![(0, vote.hash)]; @@ -836,7 +932,7 @@ mod tests { #[test] fn test_check_slots_are_valid_vote_empty_slot_hashes() { - let vote_state = VoteState::new(&Pubkey::default(), &Pubkey::default(), 0); + let vote_state = VoteState::default(); let vote = Vote::new(vec![0], Hash::default()); assert_eq!( @@ -847,7 +943,7 @@ mod tests { #[test] fn test_check_slots_are_valid_new_vote() { - let vote_state = VoteState::new(&Pubkey::default(), &Pubkey::default(), 0); + let vote_state = VoteState::default(); let vote = Vote::new(vec![0], Hash::default()); let slot_hashes: Vec<_> = vec![(*vote.slots.last().unwrap(), vote.hash)]; @@ -859,7 +955,7 @@ mod tests { #[test] fn test_check_slots_are_valid_bad_hash() { - let vote_state = VoteState::new(&Pubkey::default(), &Pubkey::default(), 0); + let vote_state = VoteState::default(); let vote = Vote::new(vec![0], Hash::default()); let slot_hashes: Vec<_> = vec![(*vote.slots.last().unwrap(), hash(vote.hash.as_ref()))]; @@ -871,7 +967,7 @@ mod tests { #[test] fn test_check_slots_are_valid_bad_slot() { - let vote_state = VoteState::new(&Pubkey::default(), &Pubkey::default(), 0); + let vote_state = VoteState::default(); let vote = Vote::new(vec![1], Hash::default()); let slot_hashes: Vec<_> = vec![(0, vote.hash)]; @@ -883,7 +979,7 @@ mod tests { #[test] fn test_check_slots_are_valid_duplicate_vote() { - let mut vote_state = VoteState::new(&Pubkey::default(), &Pubkey::default(), 0); + let mut vote_state = VoteState::default(); let vote = Vote::new(vec![0], Hash::default()); let slot_hashes: Vec<_> = vec![(*vote.slots.last().unwrap(), vote.hash)]; @@ -896,7 +992,7 @@ mod tests { #[test] fn test_check_slots_are_valid_next_vote() { - let mut vote_state = VoteState::new(&Pubkey::default(), &Pubkey::default(), 0); + let mut vote_state = VoteState::default(); let vote = Vote::new(vec![0], Hash::default()); let slot_hashes: Vec<_> = vec![(*vote.slots.last().unwrap(), vote.hash)]; @@ -912,7 +1008,7 @@ mod tests { #[test] fn test_check_slots_are_valid_next_vote_only() { - let mut vote_state = VoteState::new(&Pubkey::default(), &Pubkey::default(), 0); + let mut vote_state = VoteState::default(); let vote = Vote::new(vec![0], Hash::default()); let slot_hashes: Vec<_> = vec![(*vote.slots.last().unwrap(), vote.hash)]; @@ -925,17 +1021,28 @@ mod tests { Ok(()) ); } + #[test] + fn test_process_vote_empty_slots() { + let mut vote_state = VoteState::default(); + + let vote = Vote::new(vec![], Hash::default()); + assert_eq!( + vote_state.process_vote(&vote, &[], 0), + Err(VoteError::EmptySlots) + ); + } #[test] fn test_vote_state_commission_split() { - let vote_state = VoteState::new(&Pubkey::default(), &Pubkey::default(), 0); + let vote_state = VoteState::default(); assert_eq!(vote_state.commission_split(1.0), (0.0, 1.0, false)); - let vote_state = VoteState::new(&Pubkey::default(), &Pubkey::default(), std::u8::MAX); + let mut vote_state = VoteState::default(); + vote_state.commission = std::u8::MAX; assert_eq!(vote_state.commission_split(1.0), (1.0, 0.0, false)); - let vote_state = VoteState::new(&Pubkey::default(), &Pubkey::default(), std::u8::MAX / 2); + vote_state.commission = std::u8::MAX / 2; let (voter_portion, staker_portion, was_split) = vote_state.commission_split(10.0); assert_eq!( @@ -948,9 +1055,10 @@ mod tests { fn test_vote_state_withdraw() { let (vote_pubkey, mut vote_account) = create_test_account(); - // unsigned + // unsigned request let res = withdraw( &mut KeyedAccount::new(&vote_pubkey, false, &mut vote_account), + &[], 0, &mut KeyedAccount::new(&Pubkey::new_rand(), false, &mut Account::default()), ); @@ -959,6 +1067,7 @@ mod tests { // insufficient funds let res = withdraw( &mut KeyedAccount::new(&vote_pubkey, true, &mut vote_account), + &[], 101, &mut KeyedAccount::new(&Pubkey::new_rand(), false, &mut Account::default()), ); @@ -969,6 +1078,25 @@ mod tests { let lamports = vote_account.lamports; let res = withdraw( &mut KeyedAccount::new(&vote_pubkey, true, &mut vote_account), + &[], + lamports, + &mut KeyedAccount::new(&Pubkey::new_rand(), false, &mut to_account), + ); + assert_eq!(res, Ok(())); + assert_eq!(vote_account.lamports, 0); + assert_eq!(to_account.lamports, lamports); + + // reset balance, verify that authorized_withdrawer works + vote_account.lamports = lamports; + to_account.lamports = 0; + let mut authorized_withdrawer_account = Account::new(0, 0, &vote_pubkey); + let res = withdraw( + &mut KeyedAccount::new(&vote_pubkey, false, &mut vote_account), + &[KeyedAccount::new( + &vote_pubkey, + true, + &mut authorized_withdrawer_account, + )], lamports, &mut KeyedAccount::new(&Pubkey::new_rand(), false, &mut to_account), ); diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index 3c52bc626..028744056 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -1584,7 +1584,7 @@ mod tests { use solana_sdk::sysvar::{fees::Fees, rewards::Rewards}; use solana_stake_api::stake_state::Stake; use solana_vote_api::vote_instruction; - use solana_vote_api::vote_state::{VoteState, MAX_LOCKOUT_HISTORY}; + use solana_vote_api::vote_state::{VoteInit, VoteState, MAX_LOCKOUT_HISTORY}; use std::io::Cursor; use std::time::Duration; use tempfile::TempDir; @@ -2861,8 +2861,12 @@ mod tests { let instructions = vote_instruction::create_account( &mint_keypair.pubkey(), &vote_keypair.pubkey(), - &mint_keypair.pubkey(), - 0, + &VoteInit { + node_pubkey: mint_keypair.pubkey(), + authorized_voter: vote_keypair.pubkey(), + authorized_withdrawer: vote_keypair.pubkey(), + commission: 0, + }, 10, );