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 + 1)
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    pub hash: BlockHash,
62}
63
64impl Block {
65    pub fn genesis() -> Self {
66        Self {
67            height: Height::GENESIS,
68            parent_hash: BlockHash::GENESIS,
69            view: ViewNumber::GENESIS,
70            proposer: ValidatorId::default(),
71            payload: Vec::new(),
72            hash: BlockHash::GENESIS,
73        }
74    }
75
76    /// Compute the Blake3 hash of this block's content and return it.
77    ///
78    /// This hashes `height || parent_hash || view || proposer || payload`
79    /// (excluding the `hash` field itself).
80    pub fn compute_hash(&self) -> BlockHash {
81        let mut hasher = blake3::Hasher::new();
82        hasher.update(&self.height.as_u64().to_le_bytes());
83        hasher.update(&self.parent_hash.0);
84        hasher.update(&self.view.as_u64().to_le_bytes());
85        hasher.update(&self.proposer.0.to_le_bytes());
86        hasher.update(&self.payload);
87        let hash = hasher.finalize();
88        BlockHash(*hash.as_bytes())
89    }
90}
91
92mod hex {
93    pub fn encode(bytes: &[u8]) -> String {
94        bytes.iter().map(|b| format!("{:02x}", b)).collect()
95    }
96}
97
98#[cfg(test)]
99mod tests {
100    use super::*;
101
102    #[test]
103    fn test_block_hash_genesis() {
104        assert!(BlockHash::GENESIS.is_genesis());
105        assert!(!BlockHash([1u8; 32]).is_genesis());
106    }
107
108    #[test]
109    fn test_block_hash_display() {
110        let h = BlockHash([
111            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,
112            0, 0, 0, 0, 0, 0, 0,
113        ]);
114        assert_eq!(format!("{h}"), "abcdef12");
115    }
116
117    #[test]
118    fn test_height_next() {
119        assert_eq!(Height(0).next(), Height(1));
120        assert_eq!(Height(99).next(), Height(100));
121    }
122
123    #[test]
124    fn test_height_ordering() {
125        assert!(Height(1) < Height(2));
126        assert!(Height(5) > Height(3));
127        assert!(Height(0) <= Height::GENESIS);
128    }
129
130    #[test]
131    fn test_genesis_block() {
132        let g = Block::genesis();
133        assert_eq!(g.height, Height::GENESIS);
134        assert!(g.parent_hash.is_genesis());
135        assert!(g.hash.is_genesis());
136        assert!(g.payload.is_empty());
137    }
138}