Skip to main content

jam_primitives/
header.rs

1//! # JAM Protocol Header Structures
2//!
3//! Block header definitions for the JAM protocol.
4//! Based on the Gray Paper specification for JAM block headers.
5
6use crate::utils::codec::{Decode, Encode};
7use crate::{
8    Hashable, PrimitiveError, PrimitiveResult, Validate,
9    types::{AccountId, BlockNumber, Hash, Timestamp},
10};
11use serde::{Deserialize, Serialize};
12
13/// JAM block header
14#[derive(Debug, Clone, PartialEq, Eq, Encode, Decode, Serialize, Deserialize)]
15pub struct Header {
16    /// Hash of the parent block
17    pub parent_hash: Hash,
18    /// Block number (height in the chain)
19    pub number: BlockNumber,
20    /// Block timestamp (milliseconds since Unix epoch)
21    pub timestamp: Timestamp,
22    /// State root hash after applying this block
23    pub state_root: Hash,
24    /// Hash of extrinsics/transactions root
25    pub extrinsics_root: Hash,
26    /// Additional header data and proofs
27    pub digest: Vec<Vec<u8>>,
28    /// Block author
29    pub author: AccountId,
30    /// Current epoch
31    pub epoch: u32,
32    /// Slot within epoch
33    pub slot: u32,
34}
35
36impl Header {
37    /// Create a new header
38    pub fn new(
39        parent_hash: Hash,
40        number: BlockNumber,
41        timestamp: Timestamp,
42        state_root: Hash,
43        extrinsics_root: Hash,
44        author: AccountId,
45        epoch: u32,
46        slot: u32,
47    ) -> Self {
48        Self {
49            parent_hash,
50            number,
51            timestamp,
52            state_root,
53            extrinsics_root,
54            digest: Vec::new(),
55            author,
56            epoch,
57            slot,
58        }
59    }
60
61    /// Check if this is a genesis block header
62    pub fn is_genesis(&self) -> bool {
63        self.number == BlockNumber(0)
64    }
65
66    /// Get encoded size of the header
67    pub fn encoded_size(&self) -> usize {
68        self.encode().len()
69    }
70
71    /// Verify the header is well-formed
72    pub fn verify_basic(&self) -> PrimitiveResult<()> {
73        // Genesis block should have zero parent hash
74        if self.is_genesis() && !self.parent_hash.is_zero() {
75            return Err(PrimitiveError::InvalidHeader(
76                "Genesis block must have zero parent hash".to_string(),
77            ));
78        }
79
80        // Non-genesis blocks should have non-zero parent hash
81        if !self.is_genesis() && self.parent_hash.is_zero() {
82            return Err(PrimitiveError::InvalidHeader(
83                "Non-genesis block cannot have zero parent hash".to_string(),
84            ));
85        }
86
87        // Validate timestamp (should be reasonable)
88        if self.timestamp == Timestamp(0) {
89            return Err(PrimitiveError::InvalidHeader(
90                "Header timestamp cannot be zero".to_string(),
91            ));
92        }
93
94        // Basic digest validation
95        if self.digest.len() > 100 {
96            return Err(PrimitiveError::InvalidHeader(
97                "Too many digest items".to_string(),
98            ));
99        }
100
101        Ok(())
102    }
103
104    /// Verify header against parent
105    pub fn verify_against_parent(&self, parent: &Header) -> PrimitiveResult<()> {
106        // Check parent hash matches
107        if self.parent_hash != parent.hash() {
108            return Err(PrimitiveError::InvalidHeader(
109                "Parent hash mismatch".to_string(),
110            ));
111        }
112
113        // Check block number is sequential
114        if self.number != parent.number + 1 {
115            return Err(PrimitiveError::InvalidHeader(
116                "Block number is not sequential".to_string(),
117            ));
118        }
119
120        // Check timestamp is advancing
121        if self.timestamp <= parent.timestamp {
122            return Err(PrimitiveError::InvalidHeader(
123                "Block timestamp must advance".to_string(),
124            ));
125        }
126
127        // Check timestamp is not too far in the future (basic sanity check)
128        let now = std::time::SystemTime::now()
129            .duration_since(std::time::UNIX_EPOCH)
130            .unwrap_or_default()
131            .as_millis() as u64;
132
133        if self.timestamp > Timestamp(now + 60_000) {
134            // 1 minute in the future
135            return Err(PrimitiveError::InvalidHeader(
136                "Block timestamp too far in the future".to_string(),
137            ));
138        }
139
140        Ok(())
141    }
142}
143
144impl Validate for Header {
145    fn validate(&self) -> PrimitiveResult<()> {
146        self.verify_basic()
147    }
148}
149
150impl Hashable for Header {
151    fn hash(&self) -> Hash {
152        use crate::crypto::hashing::{Blake3Hasher, Hasher};
153        let hasher = Blake3Hasher::new();
154        hasher.hash(&self.encode())
155    }
156}
157
158/// Header digest item
159#[derive(Debug, Clone, PartialEq, Eq, Encode, Decode, Serialize, Deserialize)]
160pub struct HeaderDigest {
161    /// Engine identifier
162    pub engine_id: [u8; 4],
163    /// Digest data
164    pub data: Vec<u8>,
165}
166
167impl HeaderDigest {
168    /// Create new digest item
169    pub fn new(engine_id: [u8; 4], data: Vec<u8>) -> Self {
170        Self { engine_id, data }
171    }
172}
173
174#[cfg(test)]
175mod tests {
176    use super::*;
177
178    fn dummy_account() -> AccountId {
179        AccountId::new([1u8; 32])
180    }
181
182    #[test]
183    fn test_header_creation() {
184        let header = Header::new(
185            Hash::zero(),
186            BlockNumber(1),
187            Timestamp(1000),
188            Hash::zero(),
189            Hash::zero(),
190            dummy_account(),
191            0,
192            1,
193        );
194
195        assert_eq!(header.number, BlockNumber(1));
196        assert_eq!(header.timestamp, Timestamp(1000));
197        assert!(!header.is_genesis());
198    }
199
200    #[test]
201    fn test_genesis_header() {
202        let genesis = Header::new(
203            Hash::zero(),
204            BlockNumber(0),
205            Timestamp(1000),
206            Hash::zero(),
207            Hash::zero(),
208            dummy_account(),
209            0,
210            0,
211        );
212
213        assert!(genesis.is_genesis());
214        assert!(genesis.verify_basic().is_ok());
215    }
216
217    #[test]
218    fn test_header_validation() {
219        // Valid non-genesis header
220        let parent_hash = Hash::from([1u8; 32]);
221        let header = Header::new(
222            parent_hash,
223            BlockNumber(1),
224            Timestamp(1000),
225            Hash::zero(),
226            Hash::zero(),
227            dummy_account(),
228            0,
229            1,
230        );
231
232        assert!(header.verify_basic().is_ok());
233
234        // Invalid: non-genesis with zero parent hash
235        let invalid_header = Header::new(
236            Hash::zero(),
237            BlockNumber(1),
238            Timestamp(1000),
239            Hash::zero(),
240            Hash::zero(),
241            dummy_account(),
242            0,
243            1,
244        );
245
246        assert!(invalid_header.verify_basic().is_err());
247
248        // Invalid: zero timestamp
249        let zero_time_header = Header::new(
250            parent_hash,
251            BlockNumber(1),
252            Timestamp(0),
253            Hash::zero(),
254            Hash::zero(),
255            dummy_account(),
256            0,
257            0,
258        );
259        assert!(zero_time_header.verify_basic().is_err());
260    }
261
262    #[test]
263    fn test_header_parent_verification() {
264        let parent = Header::new(
265            Hash::zero(),
266            BlockNumber(0),
267            Timestamp(1000),
268            Hash::zero(),
269            Hash::zero(),
270            dummy_account(),
271            0,
272            0,
273        );
274
275        let child = Header::new(
276            parent.hash(),
277            BlockNumber(1),
278            Timestamp(2000),
279            Hash::zero(),
280            Hash::zero(),
281            dummy_account(),
282            0,
283            1,
284        );
285
286        assert!(child.verify_against_parent(&parent).is_ok());
287
288        // Invalid: wrong parent hash
289        let wrong_parent = Header::new(
290            Hash::from([1u8; 32]),
291            BlockNumber(1),
292            Timestamp(2000),
293            Hash::zero(),
294            Hash::zero(),
295            dummy_account(),
296            0,
297            1,
298        );
299
300        assert!(wrong_parent.verify_against_parent(&parent).is_err());
301
302        // Invalid: wrong block number
303        let wrong_number = Header::new(
304            parent.hash(),
305            BlockNumber(3),
306            Timestamp(2000),
307            Hash::zero(),
308            Hash::zero(),
309            dummy_account(),
310            0,
311            3,
312        );
313
314        assert!(wrong_number.verify_against_parent(&parent).is_err());
315
316        // Invalid: timestamp not advancing
317        let same_time = Header::new(
318            parent.hash(),
319            BlockNumber(1),
320            Timestamp(1000),
321            Hash::zero(),
322            Hash::zero(),
323            dummy_account(),
324            0,
325            1,
326        );
327
328        assert!(same_time.verify_against_parent(&parent).is_err());
329    }
330
331    #[test]
332    fn test_header_digest() {
333        let digest = HeaderDigest::new(*b"TEST", vec![1, 2, 3]);
334        assert_eq!(digest.engine_id, *b"TEST");
335        assert_eq!(digest.data, vec![1, 2, 3]);
336    }
337}