diff --git a/bench-exchange/src/main.rs b/bench-exchange/src/main.rs index ae5155ecc8..b6d9fc022d 100644 --- a/bench-exchange/src/main.rs +++ b/bench-exchange/src/main.rs @@ -2,6 +2,7 @@ pub mod bench; mod cli; pub mod order_book; +#[cfg(test)] #[macro_use] extern crate solana_exchange_program; @@ -46,7 +47,6 @@ fn main() { } info!("Funding keypair: {}", identity.pubkey()); - debug!("Exchange program name: {}", solana_exchange_program!().0); let accounts_in_groups = batch_size * account_groups; const NUM_SIGNERS: u64 = 2; diff --git a/ci/nits.sh b/ci/nits.sh index 55c69bb1f5..d84e309a9e 100755 --- a/ci/nits.sh +++ b/ci/nits.sh @@ -13,6 +13,7 @@ declare prints=( 'println!' 'eprint!' 'eprintln!' + 'dbg!' ) # Parts of the tree that are expected to be print free @@ -23,9 +24,13 @@ declare print_free_tree=( 'netutil/src' 'runtime/src' 'sdk/src' + 'programs/vote_api/src' + 'programs/vote_program/src' + 'programs/stake_api/src' + 'programs/stake_program/src' ) -if _ git grep --max-depth=0 "${prints[@]/#/-e }" -- "${print_free_tree[@]}"; then +if _ git grep -n --max-depth=0 "${prints[@]/#/-e }" -- "${print_free_tree[@]}"; then exit 1 fi @@ -34,7 +39,7 @@ fi # Default::default() # # Ref: https://github.com/solana-labs/solana/issues/2630 -if _ git grep 'Default::default()' -- '*.rs'; then +if _ git grep -n 'Default::default()' -- '*.rs'; then exit 1 fi diff --git a/core/src/leader_schedule_cache.rs b/core/src/leader_schedule_cache.rs index 05bac81bff..59fcb5eb70 100644 --- a/core/src/leader_schedule_cache.rs +++ b/core/src/leader_schedule_cache.rs @@ -168,10 +168,9 @@ mod tests { use crate::blocktree::tests::make_slot_entries; use crate::genesis_utils::create_genesis_block; use crate::genesis_utils::{create_genesis_block_with_leader, BOOTSTRAP_LEADER_LAMPORTS}; - use crate::voting_keypair::tests::new_vote_account; + use crate::staking_utils::tests::setup_vote_and_stake_accounts; use solana_runtime::bank::Bank; use solana_runtime::epoch_schedule::{EpochSchedule, MINIMUM_SLOT_LENGTH}; - use solana_sdk::signature::{Keypair, KeypairUtil}; use std::sync::mpsc::channel; use std::sync::Arc; use std::thread::Builder; @@ -373,25 +372,20 @@ mod tests { #[test] fn test_next_leader_slot_next_epoch() { - let pubkey = Pubkey::new_rand(); - let (mut genesis_block, mint_keypair, _voting_keypair) = create_genesis_block_with_leader( - 2 * BOOTSTRAP_LEADER_LAMPORTS, - &pubkey, - BOOTSTRAP_LEADER_LAMPORTS, - ); + let (mut genesis_block, mint_keypair) = create_genesis_block(10_000); genesis_block.epoch_warmup = false; let bank = Bank::new(&genesis_block); let cache = Arc::new(LeaderScheduleCache::new_from_bank(&bank)); - let delegate_id = Pubkey::new_rand(); // Create new vote account - let new_voting_keypair = Keypair::new(); - new_vote_account( - &mint_keypair, - &new_voting_keypair, - &delegate_id, + let node_id = Pubkey::new_rand(); + let vote_id = Pubkey::new_rand(); + setup_vote_and_stake_accounts( &bank, + &mint_keypair, + &vote_id, + &node_id, BOOTSTRAP_LEADER_LAMPORTS, ); @@ -412,14 +406,14 @@ mod tests { let schedule = cache.compute_epoch_schedule(epoch, &bank).unwrap(); let mut index = 0; - while schedule[index] != delegate_id { - index += 1 + while schedule[index] != node_id { + index += 1; + assert_ne!(index, genesis_block.slots_per_epoch); } - expected_slot += index; assert_eq!( - cache.next_leader_slot(&delegate_id, 0, &bank, None), + cache.next_leader_slot(&node_id, 0, &bank, None), Some(expected_slot), ); } diff --git a/core/src/lib.rs b/core/src/lib.rs index ed16167a10..787d2db643 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -67,7 +67,6 @@ pub mod streamer; pub mod test_tx; pub mod tpu; pub mod tvu; -pub mod voting_keypair; pub mod window_service; #[macro_use] @@ -83,6 +82,8 @@ extern crate log; #[macro_use] extern crate serde_derive; + +#[cfg(test)] #[macro_use] extern crate serde_json; diff --git a/core/src/local_cluster.rs b/core/src/local_cluster.rs index 34ec186772..b6d65a523f 100644 --- a/core/src/local_cluster.rs +++ b/core/src/local_cluster.rs @@ -397,7 +397,6 @@ impl LocalCluster { ), client.get_recent_blockhash().unwrap().0, ); - dbg!(vote_account_pubkey); client .retry_transfer(&from_account, &mut transaction, 5) .expect("fund vote"); @@ -407,7 +406,6 @@ impl LocalCluster { let stake_account_keypair = Keypair::new(); let stake_account_pubkey = stake_account_keypair.pubkey(); - dbg!(stake_account_pubkey); let mut transaction = Transaction::new_signed_instructions( &[from_account.as_ref()], vec![stake_instruction::create_account( @@ -424,7 +422,6 @@ impl LocalCluster { client .wait_for_balance(&stake_account_pubkey, Some(amount)) .expect("get balance"); - dbg!(amount); let mut transaction = Transaction::new_signed_instructions( &[from_account.as_ref(), &stake_account_keypair], @@ -443,7 +440,6 @@ impl LocalCluster { 0, ) .expect("delegate stake"); - dbg!("delegated"); } info!("Checking for vote account registration"); let vote_account_user_data = client.get_account_data(&vote_account_pubkey); diff --git a/core/src/staking_utils.rs b/core/src/staking_utils.rs index 94cd80f611..f74c5fb5d0 100644 --- a/core/src/staking_utils.rs +++ b/core/src/staking_utils.rs @@ -110,15 +110,18 @@ where } #[cfg(test)] -pub mod tests { +pub(crate) mod tests { use super::*; use crate::genesis_utils::{ create_genesis_block, create_genesis_block_with_leader, BOOTSTRAP_LEADER_LAMPORTS, }; - use crate::voting_keypair::tests as voting_keypair_tests; use hashbrown::HashSet; + use solana_sdk::instruction::Instruction; use solana_sdk::pubkey::Pubkey; use solana_sdk::signature::{Keypair, KeypairUtil}; + use solana_sdk::transaction::Transaction; + use solana_stake_api::stake_instruction; + use solana_vote_api::vote_instruction; use std::iter::FromIterator; use std::sync::Arc; @@ -148,14 +151,65 @@ pub mod tests { assert_eq!(vote_account_stakes_at_epoch(&bank, 1), expected); } + pub(crate) fn setup_vote_and_stake_accounts( + bank: &Bank, + from_account: &Keypair, + vote_id: &Pubkey, + node_id: &Pubkey, + amount: u64, + ) { + fn process_instructions( + bank: &Bank, + keypairs: &[&T], + ixs: Vec, + ) { + bank.process_transaction(&Transaction::new_signed_instructions( + keypairs, + ixs, + bank.last_blockhash(), + )) + .unwrap(); + } + + process_instructions( + bank, + &[from_account], + vote_instruction::create_account(&from_account.pubkey(), vote_id, node_id, 0, amount), + ); + + let stake_account_keypair = Keypair::new(); + let stake_account_pubkey = stake_account_keypair.pubkey(); + + process_instructions( + bank, + &[from_account], + vec![stake_instruction::create_account( + &from_account.pubkey(), + &stake_account_pubkey, + amount, + )], + ); + + process_instructions( + bank, + &[from_account, &stake_account_keypair], + vec![stake_instruction::delegate_stake( + &from_account.pubkey(), + &stake_account_pubkey, + vote_id, + )], + ); + } + #[test] fn test_epoch_stakes_and_lockouts() { + let stake = 42; let validator = Keypair::new(); - let (genesis_block, mint_keypair) = create_genesis_block(500); + let (genesis_block, mint_keypair) = create_genesis_block(10_000); let bank = Bank::new(&genesis_block); - let bank_voter = Keypair::new(); + let vote_id = Pubkey::new_rand(); // Give the validator some stake but don't setup a staking account // Validator has no lamports staked, so they get filtered out. Only the bootstrap leader @@ -165,12 +219,12 @@ pub 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( - &mint_keypair, - &bank_voter, - &mint_keypair.pubkey(), + setup_vote_and_stake_accounts( &bank, - 499, + &mint_keypair, + &vote_id, + &mint_keypair.pubkey(), + stake, ); // soonest slot that could be a new epoch is 1 @@ -190,7 +244,7 @@ pub mod tests { let result: HashSet<_> = HashSet::from_iter(epoch_stakes_and_lockouts(&bank, epoch)); let expected: HashSet<_> = - HashSet::from_iter(vec![(BOOTSTRAP_LEADER_LAMPORTS, None), (499, None)]); + HashSet::from_iter(vec![(BOOTSTRAP_LEADER_LAMPORTS, None), (stake, None)]); assert_eq!(result, expected); } diff --git a/core/src/voting_keypair.rs b/core/src/voting_keypair.rs deleted file mode 100644 index ef3038dc48..0000000000 --- a/core/src/voting_keypair.rs +++ /dev/null @@ -1,167 +0,0 @@ -//! The `vote_signer_proxy` votes on the `blockhash` of the bank at a regular cadence - -use jsonrpc_core; -use solana_client::rpc_client::RpcClient; -use solana_client::rpc_request::RpcRequest; -use solana_sdk::pubkey::Pubkey; -use solana_sdk::signature::{Keypair, KeypairUtil, Signature}; -use solana_vote_signer::rpc::LocalVoteSigner; -use solana_vote_signer::rpc::VoteSigner; -use std::net::SocketAddr; -use std::sync::Arc; - -pub struct RemoteVoteSigner { - rpc_client: RpcClient, -} - -impl RemoteVoteSigner { - pub fn new(signer: SocketAddr) -> Self { - let rpc_client = RpcClient::new_socket(signer); - Self { rpc_client } - } -} - -impl VoteSigner for RemoteVoteSigner { - fn register( - &self, - pubkey: &Pubkey, - sig: &Signature, - msg: &[u8], - ) -> jsonrpc_core::Result { - let params = json!([pubkey, sig, msg]); - let resp = self - .rpc_client - .retry_make_rpc_request(&RpcRequest::RegisterNode, Some(params), 5) - .unwrap(); - let vote_account: Pubkey = serde_json::from_value(resp).unwrap(); - Ok(vote_account) - } - fn sign( - &self, - pubkey: &Pubkey, - sig: &Signature, - msg: &[u8], - ) -> jsonrpc_core::Result { - let params = json!([pubkey, sig, msg]); - let resp = self - .rpc_client - .retry_make_rpc_request(&RpcRequest::SignVote, Some(params), 0) - .unwrap(); - let vote_signature: Signature = serde_json::from_value(resp).unwrap(); - Ok(vote_signature) - } - fn deregister(&self, pubkey: &Pubkey, sig: &Signature, msg: &[u8]) -> jsonrpc_core::Result<()> { - let params = json!([pubkey, sig, msg]); - let _resp = self - .rpc_client - .retry_make_rpc_request(&RpcRequest::DeregisterNode, Some(params), 5) - .unwrap(); - Ok(()) - } -} - -impl KeypairUtil for VotingKeypair { - /// Return a local VotingKeypair with a new keypair. Used for unit-tests. - fn new() -> Self { - Self::new_with_signer( - &Arc::new(Keypair::new()), - Box::new(LocalVoteSigner::default()), - ) - } - - /// Return the public key of the keypair used to sign votes - fn pubkey(&self) -> Pubkey { - self.vote_account - } - - fn sign_message(&self, msg: &[u8]) -> Signature { - let sig = self.keypair.sign_message(msg); - self.signer - .sign(&self.keypair.pubkey(), &sig, &msg) - .unwrap() - } -} - -pub struct VotingKeypair { - keypair: Arc, - signer: Box, - vote_account: Pubkey, -} - -impl VotingKeypair { - pub fn new_with_signer(keypair: &Arc, signer: Box) -> Self { - let msg = "Registering a new node"; - let sig = keypair.sign_message(msg.as_bytes()); - let vote_account = signer - .register(&keypair.pubkey(), &sig, msg.as_bytes()) - .unwrap(); - Self { - keypair: keypair.clone(), - signer, - vote_account, - } - } -} - -#[cfg(test)] -pub mod tests { - use solana_runtime::bank::Bank; - use solana_sdk::instruction::Instruction; - use solana_sdk::pubkey::Pubkey; - use solana_sdk::signature::{Keypair, KeypairUtil}; - use solana_sdk::transaction::Transaction; - 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(); - let tx = Transaction::new_signed_instructions(keypairs, ixs, blockhash); - bank.process_transaction(&tx).unwrap(); - } - - pub fn new_vote_account( - from_keypair: &Keypair, - voting_keypair: &Keypair, - node_id: &Pubkey, - bank: &Bank, - lamports: u64, - ) { - let voting_pubkey = voting_keypair.pubkey(); - 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) { - let ix = vote_instruction::vote(&voting_keypair.pubkey(), vec![Vote::new(slot)]); - process_instructions(bank, &[voting_keypair], vec![ix]); - } - - 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, - node_id, - 0, - lamports, - ); - ixs.push(vote_instruction::vote( - &voting_pubkey, - vec![Vote::new(slot)], - )); - process_instructions(bank, &[from_keypair, voting_keypair], ixs); - } -} diff --git a/programs/stake_api/src/stake_state.rs b/programs/stake_api/src/stake_state.rs index 206ce8f50f..5a256bca1c 100644 --- a/programs/stake_api/src/stake_state.rs +++ b/programs/stake_api/src/stake_state.rs @@ -42,6 +42,23 @@ const STAKE_REWARD_TARGET_RATE: f64 = 0.20; const STAKE_GETS_PAID_EVERY_VOTE: u64 = 200_000_000; // if numbers above (TICKS_YEAR) move, fix this impl StakeState { + // utility function, used by Stakes, tests + pub fn from(account: &Account) -> Option { + account.state().ok() + } + + // utility function, used by Stakes, tests + pub fn voter_id_from(account: &Account) -> Option { + Self::from(account).and_then(|state: Self| state.voter_id()) + } + + pub fn voter_id(&self) -> Option { + match self { + StakeState::Delegate { voter_id, .. } => Some(*voter_id), + _ => None, + } + } + pub fn calculate_rewards( credits_observed: u64, stake: u64, @@ -176,8 +193,6 @@ mod tests { #[test] fn test_stake_delegate_stake() { - dbg!(std::env::var("CARGO_FOO").unwrap_or("not set".to_string())); - let vote_keypair = Keypair::new(); let mut vote_state = VoteState::default(); for i in 0..1000 { diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index 56b27a6a0b..978e585e98 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -9,6 +9,7 @@ use crate::blockhash_queue::BlockhashQueue; use crate::epoch_schedule::EpochSchedule; use crate::locked_accounts_results::LockedAccountsResults; use crate::message_processor::{MessageProcessor, ProcessInstruction}; +use crate::stakes::Stakes; use crate::status_cache::StatusCache; use bincode::serialize; use hashbrown::HashMap; @@ -30,39 +31,6 @@ use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use std::sync::{Arc, RwLock}; use std::time::Instant; -/// cache of staking information -#[derive(Default, Clone)] -pub struct Stakes { - /// vote accounts - vote_accounts: HashMap, - - /// stake_accounts - stake_accounts: HashMap, -} - -impl Stakes { - pub fn is_stake(account: &Account) -> bool { - solana_vote_api::check_id(&account.owner) || solana_stake_api::check_id(&account.owner) - } - - pub fn store(&mut self, pubkey: &Pubkey, account: &Account) { - if solana_vote_api::check_id(&account.owner) { - if account.lamports != 0 { - self.vote_accounts - .insert(*pubkey, (account.lamports, account.clone())); - } else { - self.vote_accounts.remove(pubkey); - } - } else if solana_stake_api::check_id(&account.owner) { - if account.lamports != 0 { - self.stake_accounts.insert(*pubkey, account.clone()); - } else { - self.stake_accounts.remove(pubkey); - } - } - } -} - type BankStatusCache = StatusCache>; /// Manager for the state of all accounts and programs after processing its entries. @@ -959,15 +927,13 @@ impl Bank { /// current vote accounts for this bank along with the stake /// attributed to each account pub fn vote_accounts(&self) -> HashMap { - self.stakes.read().unwrap().vote_accounts.clone() + self.stakes.read().unwrap().vote_accounts().clone() } /// vote accounts for the specific epoch along with the stake /// attributed to each account pub fn epoch_vote_accounts(&self, epoch: u64) -> Option<&HashMap> { - self.epoch_stakes - .get(&epoch) - .map(|stakes| &stakes.vote_accounts) + self.epoch_stakes.get(&epoch).map(Stakes::vote_accounts) } /// given a slot, return the epoch and offset into the epoch this slot falls @@ -1922,4 +1888,5 @@ mod tests { assert!(bank.is_delta.load(Ordering::Relaxed)); } + } diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 84f0b75631..0e44d53aef 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -12,6 +12,7 @@ pub mod loader_utils; pub mod locked_accounts_results; pub mod message_processor; mod native_loader; +pub mod stakes; mod status_cache; mod system_instruction_processor; diff --git a/runtime/src/stakes.rs b/runtime/src/stakes.rs new file mode 100644 index 0000000000..fedf1e7f18 --- /dev/null +++ b/runtime/src/stakes.rs @@ -0,0 +1,252 @@ +//! Stakes serve as a cache of stake and vote accounts to derive +//! node stakes +use hashbrown::HashMap; +use solana_sdk::account::Account; +use solana_sdk::pubkey::Pubkey; +use solana_stake_api::stake_state::StakeState; + +#[derive(Default, Clone)] +pub struct Stakes { + /// vote accounts + vote_accounts: HashMap, + + /// stake_accounts + stake_accounts: HashMap, +} + +impl Stakes { + // sum the stakes that point to the given voter_id + fn calculate_stake(&self, voter_id: &Pubkey) -> u64 { + self.stake_accounts + .iter() + .filter(|(_, stake_account)| { + Some(*voter_id) == StakeState::voter_id_from(stake_account) + }) + .map(|(_, stake_account)| stake_account.lamports) + .sum() + } + + pub fn is_stake(account: &Account) -> bool { + solana_vote_api::check_id(&account.owner) || solana_stake_api::check_id(&account.owner) + } + + pub fn store(&mut self, pubkey: &Pubkey, account: &Account) { + if solana_vote_api::check_id(&account.owner) { + if account.lamports == 0 { + self.vote_accounts.remove(pubkey); + } else { + // update the stake of this entry + let stake = self + .vote_accounts + .get(pubkey) + .map_or_else(|| self.calculate_stake(pubkey), |v| v.0); + + self.vote_accounts.insert(*pubkey, (stake, account.clone())); + } + } else if solana_stake_api::check_id(&account.owner) { + // old_stake is stake lamports and voter_id from the pre-store() version + let old_stake = self.stake_accounts.get(pubkey).and_then(|old_account| { + StakeState::voter_id_from(old_account) + .map(|old_voter_id| (old_account.lamports, old_voter_id)) + }); + + let stake = + StakeState::voter_id_from(account).map(|voter_id| (account.lamports, voter_id)); + + // if adjustments need to be made... + if stake != old_stake { + if let Some((old_stake, old_voter_id)) = old_stake { + self.vote_accounts + .entry(old_voter_id) + .and_modify(|e| e.0 -= old_stake); + } + if let Some((stake, voter_id)) = stake { + self.vote_accounts + .entry(voter_id) + .and_modify(|e| e.0 += stake); + } + } + + if account.lamports == 0 { + self.stake_accounts.remove(pubkey); + } else { + self.stake_accounts.insert(*pubkey, account.clone()); + } + } + } + pub fn vote_accounts(&self) -> &HashMap { + &self.vote_accounts + } +} + +#[cfg(test)] +mod tests { + use super::*; + use solana_sdk::pubkey::Pubkey; + use solana_stake_api::stake_state; + use solana_vote_api::vote_state::{self, VoteState}; + + // set up some dummies for a staked node (( vote ) ( stake )) + fn create_staked_node_accounts(stake: u64) -> ((Pubkey, Account), (Pubkey, Account)) { + let vote_id = Pubkey::new_rand(); + let vote_account = vote_state::create_account(&vote_id, &Pubkey::new_rand(), 0, 1); + ( + (vote_id, vote_account), + create_stake_account(stake, &vote_id), + ) + } + + // add stake to a vote_id ( stake ) + fn create_stake_account(stake: u64, vote_id: &Pubkey) -> (Pubkey, Account) { + ( + Pubkey::new_rand(), + stake_state::create_delegate_stake_account(&vote_id, &VoteState::default(), stake), + ) + } + + #[test] + fn test_stakes_basic() { + let mut stakes = Stakes::default(); + + let ((vote_id, vote_account), (stake_id, mut stake_account)) = + create_staked_node_accounts(10); + + stakes.store(&vote_id, &vote_account); + stakes.store(&stake_id, &stake_account); + + { + let vote_accounts = stakes.vote_accounts(); + assert!(vote_accounts.get(&vote_id).is_some()); + assert_eq!(vote_accounts.get(&vote_id).unwrap().0, 10); + } + + stake_account.lamports = 42; + stakes.store(&stake_id, &stake_account); + { + let vote_accounts = stakes.vote_accounts(); + assert!(vote_accounts.get(&vote_id).is_some()); + assert_eq!(vote_accounts.get(&vote_id).unwrap().0, 42); + } + + stake_account.lamports = 0; + stakes.store(&stake_id, &stake_account); + { + let vote_accounts = stakes.vote_accounts(); + assert!(vote_accounts.get(&vote_id).is_some()); + assert_eq!(vote_accounts.get(&vote_id).unwrap().0, 0); + } + } + + #[test] + fn test_stakes_vote_account_disappear_reappear() { + let mut stakes = Stakes::default(); + + let ((vote_id, mut vote_account), (stake_id, stake_account)) = + create_staked_node_accounts(10); + + stakes.store(&vote_id, &vote_account); + stakes.store(&stake_id, &stake_account); + + { + let vote_accounts = stakes.vote_accounts(); + assert!(vote_accounts.get(&vote_id).is_some()); + assert_eq!(vote_accounts.get(&vote_id).unwrap().0, 10); + } + + vote_account.lamports = 0; + stakes.store(&vote_id, &vote_account); + + { + let vote_accounts = stakes.vote_accounts(); + assert!(vote_accounts.get(&vote_id).is_none()); + } + vote_account.lamports = 1; + stakes.store(&vote_id, &vote_account); + + { + let vote_accounts = stakes.vote_accounts(); + assert!(vote_accounts.get(&vote_id).is_some()); + assert_eq!(vote_accounts.get(&vote_id).unwrap().0, 10); + } + } + + #[test] + fn test_stakes_change_delegate() { + let mut stakes = Stakes::default(); + + let ((vote_id, vote_account), (stake_id, stake_account)) = create_staked_node_accounts(10); + + let ((vote_id2, vote_account2), (_stake_id2, stake_account2)) = + create_staked_node_accounts(10); + + stakes.store(&vote_id, &vote_account); + stakes.store(&vote_id2, &vote_account2); + + // delegates to vote_id + stakes.store(&stake_id, &stake_account); + + { + let vote_accounts = stakes.vote_accounts(); + assert!(vote_accounts.get(&vote_id).is_some()); + assert_eq!(vote_accounts.get(&vote_id).unwrap().0, 10); + assert!(vote_accounts.get(&vote_id2).is_some()); + assert_eq!(vote_accounts.get(&vote_id2).unwrap().0, 0); + } + + // delegates to vote_id2 + stakes.store(&stake_id, &stake_account2); + + { + let vote_accounts = stakes.vote_accounts(); + assert!(vote_accounts.get(&vote_id).is_some()); + assert_eq!(vote_accounts.get(&vote_id).unwrap().0, 0); + assert!(vote_accounts.get(&vote_id2).is_some()); + assert_eq!(vote_accounts.get(&vote_id2).unwrap().0, 10); + } + } + #[test] + fn test_stakes_multiple_stakers() { + let mut stakes = Stakes::default(); + + let ((vote_id, vote_account), (stake_id, stake_account)) = create_staked_node_accounts(10); + + let (stake_id2, stake_account2) = create_stake_account(10, &vote_id); + + stakes.store(&vote_id, &vote_account); + + // delegates to vote_id + stakes.store(&stake_id, &stake_account); + stakes.store(&stake_id2, &stake_account2); + + { + let vote_accounts = stakes.vote_accounts(); + assert!(vote_accounts.get(&vote_id).is_some()); + assert_eq!(vote_accounts.get(&vote_id).unwrap().0, 20); + } + } + + #[test] + fn test_stakes_not_delegate() { + let mut stakes = Stakes::default(); + + let ((vote_id, vote_account), (stake_id, stake_account)) = create_staked_node_accounts(10); + + stakes.store(&vote_id, &vote_account); + stakes.store(&stake_id, &stake_account); + + { + let vote_accounts = stakes.vote_accounts(); + assert!(vote_accounts.get(&vote_id).is_some()); + assert_eq!(vote_accounts.get(&vote_id).unwrap().0, 10); + } + + // not a stake account, and whacks above entry + stakes.store(&stake_id, &Account::new(1, 0, &solana_stake_api::id())); + { + let vote_accounts = stakes.vote_accounts(); + assert!(vote_accounts.get(&vote_id).is_some()); + assert_eq!(vote_accounts.get(&vote_id).unwrap().0, 0); + } + } + +}