Skip to main content

hotmint_types/
block.rs

1use serde::{Deserialize, Serialize};
2use std::fmt;
3
4use crate::evidence::EquivocationProof;
5use crate::validator::ValidatorId;
6use crate::view::ViewNumber;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
9pub struct BlockHash(pub [u8; 32]);
10
11impl BlockHash {
12    pub const GENESIS: Self = Self([0u8; 32]);
13
14    pub fn is_genesis(&self) -> bool {
15        self.0 == [0u8; 32]
16    }
17}
18
19impl fmt::Display for BlockHash {
20    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
21        write!(f, "{}", hex::encode(&self.0[..4]))
22    }
23}
24
25#[derive(
26    Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, Default,
27)]
28pub struct Height(pub u64);
29
30impl Height {
31    pub const GENESIS: Self = Self(0);
32
33    pub fn next(self) -> Self {
34        Self(self.0.checked_add(1).expect("height overflow"))
35    }
36
37    pub fn as_u64(self) -> u64 {
38        self.0
39    }
40}
41
42impl fmt::Display for Height {
43    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
44        write!(f, "h{}", self.0)
45    }
46}
47
48impl From<u64> for Height {
49    fn from(v: u64) -> Self {
50        Self(v)
51    }
52}
53
54/// Block B_k := (b_k, h_{k-1})
55#[derive(Debug, Clone, Serialize, Deserialize)]
56pub struct Block {
57    pub height: Height,
58    pub parent_hash: BlockHash,
59    pub view: ViewNumber,
60    pub proposer: ValidatorId,
61    /// Unix timestamp in milliseconds, set by the proposer.
62    ///
63    /// Validators verify that `timestamp >= parent.timestamp` and that
64    /// it is within a reasonable drift window of the local clock.
65    #[serde(default)]
66    pub timestamp: u64,
67    pub payload: Vec<u8>,
68    /// Application state root after executing the **parent** block.
69    pub app_hash: BlockHash,
70    /// Equivocation evidence collected by the proposer (C-3).
71    #[serde(default)]
72    pub evidence: Vec<EquivocationProof>,
73    pub hash: BlockHash,
74}
75
76impl Block {
77    pub fn genesis() -> Self {
78        Self {
79            height: Height::GENESIS,
80            parent_hash: BlockHash::GENESIS,
81            view: ViewNumber::GENESIS,
82            proposer: ValidatorId::default(),
83            timestamp: 0,
84            payload: Vec::new(),
85            app_hash: BlockHash::GENESIS,
86            evidence: Vec::new(),
87            hash: BlockHash::GENESIS,
88        }
89    }
90
91    /// Compute the Blake3 hash of this block's content and return it.
92    pub fn compute_hash(&self) -> BlockHash {
93        let mut hasher = blake3::Hasher::new();
94        hasher.update(&self.height.as_u64().to_le_bytes());
95        hasher.update(&self.parent_hash.0);
96        hasher.update(&self.view.as_u64().to_le_bytes());
97        hasher.update(&self.proposer.0.to_le_bytes());
98        hasher.update(&self.timestamp.to_le_bytes());
99        hasher.update(&self.app_hash.0);
100        hasher.update(&(self.evidence.len() as u64).to_le_bytes());
101        for ev in &self.evidence {
102            hasher.update(&ev.validator.0.to_le_bytes());
103            hasher.update(&ev.view.as_u64().to_le_bytes());
104            hasher.update(&[match ev.vote_type {
105                crate::vote::VoteType::Vote => 0u8,
106                crate::vote::VoteType::Vote2 => 1u8,
107            }]);
108            hasher.update(&ev.epoch.as_u64().to_le_bytes());
109            hasher.update(&ev.block_hash_a.0);
110            hasher.update(&ev.signature_a.0);
111            hasher.update(&ev.block_hash_b.0);
112            hasher.update(&ev.signature_b.0);
113        }
114        hasher.update(&self.payload);
115        let hash = hasher.finalize();
116        BlockHash(*hash.as_bytes())
117    }
118}
119
120mod hex {
121    pub fn encode(bytes: &[u8]) -> String {
122        bytes.iter().map(|b| format!("{:02x}", b)).collect()
123    }
124}
125
126#[cfg(test)]
127mod tests {
128    use super::*;
129
130    #[test]
131    fn test_block_hash_genesis() {
132        assert!(BlockHash::GENESIS.is_genesis());
133        assert!(!BlockHash([1u8; 32]).is_genesis());
134    }
135
136    #[test]
137    fn test_block_hash_display() {
138        let h = BlockHash([
139            0xab, 0xcd, 0xef, 0x12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
140            0, 0, 0, 0, 0, 0, 0,
141        ]);
142        assert_eq!(format!("{h}"), "abcdef12");
143    }
144
145    #[test]
146    fn test_height_next() {
147        assert_eq!(Height(0).next(), Height(1));
148        assert_eq!(Height(99).next(), Height(100));
149    }
150
151    #[test]
152    fn test_height_ordering() {
153        assert!(Height(1) < Height(2));
154        assert!(Height(5) > Height(3));
155        assert!(Height(0) <= Height::GENESIS);
156    }
157
158    #[test]
159    fn test_genesis_block() {
160        let g = Block::genesis();
161        assert_eq!(g.height, Height::GENESIS);
162        assert!(g.parent_hash.is_genesis());
163        assert!(g.hash.is_genesis());
164        assert!(g.payload.is_empty());
165    }
166}