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:
behzad nouri
2020-11-30 17:18:33 +00:00
committed by GitHub
parent 6203d1c94c
commit e1793e5a13
18 changed files with 433 additions and 198 deletions

View File

@ -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 {

View File

@ -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();

View File

@ -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;

View File

@ -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")]

View File

@ -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
View 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()
);
}
}