use std::{
collections::{BTreeMap, HashMap},
fmt::Debug,
};
use datasize::DataSize;
use serde::{Deserialize, Serialize};
use crate::{
components::consensus::{
protocols::zug::{Content, HashedProposal},
traits::Context,
utils::{ValidatorIndex, ValidatorMap},
},
utils::ds,
};
#[derive(Debug, DataSize, PartialEq)]
pub(crate) struct Round<C>
where
C: Context,
{
leader_idx: ValidatorIndex,
proposal: Option<HashedProposal<C>>,
#[data_size(with = ds::hashmap_sample)]
echoes: HashMap<C::Hash, BTreeMap<ValidatorIndex, C::Signature>>,
votes: BTreeMap<bool, ValidatorMap<Option<C::Signature>>>,
outcome: RoundOutcome<C>,
}
impl<C: Context> Round<C> {
pub(super) fn new(validator_count: usize, leader_idx: ValidatorIndex) -> Round<C> {
let mut votes = BTreeMap::new();
votes.insert(false, vec![None; validator_count].into());
votes.insert(true, vec![None; validator_count].into());
Round {
leader_idx,
proposal: None,
echoes: HashMap::new(),
votes,
outcome: RoundOutcome::default(),
}
}
pub(super) fn proposal(&self) -> Option<&HashedProposal<C>> {
self.proposal.as_ref()
}
pub(super) fn has_proposal(&self) -> bool {
self.proposal.is_some()
}
pub(super) fn has_echoes_for_proposal(&self, hash: &C::Hash) -> bool {
match (self.quorum_echoes(), self.echoes.get(hash)) {
(Some(quorum_hash), _) => quorum_hash == *hash,
(None, Some(echo_map)) => echo_map.contains_key(&self.leader_idx),
(None, None) => false,
}
}
pub(super) fn insert_proposal(&mut self, proposal: HashedProposal<C>) -> bool {
let hash = proposal.hash();
if self.has_echoes_for_proposal(hash) && self.proposal.as_ref() != Some(&proposal) {
self.proposal = Some(proposal);
true
} else {
false
}
}
pub(super) fn echoes(&self) -> &HashMap<C::Hash, BTreeMap<ValidatorIndex, C::Signature>> {
&self.echoes
}
pub(super) fn insert_echo(
&mut self,
hash: C::Hash,
validator_idx: ValidatorIndex,
signature: C::Signature,
) -> bool {
self.echoes
.entry(hash)
.or_default()
.insert(validator_idx, signature)
.is_none()
}
pub(super) fn has_echoed(&self, validator_idx: ValidatorIndex) -> bool {
self.echoes
.values()
.any(|echo_map| echo_map.contains_key(&validator_idx))
}
pub(super) fn set_quorum_echoes(&mut self, hash: C::Hash) {
self.outcome.quorum_echoes = Some(hash);
if self
.proposal
.as_ref()
.is_some_and(|proposal| *proposal.hash() != hash)
{
self.proposal = None;
}
}
pub(super) fn quorum_echoes(&self) -> Option<C::Hash> {
self.outcome.quorum_echoes
}
pub(super) fn votes(&self, vote: bool) -> &ValidatorMap<Option<C::Signature>> {
&self.votes[&vote]
}
pub(super) fn insert_vote(
&mut self,
vote: bool,
validator_idx: ValidatorIndex,
signature: C::Signature,
) -> bool {
let votes_map = self.votes.get_mut(&vote).unwrap();
if votes_map[validator_idx].is_none() {
votes_map[validator_idx] = Some(signature);
true
} else {
false
}
}
pub(super) fn has_voted(&self, validator_idx: ValidatorIndex) -> bool {
self.votes(true)[validator_idx].is_some() || self.votes(false)[validator_idx].is_some()
}
pub(super) fn set_quorum_votes(&mut self, vote: bool) {
self.outcome.quorum_votes = Some(vote);
}
pub(super) fn quorum_votes(&self) -> Option<bool> {
self.outcome.quorum_votes
}
pub(super) fn remove_votes_and_echoes(&mut self, validator_idx: ValidatorIndex) {
self.votes.get_mut(&false).unwrap()[validator_idx] = None;
self.votes.get_mut(&true).unwrap()[validator_idx] = None;
self.echoes.retain(|_, echo_map| {
echo_map.remove(&validator_idx);
!echo_map.is_empty()
});
}
pub(super) fn set_accepted_proposal_height(&mut self, height: u64) {
self.outcome.accepted_proposal_height = Some(height);
}
pub(super) fn accepted_proposal(&self) -> Option<(u64, &HashedProposal<C>)> {
let height = self.outcome.accepted_proposal_height?;
let proposal = self.proposal.as_ref()?;
Some((height, proposal))
}
pub(super) fn contains(&self, content: &Content<C>, validator_idx: ValidatorIndex) -> bool {
match content {
Content::Echo(hash) => self
.echoes
.get(hash)
.is_some_and(|echo_map| echo_map.contains_key(&validator_idx)),
Content::Vote(vote) => self.votes[vote][validator_idx].is_some(),
}
}
pub(super) fn prune_skipped(&mut self) {
self.proposal = None;
self.outcome.accepted_proposal_height = None;
}
pub(super) fn leader(&self) -> ValidatorIndex {
self.leader_idx
}
}
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, DataSize)]
#[serde(bound(
serialize = "C::Hash: Serialize",
deserialize = "C::Hash: Deserialize<'de>",
))]
pub(crate) struct RoundOutcome<C>
where
C: Context,
{
accepted_proposal_height: Option<u64>,
quorum_echoes: Option<C::Hash>,
quorum_votes: Option<bool>,
}
impl<C: Context> Default for RoundOutcome<C> {
fn default() -> RoundOutcome<C> {
RoundOutcome {
accepted_proposal_height: None,
quorum_echoes: None,
quorum_votes: None,
}
}
}