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

@ -176,19 +176,15 @@ impl AggregateCommitmentService {
if lamports == 0 {
continue;
}
let vote_state = VoteState::from(&account);
if vote_state.is_none() {
continue;
if let Ok(vote_state) = account.vote_state().as_ref() {
Self::aggregate_commitment_for_vote_account(
&mut commitment,
&mut rooted_stake,
vote_state,
ancestors,
lamports,
);
}
let vote_state = vote_state.unwrap();
Self::aggregate_commitment_for_vote_account(
&mut commitment,
&mut rooted_stake,
&vote_state,
ancestors,
lamports,
);
}
(commitment, rooted_stake)
@ -482,9 +478,14 @@ mod tests {
#[test]
fn test_highest_confirmed_root_advance() {
fn get_vote_account_root_slot(vote_pubkey: Pubkey, bank: &Arc<Bank>) -> Slot {
let account = &bank.vote_accounts()[&vote_pubkey].1;
let vote_state = VoteState::from(account).unwrap();
vote_state.root_slot.unwrap()
let (_stake, vote_account) = bank.get_vote_account(&vote_pubkey).unwrap();
let slot = vote_account
.vote_state()
.as_ref()
.unwrap()
.root_slot
.unwrap();
slot
}
let block_commitment_cache = RwLock::new(BlockCommitmentCache::new_for_tests());

View File

@ -5,9 +5,11 @@ use crate::{
use chrono::prelude::*;
use solana_ledger::{ancestor_iterator::AncestorIterator, blockstore::Blockstore, blockstore_db};
use solana_measure::measure::Measure;
use solana_runtime::{bank::Bank, bank_forks::BankForks, commitment::VOTE_THRESHOLD_SIZE};
use solana_runtime::{
bank::Bank, bank_forks::BankForks, commitment::VOTE_THRESHOLD_SIZE,
vote_account::ArcVoteAccount,
};
use solana_sdk::{
account::Account,
clock::{Slot, UnixTimestamp},
hash::Hash,
instruction::Instruction,
@ -214,7 +216,7 @@ impl Tower {
all_pubkeys: &mut PubkeyReferences,
) -> ComputedBankState
where
F: Iterator<Item = (Pubkey, (u64, Account))>,
F: IntoIterator<Item = (Pubkey, (u64, ArcVoteAccount))>,
{
let mut voted_stakes = HashMap::new();
let mut total_stake = 0;
@ -228,20 +230,20 @@ impl Tower {
continue;
}
trace!("{} {} with stake {}", node_pubkey, key, voted_stake);
let vote_state = VoteState::from(&account);
if vote_state.is_none() {
datapoint_warn!(
"tower_warn",
(
"warn",
format!("Unable to get vote_state from account {}", key),
String
),
);
continue;
}
let mut vote_state = vote_state.unwrap();
let mut vote_state = match account.vote_state().as_ref() {
Err(_) => {
datapoint_warn!(
"tower_warn",
(
"warn",
format!("Unable to get vote_state from account {}", key),
String
),
);
continue;
}
Ok(vote_state) => vote_state.clone(),
};
for vote in &vote_state.votes {
let key = all_pubkeys.get_or_insert(&key);
lockout_intervals
@ -376,9 +378,9 @@ impl Tower {
}
fn last_voted_slot_in_bank(bank: &Bank, vote_account_pubkey: &Pubkey) -> Option<Slot> {
let vote_account = bank.vote_accounts().get(vote_account_pubkey)?.1.clone();
let bank_vote_state = VoteState::deserialize(&vote_account.data).ok()?;
bank_vote_state.last_voted_slot()
let (_stake, vote_account) = bank.get_vote_account(vote_account_pubkey)?;
let slot = vote_account.vote_state().as_ref().ok()?.last_voted_slot();
slot
}
pub fn new_vote_from_bank(&self, bank: &Bank, vote_account_pubkey: &Pubkey) -> (Vote, usize) {
@ -509,7 +511,7 @@ impl Tower {
descendants: &HashMap<Slot, HashSet<u64>>,
progress: &ProgressMap,
total_stake: u64,
epoch_vote_accounts: &HashMap<Pubkey, (u64, Account)>,
epoch_vote_accounts: &HashMap<Pubkey, (u64, ArcVoteAccount)>,
) -> SwitchForkDecision {
self.last_voted_slot()
.map(|last_voted_slot| {
@ -703,7 +705,7 @@ impl Tower {
descendants: &HashMap<Slot, HashSet<u64>>,
progress: &ProgressMap,
total_stake: u64,
epoch_vote_accounts: &HashMap<Pubkey, (u64, Account)>,
epoch_vote_accounts: &HashMap<Pubkey, (u64, ArcVoteAccount)>,
) -> SwitchForkDecision {
let decision = self.make_check_switch_threshold_decision(
switch_slot,
@ -1058,10 +1060,12 @@ impl Tower {
root: Slot,
bank: &Bank,
) {
if let Some((_stake, vote_account)) = bank.vote_accounts().get(vote_account_pubkey) {
let vote_state = VoteState::deserialize(&vote_account.data)
.expect("vote_account isn't a VoteState?");
self.lockouts = vote_state;
if let Some((_stake, vote_account)) = bank.get_vote_account(vote_account_pubkey) {
self.lockouts = vote_account
.vote_state()
.as_ref()
.expect("vote_account isn't a VoteState?")
.clone();
self.initialize_root(root);
self.initialize_lockouts(|v| v.slot > root);
trace!(
@ -1286,7 +1290,8 @@ pub mod test {
},
};
use solana_sdk::{
clock::Slot, hash::Hash, pubkey::Pubkey, signature::Signer, slot_history::SlotHistory,
account::Account, clock::Slot, hash::Hash, pubkey::Pubkey, signature::Signer,
slot_history::SlotHistory,
};
use solana_vote_program::{
vote_state::{Vote, VoteStateVersions, MAX_LOCKOUT_HISTORY},
@ -1604,7 +1609,7 @@ pub mod test {
(bank_forks, progress, heaviest_subtree_fork_choice)
}
fn gen_stakes(stake_votes: &[(u64, &[u64])]) -> Vec<(Pubkey, (u64, Account))> {
fn gen_stakes(stake_votes: &[(u64, &[u64])]) -> Vec<(Pubkey, (u64, ArcVoteAccount))> {
let mut stakes = vec![];
for (lamports, votes) in stake_votes {
let mut account = Account::default();
@ -1619,7 +1624,10 @@ pub mod test {
&mut account.data,
)
.expect("serialize state");
stakes.push((solana_sdk::pubkey::new_rand(), (*lamports, account)));
stakes.push((
solana_sdk::pubkey::new_rand(),
(*lamports, ArcVoteAccount::from(account)),
));
}
stakes
}
@ -1973,16 +1981,16 @@ pub mod test {
}
info!("local tower: {:#?}", tower.lockouts.votes);
let vote_accounts = vote_simulator
let observed = vote_simulator
.bank_forks
.read()
.unwrap()
.get(next_unlocked_slot)
.unwrap()
.vote_accounts();
let observed = vote_accounts.get(&vote_pubkey).unwrap();
let state = VoteState::from(&observed.1).unwrap();
info!("observed tower: {:#?}", state.votes);
.get_vote_account(&vote_pubkey)
.unwrap();
let state = observed.1.vote_state();
info!("observed tower: {:#?}", state.as_ref().unwrap().votes);
let num_slots_to_try = 200;
cluster_votes

View File

@ -6,8 +6,8 @@ use crate::{
{consensus::Stake, consensus::VotedStakes},
};
use solana_ledger::blockstore_processor::{ConfirmationProgress, ConfirmationTiming};
use solana_runtime::{bank::Bank, bank_forks::BankForks};
use solana_sdk::{account::Account, clock::Slot, hash::Hash, pubkey::Pubkey};
use solana_runtime::{bank::Bank, bank_forks::BankForks, vote_account::ArcVoteAccount};
use solana_sdk::{clock::Slot, hash::Hash, pubkey::Pubkey};
use std::{
collections::{BTreeMap, HashMap, HashSet},
rc::Rc,
@ -262,7 +262,7 @@ impl PropagatedStats {
node_pubkey: &Pubkey,
all_pubkeys: &mut PubkeyReferences,
vote_account_pubkeys: &[Pubkey],
epoch_vote_accounts: &HashMap<Pubkey, (u64, Account)>,
epoch_vote_accounts: &HashMap<Pubkey, (u64, ArcVoteAccount)>,
) {
let cached_pubkey = all_pubkeys.get_or_insert(node_pubkey);
self.propagated_node_ids.insert(cached_pubkey);
@ -440,7 +440,7 @@ mod test {
let epoch_vote_accounts: HashMap<_, _> = vote_account_pubkeys
.iter()
.skip(num_vote_accounts - staked_vote_accounts)
.map(|pubkey| (*pubkey, (1, Account::default())))
.map(|pubkey| (*pubkey, (1, ArcVoteAccount::default())))
.collect();
let mut stats = PropagatedStats::default();
@ -507,7 +507,7 @@ mod test {
let epoch_vote_accounts: HashMap<_, _> = vote_account_pubkeys
.iter()
.skip(num_vote_accounts - staked_vote_accounts)
.map(|pubkey| (*pubkey, (1, Account::default())))
.map(|pubkey| (*pubkey, (1, ArcVoteAccount::default())))
.collect();
stats.add_node_pubkey_internal(
&node_pubkey,

View File

@ -42,10 +42,7 @@ use solana_sdk::{
timing::timestamp,
transaction::Transaction,
};
use solana_vote_program::{
vote_instruction,
vote_state::{Vote, VoteState},
};
use solana_vote_program::{vote_instruction, vote_state::Vote};
use std::{
collections::{HashMap, HashSet},
ops::Deref,
@ -1132,26 +1129,27 @@ impl ReplayStage {
if authorized_voter_keypairs.is_empty() {
return;
}
let vote_state =
if let Some((_, vote_account)) = bank.vote_accounts().get(vote_account_pubkey) {
if let Some(vote_state) = VoteState::from(&vote_account) {
vote_state
} else {
warn!(
"Vote account {} is unreadable. Unable to vote",
vote_account_pubkey,
);
return;
}
} else {
let vote_account = match bank.get_vote_account(vote_account_pubkey) {
None => {
warn!(
"Vote account {} does not exist. Unable to vote",
vote_account_pubkey,
);
return;
};
}
Some((_stake, vote_account)) => vote_account,
};
let vote_state = vote_account.vote_state();
let vote_state = match vote_state.as_ref() {
Err(_) => {
warn!(
"Vote account {} is unreadable. Unable to vote",
vote_account_pubkey,
);
return;
}
Ok(vote_state) => vote_state,
};
let authorized_voter_pubkey =
if let Some(authorized_voter_pubkey) = vote_state.get_authorized_voter(bank.epoch()) {
authorized_voter_pubkey

View File

@ -537,13 +537,15 @@ impl JsonRpcRequestProcessor {
let epoch_vote_accounts = bank
.epoch_vote_accounts(bank.get_epoch_and_slot_index(bank.slot()).0)
.ok_or_else(Error::invalid_request)?;
let default_vote_state = VoteState::default();
let (current_vote_accounts, delinquent_vote_accounts): (
Vec<RpcVoteAccountInfo>,
Vec<RpcVoteAccountInfo>,
) = vote_accounts
.iter()
.map(|(pubkey, (activated_stake, account))| {
let vote_state = VoteState::from(&account).unwrap_or_default();
let vote_state = account.vote_state();
let vote_state = vote_state.as_ref().unwrap_or(&default_vote_state);
let last_vote = if let Some(vote) = vote_state.votes.iter().last() {
vote.slot
} else {

View File

@ -1125,33 +1125,37 @@ fn get_stake_percent_in_gossip(bank: &Bank, cluster_info: &ClusterInfo, log: boo
let my_id = cluster_info.id();
for (activated_stake, vote_account) in bank.vote_accounts().values() {
let vote_state = VoteState::from(&vote_account).unwrap_or_default();
total_activated_stake += activated_stake;
if *activated_stake == 0 {
continue;
}
let vote_state_node_pubkey = vote_account
.vote_state()
.as_ref()
.map(|vote_state| vote_state.node_pubkey)
.unwrap_or_default();
if let Some(peer) = all_tvu_peers
.iter()
.find(|peer| peer.id == vote_state.node_pubkey)
.find(|peer| peer.id == vote_state_node_pubkey)
{
if peer.shred_version == my_shred_version {
trace!(
"observed {} in gossip, (activated_stake={})",
vote_state.node_pubkey,
vote_state_node_pubkey,
activated_stake
);
online_stake += activated_stake;
} else {
wrong_shred_stake += activated_stake;
wrong_shred_nodes.push((*activated_stake, vote_state.node_pubkey));
wrong_shred_nodes.push((*activated_stake, vote_state_node_pubkey));
}
} else if vote_state.node_pubkey == my_id {
} else if vote_state_node_pubkey == my_id {
online_stake += activated_stake; // This node is online
} else {
offline_stake += activated_stake;
offline_nodes.push((*activated_stake, vote_state.node_pubkey));
offline_nodes.push((*activated_stake, vote_state_node_pubkey));
}
}