From f1e7237c09a05b7e7d7192e605a5fb9f319ec53c Mon Sep 17 00:00:00 2001 From: Rob Walker Date: Wed, 10 Apr 2019 17:52:47 -0700 Subject: [PATCH] vote_api cleanup (#3710) * vote_api cleanup * fixups * fixup * remove unused code * revert removal of serialize and deserialize * ... * increase coverage, bootstrap staking * Sagar's STAKE to my VOTE --- core/src/entry.rs | 3 +- core/src/fullnode.rs | 5 +- core/src/leader_schedule_utils.rs | 4 +- core/src/local_cluster.rs | 21 +- core/src/locktower.rs | 15 +- core/src/replay_stage.rs | 3 +- core/src/staking_utils.rs | 10 +- core/src/voting_keypair.rs | 37 +- fullnode/src/main.rs | 6 +- genesis/src/main.rs | 12 +- multinode-demo/bootstrap-leader.sh | 8 +- multinode-demo/common.sh | 40 +-- multinode-demo/fullnode.sh | 15 +- multinode-demo/setup.sh | 6 +- programs/stake_api/src/stake_state.rs | 30 +- programs/vote_api/src/lib.rs | 1 - programs/vote_api/src/vote_instruction.rs | 226 +++++++++--- programs/vote_api/src/vote_processor.rs | 175 ---------- programs/vote_api/src/vote_state.rs | 404 +++++++++++----------- programs/vote_program/src/lib.rs | 2 +- run.sh | 7 +- runtime/src/bank.rs | 39 ++- wallet/src/main.rs | 46 +-- wallet/src/wallet.rs | 190 +++++----- 24 files changed, 638 insertions(+), 667 deletions(-) delete mode 100644 programs/vote_api/src/vote_processor.rs diff --git a/core/src/entry.rs b/core/src/entry.rs index 9eb3afaa9f..1813809614 100644 --- a/core/src/entry.rs +++ b/core/src/entry.rs @@ -451,7 +451,8 @@ mod tests { use solana_sdk::hash::hash; use solana_sdk::signature::{Keypair, KeypairUtil}; use solana_sdk::system_transaction; - use solana_vote_api::vote_instruction::{self, Vote}; + use solana_vote_api::vote_instruction; + use solana_vote_api::vote_state::Vote; use std::net::{IpAddr, Ipv4Addr, SocketAddr}; fn create_sample_payment(keypair: &Keypair, hash: Hash) -> Transaction { diff --git a/core/src/fullnode.rs b/core/src/fullnode.rs index 418d79ebcc..7109fd1c1d 100644 --- a/core/src/fullnode.rs +++ b/core/src/fullnode.rs @@ -28,7 +28,8 @@ use solana_sdk::signature::{Keypair, KeypairUtil}; use solana_sdk::system_transaction; use solana_sdk::timing::timestamp; use solana_sdk::transaction::Transaction; -use solana_vote_api::vote_instruction::{self, Vote}; +use solana_vote_api::vote_instruction; +use solana_vote_api::vote_state::Vote; use std::net::{IpAddr, Ipv4Addr, SocketAddr}; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::mpsc::{channel, Receiver}; @@ -334,6 +335,8 @@ pub fn make_active_set_entries( let new_vote_account_ixs = vote_instruction::create_account( &active_keypair.pubkey(), &vote_account_id, + &active_keypair.pubkey(), + 0, stake.saturating_sub(2), ); let new_vote_account_tx = Transaction::new_signed_instructions( diff --git a/core/src/leader_schedule_utils.rs b/core/src/leader_schedule_utils.rs index 40ec2526cd..f8011186a7 100644 --- a/core/src/leader_schedule_utils.rs +++ b/core/src/leader_schedule_utils.rs @@ -100,7 +100,7 @@ mod tests { use crate::blocktree::get_tmp_ledger_path; use crate::blocktree::tests::make_slot_entries; use crate::staking_utils; - use crate::voting_keypair::tests::new_vote_account_with_delegate; + use crate::voting_keypair::tests::new_vote_account; use solana_sdk::genesis_block::{GenesisBlock, BOOTSTRAP_LEADER_LAMPORTS}; use solana_sdk::signature::{Keypair, KeypairUtil}; use std::sync::Arc; @@ -224,7 +224,7 @@ mod tests { // Create new vote account let new_voting_keypair = Keypair::new(); - new_vote_account_with_delegate( + new_vote_account( &mint_keypair, &new_voting_keypair, &delegate_id, diff --git a/core/src/local_cluster.rs b/core/src/local_cluster.rs index 5f576ca083..c25a2d2b94 100644 --- a/core/src/local_cluster.rs +++ b/core/src/local_cluster.rs @@ -334,13 +334,15 @@ impl LocalCluster { amount: u64, ) -> Result<()> { let vote_account_pubkey = vote_account.pubkey(); - let delegate_id = from_account.pubkey(); + let node_id = from_account.pubkey(); // Create the vote account if necessary if client.poll_get_balance(&vote_account_pubkey).unwrap_or(0) == 0 { // 1) Create vote account let instructions = vote_instruction::create_account( &from_account.pubkey(), &vote_account_pubkey, + &node_id, + 0, amount, ); let mut transaction = Transaction::new_signed_instructions( @@ -355,27 +357,12 @@ impl LocalCluster { client .wait_for_balance(&vote_account_pubkey, Some(amount)) .expect("get balance"); - - // 2) Set delegate for new vote account - let vote_instruction = - vote_instruction::delegate_stake(&vote_account_pubkey, &delegate_id); - - let mut transaction = Transaction::new_signed_instructions( - &[vote_account], - vec![vote_instruction], - client.get_recent_blockhash().unwrap(), - ); - - client - .retry_transfer(&vote_account, &mut transaction, 5) - .expect("client transfer 2"); } - info!("Checking for vote account registration"); let vote_account_user_data = client.get_account_data(&vote_account_pubkey); if let Ok(Some(vote_account_user_data)) = vote_account_user_data { if let Ok(vote_state) = VoteState::deserialize(&vote_account_user_data) { - if vote_state.delegate_id == delegate_id { + if vote_state.node_id == node_id { return Ok(()); } } diff --git a/core/src/locktower.rs b/core/src/locktower.rs index d53333f392..50d2548cb6 100644 --- a/core/src/locktower.rs +++ b/core/src/locktower.rs @@ -5,8 +5,7 @@ use solana_metrics::influxdb; use solana_runtime::bank::Bank; use solana_sdk::account::Account; use solana_sdk::pubkey::Pubkey; -use solana_vote_api::vote_instruction::Vote; -use solana_vote_api::vote_state::{Lockout, VoteState, MAX_LOCKOUT_HISTORY}; +use solana_vote_api::vote_state::{Lockout, Vote, VoteState, MAX_LOCKOUT_HISTORY}; use std::sync::Arc; pub const VOTE_THRESHOLD_DEPTH: usize = 8; @@ -117,7 +116,7 @@ impl Locktower { .expect("bank should always have valid VoteState data"); if key == self.epoch_stakes.delegate_id - || vote_state.delegate_id == self.epoch_stakes.delegate_id + || vote_state.node_id == self.epoch_stakes.delegate_id { debug!("vote state {:?}", vote_state); debug!( @@ -141,7 +140,7 @@ impl Locktower { ); } let start_root = vote_state.root_slot; - vote_state.process_vote(Vote { slot: bank_slot }); + vote_state.process_vote(&Vote { slot: bank_slot }); for vote in &vote_state.votes { Self::update_ancestor_lockouts(&mut stake_lockouts, &vote, ancestors); } @@ -237,7 +236,7 @@ impl Locktower { pub fn record_vote(&mut self, slot: u64) -> Option { let root_slot = self.lockouts.root_slot; - self.lockouts.process_vote(Vote { slot }); + self.lockouts.process_vote(&Vote { slot }); solana_metrics::submit( influxdb::Point::new("counter-locktower-vote") .add_field("latest", influxdb::Value::Integer(slot as i64)) @@ -281,7 +280,7 @@ impl Locktower { pub fn is_locked_out(&self, slot: u64, descendants: &HashMap>) -> bool { let mut lockouts = self.lockouts.clone(); - lockouts.process_vote(Vote { slot }); + lockouts.process_vote(&Vote { slot }); for vote in &lockouts.votes { if vote.slot == slot { continue; @@ -303,7 +302,7 @@ impl Locktower { stake_lockouts: &HashMap, ) -> bool { let mut lockouts = self.lockouts.clone(); - lockouts.process_vote(Vote { slot }); + lockouts.process_vote(&Vote { slot }); let vote = lockouts.nth_recent_vote(self.threshold_depth); if let Some(vote) = vote { if let Some(fork_stake) = stake_lockouts.get(&vote.slot) { @@ -398,7 +397,7 @@ mod test { account.lamports = *lamports; let mut vote_state = VoteState::default(); for slot in *votes { - vote_state.process_vote(Vote { slot: *slot }); + vote_state.process_vote(&Vote { slot: *slot }); } vote_state .serialize(&mut account.data) diff --git a/core/src/replay_stage.rs b/core/src/replay_stage.rs index f27129d603..7cf0ede004 100644 --- a/core/src/replay_stage.rs +++ b/core/src/replay_stage.rs @@ -21,7 +21,8 @@ use solana_sdk::pubkey::Pubkey; use solana_sdk::signature::KeypairUtil; use solana_sdk::timing::{self, duration_as_ms}; use solana_sdk::transaction::Transaction; -use solana_vote_api::vote_instruction::{self, Vote}; +use solana_vote_api::vote_instruction; +use solana_vote_api::vote_state::Vote; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::mpsc::{channel, Receiver, RecvTimeoutError, Sender}; use std::sync::{Arc, Mutex, RwLock}; diff --git a/core/src/staking_utils.rs b/core/src/staking_utils.rs index c645bac236..237a70a332 100644 --- a/core/src/staking_utils.rs +++ b/core/src/staking_utils.rs @@ -76,7 +76,7 @@ pub fn node_staked_accounts_at_epoch( fn filter_no_delegate(account_id: &Pubkey, account: &Account) -> bool { VoteState::deserialize(&account.data) - .map(|vote_state| vote_state.delegate_id != *account_id) + .map(|vote_state| vote_state.node_id != *account_id) .unwrap_or(false) } @@ -104,7 +104,7 @@ fn to_delegated_stakes( ) -> HashMap { let mut map: HashMap = HashMap::new(); node_staked_accounts.for_each(|(stake, state)| { - let delegate = &state.delegate_id; + let delegate = &state.node_id; map.entry(*delegate) .and_modify(|s| *s += stake) .or_insert(stake); @@ -200,7 +200,7 @@ mod tests { // Make a mint vote account. Because the mint has nonzero stake, this // should show up in the active set - voting_keypair_tests::new_vote_account_with_delegate( + voting_keypair_tests::new_vote_account( &mint_keypair, &bank_voter, &mint_keypair.pubkey(), @@ -283,11 +283,11 @@ mod tests { // Delegate 1 has stake of 3 for i in 0..3 { - stakes.push((i, VoteState::new(&delegate1))); + stakes.push((i, VoteState::new(&Pubkey::new_rand(), &delegate1, 0))); } // Delegate 1 has stake of 5 - stakes.push((5, VoteState::new(&delegate2))); + stakes.push((5, VoteState::new(&Pubkey::new_rand(), &delegate2, 0))); let result = to_delegated_stakes(stakes.into_iter()); assert_eq!(result.len(), 2); diff --git a/core/src/voting_keypair.rs b/core/src/voting_keypair.rs index 447877309e..f19bdc6e3f 100644 --- a/core/src/voting_keypair.rs +++ b/core/src/voting_keypair.rs @@ -110,7 +110,8 @@ pub mod tests { use solana_sdk::pubkey::Pubkey; use solana_sdk::signature::{Keypair, KeypairUtil}; use solana_sdk::transaction::Transaction; - use solana_vote_api::vote_instruction::{self, Vote}; + use solana_vote_api::vote_instruction; + use solana_vote_api::vote_state::Vote; fn process_instructions(bank: &Bank, keypairs: &[&T], ixs: Vec) { let blockhash = bank.last_blockhash(); @@ -119,27 +120,21 @@ pub mod tests { } pub fn new_vote_account( - from_keypair: &Keypair, - voting_pubkey: &Pubkey, - bank: &Bank, - lamports: u64, - ) { - let ixs = vote_instruction::create_account(&from_keypair.pubkey(), voting_pubkey, lamports); - process_instructions(bank, &[from_keypair], ixs); - } - - pub fn new_vote_account_with_delegate( from_keypair: &Keypair, voting_keypair: &Keypair, - delegate: &Pubkey, + node_id: &Pubkey, bank: &Bank, lamports: u64, ) { let voting_pubkey = voting_keypair.pubkey(); - let mut ixs = - vote_instruction::create_account(&from_keypair.pubkey(), &voting_pubkey, lamports); - ixs.push(vote_instruction::delegate_stake(&voting_pubkey, delegate)); - process_instructions(bank, &[from_keypair, voting_keypair], ixs); + let ixs = vote_instruction::create_account( + &from_keypair.pubkey(), + &voting_pubkey, + node_id, + 0, + lamports, + ); + process_instructions(bank, &[from_keypair], ixs); } pub fn push_vote(voting_keypair: &T, bank: &Bank, slot: u64) { @@ -150,13 +145,19 @@ pub mod tests { pub fn new_vote_account_with_vote( from_keypair: &T, voting_keypair: &T, + node_id: &Pubkey, bank: &Bank, lamports: u64, slot: u64, ) { let voting_pubkey = voting_keypair.pubkey(); - let mut ixs = - vote_instruction::create_account(&from_keypair.pubkey(), &voting_pubkey, lamports); + let mut ixs = vote_instruction::create_account( + &from_keypair.pubkey(), + &voting_pubkey, + node_id, + 0, + lamports, + ); ixs.push(vote_instruction::vote(&voting_pubkey, Vote::new(slot))); process_instructions(bank, &[from_keypair, voting_keypair], ixs); } diff --git a/fullnode/src/main.rs b/fullnode/src/main.rs index 8d9b26cd99..d637611ea6 100644 --- a/fullnode/src/main.rs +++ b/fullnode/src/main.rs @@ -32,11 +32,11 @@ fn main() { .help("File containing an identity (keypair)"), ) .arg( - Arg::with_name("staking_account") - .long("staking-account") + Arg::with_name("vote_account") + .long("vote-account") .value_name("PUBKEY_BASE58_STR") .takes_value(true) - .help("Public key of the staking account, where to send votes"), + .help("Public key of the vote account, where to send votes"), ) .arg( Arg::with_name("voting_keypair") diff --git a/genesis/src/main.rs b/genesis/src/main.rs index a165a3ff20..376e30f2c1 100644 --- a/genesis/src/main.rs +++ b/genesis/src/main.rs @@ -54,10 +54,10 @@ fn main() -> Result<(), Box> { .help("Path to file containing keys of the mint"), ) .arg( - Arg::with_name("bootstrap_stake_keypair_file") + Arg::with_name("bootstrap_vote_keypair_file") .short("s") - .long("bootstrap-stake-keypair") - .value_name("BOOTSTRAP STAKE KEYPAIR") + .long("bootstrap-vote-keypair") + .value_name("BOOTSTRAP VOTE KEYPAIR") .takes_value(true) .required(true) .help("Path to file containing the bootstrap leader's staking keypair"), @@ -65,13 +65,13 @@ fn main() -> Result<(), Box> { .get_matches(); let bootstrap_leader_keypair_file = matches.value_of("bootstrap_leader_keypair_file").unwrap(); - let bootstrap_stake_keypair_file = matches.value_of("bootstrap_stake_keypair_file").unwrap(); + let bootstrap_vote_keypair_file = matches.value_of("bootstrap_vote_keypair_file").unwrap(); let ledger_path = matches.value_of("ledger_path").unwrap(); let mint_keypair_file = matches.value_of("mint_keypair_file").unwrap(); let lamports = value_t_or_exit!(matches, "lamports", u64); let bootstrap_leader_keypair = read_keypair(bootstrap_leader_keypair_file)?; - let bootstrap_stake_keypair = read_keypair(bootstrap_stake_keypair_file)?; + let bootstrap_vote_keypair = read_keypair(bootstrap_vote_keypair_file)?; let mint_keypair = read_keypair(mint_keypair_file)?; let (mut genesis_block, _mint_keypair) = GenesisBlock::new_with_leader( @@ -80,7 +80,7 @@ fn main() -> Result<(), Box> { BOOTSTRAP_LEADER_LAMPORTS, ); genesis_block.mint_id = mint_keypair.pubkey(); - genesis_block.bootstrap_leader_vote_account_id = bootstrap_stake_keypair.pubkey(); + genesis_block.bootstrap_leader_vote_account_id = bootstrap_vote_keypair.pubkey(); genesis_block .native_instruction_processors .extend_from_slice(&[ diff --git a/multinode-demo/bootstrap-leader.sh b/multinode-demo/bootstrap-leader.sh index b43620ee9d..92cac78989 100755 --- a/multinode-demo/bootstrap-leader.sh +++ b/multinode-demo/bootstrap-leader.sh @@ -64,14 +64,14 @@ tune_system $solana_ledger_tool --ledger "$SOLANA_CONFIG_DIR"/bootstrap-leader-ledger verify bootstrap_leader_id_path="$SOLANA_CONFIG_DIR"/bootstrap-leader-id.json -bootstrap_leader_staker_id_path="$SOLANA_CONFIG_DIR"/bootstrap-leader-staker-id.json -bootstrap_leader_staker_id=$($solana_wallet --keypair "$bootstrap_leader_staker_id_path" address) +bootstrap_leader_vote_id_path="$SOLANA_CONFIG_DIR"/bootstrap-leader-vote-id.json +bootstrap_leader_vote_id=$($solana_wallet --keypair "$bootstrap_leader_vote_id_path" address) trap 'kill "$pid" && wait "$pid"' INT TERM ERR $program \ --identity "$bootstrap_leader_id_path" \ - --voting-keypair "$bootstrap_leader_staker_id_path" \ - --staking-account "$bootstrap_leader_staker_id" \ + --voting-keypair "$bootstrap_leader_vote_id_path" \ + --vote-account "$bootstrap_leader_vote_id" \ --ledger "$SOLANA_CONFIG_DIR"/bootstrap-leader-ledger \ --accounts "$SOLANA_CONFIG_DIR"/bootstrap-leader-accounts \ --rpc-port 8899 \ diff --git a/multinode-demo/common.sh b/multinode-demo/common.sh index bd16e1f0f2..3462c84713 100644 --- a/multinode-demo/common.sh +++ b/multinode-demo/common.sh @@ -142,42 +142,32 @@ airdrop() { return 0 } -setup_fullnode_staking() { +setup_vote_account() { declare drone_address=$1 - declare fullnode_id_path=$2 - declare staker_id_path=$3 + declare node_id_path=$2 + declare vote_id_path=$3 - declare fullnode_id - fullnode_id=$($solana_wallet --keypair "$fullnode_id_path" address) + declare node_id + node_id=$($solana_wallet --keypair "$node_id_path" address) - declare staker_id - staker_id=$($solana_wallet --keypair "$staker_id_path" address) + declare vote_id + vote_id=$($solana_wallet --keypair "$vote_id_path" address) - if [[ -f "$staker_id_path".configured ]]; then - echo "Staking account has already been configured" + if [[ -f "$vote_id_path".configured ]]; then + echo "Vote account has already been configured" return 0 fi # A fullnode requires 43 lamports to function: # - one lamport to keep the node identity public key valid. TODO: really?? - # - 42 more for the staker account we fund - airdrop "$fullnode_id_path" "$drone_address" 43 || return $? + # - 42 more for the vote account we fund + airdrop "$node_id_path" "$drone_address" 43 || return $? - # A little wrong, fund the staking account from the - # to the node. Maybe next time consider doing this the opposite - # way or use an ephemeral account - $solana_wallet --keypair "$fullnode_id_path" --host "$drone_address" \ - create-staking-account "$staker_id" 42 || return $? + # Fund the vote account from the node, with the node as the node_id + $solana_wallet --keypair "$node_id_path" --host "$drone_address" \ + create-vote-account "$vote_id" "$node_id" 42 || return $? - # as the staker, set the node as the delegate and the staker as - # the vote-signer - $solana_wallet --keypair "$staker_id_path" --host "$drone_address" \ - configure-staking-account \ - --delegate-account "$fullnode_id" \ - --authorize-voter "$staker_id" || return $? - - - touch "$staker_id_path".configured + touch "$vote_id_path".configured return 0 } diff --git a/multinode-demo/fullnode.sh b/multinode-demo/fullnode.sh index 23229c3d94..164bbfb5ec 100755 --- a/multinode-demo/fullnode.sh +++ b/multinode-demo/fullnode.sh @@ -114,16 +114,16 @@ if ((!self_setup)); then exit 1 } fullnode_id_path=$SOLANA_CONFIG_DIR/fullnode-id.json - fullnode_staker_id_path=$SOLANA_CONFIG_DIR/fullnode-staker-id.json + fullnode_vote_id_path=$SOLANA_CONFIG_DIR/fullnode-vote-id.json ledger_config_dir=$SOLANA_CONFIG_DIR/fullnode-ledger accounts_config_dir=$SOLANA_CONFIG_DIR/fullnode-accounts else mkdir -p "$SOLANA_CONFIG_DIR" fullnode_id_path=$SOLANA_CONFIG_DIR/fullnode-id-x$self_setup_label.json - fullnode_staker_id_path=$SOLANA_CONFIG_DIR/fullnode-staker-id-x$self_setup_label.json + fullnode_vote_id_path=$SOLANA_CONFIG_DIR/fullnode-vote-id-x$self_setup_label.json [[ -f "$fullnode_id_path" ]] || $solana_keygen -o "$fullnode_id_path" - [[ -f "$fullnode_staker_id_path" ]] || $solana_keygen -o "$fullnode_staker_id_path" + [[ -f "$fullnode_vote_id_path" ]] || $solana_keygen -o "$fullnode_vote_id_path" echo "Finding a port.." # Find an available port in the range 9100-9899 @@ -141,8 +141,7 @@ else accounts_config_dir=$SOLANA_CONFIG_DIR/fullnode-accounts-x$self_setup_label fi -fullnode_id=$($solana_wallet --keypair "$fullnode_id_path" address) -fullnode_staker_id=$($solana_wallet --keypair "$fullnode_staker_id_path" address) +fullnode_vote_id=$($solana_wallet --keypair "$fullnode_vote_id_path" address) [[ -r $fullnode_id_path ]] || { @@ -192,8 +191,8 @@ while true; do $program \ --gossip-port "$gossip_port" \ --identity "$fullnode_id_path" \ - --voting-keypair "$fullnode_staker_id_path" \ - --staking-account "$fullnode_staker_id" \ + --voting-keypair "$fullnode_vote_id_path" \ + --vote-account "$fullnode_vote_id" \ --network "$leader_address" \ --ledger "$ledger_config_dir" \ --accounts "$accounts_config_dir" \ @@ -204,7 +203,7 @@ while true; do oom_score_adj "$pid" 1000 if ((setup_stakes)); then - setup_fullnode_staking "${leader_address%:*}" "$fullnode_id_path" "$fullnode_staker_id_path" + setup_vote_account "${leader_address%:*}" "$fullnode_id_path" "$fullnode_vote_id_path" fi set +x diff --git a/multinode-demo/setup.sh b/multinode-demo/setup.sh index 9fee5666ae..ff7aa849d0 100755 --- a/multinode-demo/setup.sh +++ b/multinode-demo/setup.sh @@ -76,10 +76,10 @@ if $bootstrap_leader; then set -x $solana_keygen -o "$SOLANA_CONFIG_DIR"/mint-id.json $solana_keygen -o "$SOLANA_CONFIG_DIR"/bootstrap-leader-id.json - $solana_keygen -o "$SOLANA_CONFIG_DIR"/bootstrap-leader-staker-id.json + $solana_keygen -o "$SOLANA_CONFIG_DIR"/bootstrap-leader-vote-id.json $solana_genesis \ --bootstrap-leader-keypair "$SOLANA_CONFIG_DIR"/bootstrap-leader-id.json \ - --bootstrap-stake-keypair "$SOLANA_CONFIG_DIR"/bootstrap-leader-staker-id.json \ + --bootstrap-vote-keypair "$SOLANA_CONFIG_DIR"/bootstrap-leader-vote-id.json \ --ledger "$SOLANA_RSYNC_CONFIG_DIR"/ledger \ --mint "$SOLANA_CONFIG_DIR"/mint-id.json \ --lamports "$lamports" @@ -91,6 +91,6 @@ if $fullnode; then ( set -x $solana_keygen -o "$SOLANA_CONFIG_DIR"/fullnode-id.json - $solana_keygen -o "$SOLANA_CONFIG_DIR"/fullnode-staker-id.json + $solana_keygen -o "$SOLANA_CONFIG_DIR"/fullnode-vote-id.json ) fi diff --git a/programs/stake_api/src/stake_state.rs b/programs/stake_api/src/stake_state.rs index d99d8945bd..e8fc61ccc8 100644 --- a/programs/stake_api/src/stake_state.rs +++ b/programs/stake_api/src/stake_state.rs @@ -153,20 +153,21 @@ mod tests { use super::*; use crate::id; use solana_sdk::account::Account; + use solana_sdk::pubkey::Pubkey; use solana_sdk::signature::{Keypair, KeypairUtil}; - use solana_vote_api::vote_instruction::Vote; - use solana_vote_api::vote_state::create_vote_account; + use solana_vote_api::vote_state::{self, Vote}; #[test] fn test_stake_delegate_stake() { let vote_keypair = Keypair::new(); let mut vote_state = VoteState::default(); for i in 0..1000 { - vote_state.process_vote(Vote::new(i)); + vote_state.process_vote(&Vote::new(i)); } let vote_pubkey = vote_keypair.pubkey(); - let mut vote_account = create_vote_account(100); + let mut vote_account = + vote_state::create_account(&vote_pubkey, &Pubkey::new_rand(), 0, 100); let mut vote_keyed_account = KeyedAccount::new(&vote_pubkey, false, &mut vote_account); vote_keyed_account.set_state(&vote_state).unwrap(); @@ -206,7 +207,7 @@ mod tests { // put a credit in the vote_state while vote_state.credits() == 0 { - vote_state.process_vote(Vote::new(vote_i)); + vote_state.process_vote(&Vote::new(vote_i)); vote_i += 1; } // this guy can't collect now, not enough stake to get paid on 1 credit @@ -225,7 +226,7 @@ mod tests { // put more credit in the vote_state while vote_state.credits() < 10 { - vote_state.process_vote(Vote::new(vote_i)); + vote_state.process_vote(&Vote::new(vote_i)); vote_i += 1; } vote_state.commission = 0; @@ -252,11 +253,12 @@ mod tests { let vote_keypair = Keypair::new(); let mut vote_state = VoteState::default(); for i in 0..1000 { - vote_state.process_vote(Vote::new(i)); + vote_state.process_vote(&Vote::new(i)); } let vote_pubkey = vote_keypair.pubkey(); - let mut vote_account = create_vote_account(100); + let mut vote_account = + vote_state::create_account(&vote_pubkey, &Pubkey::new_rand(), 0, 100); let mut vote_keyed_account = KeyedAccount::new(&vote_pubkey, false, &mut vote_account); vote_keyed_account.set_state(&vote_state).unwrap(); @@ -294,7 +296,7 @@ mod tests { .is_ok()); // move the vote account forward - vote_state.process_vote(Vote::new(1000)); + vote_state.process_vote(&Vote::new(1000)); vote_keyed_account.set_state(&vote_state).unwrap(); // now, no lamports in the pool! @@ -322,11 +324,12 @@ mod tests { let vote_keypair = Keypair::new(); let mut vote_state = VoteState::default(); for i in 0..1000 { - vote_state.process_vote(Vote::new(i)); + vote_state.process_vote(&Vote::new(i)); } let vote_pubkey = vote_keypair.pubkey(); - let mut vote_account = create_vote_account(100); + let mut vote_account = + vote_state::create_account(&vote_pubkey, &Pubkey::new_rand(), 0, 100); let mut vote_keyed_account = KeyedAccount::new(&vote_pubkey, false, &mut vote_account); vote_keyed_account.set_state(&vote_state).unwrap(); @@ -349,7 +352,7 @@ mod tests { let mut vote_state = VoteState::default(); for i in 0..100 { // go back in time, previous state had 1000 votes - vote_state.process_vote(Vote::new(i)); + vote_state.process_vote(&Vote::new(i)); } vote_keyed_account.set_state(&vote_state).unwrap(); // voter credits lower than stake_delegate credits... TODO: is this an error? @@ -361,7 +364,8 @@ mod tests { let vote1_keypair = Keypair::new(); let vote1_pubkey = vote1_keypair.pubkey(); - let mut vote1_account = create_vote_account(100); + let mut vote1_account = + vote_state::create_account(&vote1_pubkey, &Pubkey::new_rand(), 0, 100); let mut vote1_keyed_account = KeyedAccount::new(&vote1_pubkey, false, &mut vote1_account); vote1_keyed_account.set_state(&vote_state).unwrap(); diff --git a/programs/vote_api/src/lib.rs b/programs/vote_api/src/lib.rs index 7911b85737..5ae7daafc2 100644 --- a/programs/vote_api/src/lib.rs +++ b/programs/vote_api/src/lib.rs @@ -1,5 +1,4 @@ pub mod vote_instruction; -pub mod vote_processor; pub mod vote_state; use solana_sdk::pubkey::Pubkey; diff --git a/programs/vote_api/src/vote_instruction.rs b/programs/vote_api/src/vote_instruction.rs index bd180555cc..c77811e064 100644 --- a/programs/vote_api/src/vote_instruction.rs +++ b/programs/vote_api/src/vote_instruction.rs @@ -1,68 +1,50 @@ +//! Vote program +//! Receive and processes votes from validators + use crate::id; -use crate::vote_state::VoteState; +use crate::vote_state::{self, Vote, VoteState}; +use bincode::deserialize; +use log::*; use serde_derive::{Deserialize, Serialize}; -use solana_sdk::instruction::{AccountMeta, Instruction}; +use solana_sdk::account::KeyedAccount; +use solana_sdk::instruction::{AccountMeta, Instruction, InstructionError}; use solana_sdk::pubkey::Pubkey; use solana_sdk::system_instruction; -#[derive(Serialize, Default, Deserialize, Debug, PartialEq, Eq, Clone)] -pub struct Vote { - // TODO: add signature of the state here as well - /// A vote for height slot - pub slot: u64, -} - -impl Vote { - pub fn new(slot: u64) -> Self { - Self { slot } - } -} - #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] pub enum VoteInstruction { /// Initialize the VoteState for this `vote account` - /// * Instruction::keys[0] - the new "vote account" to be associated with the delegate - InitializeAccount, - - /// `Delegate` or `Assign` a vote account to a particular node - DelegateStake(Pubkey), + /// takes a node_id and commission + InitializeAccount(Pubkey, u32), /// Authorize a voter to send signed votes. AuthorizeVoter(Pubkey), Vote(Vote), - - /// Clear the credits in the vote account - /// * Transaction::keys[0] - the "vote account" - ClearCredits, } -fn initialize_account(vote_id: &Pubkey) -> Instruction { +fn initialize_account(vote_id: &Pubkey, node_id: &Pubkey, commission: u32) -> Instruction { let account_metas = vec![AccountMeta::new(*vote_id, false)]; - Instruction::new(id(), &VoteInstruction::InitializeAccount, account_metas) -} - -pub fn create_account(from_id: &Pubkey, staker_id: &Pubkey, lamports: u64) -> Vec { - let space = VoteState::max_size() as u64; - let create_ix = system_instruction::create_account(&from_id, staker_id, lamports, space, &id()); - let init_ix = initialize_account(staker_id); - vec![create_ix, init_ix] -} - -pub fn clear_credits(vote_id: &Pubkey) -> Instruction { - let account_metas = vec![AccountMeta::new(*vote_id, true)]; - Instruction::new(id(), &VoteInstruction::ClearCredits, account_metas) -} - -pub fn delegate_stake(vote_id: &Pubkey, delegate_id: &Pubkey) -> Instruction { - let account_metas = vec![AccountMeta::new(*vote_id, true)]; Instruction::new( id(), - &VoteInstruction::DelegateStake(*delegate_id), + &VoteInstruction::InitializeAccount(*node_id, commission), account_metas, ) } +pub fn create_account( + from_id: &Pubkey, + vote_id: &Pubkey, + node_id: &Pubkey, + commission: u32, + lamports: u64, +) -> Vec { + let space = VoteState::size_of() as u64; + let create_ix = system_instruction::create_account(&from_id, vote_id, lamports, space, &id()); + let init_ix = initialize_account(vote_id, node_id, commission); + vec![create_ix, init_ix] +} + pub fn authorize_voter(vote_id: &Pubkey, authorized_voter_id: &Pubkey) -> Instruction { let account_metas = vec![AccountMeta::new(*vote_id, true)]; Instruction::new( @@ -76,3 +58,161 @@ pub fn vote(vote_id: &Pubkey, vote: Vote) -> Instruction { let account_metas = vec![AccountMeta::new(*vote_id, true)]; Instruction::new(id(), &VoteInstruction::Vote(vote), account_metas) } + +pub fn process_instruction( + _program_id: &Pubkey, + keyed_accounts: &mut [KeyedAccount], + data: &[u8], + _tick_height: u64, +) -> Result<(), InstructionError> { + solana_logger::setup(); + + trace!("process_instruction: {:?}", data); + trace!("keyed_accounts: {:?}", keyed_accounts); + + match deserialize(data).map_err(|_| InstructionError::InvalidInstructionData)? { + VoteInstruction::InitializeAccount(node_id, commission) => { + let mut vote_account = &mut keyed_accounts[0]; + vote_state::initialize_account(&mut vote_account, &node_id, commission) + } + VoteInstruction::AuthorizeVoter(voter_id) => { + let (vote_account, other_signers) = keyed_accounts.split_at_mut(1); + let vote_account = &mut vote_account[0]; + + vote_state::authorize_voter(vote_account, other_signers, &voter_id) + } + VoteInstruction::Vote(vote) => { + solana_metrics::submit( + solana_metrics::influxdb::Point::new("vote-native") + .add_field("count", solana_metrics::influxdb::Value::Integer(1)) + .to_owned(), + ); + let (vote_account, other_signers) = keyed_accounts.split_at_mut(1); + let vote_account = &mut vote_account[0]; + + vote_state::process_vote(vote_account, other_signers, &vote) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::id; + use crate::vote_instruction; + use crate::vote_state::{Vote, VoteState}; + use solana_runtime::bank::Bank; + use solana_runtime::bank_client::BankClient; + use solana_sdk::genesis_block::GenesisBlock; + use solana_sdk::instruction::InstructionError; + use solana_sdk::message::Message; + use solana_sdk::pubkey::Pubkey; + use solana_sdk::signature::{Keypair, KeypairUtil}; + use solana_sdk::sync_client::SyncClient; + use solana_sdk::system_instruction; + use solana_sdk::transaction::{Result, TransactionError}; + + fn create_bank(lamports: u64) -> (Bank, Keypair) { + let (genesis_block, mint_keypair) = GenesisBlock::new(lamports); + let mut bank = Bank::new(&genesis_block); + bank.add_instruction_processor(id(), process_instruction); + (bank, mint_keypair) + } + + fn create_vote_account( + bank_client: &BankClient, + from_keypair: &Keypair, + vote_id: &Pubkey, + lamports: u64, + ) -> Result<()> { + let ixs = vote_instruction::create_account( + &from_keypair.pubkey(), + vote_id, + &Pubkey::new_rand(), + 0, + lamports, + ); + let message = Message::new(ixs); + bank_client + .send_message(&[from_keypair], message) + .map_err(|err| err.unwrap())?; + Ok(()) + } + + fn submit_vote( + bank_client: &BankClient, + vote_keypair: &Keypair, + tick_height: u64, + ) -> Result<()> { + let vote_ix = vote_instruction::vote(&vote_keypair.pubkey(), Vote::new(tick_height)); + bank_client + .send_instruction(vote_keypair, vote_ix) + .map_err(|err| err.unwrap())?; + Ok(()) + } + + #[test] + fn test_vote_bank_basic() { + let (bank, from_keypair) = create_bank(10_000); + let bank_client = BankClient::new(&bank); + + let vote_keypair = Keypair::new(); + let vote_id = vote_keypair.pubkey(); + + create_vote_account(&bank_client, &from_keypair, &vote_id, 100).unwrap(); + submit_vote(&bank_client, &vote_keypair, 0).unwrap(); + + let vote_account_data = bank_client.get_account_data(&vote_id).unwrap().unwrap(); + let vote_state = VoteState::deserialize(&vote_account_data).unwrap(); + assert_eq!(vote_state.votes.len(), 1); + } + + #[test] + fn test_vote_via_bank_authorize_voter() { + let (bank, mallory_keypair) = create_bank(10_000); + let bank_client = BankClient::new(&bank); + + let vote_keypair = Keypair::new(); + let vote_id = vote_keypair.pubkey(); + + create_vote_account(&bank_client, &mallory_keypair, &vote_id, 100).unwrap(); + + let mallory_id = mallory_keypair.pubkey(); + let vote_ix = vote_instruction::authorize_voter(&vote_id, &mallory_id); + + let message = Message::new(vec![vote_ix]); + assert!(bank_client.send_message(&[&vote_keypair], message).is_ok()); + } + + #[test] + fn test_vote_via_bank_with_no_signature() { + let (bank, mallory_keypair) = create_bank(10_000); + let bank_client = BankClient::new(&bank); + + let vote_keypair = Keypair::new(); + let vote_id = vote_keypair.pubkey(); + + create_vote_account(&bank_client, &mallory_keypair, &vote_id, 100).unwrap(); + + let mallory_id = mallory_keypair.pubkey(); + let mut vote_ix = vote_instruction::vote(&vote_id, Vote::new(0)); + vote_ix.accounts[0].is_signer = false; // <--- attack!! No signer required. + + // Sneak in an instruction so that the transaction is signed but + // the 0th account in the second instruction is not! The program + // needs to check that it's signed. + let move_ix = system_instruction::transfer(&mallory_id, &vote_id, 1); + let message = Message::new(vec![move_ix, vote_ix]); + let result = bank_client.send_message(&[&mallory_keypair], message); + + // And ensure there's no vote. + let vote_account_data = bank_client.get_account_data(&vote_id).unwrap().unwrap(); + let vote_state = VoteState::deserialize(&vote_account_data).unwrap(); + assert_eq!(vote_state.votes.len(), 0); + + assert_eq!( + result.unwrap_err().unwrap(), + TransactionError::InstructionError(1, InstructionError::MissingRequiredSignature) + ); + } +} diff --git a/programs/vote_api/src/vote_processor.rs b/programs/vote_api/src/vote_processor.rs deleted file mode 100644 index 45c7769884..0000000000 --- a/programs/vote_api/src/vote_processor.rs +++ /dev/null @@ -1,175 +0,0 @@ -//! Vote program -//! Receive and processes votes from validators - -use crate::vote_instruction::VoteInstruction; -use crate::vote_state; -use bincode::deserialize; -use log::*; -use solana_sdk::account::KeyedAccount; -use solana_sdk::instruction::InstructionError; -use solana_sdk::pubkey::Pubkey; - -pub fn process_instruction( - _program_id: &Pubkey, - keyed_accounts: &mut [KeyedAccount], - data: &[u8], - _tick_height: u64, -) -> Result<(), InstructionError> { - solana_logger::setup(); - - trace!("process_instruction: {:?}", data); - trace!("keyed_accounts: {:?}", keyed_accounts); - - match deserialize(data).map_err(|_| InstructionError::InvalidInstructionData)? { - VoteInstruction::InitializeAccount => vote_state::initialize_account(keyed_accounts), - VoteInstruction::DelegateStake(delegate_id) => { - vote_state::delegate_stake(keyed_accounts, &delegate_id) - } - VoteInstruction::AuthorizeVoter(voter_id) => { - vote_state::authorize_voter(keyed_accounts, &voter_id) - } - VoteInstruction::Vote(vote) => { - debug!("{:?} by {}", vote, keyed_accounts[0].signer_key().unwrap()); - solana_metrics::submit( - solana_metrics::influxdb::Point::new("vote-native") - .add_field("count", solana_metrics::influxdb::Value::Integer(1)) - .to_owned(), - ); - vote_state::process_vote(keyed_accounts, vote) - } - VoteInstruction::ClearCredits => vote_state::clear_credits(keyed_accounts), - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::id; - use crate::vote_instruction::{self, Vote}; - use crate::vote_state::VoteState; - use solana_runtime::bank::Bank; - use solana_runtime::bank_client::BankClient; - use solana_sdk::genesis_block::GenesisBlock; - use solana_sdk::instruction::InstructionError; - use solana_sdk::message::Message; - use solana_sdk::pubkey::Pubkey; - use solana_sdk::signature::{Keypair, KeypairUtil}; - use solana_sdk::sync_client::SyncClient; - use solana_sdk::system_instruction; - use solana_sdk::transaction::{Result, TransactionError}; - - fn create_bank(lamports: u64) -> (Bank, Keypair) { - let (genesis_block, mint_keypair) = GenesisBlock::new(lamports); - let mut bank = Bank::new(&genesis_block); - bank.add_instruction_processor(id(), process_instruction); - (bank, mint_keypair) - } - - fn create_vote_account( - bank_client: &BankClient, - from_keypair: &Keypair, - vote_id: &Pubkey, - lamports: u64, - ) -> Result<()> { - let ixs = vote_instruction::create_account(&from_keypair.pubkey(), vote_id, lamports); - let message = Message::new(ixs); - bank_client - .send_message(&[from_keypair], message) - .map_err(|err| err.unwrap())?; - Ok(()) - } - - fn create_vote_account_with_delegate( - bank_client: &BankClient, - from_keypair: &Keypair, - vote_keypair: &Keypair, - delegate_id: &Pubkey, - lamports: u64, - ) -> Result<()> { - let vote_id = vote_keypair.pubkey(); - let mut ixs = vote_instruction::create_account(&from_keypair.pubkey(), &vote_id, lamports); - let delegate_ix = vote_instruction::delegate_stake(&vote_id, delegate_id); - ixs.push(delegate_ix); - let message = Message::new(ixs); - bank_client - .send_message(&[from_keypair, vote_keypair], message) - .map_err(|err| err.unwrap())?; - Ok(()) - } - - fn submit_vote( - bank_client: &BankClient, - vote_keypair: &Keypair, - tick_height: u64, - ) -> Result<()> { - let vote_ix = vote_instruction::vote(&vote_keypair.pubkey(), Vote::new(tick_height)); - bank_client - .send_instruction(vote_keypair, vote_ix) - .map_err(|err| err.unwrap())?; - Ok(()) - } - - #[test] - fn test_vote_bank_basic() { - let (bank, from_keypair) = create_bank(10_000); - let bank_client = BankClient::new(&bank); - - let vote_keypair = Keypair::new(); - let vote_id = vote_keypair.pubkey(); - - create_vote_account(&bank_client, &from_keypair, &vote_id, 100).unwrap(); - submit_vote(&bank_client, &vote_keypair, 0).unwrap(); - - let vote_account_data = bank_client.get_account_data(&vote_id).unwrap().unwrap(); - let vote_state = VoteState::deserialize(&vote_account_data).unwrap(); - assert_eq!(vote_state.votes.len(), 1); - } - - #[test] - fn test_vote_bank_delegate() { - let (bank, from_keypair) = create_bank(10_000); - let vote_keypair = Keypair::new(); - let bank_client = BankClient::new(&bank); - let delegate_id = Pubkey::new_rand(); - create_vote_account_with_delegate( - &bank_client, - &from_keypair, - &vote_keypair, - &delegate_id, - 100, - ) - .unwrap(); - } - - #[test] - fn test_vote_via_bank_with_no_signature() { - let (bank, mallory_keypair) = create_bank(10_000); - let bank_client = BankClient::new(&bank); - - let vote_keypair = Keypair::new(); - let vote_id = vote_keypair.pubkey(); - - create_vote_account(&bank_client, &mallory_keypair, &vote_id, 100).unwrap(); - - let mallory_id = mallory_keypair.pubkey(); - let mut vote_ix = vote_instruction::vote(&vote_id, Vote::new(0)); - vote_ix.accounts[0].is_signer = false; // <--- attack!! No signer required. - - // Sneak in an instruction so that the transaction is signed but - // the 0th account in the second instruction is not! The program - // needs to check that it's signed. - let move_ix = system_instruction::transfer(&mallory_id, &vote_id, 1); - let message = Message::new(vec![move_ix, vote_ix]); - let result = bank_client.send_message(&[&mallory_keypair], message); - - // And ensure there's no vote. - let vote_account_data = bank_client.get_account_data(&vote_id).unwrap().unwrap(); - let vote_state = VoteState::deserialize(&vote_account_data).unwrap(); - assert_eq!(vote_state.votes.len(), 0); - - assert_eq!( - result.unwrap_err().unwrap(), - TransactionError::InstructionError(1, InstructionError::InvalidArgument) - ); - } -} diff --git a/programs/vote_api/src/vote_state.rs b/programs/vote_api/src/vote_state.rs index 52c1ccefdf..f72b4afde5 100644 --- a/programs/vote_api/src/vote_state.rs +++ b/programs/vote_api/src/vote_state.rs @@ -1,13 +1,11 @@ -//! Vote stte +//! Vote state, vote program //! Receive and processes votes from validators - -use crate::vote_instruction::Vote; -use crate::{check_id, id}; +use crate::id; use bincode::{deserialize, serialize_into, serialized_size, ErrorKind}; -use log::*; use serde_derive::{Deserialize, Serialize}; use solana_sdk::account::{Account, KeyedAccount}; use solana_sdk::instruction::InstructionError; +use solana_sdk::instruction_processor_utils::State; use solana_sdk::pubkey::Pubkey; use std::collections::VecDeque; @@ -15,6 +13,19 @@ use std::collections::VecDeque; pub const MAX_LOCKOUT_HISTORY: usize = 31; pub const INITIAL_LOCKOUT: usize = 2; +#[derive(Serialize, Default, Deserialize, Debug, PartialEq, Eq, Clone)] +pub struct Vote { + // TODO: add signature of the state here as well + /// A vote for height slot + pub slot: u64, +} + +impl Vote { + pub fn new(slot: u64) -> Self { + Self { slot } + } +} + #[derive(Serialize, Default, Deserialize, Debug, PartialEq, Eq, Clone)] pub struct Lockout { pub slot: u64, @@ -47,7 +58,7 @@ impl Lockout { #[derive(Debug, Default, Serialize, Deserialize, PartialEq, Eq, Clone)] pub struct VoteState { pub votes: VecDeque, - pub delegate_id: Pubkey, + pub node_id: Pubkey, pub authorized_voter_id: Pubkey, /// fraction of std::u32::MAX that represents what part of a rewards /// payout should be given to this VoteAccount @@ -57,24 +68,23 @@ pub struct VoteState { } impl VoteState { - pub fn new(staker_id: &Pubkey) -> Self { + pub fn new(vote_id: &Pubkey, node_id: &Pubkey, commission: u32) -> Self { let votes = VecDeque::new(); let credits = 0; let root_slot = None; - let commission = 0; Self { votes, - delegate_id: *staker_id, - authorized_voter_id: *staker_id, + node_id: *node_id, + authorized_voter_id: *vote_id, credits, commission, root_slot, } } - pub fn max_size() -> usize { + pub fn size_of() -> usize { // Upper limit on the size of the Vote State. Equal to - // sizeof(VoteState) when votes.len() is MAX_LOCKOUT_HISTORY + // size_of(VoteState) when votes.len() is MAX_LOCKOUT_HISTORY let mut vote_state = Self::default(); vote_state.votes = VecDeque::from(vec![Lockout::default(); MAX_LOCKOUT_HISTORY]); vote_state.root_slot = Some(std::u64::MAX); @@ -107,7 +117,7 @@ impl VoteState { } } - pub fn process_vote(&mut self, vote: Vote) { + pub fn process_vote(&mut self, vote: &Vote) { // Ignore votes for slots earlier than we already have votes for if self .votes @@ -148,11 +158,6 @@ impl VoteState { self.credits } - /// Clear any credits. - pub fn clear_credits(&mut self) { - self.credits = 0; - } - fn pop_expired_votes(&mut self, slot: u64) { loop { if self.votes.back().map_or(false, |v| v.is_expired(slot)) { @@ -175,248 +180,257 @@ impl VoteState { } } -pub fn delegate_stake( - keyed_accounts: &mut [KeyedAccount], - node_id: &Pubkey, -) -> Result<(), InstructionError> { - if !check_id(&keyed_accounts[0].account.owner) { - error!("account[0] is not assigned to the VOTE_PROGRAM"); - Err(InstructionError::InvalidArgument)?; - } - - if keyed_accounts[0].signer_key().is_none() { - error!("account[0] should sign the transaction"); - Err(InstructionError::InvalidArgument)?; - } - - let vote_state = VoteState::deserialize(&keyed_accounts[0].account.data); - if let Ok(mut vote_state) = vote_state { - vote_state.delegate_id = *node_id; - vote_state.serialize(&mut keyed_accounts[0].account.data)?; - } else { - error!("account[0] does not valid data"); - Err(InstructionError::InvalidAccountData)?; - } - - Ok(()) -} - /// Authorize the given pubkey to 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( - keyed_accounts: &mut [KeyedAccount], - voter_id: &Pubkey, + vote_account: &mut KeyedAccount, + other_signers: &[KeyedAccount], + authorized_voter_id: &Pubkey, ) -> Result<(), InstructionError> { - if !check_id(&keyed_accounts[0].account.owner) { - error!("account[0] is not assigned to the VOTE_PROGRAM"); - Err(InstructionError::InvalidArgument)?; + let mut vote_state: VoteState = vote_account.state()?; + + // current authorized signer must say "yay" + let authorized = Some(&vote_state.authorized_voter_id); + if vote_account.signer_key() != authorized + && other_signers + .iter() + .all(|account| account.signer_key() != authorized) + { + return Err(InstructionError::MissingRequiredSignature); } - if keyed_accounts[0].signer_key().is_none() { - error!("account[0] should sign the transaction"); - Err(InstructionError::InvalidArgument)?; - } - - let vote_state = VoteState::deserialize(&keyed_accounts[0].account.data); - if let Ok(mut vote_state) = vote_state { - vote_state.authorized_voter_id = *voter_id; - vote_state.serialize(&mut keyed_accounts[0].account.data)?; - } else { - error!("account[0] does not valid data"); - Err(InstructionError::InvalidAccountData)?; - } - - Ok(()) + vote_state.authorized_voter_id = *authorized_voter_id; + vote_account.set_state(&vote_state) } /// Initialize the vote_state for a vote account /// Assumes that the account is being init as part of a account creation or balance transfer and /// that the transaction must be signed by the staker's keys -pub fn initialize_account(keyed_accounts: &mut [KeyedAccount]) -> Result<(), InstructionError> { - if !check_id(&keyed_accounts[0].account.owner) { - error!("account[0] is not assigned to the VOTE_PROGRAM"); - Err(InstructionError::InvalidArgument)?; - } +pub fn initialize_account( + vote_account: &mut KeyedAccount, + node_id: &Pubkey, + commission: u32, +) -> Result<(), InstructionError> { + let vote_state: VoteState = vote_account.state()?; - let staker_id = keyed_accounts[0].unsigned_key(); - let vote_state = VoteState::deserialize(&keyed_accounts[0].account.data); - if let Ok(vote_state) = vote_state { - if vote_state.delegate_id == Pubkey::default() { - let vote_state = VoteState::new(staker_id); - vote_state.serialize(&mut keyed_accounts[0].account.data)?; - } else { - error!("account[0] data already initialized"); - Err(InstructionError::InvalidAccountData)?; - } - } else { - error!("account[0] does not have valid data"); - Err(InstructionError::InvalidAccountData)?; + if vote_state.authorized_voter_id != Pubkey::default() { + return Err(InstructionError::AccountAlreadyInitialized); } - - Ok(()) + vote_account.set_state(&VoteState::new( + vote_account.unsigned_key(), + node_id, + commission, + )) } pub fn process_vote( - keyed_accounts: &mut [KeyedAccount], - vote: Vote, + vote_account: &mut KeyedAccount, + other_signers: &[KeyedAccount], + vote: &Vote, ) -> Result<(), InstructionError> { - if !check_id(&keyed_accounts[0].account.owner) { - error!("account[0] is not assigned to the VOTE_PROGRAM"); - Err(InstructionError::InvalidArgument)?; + let mut vote_state: VoteState = vote_account.state()?; + + if vote_state.authorized_voter_id == Pubkey::default() { + return Err(InstructionError::UninitializedAccount); } - let mut vote_state = VoteState::deserialize(&keyed_accounts[0].account.data)?; - - // If no voter was authorized, expect account[0] to be the signer, otherwise account[1]. - let signer_index = if vote_state.authorized_voter_id == *keyed_accounts[0].unsigned_key() { - 0 - } else { - 1 - }; - - if keyed_accounts.get(signer_index).is_none() { - error!("account[{}] not provided", signer_index); - Err(InstructionError::InvalidArgument)?; - } - if keyed_accounts[signer_index].signer_key().is_none() { - error!("account[{}] should sign the transaction", signer_index); - Err(InstructionError::InvalidArgument)?; + let authorized = Some(&vote_state.authorized_voter_id); + // find a signer that matches the authorized_voter_id + if vote_account.signer_key() != authorized + && other_signers + .iter() + .all(|account| account.signer_key() != authorized) + { + return Err(InstructionError::MissingRequiredSignature); } vote_state.process_vote(vote); - vote_state.serialize(&mut keyed_accounts[0].account.data)?; - Ok(()) + vote_account.set_state(&vote_state) } -pub fn clear_credits(keyed_accounts: &mut [KeyedAccount]) -> Result<(), InstructionError> { - if !check_id(&keyed_accounts[0].account.owner) { - error!("account[0] is not assigned to the VOTE_PROGRAM"); - Err(InstructionError::InvalidArgument)?; - } +// utility function, used by Bank, tests +pub fn create_account( + vote_id: &Pubkey, + node_id: &Pubkey, + commission: u32, + lamports: u64, +) -> Account { + let mut vote_account = Account::new(lamports, VoteState::size_of(), &id()); - let mut vote_state = VoteState::deserialize(&keyed_accounts[0].account.data)?; - vote_state.clear_credits(); - vote_state.serialize(&mut keyed_accounts[0].account.data)?; - Ok(()) + initialize_account( + &mut KeyedAccount::new(vote_id, false, &mut vote_account), + node_id, + commission, + ) + .unwrap(); + vote_account } -pub fn create_vote_account(lamports: u64) -> Account { - let space = VoteState::max_size(); - Account::new(lamports, space, &id()) -} - -pub fn initialize_and_deserialize( +// utility function, used by Bank, tests +pub fn vote( vote_id: &Pubkey, vote_account: &mut Account, + vote: &Vote, ) -> Result { - let mut keyed_accounts = [KeyedAccount::new(vote_id, false, vote_account)]; - initialize_account(&mut keyed_accounts)?; - let vote_state = VoteState::deserialize(&vote_account.data).unwrap(); - Ok(vote_state) -} - -pub fn vote_and_deserialize( - vote_id: &Pubkey, - vote_account: &mut Account, - vote: Vote, -) -> Result { - let mut keyed_accounts = [KeyedAccount::new(vote_id, true, vote_account)]; - process_vote(&mut keyed_accounts, vote)?; - let vote_state = VoteState::deserialize(&vote_account.data).unwrap(); - Ok(vote_state) + process_vote( + &mut KeyedAccount::new(vote_id, true, vote_account), + &[], + vote, + )?; + vote_account.state() } #[cfg(test)] mod tests { use super::*; + use crate::vote_state; #[test] fn test_initialize_vote_account() { let vote_account_id = Pubkey::new_rand(); - let mut vote_account = create_vote_account(100); + let mut vote_account = Account::new(100, VoteState::size_of(), &id()); - let bogus_account_id = Pubkey::new_rand(); - let mut bogus_account = Account::new(100, 0, &id()); - - let mut keyed_accounts = [KeyedAccount::new( - &bogus_account_id, - false, - &mut bogus_account, - )]; + let node_id = Pubkey::new_rand(); //init should pass - keyed_accounts[0] = KeyedAccount::new(&vote_account_id, false, &mut vote_account); - let res = initialize_account(&mut keyed_accounts); + let mut vote_account = KeyedAccount::new(&vote_account_id, false, &mut vote_account); + let res = initialize_account(&mut vote_account, &node_id, 0); assert_eq!(res, Ok(())); // reinit should fail - let res = initialize_account(&mut keyed_accounts); - assert_eq!(res, Err(InstructionError::InvalidAccountData)); + let res = initialize_account(&mut vote_account, &node_id, 0); + assert_eq!(res, Err(InstructionError::AccountAlreadyInitialized)); + } + + fn create_test_account() -> (Pubkey, Account) { + let vote_id = Pubkey::new_rand(); + ( + vote_id, + vote_state::create_account(&vote_id, &Pubkey::new_rand(), 0, 100), + ) } #[test] fn test_vote_serialize() { - let mut buffer: Vec = vec![0; VoteState::max_size()]; + let mut buffer: Vec = vec![0; VoteState::size_of()]; let mut vote_state = VoteState::default(); vote_state .votes .resize(MAX_LOCKOUT_HISTORY, Lockout::default()); + assert!(vote_state.serialize(&mut buffer[0..4]).is_err()); vote_state.serialize(&mut buffer).unwrap(); assert_eq!(VoteState::deserialize(&buffer).unwrap(), vote_state); } #[test] fn test_voter_registration() { - let vote_id = Pubkey::new_rand(); - let mut vote_account = create_vote_account(100); + let (vote_id, vote_account) = create_test_account(); - let vote_state = initialize_and_deserialize(&vote_id, &mut vote_account).unwrap(); - assert_eq!(vote_state.delegate_id, vote_id); + let vote_state: VoteState = vote_account.state().unwrap(); + assert_eq!(vote_state.authorized_voter_id, vote_id); assert!(vote_state.votes.is_empty()); } #[test] fn test_vote() { - let vote_id = Pubkey::new_rand(); - let mut vote_account = create_vote_account(100); - initialize_and_deserialize(&vote_id, &mut vote_account).unwrap(); + let (vote_id, mut vote_account) = create_test_account(); let vote = Vote::new(1); - let vote_state = vote_and_deserialize(&vote_id, &mut vote_account, vote.clone()).unwrap(); + let vote_state = vote_state::vote(&vote_id, &mut vote_account, &vote).unwrap(); assert_eq!(vote_state.votes, vec![Lockout::new(&vote)]); assert_eq!(vote_state.credits(), 0); } #[test] fn test_vote_signature() { - let vote_id = Pubkey::new_rand(); - let mut vote_account = create_vote_account(100); - initialize_and_deserialize(&vote_id, &mut vote_account).unwrap(); + let (vote_id, mut vote_account) = create_test_account(); let vote = Vote::new(1); - let mut keyed_accounts = [KeyedAccount::new(&vote_id, false, &mut vote_account)]; - let res = process_vote(&mut keyed_accounts, vote); - assert_eq!(res, Err(InstructionError::InvalidArgument)); + + // unsigned + let res = process_vote( + &mut KeyedAccount::new(&vote_id, false, &mut vote_account), + &[], + &vote, + ); + assert_eq!(res, Err(InstructionError::MissingRequiredSignature)); + + // unsigned + let res = process_vote( + &mut KeyedAccount::new(&vote_id, true, &mut vote_account), + &[], + &vote, + ); + assert_eq!(res, Ok(())); + + // another voter + let authorized_voter_id = Pubkey::new_rand(); + let res = authorize_voter( + &mut KeyedAccount::new(&vote_id, false, &mut vote_account), + &[], + &authorized_voter_id, + ); + assert_eq!(res, Err(InstructionError::MissingRequiredSignature)); + + let res = authorize_voter( + &mut KeyedAccount::new(&vote_id, true, &mut vote_account), + &[], + &authorized_voter_id, + ); + assert_eq!(res, Ok(())); + // verify authorized_voter_id can authorize authorized_voter_id ;) + let res = authorize_voter( + &mut KeyedAccount::new(&vote_id, false, &mut vote_account), + &[KeyedAccount::new( + &authorized_voter_id, + true, + &mut Account::default(), + )], + &authorized_voter_id, + ); + assert_eq!(res, Ok(())); + + // not signed by authorized voter + let vote = Vote::new(2); + let res = process_vote( + &mut KeyedAccount::new(&vote_id, true, &mut vote_account), + &[], + &vote, + ); + assert_eq!(res, Err(InstructionError::MissingRequiredSignature)); + + // signed by authorized voter + let vote = Vote::new(2); + let res = process_vote( + &mut KeyedAccount::new(&vote_id, false, &mut vote_account), + &[KeyedAccount::new( + &authorized_voter_id, + true, + &mut Account::default(), + )], + &vote, + ); + assert_eq!(res, Ok(())); } #[test] fn test_vote_without_initialization() { let vote_id = Pubkey::new_rand(); - let mut vote_account = create_vote_account(100); + let mut vote_account = Account::new(100, VoteState::size_of(), &id()); - let vote = Vote::new(1); - let res = vote_and_deserialize(&vote_id, &mut vote_account, vote.clone()); - assert_eq!(res, Err(InstructionError::InvalidArgument)); + let res = vote_state::vote(&vote_id, &mut vote_account, &Vote::new(1)); + assert_eq!(res, Err(InstructionError::UninitializedAccount)); } #[test] fn test_vote_lockout() { - let voter_id = Pubkey::new_rand(); - let mut vote_state = VoteState::new(&voter_id); + let (_vote_id, vote_account) = create_test_account(); + + let mut vote_state: VoteState = vote_account.state().unwrap(); for i in 0..(MAX_LOCKOUT_HISTORY + 1) { - vote_state.process_vote(Vote::new((INITIAL_LOCKOUT as usize * i) as u64)); + vote_state.process_vote(&Vote::new((INITIAL_LOCKOUT as usize * i) as u64)); } // The last vote should have been popped b/c it reached a depth of MAX_LOCKOUT_HISTORY @@ -428,14 +442,14 @@ mod tests { // the root_slot should change to the // second vote let top_vote = vote_state.votes.front().unwrap().slot; - vote_state.process_vote(Vote::new( + vote_state.process_vote(&Vote::new( vote_state.votes.back().unwrap().expiration_slot(), )); assert_eq!(Some(top_vote), vote_state.root_slot); // Expire everything except the first vote let vote = Vote::new(vote_state.votes.front().unwrap().expiration_slot()); - vote_state.process_vote(vote); + vote_state.process_vote(&vote); // First vote and new vote are both stored for a total of 2 votes assert_eq!(vote_state.votes.len(), 2); } @@ -443,11 +457,11 @@ mod tests { #[test] fn test_vote_double_lockout_after_expiration() { let voter_id = Pubkey::new_rand(); - let mut vote_state = VoteState::new(&voter_id); + let mut vote_state = VoteState::new(&voter_id, &Pubkey::new_rand(), 0); for i in 0..3 { let vote = Vote::new(i as u64); - vote_state.process_vote(vote); + vote_state.process_vote(&vote); } check_lockouts(&vote_state); @@ -455,35 +469,35 @@ mod tests { // Expire the third vote (which was a vote for slot 2). The height of the // vote stack is unchanged, so none of the previous votes should have // doubled in lockout - vote_state.process_vote(Vote::new((2 + INITIAL_LOCKOUT + 1) as u64)); + vote_state.process_vote(&Vote::new((2 + INITIAL_LOCKOUT + 1) as u64)); check_lockouts(&vote_state); // Vote again, this time the vote stack depth increases, so the lockouts should // double for everybody - vote_state.process_vote(Vote::new((2 + INITIAL_LOCKOUT + 2) as u64)); + vote_state.process_vote(&Vote::new((2 + INITIAL_LOCKOUT + 2) as u64)); check_lockouts(&vote_state); // Vote again, this time the vote stack depth increases, so the lockouts should // double for everybody - vote_state.process_vote(Vote::new((2 + INITIAL_LOCKOUT + 3) as u64)); + vote_state.process_vote(&Vote::new((2 + INITIAL_LOCKOUT + 3) as u64)); check_lockouts(&vote_state); } #[test] fn test_expire_multiple_votes() { let voter_id = Pubkey::new_rand(); - let mut vote_state = VoteState::new(&voter_id); + let mut vote_state = VoteState::new(&voter_id, &Pubkey::new_rand(), 0); for i in 0..3 { let vote = Vote::new(i as u64); - vote_state.process_vote(vote); + vote_state.process_vote(&vote); } assert_eq!(vote_state.votes[0].confirmation_count, 3); // Expire the second and third votes let expire_slot = vote_state.votes[1].slot + vote_state.votes[1].lockout() + 1; - vote_state.process_vote(Vote::new(expire_slot)); + vote_state.process_vote(&Vote::new(expire_slot)); assert_eq!(vote_state.votes.len(), 2); // Check that the old votes expired @@ -491,7 +505,7 @@ mod tests { assert_eq!(vote_state.votes[1].slot, expire_slot); // Process one more vote - vote_state.process_vote(Vote::new(expire_slot + 1)); + vote_state.process_vote(&Vote::new(expire_slot + 1)); // Confirmation count for the older first vote should remain unchanged assert_eq!(vote_state.votes[0].confirmation_count, 3); @@ -504,31 +518,29 @@ mod tests { #[test] fn test_vote_credits() { let voter_id = Pubkey::new_rand(); - let mut vote_state = VoteState::new(&voter_id); + let mut vote_state = VoteState::new(&voter_id, &Pubkey::new_rand(), 0); for i in 0..MAX_LOCKOUT_HISTORY { - vote_state.process_vote(Vote::new(i as u64)); + vote_state.process_vote(&Vote::new(i as u64)); } assert_eq!(vote_state.credits, 0); - vote_state.process_vote(Vote::new(MAX_LOCKOUT_HISTORY as u64 + 1)); + vote_state.process_vote(&Vote::new(MAX_LOCKOUT_HISTORY as u64 + 1)); assert_eq!(vote_state.credits, 1); - vote_state.process_vote(Vote::new(MAX_LOCKOUT_HISTORY as u64 + 2)); + vote_state.process_vote(&Vote::new(MAX_LOCKOUT_HISTORY as u64 + 2)); assert_eq!(vote_state.credits(), 2); - vote_state.process_vote(Vote::new(MAX_LOCKOUT_HISTORY as u64 + 3)); + vote_state.process_vote(&Vote::new(MAX_LOCKOUT_HISTORY as u64 + 3)); assert_eq!(vote_state.credits(), 3); - vote_state.clear_credits(); - assert_eq!(vote_state.credits(), 0); } #[test] fn test_duplicate_vote() { let voter_id = Pubkey::new_rand(); - let mut vote_state = VoteState::new(&voter_id); - vote_state.process_vote(Vote::new(0)); - vote_state.process_vote(Vote::new(1)); - vote_state.process_vote(Vote::new(0)); + let mut vote_state = VoteState::new(&voter_id, &Pubkey::new_rand(), 0); + vote_state.process_vote(&Vote::new(0)); + vote_state.process_vote(&Vote::new(1)); + vote_state.process_vote(&Vote::new(0)); assert_eq!(vote_state.nth_recent_vote(0).unwrap().slot, 1); assert_eq!(vote_state.nth_recent_vote(1).unwrap().slot, 0); assert!(vote_state.nth_recent_vote(2).is_none()); @@ -537,9 +549,9 @@ mod tests { #[test] fn test_nth_recent_vote() { let voter_id = Pubkey::new_rand(); - let mut vote_state = VoteState::new(&voter_id); + let mut vote_state = VoteState::new(&voter_id, &Pubkey::new_rand(), 0); for i in 0..MAX_LOCKOUT_HISTORY { - vote_state.process_vote(Vote::new(i as u64)); + vote_state.process_vote(&Vote::new(i as u64)); } for i in 0..(MAX_LOCKOUT_HISTORY - 1) { assert_eq!( diff --git a/programs/vote_program/src/lib.rs b/programs/vote_program/src/lib.rs index 5f53d953a9..bda9f1a50c 100644 --- a/programs/vote_program/src/lib.rs +++ b/programs/vote_program/src/lib.rs @@ -1,3 +1,3 @@ -use solana_vote_api::vote_processor::process_instruction; +use solana_vote_api::vote_instruction::process_instruction; solana_sdk::solana_entrypoint!(process_instruction); diff --git a/run.sh b/run.sh index 3c6126d32e..cd8b9b4d7c 100755 --- a/run.sh +++ b/run.sh @@ -87,12 +87,7 @@ trap abort INT TERM EXIT solana-wallet --keypair "$dataDir"/config/leader-keypair.json airdrop 42 solana-wallet \ --keypair "$dataDir"/config/leader-keypair.json \ - create-staking-account "$leaderStakingAccountPubkey" 42 -solana-wallet \ - --keypair "$dataDir"/config/leader-staking-account-keypair.json \ - configure-staking-account \ - --delegate-account "$leaderPubkey" \ - --authorize-voter "$leaderStakingAccountPubkey" + create-vote-account "$leaderStakingAccountPubkey" "$leaderPubkey" 42 solana-wallet --keypair "$dataDir"/config/leader-keypair.json balance wait "$fullnode" diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index bafd218f9b..c77a47b906 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -23,8 +23,7 @@ use solana_sdk::signature::{Keypair, Signature}; use solana_sdk::system_transaction; use solana_sdk::timing::{duration_as_ms, duration_as_us, MAX_RECENT_BLOCKHASHES}; use solana_sdk::transaction::{Result, Transaction, TransactionError}; -use solana_vote_api::vote_instruction::Vote; -use solana_vote_api::vote_state::{Lockout, VoteState}; +use solana_vote_api::vote_state::{self, Vote}; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use std::sync::{Arc, RwLock}; use std::time::Instant; @@ -321,19 +320,19 @@ impl Bank { // Construct a vote account for the bootstrap_leader such that the leader_scheduler // will be forced to select it as the leader for height 0 - let mut bootstrap_leader_vote_account = Account { - lamports: bootstrap_leader_stake, - data: vec![0; VoteState::max_size() as usize], - owner: solana_vote_api::id(), - executable: false, - }; + let mut bootstrap_leader_vote_account = vote_state::create_account( + &genesis_block.bootstrap_leader_vote_account_id, + &genesis_block.bootstrap_leader_id, + 0, + bootstrap_leader_stake, + ); - let mut vote_state = VoteState::new(&genesis_block.bootstrap_leader_id); - vote_state.votes.push_back(Lockout::new(&Vote::new(0))); - vote_state.authorized_voter_id = genesis_block.bootstrap_leader_vote_account_id; - vote_state - .serialize(&mut bootstrap_leader_vote_account.data) - .unwrap(); + vote_state::vote( + &genesis_block.bootstrap_leader_vote_account_id, + &mut bootstrap_leader_vote_account, + &Vote::new(0), + ) + .unwrap(); self.store( &genesis_block.bootstrap_leader_vote_account_id, @@ -1037,6 +1036,7 @@ mod tests { use solana_sdk::system_instruction; use solana_sdk::system_transaction; use solana_vote_api::vote_instruction; + use solana_vote_api::vote_state::VoteState; #[test] fn test_bank_new() { @@ -1654,7 +1654,7 @@ mod tests { .iter() .filter_map(|(pubkey, account)| { if let Ok(vote_state) = VoteState::deserialize(&account.data) { - if vote_state.delegate_id == leader_id { + if vote_state.node_id == leader_id { Some((*pubkey, true)) } else { None @@ -1886,8 +1886,13 @@ mod tests { // to have a vote account let vote_keypair = Keypair::new(); - let instructions = - vote_instruction::create_account(&mint_keypair.pubkey(), &vote_keypair.pubkey(), 10); + let instructions = vote_instruction::create_account( + &mint_keypair.pubkey(), + &vote_keypair.pubkey(), + &mint_keypair.pubkey(), + 0, + 10, + ); let transaction = Transaction::new_signed_instructions( &[&mint_keypair], diff --git a/wallet/src/main.rs b/wallet/src/main.rs index 6090c8986c..de2fdd3b88 100644 --- a/wallet/src/main.rs +++ b/wallet/src/main.rs @@ -1,6 +1,5 @@ use clap::{ - crate_description, crate_name, crate_version, App, AppSettings, Arg, ArgGroup, ArgMatches, - SubCommand, + crate_description, crate_name, crate_version, App, AppSettings, Arg, ArgMatches, SubCommand, }; use solana_sdk::pubkey::Pubkey; use solana_sdk::signature::{gen_keypair_file, read_keypair, KeypairUtil}; @@ -207,33 +206,20 @@ fn main() -> Result<(), Box> { ), ) .subcommand( - SubCommand::with_name("configure-staking-account") - .about("Configure staking account for node") - .group( - ArgGroup::with_name("options") - .args(&["delegate", "authorize"]) - .multiple(true) - .required(true), - ) + SubCommand::with_name("authorize-voter") + .about("Authorize a different voter for this account") .arg( - Arg::with_name("delegate") - .long("delegate-account") - .value_name("PUBKEY") - .takes_value(true) - .validator(is_pubkey) - .help("Address to delegate this vote account to"), - ) - .arg( - Arg::with_name("authorize") - .long("authorize-voter") + Arg::with_name("authorized-voter-id") + .index(1) .value_name("PUBKEY") .takes_value(true) + .required(true) .validator(is_pubkey) .help("Vote signer to authorize"), ), ) .subcommand( - SubCommand::with_name("create-staking-account") + SubCommand::with_name("create-vote-account") .about("Create staking account for node") .arg( Arg::with_name("voting_account_id") @@ -245,13 +231,29 @@ fn main() -> Result<(), Box> { .help("Staking account address to fund"), ) .arg( - Arg::with_name("lamports") + Arg::with_name("node_id") .index(2) + .value_name("PUBKEY") + .takes_value(true) + .required(true) + .validator(is_pubkey) + .help("Staking account address to fund"), + ) + .arg( + Arg::with_name("lamports") + .index(3) .value_name("NUM") .takes_value(true) .required(true) .help("The number of lamports to send to staking account"), + ) + .arg( + Arg::with_name("commission") + .value_name("NUM") + .takes_value(true) + .help("The commission on rewards this vote account should take, defaults to zero") ), + ) .subcommand( SubCommand::with_name("deploy") diff --git a/wallet/src/wallet.rs b/wallet/src/wallet.rs index ae110fba66..e93ac01d6e 100644 --- a/wallet/src/wallet.rs +++ b/wallet/src/wallet.rs @@ -36,8 +36,8 @@ pub enum WalletCommand { Cancel(Pubkey), Confirm(Signature), // ConfigureStakingAccount(delegate_id, authorized_voter_id) - ConfigureStakingAccount(Option, Option), - CreateStakingAccount(Pubkey, u64), + AuthorizeVoter(Pubkey), + CreateVoteAccount(Pubkey, Pubkey, u32, u64), Deploy(String), GetTransactionCount, // Pay(lamports, to, timestamp, timestamp_pubkey, witness(es), cancelable) @@ -164,19 +164,23 @@ pub fn parse_command( Err(WalletError::BadParameter("Invalid signature".to_string())) } } - ("configure-staking-account", Some(staking_config_matches)) => { - let delegate_id = pubkey_of(staking_config_matches, "delegate"); - let authorized_voter_id = pubkey_of(staking_config_matches, "authorize"); - Ok(WalletCommand::ConfigureStakingAccount( - delegate_id, - authorized_voter_id, - )) + ("authorize-voter", Some(matches)) => { + let authorized_voter_id = pubkey_of(matches, "authorized_voter_id").unwrap(); + Ok(WalletCommand::AuthorizeVoter(authorized_voter_id)) } - ("create-staking-account", Some(staking_matches)) => { - let voting_account_id = pubkey_of(staking_matches, "voting_account_id").unwrap(); - let lamports = staking_matches.value_of("lamports").unwrap().parse()?; - Ok(WalletCommand::CreateStakingAccount( + ("create-vote-account", Some(matches)) => { + let voting_account_id = pubkey_of(matches, "voting_account_id").unwrap(); + let node_id = pubkey_of(matches, "node_id").unwrap(); + let commission = if let Some(commission) = matches.value_of("commission") { + commission.parse()? + } else { + 0 + }; + let lamports = matches.value_of("lamports").unwrap().parse()?; + Ok(WalletCommand::CreateVoteAccount( voting_account_id, + node_id, + commission, lamports, )) } @@ -327,26 +331,17 @@ fn process_confirm(rpc_client: &RpcClient, signature: Signature) -> ProcessResul } } -fn process_configure_staking( +fn process_authorize_voter( rpc_client: &RpcClient, config: &WalletConfig, - delegate_option: Option, - authorized_voter_option: Option, + authorized_voter_id: Pubkey, ) -> ProcessResult { let recent_blockhash = rpc_client.get_recent_blockhash()?; - let mut ixs = vec![]; - if let Some(delegate_id) = delegate_option { - ixs.push(vote_instruction::delegate_stake( - &config.keypair.pubkey(), - &delegate_id, - )); - } - if let Some(authorized_voter_id) = authorized_voter_option { - ixs.push(vote_instruction::authorize_voter( - &config.keypair.pubkey(), - &authorized_voter_id, - )); - } + let ixs = vec![vote_instruction::authorize_voter( + &config.keypair.pubkey(), + &authorized_voter_id, + )]; + let mut tx = Transaction::new_signed_instructions(&[&config.keypair], ixs, recent_blockhash); let signature_str = rpc_client.send_and_confirm_transaction(&mut tx, &config.keypair)?; Ok(signature_str.to_string()) @@ -356,11 +351,18 @@ fn process_create_staking( rpc_client: &RpcClient, config: &WalletConfig, voting_account_id: &Pubkey, + node_id: &Pubkey, + commission: u32, lamports: u64, ) -> ProcessResult { let recent_blockhash = rpc_client.get_recent_blockhash()?; - let ixs = - vote_instruction::create_account(&config.keypair.pubkey(), voting_account_id, lamports); + let ixs = vote_instruction::create_account( + &config.keypair.pubkey(), + voting_account_id, + node_id, + commission, + lamports, + ); let mut tx = Transaction::new_signed_instructions(&[&config.keypair], ixs, recent_blockhash); let signature_str = rpc_client.send_and_confirm_transaction(&mut tx, &config.keypair)?; Ok(signature_str.to_string()) @@ -617,18 +619,20 @@ pub fn process_command(config: &WalletConfig) -> ProcessResult { WalletCommand::Confirm(signature) => process_confirm(&rpc_client, signature), // Configure staking account already created - WalletCommand::ConfigureStakingAccount(delegate_option, authorized_voter_option) => { - process_configure_staking( - &rpc_client, - config, - delegate_option, - authorized_voter_option, - ) + WalletCommand::AuthorizeVoter(authorized_voter_id) => { + process_authorize_voter(&rpc_client, config, authorized_voter_id) } // Create staking account - WalletCommand::CreateStakingAccount(voting_account_id, lamports) => { - process_create_staking(&rpc_client, config, &voting_account_id, lamports) + WalletCommand::CreateVoteAccount(voting_account_id, node_id, commission, lamports) => { + process_create_staking( + &rpc_client, + config, + &voting_account_id, + &node_id, + commission, + lamports, + ) } // Deploy a custom program to the chain @@ -723,7 +727,7 @@ pub fn request_and_confirm_airdrop( #[cfg(test)] mod tests { use super::*; - use clap::{App, Arg, ArgGroup, SubCommand}; + use clap::{App, Arg, SubCommand}; use serde_json::Value; use solana_client::mock_rpc_client_request::SIGNATURE; use solana_sdk::signature::{gen_keypair_file, read_keypair, read_pkcs8, Keypair, KeypairUtil}; @@ -802,31 +806,19 @@ mod tests { ), ) .subcommand( - SubCommand::with_name("configure-staking-account") + SubCommand::with_name("authorize-voter") .about("Configure staking account for node") - .group( - ArgGroup::with_name("options") - .args(&["delegate", "authorize"]) - .multiple(true) - .required(true), - ) .arg( - Arg::with_name("delegate") - .long("delegate-account") + Arg::with_name("authorized_voter_id") + .index(1) .value_name("PUBKEY") .takes_value(true) + .required(true) .help("Address to delegate this vote account to"), - ) - .arg( - Arg::with_name("authorize") - .long("authorize-voter") - .value_name("PUBKEY") - .takes_value(true) - .help("Vote signer to authorize"), ), ) .subcommand( - SubCommand::with_name("create-staking-account") + SubCommand::with_name("create-vote-account") .about("Create staking account for node") .arg( Arg::with_name("voting_account_id") @@ -837,12 +829,27 @@ mod tests { .help("Staking account address to fund"), ) .arg( - Arg::with_name("lamports") + Arg::with_name("node_id") .index(2) + .value_name("PUBKEY") + .takes_value(true) + .required(true) + .help("Node that will vote in this account"), + ) + .arg( + Arg::with_name("lamports") + .index(3) .value_name("NUM") .takes_value(true) .required(true) .help("The number of lamports to send to staking account"), + ) + .arg( + Arg::with_name("commission") + .long("commission") + .value_name("NUM") + .takes_value(true) + .help("The commission taken on reward redemption"), ), ) .subcommand( @@ -1004,42 +1011,42 @@ mod tests { .get_matches_from(vec!["test", "confirm", "deadbeef"]); assert!(parse_command(&pubkey, &test_bad_signature).is_err()); - // Test ConfigureStakingAccount Subcommand - let second_pubkey = Pubkey::new_rand(); - let second_pubkey_string = format!("{}", second_pubkey); - let test_configure_staking_account = test_commands.clone().get_matches_from(vec![ - "test", - "configure-staking-account", - "--delegate-account", - &pubkey_string, - "--authorize-voter", - &second_pubkey_string, - ]); + // Test AuthorizeVoter Subcommand + let test_authorize_voter = + test_commands + .clone() + .get_matches_from(vec!["test", "authorize-voter", &pubkey_string]); assert_eq!( - parse_command(&pubkey, &test_configure_staking_account).unwrap(), - WalletCommand::ConfigureStakingAccount(Some(pubkey), Some(second_pubkey)) - ); - let test_configure_staking_account = test_commands.clone().get_matches_from(vec![ - "test", - "configure-staking-account", - "--delegate-account", - &pubkey_string, - ]); - assert_eq!( - parse_command(&pubkey, &test_configure_staking_account).unwrap(), - WalletCommand::ConfigureStakingAccount(Some(pubkey), None) + parse_command(&pubkey, &test_authorize_voter).unwrap(), + WalletCommand::AuthorizeVoter(pubkey) ); - // Test CreateStakingAccount SubCommand - let test_create_staking_account = test_commands.clone().get_matches_from(vec![ + // Test CreateVoteAccount SubCommand + let node_id = Pubkey::new_rand(); + let node_id_string = format!("{}", node_id); + let test_create_vote_account = test_commands.clone().get_matches_from(vec![ "test", - "create-staking-account", + "create-vote-account", &pubkey_string, + &node_id_string, + "50", + "--commission", + "10", + ]); + assert_eq!( + parse_command(&pubkey, &test_create_vote_account).unwrap(), + WalletCommand::CreateVoteAccount(pubkey, node_id, 10, 50) + ); + let test_create_vote_account2 = test_commands.clone().get_matches_from(vec![ + "test", + "create-vote-account", + &pubkey_string, + &node_id_string, "50", ]); assert_eq!( - parse_command(&pubkey, &test_create_staking_account).unwrap(), - WalletCommand::CreateStakingAccount(pubkey, 50) + parse_command(&pubkey, &test_create_vote_account2).unwrap(), + WalletCommand::CreateVoteAccount(pubkey, node_id, 0, 50) ); // Test Deploy Subcommand @@ -1191,11 +1198,12 @@ mod tests { assert_eq!(process_command(&config).unwrap(), "Confirmed"); let bob_pubkey = Pubkey::new_rand(); - config.command = WalletCommand::ConfigureStakingAccount(None, Some(bob_pubkey)); + config.command = WalletCommand::AuthorizeVoter(bob_pubkey); let signature = process_command(&config); assert_eq!(signature.unwrap(), SIGNATURE.to_string()); - config.command = WalletCommand::CreateStakingAccount(bob_pubkey, 10); + let node_id = Pubkey::new_rand(); + config.command = WalletCommand::CreateVoteAccount(bob_pubkey, node_id, 0, 10); let signature = process_command(&config); assert_eq!(signature.unwrap(), SIGNATURE.to_string()); @@ -1300,10 +1308,10 @@ mod tests { config.command = WalletCommand::Balance(config.keypair.pubkey()); assert!(process_command(&config).is_err()); - config.command = WalletCommand::ConfigureStakingAccount(None, Some(bob_pubkey)); + config.command = WalletCommand::AuthorizeVoter(bob_pubkey); assert!(process_command(&config).is_err()); - config.command = WalletCommand::CreateStakingAccount(bob_pubkey, 10); + config.command = WalletCommand::CreateVoteAccount(bob_pubkey, node_id, 0, 10); assert!(process_command(&config).is_err()); config.command = WalletCommand::GetTransactionCount;