Skip to main content

hotmint_types/
block.rs

1use serde::{Deserialize, Serialize};
2use std::fmt;
3
4use crate::validator::ValidatorId;
5use crate::view::ViewNumber;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
8pub struct BlockHash(pub [u8; 32]);
9
10impl BlockHash {
11    pub const GENESIS: Self = Self([0u8; 32]);
12
13    pub fn is_genesis(&self) -> bool {
14        self.0 == [0u8; 32]
15    }
16}
17
18impl fmt::Display for BlockHash {
19    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
20        write!(f, "{}", hex::encode(&self.0[..4]))
21    }
22}
23
24#[derive(
25    Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, Default,
26)]
27pub struct Height(pub u64);
28
29impl Height {
30    pub const GENESIS: Self = Self(0);
31
32    pub fn next(self) -> Self {
33        Self(self.0.checked_add(1).expect("height overflow"))
34    }
35
36    pub fn as_u64(self) -> u64 {
37        self.0
38    }
39}
40
41impl fmt::Display for Height {
42    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
43        write!(f, "h{}", self.0)
44    }
45}
46
47impl From<u64> for Height {
48    fn from(v: u64) -> Self {
49        Self(v)
50    }
51}
52
53/// Block B_k := (b_k, h_{k-1})
54#[derive(Debug, Clone, Serialize, Deserialize)]
55pub struct Block {
56    pub height: Height,
57    pub parent_hash: BlockHash,
58    pub view: ViewNumber,
59    pub proposer: ValidatorId,
60    pub payload: Vec<u8>,
61    /// Application state root after executing the **parent** block.
62    ///
63    /// Block N+1 carries the `app_hash` produced by `execute_block(Block N)`.
64    /// This ties the state transition chain to the block chain, allowing nodes
65    /// to detect divergent application state before voting.
66    pub app_hash: BlockHash,
67    pub hash: BlockHash,
68}
69
70impl Block {
71    pub fn genesis() -> Self {
72        Self {
73            height: Height::GENESIS,
74            parent_hash: BlockHash::GENESIS,
75            view: ViewNumber::GENESIS,
76            proposer: ValidatorId::default(),
77            payload: Vec::new(),
78            app_hash: BlockHash::GENESIS,
79            hash: BlockHash::GENESIS,
80        }
81    }
82
83    /// Compute the Blake3 hash of this block's content and return it.
84    ///
85    /// This hashes `height || parent_hash || view || proposer || app_hash || payload`
86    /// (excluding the `hash` field itself).
87    pub fn compute_hash(&self) -> BlockHash {
88        let mut hasher = blake3::Hasher::new();
89        hasher.update(&self.height.as_u64().to_le_bytes());
90        hasher.update(&self.parent_hash.0);
91        hasher.update(&self.view.as_u64().to_le_bytes());
92        hasher.update(&self.proposer.0.to_le_bytes());
93        hasher.update(&self.app_hash.0);
94        hasher.update(&self.payload);
95        let hash = hasher.finalize();
96        BlockHash(*hash.as_bytes())
97    }
98}
99
100mod hex {
101    pub fn encode(bytes: &[u8]) -> String {
102        bytes.iter().map(|b| format!("{:02x}", b)).collect()
103    }
104}
105
106#[cfg(test)]
107mod tests {
108    use super::*;
109
110    #[test]
111    fn test_block_hash_genesis() {
112        assert!(BlockHash::GENESIS.is_genesis());
113        assert!(!BlockHash([1u8; 32]).is_genesis());
114    }
115
116    #[test]
117    fn test_block_hash_display() {
118        let h = BlockHash([
119            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,
120            0, 0, 0, 0, 0, 0, 0,
121        ]);
122        assert_eq!(format!("{h}"), "abcdef12");
123    }
124
125    #[test]
126    fn test_height_next() {
127        assert_eq!(Height(0).next(), Height(1));
128        assert_eq!(Height(99).next(), Height(100));
129    }
130
131    #[test]
132    fn test_height_ordering() {
133        assert!(Height(1) < Height(2));
134        assert!(Height(5) > Height(3));
135        assert!(Height(0) <= Height::GENESIS);
136    }
137
138    #[test]
139    fn test_genesis_block() {
140        let g = Block::genesis();
141        assert_eq!(g.height, Height::GENESIS);
142        assert!(g.parent_hash.is_genesis());
143        assert!(g.hash.is_genesis());
144        assert!(g.payload.is_empty());
145    }
146}