Skip to main content

hotmint_consensus/
state.rs

1use hotmint_types::{
2    BlockHash, DoubleCertificate, Epoch, Height, QuorumCertificate, ValidatorId, ValidatorSet,
3    ViewNumber,
4};
5
6/// Role of the validator in the current view
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum ViewRole {
9    Leader,
10    Replica,
11}
12
13/// Step within a view (state machine progression)
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub enum ViewStep {
16    Entered,
17    WaitingForStatus,
18    Proposed,
19    WaitingForProposal,
20    Voted,
21    CollectingVotes,
22    Prepared,
23    SentVote2,
24    Done,
25}
26
27/// Mutable consensus state for a single validator
28pub struct ConsensusState {
29    pub validator_id: ValidatorId,
30    pub validator_set: ValidatorSet,
31    /// Blake3 hash of the chain identifier — included in all signing bytes
32    /// to prevent cross-chain signature replay.
33    pub chain_id_hash: [u8; 32],
34    pub current_view: ViewNumber,
35    pub role: ViewRole,
36    pub step: ViewStep,
37    pub locked_qc: Option<QuorumCertificate>,
38    pub highest_double_cert: Option<DoubleCertificate>,
39    pub highest_qc: Option<QuorumCertificate>,
40    pub last_committed_height: Height,
41    /// Application state root from the most recently committed block's execution.
42    /// Included in the next proposed block for cross-node state divergence detection.
43    pub last_app_hash: BlockHash,
44    pub current_epoch: Epoch,
45    /// Vote extensions from the most recent DoubleCertificate's Vote2 round.
46    /// Set when a DC triggers a view advance; consumed by the next `create_payload`.
47    pub pending_vote_extensions: Vec<(ValidatorId, Vec<u8>)>,
48}
49
50impl ConsensusState {
51    pub fn new(validator_id: ValidatorId, validator_set: ValidatorSet) -> Self {
52        Self::with_chain_id(validator_id, validator_set, "")
53    }
54
55    pub fn with_chain_id(
56        validator_id: ValidatorId,
57        validator_set: ValidatorSet,
58        chain_id: &str,
59    ) -> Self {
60        let current_epoch = Epoch::genesis(validator_set.clone());
61        let chain_id_hash = *blake3::hash(chain_id.as_bytes()).as_bytes();
62        Self {
63            validator_id,
64            validator_set,
65            chain_id_hash,
66            current_view: ViewNumber::GENESIS,
67            role: ViewRole::Replica,
68            step: ViewStep::Entered,
69            locked_qc: None,
70            highest_double_cert: None,
71            highest_qc: None,
72            last_committed_height: Height::GENESIS,
73            last_app_hash: BlockHash::GENESIS,
74            current_epoch,
75            pending_vote_extensions: vec![],
76        }
77    }
78
79    pub fn is_leader(&self) -> bool {
80        self.role == ViewRole::Leader
81    }
82
83    pub fn update_highest_qc(&mut self, qc: &QuorumCertificate) {
84        let dominated = self.highest_qc.as_ref().is_none_or(|h| qc.view > h.view);
85        if dominated {
86            self.highest_qc = Some(qc.clone());
87        }
88    }
89
90    pub fn update_locked_qc(&mut self, qc: &QuorumCertificate) {
91        let dominated = self.locked_qc.as_ref().is_none_or(|h| qc.view > h.view);
92        if dominated {
93            self.locked_qc = Some(qc.clone());
94        }
95    }
96}