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}
46
47impl ConsensusState {
48    pub fn new(validator_id: ValidatorId, validator_set: ValidatorSet) -> Self {
49        Self::with_chain_id(validator_id, validator_set, "")
50    }
51
52    pub fn with_chain_id(
53        validator_id: ValidatorId,
54        validator_set: ValidatorSet,
55        chain_id: &str,
56    ) -> Self {
57        let current_epoch = Epoch::genesis(validator_set.clone());
58        let chain_id_hash = *blake3::hash(chain_id.as_bytes()).as_bytes();
59        Self {
60            validator_id,
61            validator_set,
62            chain_id_hash,
63            current_view: ViewNumber::GENESIS,
64            role: ViewRole::Replica,
65            step: ViewStep::Entered,
66            locked_qc: None,
67            highest_double_cert: None,
68            highest_qc: None,
69            last_committed_height: Height::GENESIS,
70            last_app_hash: BlockHash::GENESIS,
71            current_epoch,
72        }
73    }
74
75    pub fn is_leader(&self) -> bool {
76        self.role == ViewRole::Leader
77    }
78
79    pub fn update_highest_qc(&mut self, qc: &QuorumCertificate) {
80        let dominated = self.highest_qc.as_ref().is_none_or(|h| qc.view > h.view);
81        if dominated {
82            self.highest_qc = Some(qc.clone());
83        }
84    }
85
86    pub fn update_locked_qc(&mut self, qc: &QuorumCertificate) {
87        let dominated = self.locked_qc.as_ref().is_none_or(|h| qc.view > h.view);
88        if dominated {
89            self.locked_qc = Some(qc.clone());
90        }
91    }
92}