caches vote-state de-serialized from vote accounts (#13795)
Gossip and other places repeatedly de-serialize vote-state stored in vote accounts. Ideally the first de-serialization should cache the result. This commit adds new VoteAccount type which lazily de-serializes VoteState from Account data and caches the result internally. Serialize and Deserialize traits are manually implemented to match existing code. So, despite changes to frozen_abi, this commit should be backward compatible.
This commit is contained in:
@ -21,6 +21,7 @@ use crate::{
|
||||
system_instruction_processor::{get_system_account_kind, SystemAccountKind},
|
||||
transaction_batch::TransactionBatch,
|
||||
transaction_utils::OrderedIterator,
|
||||
vote_account::ArcVoteAccount,
|
||||
};
|
||||
use byteorder::{ByteOrder, LittleEndian};
|
||||
use itertools::Itertools;
|
||||
@ -68,7 +69,7 @@ use solana_sdk::{
|
||||
use solana_stake_program::stake_state::{
|
||||
self, Delegation, InflationPointCalculationEvent, PointValue,
|
||||
};
|
||||
use solana_vote_program::{vote_instruction::VoteInstruction, vote_state::VoteState};
|
||||
use solana_vote_program::vote_instruction::VoteInstruction;
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
collections::{HashMap, HashSet},
|
||||
@ -379,7 +380,7 @@ pub struct TransactionBalancesSet {
|
||||
pub post_balances: TransactionBalances,
|
||||
}
|
||||
pub struct OverwrittenVoteAccount {
|
||||
pub account: Account,
|
||||
pub account: ArcVoteAccount,
|
||||
pub transaction_index: usize,
|
||||
pub transaction_result_index: usize,
|
||||
}
|
||||
@ -1700,7 +1701,7 @@ impl Bank {
|
||||
.vote_accounts()
|
||||
.into_iter()
|
||||
.filter_map(|(pubkey, (_, account))| {
|
||||
VoteState::from(&account).and_then(|state| {
|
||||
account.vote_state().as_ref().ok().and_then(|state| {
|
||||
let timestamp_slot = state.last_timestamp.slot;
|
||||
if (self
|
||||
.feature_set
|
||||
@ -2955,7 +2956,7 @@ impl Bank {
|
||||
#[allow(clippy::needless_collect)]
|
||||
fn distribute_rent_to_validators(
|
||||
&self,
|
||||
vote_account_hashmap: &HashMap<Pubkey, (u64, Account)>,
|
||||
vote_account_hashmap: &HashMap<Pubkey, (u64, ArcVoteAccount)>,
|
||||
rent_to_be_distributed: u64,
|
||||
) {
|
||||
let mut total_staked = 0;
|
||||
@ -2970,9 +2971,8 @@ impl Bank {
|
||||
None
|
||||
} else {
|
||||
total_staked += *staked;
|
||||
VoteState::deserialize(&account.data)
|
||||
.ok()
|
||||
.map(|vote_state| (vote_state.node_pubkey, *staked))
|
||||
let node_pubkey = account.vote_state().as_ref().ok()?.node_pubkey;
|
||||
Some((node_pubkey, *staked))
|
||||
}
|
||||
})
|
||||
.collect::<Vec<(Pubkey, u64)>>();
|
||||
@ -4098,10 +4098,25 @@ impl Bank {
|
||||
|
||||
/// current vote accounts for this bank along with the stake
|
||||
/// attributed to each account
|
||||
pub fn vote_accounts(&self) -> HashMap<Pubkey, (u64, Account)> {
|
||||
/// Note: This clones the entire vote-accounts hashmap. For a single
|
||||
/// account lookup use get_vote_account instead.
|
||||
pub fn vote_accounts(&self) -> HashMap<Pubkey, (u64 /*stake*/, ArcVoteAccount)> {
|
||||
self.stakes.read().unwrap().vote_accounts().clone()
|
||||
}
|
||||
|
||||
/// Vote account for the given vote account pubkey along with the stake.
|
||||
pub fn get_vote_account(
|
||||
&self,
|
||||
vote_account: &Pubkey,
|
||||
) -> Option<(u64 /*stake*/, ArcVoteAccount)> {
|
||||
self.stakes
|
||||
.read()
|
||||
.unwrap()
|
||||
.vote_accounts()
|
||||
.get(vote_account)
|
||||
.cloned()
|
||||
}
|
||||
|
||||
/// Get the EpochStakes for a given epoch
|
||||
pub fn epoch_stakes(&self, epoch: Epoch) -> Option<&EpochStakes> {
|
||||
self.epoch_stakes.get(&epoch)
|
||||
@ -4113,7 +4128,10 @@ impl Bank {
|
||||
|
||||
/// vote accounts for the specific epoch along with the stake
|
||||
/// attributed to each account
|
||||
pub fn epoch_vote_accounts(&self, epoch: Epoch) -> Option<&HashMap<Pubkey, (u64, Account)>> {
|
||||
pub fn epoch_vote_accounts(
|
||||
&self,
|
||||
epoch: Epoch,
|
||||
) -> Option<&HashMap<Pubkey, (u64, ArcVoteAccount)>> {
|
||||
self.epoch_stakes
|
||||
.get(&epoch)
|
||||
.map(|epoch_stakes| Stakes::vote_accounts(epoch_stakes.stakes()))
|
||||
@ -7741,7 +7759,7 @@ pub(crate) mod tests {
|
||||
accounts
|
||||
.iter()
|
||||
.filter_map(|(pubkey, (stake, account))| {
|
||||
if let Ok(vote_state) = VoteState::deserialize(&account.data) {
|
||||
if let Ok(vote_state) = account.vote_state().as_ref() {
|
||||
if vote_state.node_pubkey == leader_pubkey {
|
||||
Some((*pubkey, *stake))
|
||||
} else {
|
||||
|
@ -1,7 +1,6 @@
|
||||
use crate::stakes::Stakes;
|
||||
use crate::{stakes::Stakes, vote_account::ArcVoteAccount};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use solana_sdk::{account::Account, clock::Epoch, pubkey::Pubkey};
|
||||
use solana_vote_program::vote_state::VoteState;
|
||||
use solana_sdk::{clock::Epoch, pubkey::Pubkey};
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
|
||||
pub type NodeIdToVoteAccounts = HashMap<Pubkey, NodeVoteAccounts>;
|
||||
@ -58,7 +57,7 @@ impl EpochStakes {
|
||||
}
|
||||
|
||||
fn parse_epoch_vote_accounts(
|
||||
epoch_vote_accounts: &HashMap<Pubkey, (u64, Account)>,
|
||||
epoch_vote_accounts: &HashMap<Pubkey, (u64, ArcVoteAccount)>,
|
||||
leader_schedule_epoch: Epoch,
|
||||
) -> (u64, NodeIdToVoteAccounts, EpochAuthorizedVoters) {
|
||||
let mut node_id_to_vote_accounts: NodeIdToVoteAccounts = HashMap::new();
|
||||
@ -69,19 +68,21 @@ impl EpochStakes {
|
||||
let epoch_authorized_voters = epoch_vote_accounts
|
||||
.iter()
|
||||
.filter_map(|(key, (stake, account))| {
|
||||
let vote_state = VoteState::from(&account);
|
||||
if vote_state.is_none() {
|
||||
datapoint_warn!(
|
||||
"parse_epoch_vote_accounts",
|
||||
(
|
||||
"warn",
|
||||
format!("Unable to get vote_state from account {}", key),
|
||||
String
|
||||
),
|
||||
);
|
||||
return None;
|
||||
}
|
||||
let vote_state = vote_state.unwrap();
|
||||
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
|
||||
@ -113,6 +114,7 @@ impl EpochStakes {
|
||||
#[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;
|
||||
|
||||
@ -181,9 +183,12 @@ pub(crate) mod tests {
|
||||
let epoch_vote_accounts: HashMap<_, _> = vote_accounts_map
|
||||
.iter()
|
||||
.flat_map(|(_, vote_accounts)| {
|
||||
vote_accounts
|
||||
.iter()
|
||||
.map(|v| (v.vote_account, (stake_per_account, v.account.clone())))
|
||||
vote_accounts.iter().map(|v| {
|
||||
(
|
||||
v.vote_account,
|
||||
(stake_per_account, ArcVoteAccount::from(v.account.clone())),
|
||||
)
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
||||
|
@ -29,6 +29,7 @@ pub mod status_cache;
|
||||
mod system_instruction_processor;
|
||||
pub mod transaction_batch;
|
||||
pub mod transaction_utils;
|
||||
pub mod vote_account;
|
||||
pub mod vote_sender_types;
|
||||
|
||||
extern crate solana_config_program;
|
||||
|
@ -261,7 +261,7 @@ mod test_bank_serialize {
|
||||
|
||||
// These some what long test harness is required to freeze the ABI of
|
||||
// Bank's serialization due to versioned nature
|
||||
#[frozen_abi(digest = "Giao4XJq9QgW78sqmT3nRMvENt4BgHXdzphCDGFPbXqW")]
|
||||
#[frozen_abi(digest = "8bNY87hccyDYCRar1gM3NSvpvtiUM3W3rGeJLJayz42e")]
|
||||
#[derive(Serialize, AbiExample)]
|
||||
pub struct BankAbiTestWrapperFuture {
|
||||
#[serde(serialize_with = "wrapper_future")]
|
||||
|
@ -1,16 +1,16 @@
|
||||
//! Stakes serve as a cache of stake and vote accounts to derive
|
||||
//! node stakes
|
||||
use crate::vote_account::ArcVoteAccount;
|
||||
use solana_sdk::{
|
||||
account::Account, clock::Epoch, pubkey::Pubkey, sysvar::stake_history::StakeHistory,
|
||||
};
|
||||
use solana_stake_program::stake_state::{new_stake_history_entry, Delegation, StakeState};
|
||||
use solana_vote_program::vote_state::VoteState;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Default, Clone, PartialEq, Debug, Deserialize, Serialize, AbiExample)]
|
||||
pub struct Stakes {
|
||||
/// vote accounts
|
||||
vote_accounts: HashMap<Pubkey, (u64, Account)>,
|
||||
vote_accounts: HashMap<Pubkey, (u64, ArcVoteAccount)>,
|
||||
|
||||
/// stake_delegations
|
||||
stake_delegations: HashMap<Pubkey, Delegation>,
|
||||
@ -106,7 +106,7 @@ impl Stakes {
|
||||
+ self
|
||||
.vote_accounts
|
||||
.iter()
|
||||
.map(|(_pubkey, (_staked, vote_account))| vote_account.lamports)
|
||||
.map(|(_pubkey, (_staked, vote_account))| vote_account.lamports())
|
||||
.sum::<u64>()
|
||||
}
|
||||
|
||||
@ -121,7 +121,7 @@ impl Stakes {
|
||||
pubkey: &Pubkey,
|
||||
account: &Account,
|
||||
fix_stake_deactivate: bool,
|
||||
) -> Option<Account> {
|
||||
) -> Option<ArcVoteAccount> {
|
||||
if solana_vote_program::check_id(&account.owner) {
|
||||
let old = self.vote_accounts.remove(pubkey);
|
||||
if account.lamports != 0 {
|
||||
@ -137,7 +137,8 @@ impl Stakes {
|
||||
|v| v.0,
|
||||
);
|
||||
|
||||
self.vote_accounts.insert(*pubkey, (stake, account.clone()));
|
||||
self.vote_accounts
|
||||
.insert(*pubkey, (stake, ArcVoteAccount::from(account.clone())));
|
||||
}
|
||||
old.map(|(_, account)| account)
|
||||
} else if solana_stake_program::check_id(&account.owner) {
|
||||
@ -191,7 +192,7 @@ impl Stakes {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn vote_accounts(&self) -> &HashMap<Pubkey, (u64, Account)> {
|
||||
pub fn vote_accounts(&self) -> &HashMap<Pubkey, (u64, ArcVoteAccount)> {
|
||||
&self.vote_accounts
|
||||
}
|
||||
|
||||
@ -200,11 +201,12 @@ impl Stakes {
|
||||
}
|
||||
|
||||
pub fn highest_staked_node(&self) -> Option<Pubkey> {
|
||||
self.vote_accounts
|
||||
let (_pubkey, (_stake, vote_account)) = self
|
||||
.vote_accounts
|
||||
.iter()
|
||||
.max_by(|(_ak, av), (_bk, bv)| av.0.cmp(&bv.0))
|
||||
.and_then(|(_k, (_stake, account))| VoteState::from(account))
|
||||
.map(|vote_state| vote_state.node_pubkey)
|
||||
.max_by(|(_ak, av), (_bk, bv)| av.0.cmp(&bv.0))?;
|
||||
let node_pubkey = vote_account.vote_state().as_ref().ok()?.node_pubkey;
|
||||
Some(node_pubkey)
|
||||
}
|
||||
}
|
||||
|
||||
@ -523,7 +525,7 @@ pub mod tests {
|
||||
pub fn vote_balance_and_warmed_staked(&self) -> u64 {
|
||||
self.vote_accounts
|
||||
.iter()
|
||||
.map(|(_pubkey, (staked, account))| staked + account.lamports)
|
||||
.map(|(_pubkey, (staked, account))| staked + account.lamports())
|
||||
.sum()
|
||||
}
|
||||
}
|
||||
|
181
runtime/src/vote_account.rs
Normal file
181
runtime/src/vote_account.rs
Normal file
@ -0,0 +1,181 @@
|
||||
use serde::de::{Deserialize, Deserializer};
|
||||
use serde::ser::{Serialize, Serializer};
|
||||
use solana_sdk::{account::Account, instruction::InstructionError};
|
||||
use solana_vote_program::vote_state::VoteState;
|
||||
use std::ops::Deref;
|
||||
use std::sync::{Arc, Once, RwLock, RwLockReadGuard};
|
||||
|
||||
// The value here does not matter. It will be overwritten
|
||||
// at the first call to VoteAccount::vote_state().
|
||||
const INVALID_VOTE_STATE: Result<VoteState, InstructionError> =
|
||||
Err(InstructionError::InvalidAccountData);
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq, AbiExample)]
|
||||
pub struct ArcVoteAccount(Arc<VoteAccount>);
|
||||
|
||||
#[derive(Debug, AbiExample)]
|
||||
pub struct VoteAccount {
|
||||
account: Account,
|
||||
vote_state: RwLock<Result<VoteState, InstructionError>>,
|
||||
vote_state_once: Once,
|
||||
}
|
||||
|
||||
impl VoteAccount {
|
||||
pub fn lamports(&self) -> u64 {
|
||||
self.account.lamports
|
||||
}
|
||||
|
||||
pub fn vote_state(&self) -> RwLockReadGuard<Result<VoteState, InstructionError>> {
|
||||
self.vote_state_once.call_once(|| {
|
||||
*self.vote_state.write().unwrap() = VoteState::deserialize(&self.account.data);
|
||||
});
|
||||
self.vote_state.read().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for ArcVoteAccount {
|
||||
type Target = VoteAccount;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.0.deref()
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for ArcVoteAccount {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
self.account.serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for ArcVoteAccount {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let account = Account::deserialize(deserializer)?;
|
||||
Ok(Self::from(account))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Account> for ArcVoteAccount {
|
||||
fn from(account: Account) -> Self {
|
||||
Self(Arc::new(VoteAccount::from(account)))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Account> for VoteAccount {
|
||||
fn from(account: Account) -> Self {
|
||||
Self {
|
||||
account,
|
||||
vote_state: RwLock::new(INVALID_VOTE_STATE),
|
||||
vote_state_once: Once::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for VoteAccount {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
account: Account::default(),
|
||||
vote_state: RwLock::new(INVALID_VOTE_STATE),
|
||||
vote_state_once: Once::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<VoteAccount> for VoteAccount {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.account == other.account
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use rand::Rng;
|
||||
use solana_sdk::{pubkey::Pubkey, sysvar::clock::Clock};
|
||||
use solana_vote_program::vote_state::{VoteInit, VoteStateVersions};
|
||||
|
||||
fn new_rand_vote_account<R: Rng>(rng: &mut R) -> (Account, VoteState) {
|
||||
let vote_init = VoteInit {
|
||||
node_pubkey: Pubkey::new_unique(),
|
||||
authorized_voter: Pubkey::new_unique(),
|
||||
authorized_withdrawer: Pubkey::new_unique(),
|
||||
commission: rng.gen(),
|
||||
};
|
||||
let clock = Clock {
|
||||
slot: rng.gen(),
|
||||
epoch_start_timestamp: rng.gen(),
|
||||
epoch: rng.gen(),
|
||||
leader_schedule_epoch: rng.gen(),
|
||||
unix_timestamp: rng.gen(),
|
||||
};
|
||||
let vote_state = VoteState::new(&vote_init, &clock);
|
||||
let account = Account::new_data(
|
||||
rng.gen(), // lamports
|
||||
&VoteStateVersions::Current(Box::new(vote_state.clone())),
|
||||
&Pubkey::new_unique(), // owner
|
||||
)
|
||||
.unwrap();
|
||||
(account, vote_state)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_vote_account() {
|
||||
let mut rng = rand::thread_rng();
|
||||
let (account, vote_state) = new_rand_vote_account(&mut rng);
|
||||
let lamports = account.lamports;
|
||||
let vote_account = ArcVoteAccount::from(account);
|
||||
assert_eq!(lamports, vote_account.lamports());
|
||||
assert_eq!(vote_state, *vote_account.vote_state().as_ref().unwrap());
|
||||
// 2nd call to .vote_state() should return the cached value.
|
||||
assert_eq!(vote_state, *vote_account.vote_state().as_ref().unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_vote_account_serialize() {
|
||||
let mut rng = rand::thread_rng();
|
||||
let (account, vote_state) = new_rand_vote_account(&mut rng);
|
||||
let vote_account = ArcVoteAccount::from(account.clone());
|
||||
assert_eq!(vote_state, *vote_account.vote_state().as_ref().unwrap());
|
||||
// Assert than ArcVoteAccount has the same wire format as Account.
|
||||
assert_eq!(
|
||||
bincode::serialize(&account).unwrap(),
|
||||
bincode::serialize(&vote_account).unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_vote_account_deserialize() {
|
||||
let mut rng = rand::thread_rng();
|
||||
let (account, vote_state) = new_rand_vote_account(&mut rng);
|
||||
let data = bincode::serialize(&account).unwrap();
|
||||
let vote_account = ArcVoteAccount::from(account);
|
||||
assert_eq!(vote_state, *vote_account.vote_state().as_ref().unwrap());
|
||||
let other_vote_account: ArcVoteAccount = bincode::deserialize(&data).unwrap();
|
||||
assert_eq!(vote_account, other_vote_account);
|
||||
assert_eq!(
|
||||
vote_state,
|
||||
*other_vote_account.vote_state().as_ref().unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_vote_account_round_trip() {
|
||||
let mut rng = rand::thread_rng();
|
||||
let (account, vote_state) = new_rand_vote_account(&mut rng);
|
||||
let vote_account = ArcVoteAccount::from(account);
|
||||
assert_eq!(vote_state, *vote_account.vote_state().as_ref().unwrap());
|
||||
let data = bincode::serialize(&vote_account).unwrap();
|
||||
let other_vote_account: ArcVoteAccount = bincode::deserialize(&data).unwrap();
|
||||
// Assert that serialize->deserialized returns the same ArcVoteAccount.
|
||||
assert_eq!(vote_account, other_vote_account);
|
||||
assert_eq!(
|
||||
vote_state,
|
||||
*other_vote_account.vote_state().as_ref().unwrap()
|
||||
);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user