use std::{ sync::Arc, collections::{HashSet, HashMap}, }; use crate::{ time::CanonicalInstant, ext::{RoundNumber, BlockNumber, Block, Network}, round::RoundData, message_log::MessageLog, Step, Data, DataFor, Message, MessageFor, }; pub(crate) struct BlockData { pub(crate) number: BlockNumber, pub(crate) validator_id: Option, pub(crate) proposal: N::Block, pub(crate) log: MessageLog, pub(crate) slashes: HashSet, // We track the end times of each round for two reasons: // 1) Knowing the start time of the next round // 2) Validating precommits, which include the end time of the round which produced it // This HashMap contains the end time of the round we're currently in and every round prior pub(crate) end_time: HashMap, pub(crate) round: Option>, pub(crate) locked: Option<(RoundNumber, ::Id)>, pub(crate) valid: Option<(RoundNumber, N::Block)>, } impl BlockData { pub(crate) fn new( weights: Arc, number: BlockNumber, validator_id: Option, proposal: N::Block, ) -> BlockData { BlockData { number, validator_id, proposal, log: MessageLog::new(weights), slashes: HashSet::new(), end_time: HashMap::new(), // The caller of BlockData::new is expected to be populated after by the caller round: None, locked: None, valid: None, } } pub(crate) fn round(&self) -> &RoundData { self.round.as_ref().unwrap() } pub(crate) fn round_mut(&mut self) -> &mut RoundData { self.round.as_mut().unwrap() } // Populate the end time up to the specified round // This is generally used when moving to the next round, where this will only populate one time, // yet is also used when jumping rounds (when 33% of the validators are on a round ahead of us) pub(crate) fn populate_end_time(&mut self, round: RoundNumber) { // Starts from the current round since we only start the current round once we have have all // the prior time data for r in (self.round().number.0 + 1) ..= round.0 { self.end_time.insert( RoundNumber(r), RoundData::::new(RoundNumber(r), self.end_time[&RoundNumber(r - 1)]).end_time(), ); } } // Start a new round. Optionally takes in the time for when this is the first round, and the time // isn't simply the time of the prior round (yet rather the prior block). Returns the proposal // data, if we are the proposer. pub(crate) fn new_round( &mut self, round: RoundNumber, proposer: N::ValidatorId, time: Option, ) -> Option> { debug_assert_eq!(round.0 == 0, time.is_some()); // If this is the first round, we don't have a prior round's end time to use as the start // We use the passed in time instead // If this isn't the first round, ensure we have the prior round's end time by populating the // map with all rounds till this round // This can happen we jump from round x to round x+n, where n != 1 // The paper says to do so whenever you observe a sufficient amount of peers on a higher round if round.0 != 0 { self.populate_end_time(round); } // 11-13 self.round = Some(RoundData::::new( round, time.unwrap_or_else(|| self.end_time[&RoundNumber(round.0 - 1)]), )); self.end_time.insert(round, self.round().end_time()); // 14-21 if Some(proposer) == self.validator_id { let (round, block) = if let Some((round, block)) = &self.valid { (Some(*round), block.clone()) } else { (None, self.proposal.clone()) }; Some(Data::Proposal(round, block)) } else { self.round_mut().set_timeout(Step::Propose); None } } // Transform Data into an actual Message, using the contextual data from this block pub(crate) fn message(&mut self, data: DataFor) -> Option> { debug_assert_eq!( self.round().step, match data.step() { Step::Propose | Step::Prevote => Step::Propose, Step::Precommit => Step::Prevote, }, ); // Tendermint always sets the round's step to whatever it just broadcasted // Consolidate all of those here to ensure they aren't missed by an oversight // 27, 33, 41, 46, 60, 64 self.round_mut().step = data.step(); // Only return a message to if we're actually a current validator self.validator_id.map(|validator_id| Message { sender: validator_id, block: self.number, round: self.round().number, data, }) } }