Skip to content

Commit

Permalink
Add checks to vote state updates to handle updates outside of SlotHas…
Browse files Browse the repository at this point in the history
…h history (solana-labs#22358)
  • Loading branch information
carllin authored Jan 25, 2022
1 parent 1192e76 commit 1cf6c97
Show file tree
Hide file tree
Showing 2 changed files with 860 additions and 59 deletions.
213 changes: 177 additions & 36 deletions programs/vote/benches/process_vote.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,45 +10,43 @@ use {
feature_set::FeatureSet,
hash::Hash,
instruction::{AccountMeta, Instruction},
keyed_account::KeyedAccount,
pubkey::Pubkey,
slot_hashes::{SlotHashes, MAX_ENTRIES},
sysvar,
transaction_context::{InstructionAccount, TransactionContext},
},
solana_vote_program::{
vote_instruction::VoteInstruction,
vote_state::{Vote, VoteInit, VoteState, VoteStateVersions, MAX_LOCKOUT_HISTORY},
vote_state::{
self, Vote, VoteInit, VoteState, VoteStateUpdate, VoteStateVersions,
MAX_LOCKOUT_HISTORY,
},
},
std::sync::Arc,
std::{cell::RefCell, collections::HashSet, sync::Arc},
test::Bencher,
};

/// `feature` can be used to change vote program behavior per bench run.
fn do_bench(bencher: &mut Bencher, feature: Option<Pubkey>) {
// vote accounts are usually almost full of votes in normal operation
let num_initial_votes = MAX_LOCKOUT_HISTORY;
let num_vote_slots: usize = 4;
let last_vote_slot = num_initial_votes
.saturating_add(num_vote_slots)
.saturating_sub(1);
let last_vote_hash = Hash::new_unique();
struct VoteComponents {
slot_hashes: SlotHashes,
clock: Clock,
signers: HashSet<Pubkey>,
authority_pubkey: Pubkey,
vote_pubkey: Pubkey,
vote_account: Account,
}

fn create_components(num_initial_votes: Slot) -> VoteComponents {
let clock = Clock::default();
let mut slot_hashes = SlotHashes::new(&[]);
for i in 0..MAX_ENTRIES {
// slot hashes is full in normal operation
slot_hashes.add(
i as Slot,
if i == last_vote_slot {
last_vote_hash
} else {
Hash::default()
},
);
slot_hashes.add(i as Slot, Hash::new_unique());
}

let vote_pubkey = Pubkey::new_unique();
let authority_pubkey = Pubkey::new_unique();
let signers: HashSet<Pubkey> = vec![authority_pubkey].into_iter().collect();
let vote_account = {
let mut vote_state = VoteState::new(
&VoteInit {
Expand All @@ -60,12 +58,11 @@ fn do_bench(bencher: &mut Bencher, feature: Option<Pubkey>) {
&clock,
);

for next_vote_slot in 0..num_initial_votes as u64 {
for next_vote_slot in 0..num_initial_votes {
vote_state.process_next_vote_slot(next_vote_slot, 0);
}

let mut vote_account_data: Vec<u8> = vec![0; VoteState::size_of()];
let versioned = VoteStateVersions::new_current(vote_state);
let versioned = VoteStateVersions::new_current(vote_state.clone());
VoteState::serialize(&versioned, &mut vote_account_data).unwrap();

Account {
Expand All @@ -76,22 +73,53 @@ fn do_bench(bencher: &mut Bencher, feature: Option<Pubkey>) {
rent_epoch: 0,
}
};

VoteComponents {
slot_hashes,
clock,
signers,
authority_pubkey,
vote_pubkey,
vote_account,
}
}

/// `feature` can be used to change vote program behavior per bench run.
fn do_bench_process_vote_instruction(bencher: &mut Bencher, feature: Option<Pubkey>) {
// vote accounts are usually almost full of votes in normal operation
let num_initial_votes = MAX_LOCKOUT_HISTORY as Slot;

let VoteComponents {
slot_hashes,
clock,
authority_pubkey,
vote_pubkey,
vote_account,
..
} = create_components(num_initial_votes);

let slot_hashes_account = create_account_for_test(&slot_hashes);
let clock_account = create_account_for_test(&clock);
let authority_account = Account::default();

let mut sysvar_cache = SysvarCache::default();
sysvar_cache.set_clock(clock);
sysvar_cache.set_slot_hashes(slot_hashes);

let mut feature_set = FeatureSet::all_enabled();
if let Some(feature) = feature {
feature_set.activate(&feature, 0);
}
let feature_set = Arc::new(feature_set);

let num_vote_slots = 4;
let last_vote_slot = num_initial_votes
.saturating_add(num_vote_slots)
.saturating_sub(1);
let last_vote_hash = slot_hashes
.iter()
.find(|(slot, _hash)| *slot == last_vote_slot)
.unwrap()
.1;

let vote_ix_data = bincode::serialize(&VoteInstruction::Vote(Vote::new(
(num_initial_votes as u64..).take(num_vote_slots).collect(),
(num_initial_votes..=last_vote_slot).collect(),
last_vote_hash,
)))
.unwrap();
Expand Down Expand Up @@ -120,6 +148,10 @@ fn do_bench(bencher: &mut Bencher, feature: Option<Pubkey>) {
})
.collect::<Vec<_>>();

let mut sysvar_cache = SysvarCache::default();
sysvar_cache.set_clock(clock);
sysvar_cache.set_slot_hashes(slot_hashes);

bencher.iter(|| {
let mut transaction_context = TransactionContext::new(
vec![
Expand Down Expand Up @@ -153,18 +185,127 @@ fn do_bench(bencher: &mut Bencher, feature: Option<Pubkey>) {
.unwrap();

let first_instruction_account = 1;
assert_eq!(
solana_vote_program::vote_processor::process_instruction(
first_instruction_account,
&instruction.data,
&mut invoke_context
),
Ok(())
);
assert!(solana_vote_program::vote_processor::process_instruction(
first_instruction_account,
&instruction.data,
&mut invoke_context
)
.is_ok());
});
}

/// `feature` can be used to change vote program behavior per bench run.
fn do_bench_process_vote(bencher: &mut Bencher, feature: Option<Pubkey>) {
// vote accounts are usually almost full of votes in normal operation
let num_initial_votes = MAX_LOCKOUT_HISTORY as Slot;

let VoteComponents {
slot_hashes,
clock,
signers,
vote_pubkey,
vote_account,
..
} = create_components(num_initial_votes);

let num_vote_slots = 4;
let last_vote_slot = num_initial_votes
.saturating_add(num_vote_slots)
.saturating_sub(1);
let last_vote_hash = slot_hashes
.iter()
.find(|(slot, _hash)| *slot == last_vote_slot)
.unwrap()
.1;

let vote = Vote::new(
(num_initial_votes..=last_vote_slot).collect(),
last_vote_hash,
);

let mut feature_set = FeatureSet::all_enabled();
if let Some(feature) = feature {
feature_set.activate(&feature, 0);
}
let feature_set = Arc::new(feature_set);

bencher.iter(|| {
let vote_account = RefCell::new(AccountSharedData::from(vote_account.clone()));
let keyed_account = KeyedAccount::new(&vote_pubkey, true, &vote_account);
assert!(vote_state::process_vote(
&keyed_account,
&slot_hashes,
&clock,
&vote,
&signers,
&feature_set,
)
.is_ok());
});
}

fn do_bench_process_vote_state_update(bencher: &mut Bencher) {
// vote accounts are usually almost full of votes in normal operation
let num_initial_votes = MAX_LOCKOUT_HISTORY as Slot;

let VoteComponents {
slot_hashes,
clock,
signers,
vote_pubkey,
vote_account,
..
} = create_components(num_initial_votes);

let num_vote_slots = MAX_LOCKOUT_HISTORY as Slot;
let last_vote_slot = num_initial_votes
.saturating_add(num_vote_slots)
.saturating_sub(1);
let last_vote_hash = slot_hashes
.iter()
.find(|(slot, _hash)| *slot == last_vote_slot)
.unwrap()
.1;

let slots_and_lockouts: Vec<(Slot, u32)> =
((num_initial_votes.saturating_add(1)..=last_vote_slot).zip((1u32..=31).rev())).collect();

let mut vote_state_update = VoteStateUpdate::from(slots_and_lockouts);
vote_state_update.root = Some(num_initial_votes);

vote_state_update.hash = last_vote_hash;

bencher.iter(|| {
let vote_account = RefCell::new(AccountSharedData::from(vote_account.clone()));
let keyed_account = KeyedAccount::new(&vote_pubkey, true, &vote_account);
let vote_state_update = vote_state_update.clone();
assert!(vote_state::process_vote_state_update(
&keyed_account,
&slot_hashes,
&clock,
vote_state_update,
&signers,
)
.is_ok());
});
}

#[bench]
#[ignore]
fn bench_process_vote_instruction(bencher: &mut Bencher) {
do_bench(bencher, None);
do_bench_process_vote_instruction(bencher, None);
}

// Benches a specific type of vote instruction
#[bench]
#[ignore]
fn bench_process_vote(bencher: &mut Bencher) {
do_bench_process_vote(bencher, None);
}

// Benches a specific type of vote instruction
#[bench]
#[ignore]
fn bench_process_vote_state_update(bencher: &mut Bencher) {
do_bench_process_vote_state_update(bencher);
}
Loading

0 comments on commit 1cf6c97

Please sign in to comment.