use std::collections::HashMap;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Step {
Propose,
Prevote,
Precommit,
Committed,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct Vote {
pub height: u64,
pub round: u32,
pub block_hash: Option<[u8; 32]>,
pub validator_pubkey: Vec<u8>,
pub signature: Vec<u8>,
}
pub struct RoundState {
pub height: u64,
pub round: u32,
pub step: Step,
pub proposal: Option<[u8; 32]>,
pub prevotes: HashMap<u32, HashMap<Vec<u8>, Vote>>,
pub precommits: HashMap<u32, HashMap<Vec<u8>, Vote>>,
pub locked_block: Option<[u8; 32]>,
pub locked_round: i64,
}
impl RoundState {
pub fn new(height: u64) -> Self {
Self {
height,
round: 0,
step: Step::Propose,
proposal: None,
prevotes: HashMap::new(),
precommits: HashMap::new(),
locked_block: None,
locked_round: -1,
}
}
pub fn next_round(&mut self) {
self.round += 1;
self.step = Step::Propose;
self.proposal = None;
}
pub fn add_prevote(&mut self, vote: Vote) -> bool {
let by_val = self.prevotes.entry(vote.round).or_default();
if by_val.contains_key(&vote.validator_pubkey) {
return false; }
by_val.insert(vote.validator_pubkey.clone(), vote);
true
}
pub fn add_precommit(&mut self, vote: Vote) -> bool {
let by_val = self.precommits.entry(vote.round).or_default();
if by_val.contains_key(&vote.validator_pubkey) {
return false;
}
by_val.insert(vote.validator_pubkey.clone(), vote);
true
}
pub fn prevote_stake(
&self,
round: u32,
block_hash: Option<[u8; 32]>,
stake_map: &HashMap<Vec<u8>, u64>,
) -> u64 {
self.prevotes
.get(&round)
.map(|by_val| {
by_val
.values()
.filter(|v| v.block_hash == block_hash)
.filter_map(|v| stake_map.get(&v.validator_pubkey))
.sum()
})
.unwrap_or(0)
}
pub fn precommit_stake(
&self,
round: u32,
block_hash: Option<[u8; 32]>,
stake_map: &HashMap<Vec<u8>, u64>,
) -> u64 {
self.precommits
.get(&round)
.map(|by_val| {
by_val
.values()
.filter(|v| v.block_hash == block_hash)
.filter_map(|v| stake_map.get(&v.validator_pubkey))
.sum()
})
.unwrap_or(0)
}
pub fn prevote_quorum(
&self,
round: u32,
threshold: u64,
stake_map: &HashMap<Vec<u8>, u64>,
) -> Option<[u8; 32]> {
let by_val = self.prevotes.get(&round)?;
let mut tally: HashMap<[u8; 32], u64> = HashMap::new();
for vote in by_val.values() {
if let Some(h) = vote.block_hash {
if let Some(s) = stake_map.get(&vote.validator_pubkey) {
*tally.entry(h).or_insert(0) += s;
}
}
}
tally
.into_iter()
.find(|(_, s)| *s >= threshold)
.map(|(h, _)| h)
}
pub fn precommit_quorum(
&self,
round: u32,
threshold: u64,
stake_map: &HashMap<Vec<u8>, u64>,
) -> Option<[u8; 32]> {
let by_val = self.precommits.get(&round)?;
let mut tally: HashMap<[u8; 32], u64> = HashMap::new();
for vote in by_val.values() {
if let Some(h) = vote.block_hash {
if let Some(s) = stake_map.get(&vote.validator_pubkey) {
*tally.entry(h).or_insert(0) += s;
}
}
}
tally
.into_iter()
.find(|(_, s)| *s >= threshold)
.map(|(h, _)| h)
}
pub fn should_prevote(
&self,
proposal_hash: [u8; 32],
threshold: u64,
stake_map: &HashMap<Vec<u8>, u64>,
) -> bool {
match self.locked_block {
None => true,
Some(locked) if locked == proposal_hash => true,
Some(_) => {
for (&r, _) in &self.prevotes {
if r as i64 > self.locked_round {
if self.prevote_stake(r, Some(proposal_hash), stake_map) >= threshold {
return true;
}
}
}
false
}
}
}
}