1use 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#[derive(Debug, Clone, PartialEq, Eq, Encode, Decode, Serialize, Deserialize)]
15pub struct Header {
16 pub parent_hash: Hash,
18 pub number: BlockNumber,
20 pub timestamp: Timestamp,
22 pub state_root: Hash,
24 pub extrinsics_root: Hash,
26 pub digest: Vec<Vec<u8>>,
28 pub author: AccountId,
30 pub epoch: u32,
32 pub slot: u32,
34}
35
36impl Header {
37 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 pub fn is_genesis(&self) -> bool {
63 self.number == BlockNumber(0)
64 }
65
66 pub fn encoded_size(&self) -> usize {
68 self.encode().len()
69 }
70
71 pub fn verify_basic(&self) -> PrimitiveResult<()> {
73 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 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 if self.timestamp == Timestamp(0) {
89 return Err(PrimitiveError::InvalidHeader(
90 "Header timestamp cannot be zero".to_string(),
91 ));
92 }
93
94 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 pub fn verify_against_parent(&self, parent: &Header) -> PrimitiveResult<()> {
106 if self.parent_hash != parent.hash() {
108 return Err(PrimitiveError::InvalidHeader(
109 "Parent hash mismatch".to_string(),
110 ));
111 }
112
113 if self.number != parent.number + 1 {
115 return Err(PrimitiveError::InvalidHeader(
116 "Block number is not sequential".to_string(),
117 ));
118 }
119
120 if self.timestamp <= parent.timestamp {
122 return Err(PrimitiveError::InvalidHeader(
123 "Block timestamp must advance".to_string(),
124 ));
125 }
126
127 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 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#[derive(Debug, Clone, PartialEq, Eq, Encode, Decode, Serialize, Deserialize)]
160pub struct HeaderDigest {
161 pub engine_id: [u8; 4],
163 pub data: Vec<u8>,
165}
166
167impl HeaderDigest {
168 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 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 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 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 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 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 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}