solana/core/src/unfrozen_gossip_verified_vo...

133 lines
5.2 KiB
Rust

use crate::latest_validator_votes_for_frozen_banks::LatestValidatorVotesForFrozenBanks;
use solana_sdk::{clock::Slot, hash::Hash, pubkey::Pubkey};
use std::collections::{BTreeMap, HashMap};
#[derive(Default)]
pub(crate) struct UnfrozenGossipVerifiedVoteHashes {
pub votes_per_slot: BTreeMap<Slot, HashMap<Hash, Vec<Pubkey>>>,
}
impl UnfrozenGossipVerifiedVoteHashes {
// Update `latest_validator_votes_for_frozen_banks` if gossip has seen a newer vote
// for a frozen bank.
#[allow(dead_code)]
pub(crate) fn add_vote(
&mut self,
pubkey: Pubkey,
vote_slot: Slot,
hash: Hash,
is_frozen: bool,
latest_validator_votes_for_frozen_banks: &mut LatestValidatorVotesForFrozenBanks,
) {
// If this is a frozen bank, then we need to update the `latest_validator_votes_for_frozen_banks`
let frozen_hash = if is_frozen { Some(hash) } else { None };
let (was_added, latest_frozen_vote_slot) = latest_validator_votes_for_frozen_banks
.check_add_vote(pubkey, vote_slot, frozen_hash, false);
if !was_added
&& latest_frozen_vote_slot
.map(|latest_frozen_vote_slot| vote_slot >= latest_frozen_vote_slot)
// If there's no latest frozen vote slot yet, then we should also insert
.unwrap_or(true)
{
// At this point it must be that:
// 1) `vote_slot` was not yet frozen
// 2) and `vote_slot` >= than the latest frozen vote slot.
// Thus we want to record this vote for later, in case a slot with this `vote_slot` + hash gets
// frozen later
self.votes_per_slot
.entry(vote_slot)
.or_default()
.entry(hash)
.or_default()
.push(pubkey);
}
}
// Cleanup `votes_per_slot` based on new roots
pub(crate) fn set_root(&mut self, new_root: Slot) {
let mut slots_ge_root = self.votes_per_slot.split_off(&new_root);
// `self.votes_per_slot` now only contains entries >= `new_root`
std::mem::swap(&mut self.votes_per_slot, &mut slots_ge_root);
}
pub(crate) fn remove_slot_hash(&mut self, slot: Slot, hash: &Hash) -> Option<Vec<Pubkey>> {
self.votes_per_slot.get_mut(&slot).and_then(|slot_hashes| {
slot_hashes.remove(hash)
// If `slot_hashes` becomes empty, it'll be removed by `set_root()` later
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_unfrozen_gossip_verified_vote_hashes_add_vote() {
let mut unfrozen_gossip_verified_vote_hashes = UnfrozenGossipVerifiedVoteHashes::default();
let mut latest_validator_votes_for_frozen_banks =
LatestValidatorVotesForFrozenBanks::default();
let num_validators = 10;
let validator_keys: Vec<Pubkey> = std::iter::repeat_with(Pubkey::new_unique)
.take(num_validators)
.collect();
// Case 1: Frozen banks shouldn't be added
let frozen_vote_slot = 1;
let num_repeated_iterations = 10;
for _ in 0..num_repeated_iterations {
let hash = Hash::new_unique();
let is_frozen = true;
for vote_pubkey in validator_keys.iter() {
unfrozen_gossip_verified_vote_hashes.add_vote(
*vote_pubkey,
frozen_vote_slot,
hash,
is_frozen,
&mut latest_validator_votes_for_frozen_banks,
);
}
assert!(unfrozen_gossip_verified_vote_hashes
.votes_per_slot
.is_empty());
}
// Case 2: Other >= non-frozen banks should be added in case they're frozen later
for unfrozen_vote_slot in &[frozen_vote_slot - 1, frozen_vote_slot, frozen_vote_slot + 1] {
// If the vote slot is smaller than the latest known frozen `vote_slot`
// for each pubkey (which was added above), then they shouldn't be added
let num_duplicate_hashes = 10;
for _ in 0..num_duplicate_hashes {
let hash = Hash::new_unique();
let is_frozen = false;
for vote_pubkey in validator_keys.iter() {
unfrozen_gossip_verified_vote_hashes.add_vote(
*vote_pubkey,
*unfrozen_vote_slot,
hash,
is_frozen,
&mut latest_validator_votes_for_frozen_banks,
);
}
}
if *unfrozen_vote_slot >= frozen_vote_slot {
let vote_hashes_map = unfrozen_gossip_verified_vote_hashes
.votes_per_slot
.get(unfrozen_vote_slot)
.unwrap();
assert_eq!(vote_hashes_map.len(), num_duplicate_hashes);
for pubkey_votes in vote_hashes_map.values() {
assert_eq!(*pubkey_votes, validator_keys);
}
} else {
assert!(unfrozen_gossip_verified_vote_hashes
.votes_per_slot
.is_empty());
}
}
}
}