use {
super::{AddVoteError, BuildSigBitmapError, partial_cert::PartialCert},
crate::consensus_rewards::BuildRewardCertsRespError,
agave_votor_messages::reward_certificate::NotarRewardCertificate,
solana_bls_signatures::Signature as BLSSignature,
solana_clock::Slot,
solana_hash::Hash,
solana_pubkey::Pubkey,
solana_runtime::epoch_stakes::BLSPubkeyToRankMap,
std::collections::{HashMap, HashSet},
};
#[derive(Clone)]
pub(super) struct NotarEntry {
voted: HashSet<u16>,
partials: HashMap<Hash, PartialCert>,
}
impl NotarEntry {
pub(super) fn new(max_validators: usize) -> Self {
Self {
voted: HashSet::with_capacity(max_validators),
partials: HashMap::with_capacity(5),
}
}
pub(super) fn wants_vote(&self, rank: u16) -> bool {
!self.voted.contains(&rank)
}
pub(super) fn add_vote(
&mut self,
rank_map: &BLSPubkeyToRankMap,
rank: u16,
signature: &BLSSignature,
block_id: Hash,
max_validators: usize,
) -> Result<(), AddVoteError> {
if !self.voted.insert(rank) {
return Err(AddVoteError::Duplicate);
}
let partial = self
.partials
.entry(block_id)
.or_insert(PartialCert::new(max_validators));
let res = partial.add_vote(rank_map, rank, signature);
if res.is_err() {
self.voted.remove(&rank);
}
res
}
pub(super) fn build_cert(
self,
reward_slot: Slot,
) -> Result<Option<(NotarRewardCertificate, Vec<Pubkey>)>, BuildRewardCertsRespError> {
let res = self
.partials
.into_iter()
.max_by_key(|(_block_id, partial)| partial.votes_seen());
let Some((block_id, partial)) = res else {
return Ok(None);
};
match partial.build_sig_bitmap() {
Err(e) => match e {
BuildSigBitmapError::Empty => Ok(None),
BuildSigBitmapError::Encode(e) => Err(BuildRewardCertsRespError::Encode(e)),
},
Ok((signature, bitmap, validators)) => {
let cert =
NotarRewardCertificate::try_new(reward_slot, block_id, signature, bitmap)?;
Ok(Some((cert, validators)))
}
}
}
}
#[cfg(test)]
mod tests {
use {
super::*,
crate::consensus_rewards::entry::tests::{
get_rank_map_keypairs, new_vote, validate_bitmap,
},
agave_votor_messages::{consensus_message::VoteMessage, vote::Vote},
solana_bls_signatures::signature::BLS_SIGNATURE_AFFINE_SIZE,
solana_hash::Hash,
};
#[test]
fn validator_add_vote() {
let slot = 123;
let max_validators = 5;
let (rank_map, keypairs) = get_rank_map_keypairs(max_validators, slot);
let rank = 0;
let mut entry = NotarEntry::new(max_validators);
let blockid0 = Hash::new_unique();
let notar = Vote::new_notarization_vote(slot, blockid0);
let invalid_vote = VoteMessage {
vote: notar,
signature: BLSSignature([0; BLS_SIGNATURE_AFFINE_SIZE]),
rank,
};
entry
.add_vote(
&rank_map,
invalid_vote.rank,
&invalid_vote.signature,
blockid0,
max_validators,
)
.unwrap_err();
let vote = new_vote(notar, rank as usize, &keypairs);
entry
.add_vote(
&rank_map,
vote.rank,
&vote.signature,
blockid0,
max_validators,
)
.unwrap();
let err = entry
.add_vote(
&rank_map,
vote.rank,
&vote.signature,
blockid0,
max_validators,
)
.unwrap_err();
assert!(matches!(err, AddVoteError::Duplicate));
}
#[test]
fn validate_build_cert() {
let slot = 123;
let max_validators = 5;
let (rank_map, keypairs) = get_rank_map_keypairs(max_validators, slot);
let mut entry = NotarEntry::new(max_validators);
assert_eq!(entry.clone().build_cert(slot).unwrap(), None);
let blockid0 = Hash::new_unique();
let blockid1 = Hash::new_unique();
for rank in 0..2 {
let notar = Vote::new_notarization_vote(slot, blockid0);
let vote = new_vote(notar, rank, &keypairs);
entry
.add_vote(
&rank_map,
vote.rank,
&vote.signature,
blockid0,
max_validators,
)
.unwrap();
}
for rank in 2..5 {
let notar = Vote::new_notarization_vote(slot, blockid1);
let vote = new_vote(notar, rank, &keypairs);
entry
.add_vote(
&rank_map,
vote.rank,
&vote.signature,
blockid1,
max_validators,
)
.unwrap();
}
let (notar_cert, _) = entry.build_cert(slot).unwrap().unwrap();
assert_eq!(notar_cert.slot, slot);
assert_eq!(notar_cert.block_id, blockid1);
validate_bitmap(notar_cert.bitmap(), 3, 5);
}
}