use crate::{stakes::Stakes, vote_account::ArcVoteAccount}; use serde::{Deserialize, Serialize}; use solana_sdk::{clock::Epoch, pubkey::Pubkey}; use std::{collections::HashMap, sync::Arc}; pub type NodeIdToVoteAccounts = HashMap; pub type EpochAuthorizedVoters = HashMap; #[derive(Clone, Serialize, Debug, Deserialize, Default, PartialEq, Eq, AbiExample)] pub struct NodeVoteAccounts { pub vote_accounts: Vec, pub total_stake: u64, } #[derive(Clone, Debug, Serialize, Deserialize, AbiExample, PartialEq)] pub struct EpochStakes { stakes: Arc, total_stake: u64, node_id_to_vote_accounts: Arc, epoch_authorized_voters: Arc, } impl EpochStakes { pub fn new(stakes: &Stakes, leader_schedule_epoch: Epoch) -> Self { let epoch_vote_accounts = Stakes::vote_accounts(stakes); let (total_stake, node_id_to_vote_accounts, epoch_authorized_voters) = Self::parse_epoch_vote_accounts(&epoch_vote_accounts, leader_schedule_epoch); Self { stakes: Arc::new(stakes.clone()), total_stake, node_id_to_vote_accounts: Arc::new(node_id_to_vote_accounts), epoch_authorized_voters: Arc::new(epoch_authorized_voters), } } pub fn stakes(&self) -> &Stakes { &self.stakes } pub fn total_stake(&self) -> u64 { self.total_stake } pub fn node_id_to_vote_accounts(&self) -> &Arc { &self.node_id_to_vote_accounts } pub fn epoch_authorized_voters(&self) -> &Arc { &self.epoch_authorized_voters } pub fn vote_account_stake(&self, vote_account: &Pubkey) -> u64 { Stakes::vote_accounts(&self.stakes) .get(vote_account) .map(|(stake, _)| *stake) .unwrap_or(0) } fn parse_epoch_vote_accounts( epoch_vote_accounts: &HashMap, leader_schedule_epoch: Epoch, ) -> (u64, NodeIdToVoteAccounts, EpochAuthorizedVoters) { let mut node_id_to_vote_accounts: NodeIdToVoteAccounts = HashMap::new(); let total_stake = epoch_vote_accounts .iter() .map(|(_, (stake, _))| stake) .sum(); let epoch_authorized_voters = epoch_vote_accounts .iter() .filter_map(|(key, (stake, account))| { let vote_state = account.vote_state(); let vote_state = match vote_state.as_ref() { Err(_) => { datapoint_warn!( "parse_epoch_vote_accounts", ( "warn", format!("Unable to get vote_state from account {}", key), String ), ); return None; } Ok(vote_state) => vote_state, }; if *stake > 0 { // Read out the authorized voters let authorized_voter = vote_state .authorized_voters() .get_authorized_voter(leader_schedule_epoch) .expect("Authorized voter for current epoch must be known"); let node_vote_accounts = node_id_to_vote_accounts .entry(vote_state.node_pubkey) .or_default(); node_vote_accounts.total_stake += stake; node_vote_accounts.vote_accounts.push(*key); Some((*key, authorized_voter)) } else { None } }) .collect(); ( total_stake, node_id_to_vote_accounts, epoch_authorized_voters, ) } } #[cfg(test)] pub(crate) mod tests { use super::*; use solana_sdk::account::Account; use solana_vote_program::vote_state::create_account_with_authorized; use std::iter; struct VoteAccountInfo { vote_account: Pubkey, account: Account, authorized_voter: Pubkey, } #[test] fn test_parse_epoch_vote_accounts() { let stake_per_account = 100; let num_vote_accounts_per_node = 2; // Create some vote accounts for each pubkey let vote_accounts_map: HashMap> = (0..10) .map(|_| { let node_id = solana_sdk::pubkey::new_rand(); ( node_id, iter::repeat_with(|| { let authorized_voter = solana_sdk::pubkey::new_rand(); VoteAccountInfo { vote_account: solana_sdk::pubkey::new_rand(), account: create_account_with_authorized( &node_id, &authorized_voter, &node_id, 0, 100, ), authorized_voter, } }) .take(num_vote_accounts_per_node) .collect(), ) }) .collect(); let expected_authorized_voters: HashMap<_, _> = vote_accounts_map .iter() .flat_map(|(_, vote_accounts)| { vote_accounts .iter() .map(|v| (v.vote_account, v.authorized_voter)) }) .collect(); let expected_node_id_to_vote_accounts: HashMap<_, _> = vote_accounts_map .iter() .map(|(node_pubkey, vote_accounts)| { let mut vote_accounts = vote_accounts .iter() .map(|v| (v.vote_account)) .collect::>(); vote_accounts.sort(); let node_vote_accounts = NodeVoteAccounts { vote_accounts, total_stake: stake_per_account * num_vote_accounts_per_node as u64, }; (*node_pubkey, node_vote_accounts) }) .collect(); // Create and process the vote accounts let epoch_vote_accounts: HashMap<_, _> = vote_accounts_map .iter() .flat_map(|(_, vote_accounts)| { vote_accounts.iter().map(|v| { ( v.vote_account, (stake_per_account, ArcVoteAccount::from(v.account.clone())), ) }) }) .collect(); let (total_stake, mut node_id_to_vote_accounts, epoch_authorized_voters) = EpochStakes::parse_epoch_vote_accounts(&epoch_vote_accounts, 0); // Verify the results node_id_to_vote_accounts .iter_mut() .for_each(|(_, node_vote_accounts)| node_vote_accounts.vote_accounts.sort()); assert!( node_id_to_vote_accounts.len() == expected_node_id_to_vote_accounts.len() && node_id_to_vote_accounts .iter() .all(|(k, v)| expected_node_id_to_vote_accounts.get(k).unwrap() == v) ); assert!( epoch_authorized_voters.len() == expected_authorized_voters.len() && epoch_authorized_voters .iter() .all(|(k, v)| expected_authorized_voters.get(k).unwrap() == v) ); assert_eq!( total_stake, vote_accounts_map.len() as u64 * num_vote_accounts_per_node as u64 * 100 ); } }