massa_models/
block_header.rs

1use crate::block_id::{BlockId, BlockIdDeserializer, BlockIdSerializer};
2use crate::denunciation::{Denunciation, DenunciationDeserializer, DenunciationSerializer};
3use crate::endorsement::{
4    Endorsement, EndorsementDeserializerLW, EndorsementId, EndorsementSerializer,
5    EndorsementSerializerLW, SecureShareEndorsement,
6};
7use crate::secure_share::{
8    SecureShare, SecureShareContent, SecureShareDeserializer, SecureShareSerializer,
9};
10use crate::slot::{Slot, SlotDeserializer, SlotSerializer};
11use massa_hash::{Hash, HashDeserializer};
12use massa_serialization::{
13    Deserializer, OptionDeserializer, OptionSerializer, SerializeError, Serializer,
14    U32VarIntDeserializer, U32VarIntSerializer,
15};
16use massa_signature::PublicKey;
17use nom::branch::alt;
18use nom::bytes::complete::tag;
19use nom::error::{context, ContextError, ParseError};
20use nom::multi::{count, length_count};
21use nom::sequence::{preceded, tuple};
22use nom::{IResult, Parser};
23use serde::{Deserialize, Serialize};
24use std::collections::Bound::{Excluded, Included};
25use std::collections::HashSet;
26use std::fmt::Formatter;
27
28/// block header
29#[derive(Debug, Clone, Serialize, Deserialize)]
30pub struct BlockHeader {
31    /// current network version
32    pub current_version: u32,
33    /// announced network version
34    pub announced_version: Option<u32>,
35    /// slot
36    pub slot: Slot,
37    /// parents
38    pub parents: Vec<BlockId>,
39    /// all operations hash
40    pub operation_merkle_root: Hash,
41    /// endorsements
42    pub endorsements: Vec<SecureShareEndorsement>,
43    /// denunciations
44    pub denunciations: Vec<Denunciation>,
45}
46
47// TODO: gh-issue #3398
48#[cfg(any(test, feature = "test-exports"))]
49impl BlockHeader {
50    /// This is an intentional duplication of invariant checks. In production code,
51    /// these checks are dispersed throughout the deserialization process. This test-only function
52    /// allows all the checks to be in one place.
53    fn assert_invariants(
54        &self,
55        thread_count: u8,
56        endorsement_count: u32,
57    ) -> Result<(), Box<dyn std::error::Error>> {
58        if self.slot.period == 0 {
59            if !self.parents.is_empty() {
60                return Err("Invariant broken: genesis block with parent(s)".into());
61            }
62            if !self.endorsements.is_empty() {
63                return Err("Invariant broken: genesis block with endorsement(s)".into());
64            }
65        } else {
66            if self.parents.len() != thread_count as usize {
67                return Err(
68                    "Invariant broken: non-genesis block with incorrect number of parents".into(),
69                );
70            }
71            if self.endorsements.len() > endorsement_count as usize {
72                return Err("Invariant broken: endorsement count too high".into());
73            }
74
75            let parent_id = self.parents[self.slot.thread as usize];
76            for endo in self.endorsements.iter() {
77                if endo.content.endorsed_block != parent_id {
78                    return Err("Invariant broken: endorsement doesn't match parent".into());
79                }
80            }
81        }
82
83        // assert that the endorsement indexes are all unique...
84        let mut set = HashSet::new();
85        for endo in self.endorsements.iter() {
86            if !set.insert(endo.content.index) {
87                return Err("Endorsement duplicate index found".into());
88            }
89        }
90
91        for de in self.denunciations.iter() {
92            de.check_invariants()?;
93        }
94
95        Ok(())
96    }
97}
98
99/// BlockHeader wrapped up alongside verification data
100pub type SecuredHeader = SecureShare<BlockHeader, BlockId>;
101
102impl SecureShareContent for BlockHeader {
103    /// Compute the signed hash. `sig_chain_id` selects the layout:
104    /// * `None` -> legacy (chain-agnostic) layout, byte-for-byte identical to the
105    ///   pre-fix layout (used pre MIP-0002 activation).
106    /// * `Some(chain_id)` -> chain-scoped layout: `chain_id` is folded into the
107    ///   signed hash so the same block header signed on two different chains
108    ///   produces two different signed hashes. This prevents cross-chain replay
109    ///   of block headers as denunciations (F90 / PDF #11).
110    fn compute_signed_hash(
111        &self,
112        public_key: &PublicKey,
113        content_hash: &Hash,
114        sig_chain_id: Option<u64>,
115    ) -> Hash {
116        let mut signed_data: Vec<u8> = Vec::new();
117        signed_data.extend(public_key.to_bytes());
118        signed_data.extend(BlockHeaderDenunciationData::new(self.slot, sig_chain_id).to_bytes());
119        signed_data.extend(content_hash.to_bytes());
120        Hash::compute_from(&signed_data)
121    }
122}
123
124impl SecuredHeader {
125    /// gets the header fitness
126    pub fn get_fitness(&self) -> u64 {
127        (self.content.endorsements.len() as u64) + 1
128    }
129    // TODO: gh-issue #3398
130    #[allow(dead_code)]
131    #[cfg(any(test, feature = "test-exports"))]
132    pub(crate) fn assert_invariants(
133        &self,
134        thread_count: u8,
135        endorsement_count: u32,
136    ) -> Result<(), Box<dyn std::error::Error>> {
137        self.content
138            .assert_invariants(thread_count, endorsement_count)?;
139        self.verify_signature(Some(*crate::config::CHAINID))
140            .map_err(|er| format!("{}", er).into())
141    }
142}
143
144/// Serializer for `BlockHeader`
145pub struct BlockHeaderSerializer {
146    slot_serializer: SlotSerializer,
147    endorsement_serializer: SecureShareSerializer,
148    endorsement_content_serializer: EndorsementSerializerLW,
149    denunciation_serializer: DenunciationSerializer,
150    u32_serializer: U32VarIntSerializer,
151    opt_serializer: OptionSerializer<u32, U32VarIntSerializer>,
152    block_id_serializer: BlockIdSerializer,
153}
154
155impl BlockHeaderSerializer {
156    /// Creates a new `BlockHeaderSerializer`
157    pub fn new() -> Self {
158        Self {
159            slot_serializer: SlotSerializer::new(),
160            endorsement_serializer: SecureShareSerializer::new(),
161            u32_serializer: U32VarIntSerializer::new(),
162            opt_serializer: OptionSerializer::new(U32VarIntSerializer),
163            endorsement_content_serializer: EndorsementSerializerLW::new(),
164            denunciation_serializer: DenunciationSerializer::new(),
165            block_id_serializer: BlockIdSerializer::new(),
166        }
167    }
168}
169
170impl Default for BlockHeaderSerializer {
171    fn default() -> Self {
172        Self::new()
173    }
174}
175
176impl Serializer<BlockHeader> for BlockHeaderSerializer {
177    /// ## Example:
178    /// ```rust
179    /// use massa_models::{block_id::BlockId, block_header::BlockHeader, block_header::BlockHeaderSerializer};
180    /// use massa_models::endorsement::{Endorsement, EndorsementSerializer};
181    /// use massa_models::secure_share::SecureShareContent;
182    /// use massa_models::{config::THREAD_COUNT, slot::Slot};
183    /// use massa_hash::Hash;
184    /// use massa_models::config::CHAINID;
185    /// use massa_signature::KeyPair;
186    /// use massa_serialization::Serializer;
187    ///
188    /// let keypair = KeyPair::generate(0).unwrap();
189    /// let parents = (0..THREAD_COUNT)
190    ///   .map(|i| BlockId::generate_from_hash(Hash::compute_from(&[i])))
191    ///   .collect();
192    /// let header = BlockHeader {
193    ///   current_version: 0,
194    ///   announced_version: None,
195    ///   slot: Slot::new(1, 1),
196    ///   parents,
197    ///   operation_merkle_root: Hash::compute_from("mno".as_bytes()),
198    ///   endorsements: vec![
199    ///     Endorsement::new_verifiable(
200    ///        Endorsement {
201    ///          slot: Slot::new(1, 1),
202    ///          index: 1,
203    ///          endorsed_block: BlockId::generate_from_hash(Hash::compute_from("blk1".as_bytes())),
204    ///        },
205    ///     EndorsementSerializer::new(),
206    ///     &keypair,
207    ///     *CHAINID,
208    ///     None
209    ///     )
210    ///     .unwrap(),
211    ///     Endorsement::new_verifiable(
212    ///       Endorsement {
213    ///         slot: Slot::new(4, 0),
214    ///         index: 3,
215    ///         endorsed_block: BlockId::generate_from_hash(Hash::compute_from("blk2".as_bytes())),
216    ///       },
217    ///     EndorsementSerializer::new(),
218    ///     &keypair,
219    ///     *CHAINID,
220    ///     None
221    ///     )
222    ///     .unwrap(),
223    ///    ],
224    ///   denunciations: vec![],
225    /// };
226    /// let mut buffer = vec![];
227    /// BlockHeaderSerializer::new().serialize(&header, &mut buffer).unwrap();
228    /// ```
229    fn serialize(&self, value: &BlockHeader, buffer: &mut Vec<u8>) -> Result<(), SerializeError> {
230        // network versions
231        self.u32_serializer
232            .serialize(&value.current_version, buffer)?;
233        self.opt_serializer
234            .serialize(&value.announced_version, buffer)?;
235
236        // slot
237        self.slot_serializer.serialize(&value.slot, buffer)?;
238
239        // parents (note: there should be none if slot period=0)
240        if value.parents.is_empty() {
241            buffer.push(0);
242        } else {
243            buffer.push(1);
244        }
245        for parent_h in value.parents.iter() {
246            self.block_id_serializer.serialize(parent_h, buffer)?;
247        }
248
249        // operations merkle root
250        buffer.extend(value.operation_merkle_root.to_bytes());
251
252        self.u32_serializer.serialize(
253            &value.endorsements.len().try_into().map_err(|err| {
254                SerializeError::GeneralError(format!("too many endorsements: {}", err))
255            })?,
256            buffer,
257        )?;
258
259        for endorsement in value.endorsements.iter() {
260            self.endorsement_serializer.serialize_with(
261                &self.endorsement_content_serializer,
262                endorsement,
263                buffer,
264            )?;
265        }
266        self.u32_serializer.serialize(
267            &value.denunciations.len().try_into().map_err(|err| {
268                SerializeError::GeneralError(format!("too many denunciations: {}", err))
269            })?,
270            buffer,
271        )?;
272        for denunciation in value.denunciations.iter() {
273            self.denunciation_serializer
274                .serialize(denunciation, buffer)?;
275        }
276
277        Ok(())
278    }
279}
280
281/// Deserializer for `BlockHeader`
282pub struct BlockHeaderDeserializer {
283    slot_deserializer: SlotDeserializer,
284    endorsement_serializer: EndorsementSerializer,
285    endorsement_len_deserializer: U32VarIntDeserializer,
286    hash_deserializer: HashDeserializer,
287    thread_count: u8,
288    endorsement_count: u32,
289    last_start_period: Option<u64>,
290    denunciation_len_deserializer: U32VarIntDeserializer,
291    denunciation_deserializer: DenunciationDeserializer,
292    network_versions_deserializer: U32VarIntDeserializer,
293    opt_deserializer: OptionDeserializer<u32, U32VarIntDeserializer>,
294    block_id_deserializer: BlockIdDeserializer,
295    chain_id: u64,
296}
297
298impl BlockHeaderDeserializer {
299    /// Creates a new `BlockHeaderDeserializer`
300    /// If last_start_period is Some(lsp), then the deserializer will check for valid (non)-genesis blocks
301    pub fn new(
302        thread_count: u8,
303        endorsement_count: u32,
304        max_denunciations_in_block_header: u32,
305        last_start_period: Option<u64>,
306        chain_id: u64,
307    ) -> Self {
308        Self {
309            slot_deserializer: SlotDeserializer::new(
310                (Included(0), Included(u64::MAX)),
311                (Included(0), Excluded(thread_count)),
312            ),
313            endorsement_serializer: EndorsementSerializer::new(),
314            endorsement_len_deserializer: U32VarIntDeserializer::new(
315                Included(0),
316                Included(endorsement_count),
317            ),
318            hash_deserializer: HashDeserializer::new(),
319            denunciation_len_deserializer: U32VarIntDeserializer::new(
320                Included(0),
321                Included(max_denunciations_in_block_header),
322            ),
323            network_versions_deserializer: U32VarIntDeserializer::new(
324                Included(0),
325                Included(u32::MAX),
326            ),
327            opt_deserializer: OptionDeserializer::new(U32VarIntDeserializer::new(
328                Included(0),
329                Included(u32::MAX),
330            )),
331            denunciation_deserializer: DenunciationDeserializer::new(
332                thread_count,
333                endorsement_count,
334            ),
335            block_id_deserializer: BlockIdDeserializer::new(),
336            thread_count,
337            endorsement_count,
338            last_start_period,
339            chain_id,
340        }
341    }
342}
343
344impl Deserializer<BlockHeader> for BlockHeaderDeserializer {
345    /// ## Example:
346    /// ```rust
347    /// use massa_models::block_header::{BlockHeader, BlockHeaderDeserializer, BlockHeaderSerializer};
348    /// use massa_models::block_id::{BlockId};
349    /// use massa_models::{config::THREAD_COUNT, slot::Slot, secure_share::SecureShareContent};
350    /// use massa_models::endorsement::{Endorsement, EndorsementSerializer};
351    /// use massa_hash::Hash;
352    /// use massa_models::config::CHAINID;
353    /// use massa_signature::KeyPair;
354    /// use massa_serialization::{Serializer, Deserializer, DeserializeError};
355    ///
356    /// let keypair = KeyPair::generate(0).unwrap();
357    /// let parents: Vec<BlockId> = (0..THREAD_COUNT)
358    ///   .map(|i| BlockId::generate_from_hash(Hash::compute_from(&[i])))
359    ///   .collect();
360    /// let header = BlockHeader {
361    ///   current_version: 0,
362    ///   announced_version: None,
363    ///   slot: Slot::new(1, 1),
364    ///   parents: parents.clone(),
365    ///   operation_merkle_root: Hash::compute_from("mno".as_bytes()),
366    ///   endorsements: vec![
367    ///     Endorsement::new_verifiable(
368    ///        Endorsement {
369    ///          slot: Slot::new(1, 1),
370    ///          index: 0,
371    ///          endorsed_block: parents[1].clone(),
372    ///        },
373    ///     EndorsementSerializer::new(),
374    ///     &keypair,
375    ///     *CHAINID,
376    ///     None
377    ///     )
378    ///     .unwrap(),
379    ///     Endorsement::new_verifiable(
380    ///       Endorsement {
381    ///         slot: Slot::new(1, 1),
382    ///         index: 1,
383    ///         endorsed_block: parents[1].clone(),
384    ///       },
385    ///     EndorsementSerializer::new(),
386    ///     &keypair,
387    ///     *CHAINID,
388    ///     None
389    ///     )
390    ///     .unwrap(),
391    ///    ],
392    ///    denunciations: vec![],
393    /// };
394    /// let mut buffer = vec![];
395    /// BlockHeaderSerializer::new().serialize(&header, &mut buffer).unwrap();
396    /// let (rest, deserialized_header) = BlockHeaderDeserializer::new(32, 9, 10, Some(0), 77).deserialize::<DeserializeError>(&buffer).unwrap();
397    /// assert_eq!(rest.len(), 0);
398    /// let mut buffer2 = Vec::new();
399    /// BlockHeaderSerializer::new().serialize(&deserialized_header, &mut buffer2).unwrap();
400    /// assert_eq!(buffer, buffer2);
401    /// ```
402    #[allow(clippy::type_complexity)]
403    fn deserialize<'a, E: ParseError<&'a [u8]> + ContextError<&'a [u8]>>(
404        &self,
405        buffer: &'a [u8],
406    ) -> IResult<&'a [u8], BlockHeader, E> {
407        let (rest, (current_version, announced_version, slot, parents, operation_merkle_root)): (
408            &[u8],
409            (u32, Option<u32>, Slot, Vec<BlockId>, Hash),
410        ) = context("Failed BlockHeader deserialization", |input| {
411            let (rest, (current_version, announced_version, slot, parents)) = tuple((
412                context("Failed current_version deserialization", |input| {
413                    self.network_versions_deserializer.deserialize(input)
414                }),
415                context("Failed announced_version deserialization", |input| {
416                    self.opt_deserializer.deserialize(input)
417                }),
418                context("Failed slot deserialization", |input| {
419                    self.slot_deserializer.deserialize(input)
420                }),
421                context(
422                    "Failed parents deserialization",
423                    alt((
424                        preceded(tag(&[0]), |input| Ok((input, Vec::new()))),
425                        preceded(
426                            tag(&[1]),
427                            count(
428                                context("Failed block_id deserialization", |input| {
429                                    self.block_id_deserializer.deserialize(input)
430                                }),
431                                self.thread_count as usize,
432                            ),
433                        ),
434                    )),
435                ),
436            ))
437            .parse(input)?;
438
439            // validate the parent/slot invariants before moving on to other fields
440            if let Some(last_start_period) = self.last_start_period {
441                if slot.period == last_start_period && !parents.is_empty() {
442                    return Err(nom::Err::Failure(ContextError::add_context(
443                        rest,
444                        "Genesis block cannot contain parents",
445                        ParseError::from_error_kind(rest, nom::error::ErrorKind::Fail),
446                    )));
447                } else if slot.period != last_start_period
448                    && parents.len() != self.thread_count as usize
449                {
450                    return Err(nom::Err::Failure(ContextError::add_context(
451                        rest,
452                        "Non-genesis block must have same numbers of parents as threads count",
453                        ParseError::from_error_kind(rest, nom::error::ErrorKind::Fail),
454                    )));
455                }
456            }
457
458            let (rest, merkle) = context("Failed operation_merkle_root", |input| {
459                self.hash_deserializer.deserialize(input)
460            })
461            .parse(rest)?;
462            Ok((
463                rest,
464                (current_version, announced_version, slot, parents, merkle),
465            ))
466        })
467        .parse(buffer)?;
468
469        if parents.is_empty() {
470            let res = BlockHeader {
471                current_version,
472                announced_version,
473                slot,
474                parents,
475                operation_merkle_root,
476                endorsements: Vec::new(),
477                denunciations: Vec::new(),
478            };
479
480            // TODO: gh-issue #3398
481            #[cfg(any(test, feature = "test-exports"))]
482            res.assert_invariants(self.thread_count, self.endorsement_count)
483                .unwrap();
484
485            // As we have 0 endorsements & 0 denunciations, rest = [0, 0] (length 0 & length 0)
486            // As we want to return an empty "res" we use nom tag
487            let (rest2, _) = tag(&[0, 0])(rest)?;
488            return Ok((rest2, res));
489        }
490
491        // Now deser the endorsements (which were light-weight serialized)
492        let endorsement_deserializer = SecureShareDeserializer::new(
493            EndorsementDeserializerLW::new(
494                self.endorsement_count,
495                slot,
496                parents[slot.thread as usize],
497            ),
498            self.chain_id,
499        );
500
501        let parent_id = parents[slot.thread as usize];
502        let (rest, endorsements): (&[u8], Vec<SecureShare<Endorsement, EndorsementId>>) = context(
503            "Failed endorsements deserialization",
504            length_count::<&[u8], SecureShare<Endorsement, EndorsementId>, u32, E, _, _>(
505                context("Failed length deserialization", |input| {
506                    self.endorsement_len_deserializer.deserialize(input)
507                }),
508                context("Failed endorsement deserialization", |input| {
509                    let (rest, endo) = endorsement_deserializer
510                        .deserialize_with(&self.endorsement_serializer, input)?;
511
512                    if endo.content.endorsed_block != parent_id {
513                        return Err(nom::Err::Failure(ContextError::add_context(
514                            rest,
515                            "Endorsement does not match block parents",
516                            ParseError::from_error_kind(rest, nom::error::ErrorKind::Fail),
517                        )));
518                    }
519
520                    Ok((rest, endo))
521                }),
522            ),
523        )
524        .parse(rest)?;
525
526        let mut set = HashSet::new();
527        for end in endorsements.iter() {
528            if !set.insert(end.content.index) {
529                return Err(nom::Err::Failure(ContextError::add_context(
530                    rest,
531                    "Duplicate endorsement index found",
532                    ParseError::from_error_kind(rest, nom::error::ErrorKind::Fail),
533                )));
534            }
535        }
536
537        let (rest, denunciations): (&[u8], Vec<Denunciation>) = context(
538            "Failed denunciations deserialization",
539            length_count::<&[u8], Denunciation, u32, E, _, _>(
540                context("Failed length deserialization", |input| {
541                    let (res, count) = self.denunciation_len_deserializer.deserialize(input)?;
542                    IResult::Ok((res, count))
543                }),
544                context("Failed denunciation deserialization", |input| {
545                    self.denunciation_deserializer.deserialize(input)
546                }),
547            ),
548        )
549        .parse(rest)?;
550
551        let header = BlockHeader {
552            current_version,
553            announced_version,
554            slot,
555            parents,
556            operation_merkle_root,
557            endorsements,
558            denunciations,
559        };
560
561        // TODO: gh-issue #3398
562        #[cfg(any(test, feature = "test-exports"))]
563        header
564            .assert_invariants(self.thread_count, self.endorsement_count)
565            .unwrap();
566
567        Ok((rest, header))
568    }
569}
570
571impl std::fmt::Display for BlockHeader {
572    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
573        writeln!(
574            f,
575            "\t(period: {}, thread: {})",
576            self.slot.period, self.slot.thread,
577        )?;
578        writeln!(f, "\tMerkle root: {}", self.operation_merkle_root,)?;
579        writeln!(f, "\tParents: ")?;
580        for id in self.parents.iter() {
581            let str_id = id.to_string();
582            writeln!(f, "\t\t{}", str_id)?;
583        }
584        if self.parents.is_empty() {
585            writeln!(f, "No parents found: This is a genesis header")?;
586        }
587        writeln!(f, "\tEndorsements:")?;
588        for ed in self.endorsements.iter() {
589            writeln!(f, "\t\t-----")?;
590            writeln!(f, "\t\tId: {}", ed.id)?;
591            writeln!(f, "\t\tIndex: {}", ed.content.index)?;
592            writeln!(f, "\t\tEndorsed slot: {}", ed.content.slot)?;
593            writeln!(
594                f,
595                "\t\tEndorser's public key: {}",
596                ed.content_creator_pub_key
597            )?;
598            writeln!(f, "\t\tEndorsed block: {}", ed.content.endorsed_block)?;
599            writeln!(f, "\t\tSignature: {}", ed.signature)?;
600        }
601        if self.endorsements.is_empty() {
602            writeln!(f, "\tNo endorsements found")?;
603        }
604        Ok(())
605    }
606}
607
608/// A denunciation data for block header
609#[derive(Debug)]
610pub struct BlockHeaderDenunciationData {
611    slot: Slot,
612    /// `None` for the legacy (chain-agnostic) signed-hash layout.
613    /// `Some(chain_id)` for the chain-scoped layout (post MIP-0002 `Execution` v2).
614    chain_id: Option<u64>,
615}
616
617impl BlockHeaderDenunciationData {
618    /// Create a new DenunciationData for block header. Pass `None` for the legacy
619    /// layout, `Some(chain_id)` for the chain-scoped layout.
620    pub fn new(slot: Slot, chain_id: Option<u64>) -> Self {
621        Self { slot, chain_id }
622    }
623
624    /// Legacy layout: bytes are byte-for-byte identical to the pre-fix layout, so
625    /// block headers signed before MIP-0002 activation keep verifying.
626    /// Equivalent to `Self::new(slot, None)`.
627    #[allow(dead_code)]
628    pub fn new_legacy(slot: Slot) -> Self {
629        Self {
630            slot,
631            chain_id: None,
632        }
633    }
634
635    /// Get byte array
636    pub fn to_bytes(&self) -> Vec<u8> {
637        let mut buf = Vec::new();
638        buf.extend(self.slot.to_bytes_key());
639        if let Some(chain_id) = self.chain_id {
640            buf.extend(chain_id.to_be_bytes());
641        }
642        buf
643    }
644}
645
646#[cfg(test)]
647mod test {
648    use super::*;
649    use massa_serialization::DeserializeError;
650    use serde_json::Value;
651
652    use crate::config::{
653        CHAINID, ENDORSEMENT_COUNT, MAX_DENUNCIATIONS_PER_BLOCK_HEADER, THREAD_COUNT,
654    };
655
656    use crate::test_exports::{
657        gen_block_headers_for_denunciation, gen_endorsements_for_denunciation,
658    };
659    use massa_signature::{verify_signature_batch, KeyPair};
660
661    // Only for testing purpose
662    impl PartialEq for BlockHeader {
663        fn eq(&self, other: &Self) -> bool {
664            self.slot == other.slot
665                && self.parents == other.parents
666                && self.operation_merkle_root == other.operation_merkle_root
667                && self.endorsements == other.endorsements
668                && self.denunciations == other.denunciations
669        }
670    }
671
672    #[test]
673    fn test_block_header_ser_der() {
674        let keypair = KeyPair::generate(0).unwrap();
675
676        let slot = Slot::new(7, 1);
677        let parents_1: Vec<BlockId> = (0..THREAD_COUNT)
678            .map(|i| BlockId::generate_from_hash(Hash::compute_from(&[i])))
679            .collect();
680
681        let endorsement_1 = Endorsement {
682            slot,
683            index: 1,
684            endorsed_block: parents_1[1],
685        };
686
687        assert_eq!(parents_1[1], endorsement_1.endorsed_block);
688
689        let s_endorsement_1: SecureShareEndorsement = Endorsement::new_verifiable(
690            endorsement_1,
691            EndorsementSerializer::new(),
692            &keypair,
693            *CHAINID,
694            Some(*CHAINID),
695        )
696        .unwrap();
697
698        let (slot_a, _, s_header_1, s_header_2, _) = gen_block_headers_for_denunciation(None, None);
699        assert!(slot_a < slot);
700        let de_a = Denunciation::try_from((&s_header_1, &s_header_2, Some(*CHAINID))).unwrap();
701        let (slot_b, _, s_endo_1, s_endo_2, _) = gen_endorsements_for_denunciation(None, None);
702        assert!(slot_b < slot);
703        let de_b = Denunciation::try_from((&s_endo_1, &s_endo_2, Some(*CHAINID))).unwrap();
704
705        let block_header_1 = BlockHeader {
706            current_version: 0,
707            announced_version: None,
708            slot,
709            parents: parents_1,
710            operation_merkle_root: Hash::compute_from("mno".as_bytes()),
711            endorsements: vec![s_endorsement_1],
712            denunciations: vec![de_a, de_b],
713        };
714
715        let mut buffer = Vec::new();
716        let ser = BlockHeaderSerializer::new();
717        ser.serialize(&block_header_1, &mut buffer).unwrap();
718        let der = BlockHeaderDeserializer::new(
719            THREAD_COUNT,
720            ENDORSEMENT_COUNT,
721            MAX_DENUNCIATIONS_PER_BLOCK_HEADER,
722            None,
723            *CHAINID,
724        );
725
726        let (rem, block_header_der) = der.deserialize::<DeserializeError>(&buffer).unwrap();
727
728        assert!(rem.is_empty());
729        assert_eq!(block_header_1, block_header_der);
730    }
731
732    #[test]
733    fn test_block_header_no_parents_ser_der() {
734        let slot = Slot::new(0, 1);
735        let block_header_1 = BlockHeader {
736            current_version: 0,
737            announced_version: None,
738            slot,
739            parents: vec![],
740            operation_merkle_root: Hash::compute_from("mno".as_bytes()),
741            endorsements: vec![],
742            denunciations: vec![],
743        };
744
745        let mut buffer = Vec::new();
746        let ser = BlockHeaderSerializer::new();
747        ser.serialize(&block_header_1, &mut buffer).unwrap();
748        let der = BlockHeaderDeserializer::new(
749            THREAD_COUNT,
750            ENDORSEMENT_COUNT,
751            MAX_DENUNCIATIONS_PER_BLOCK_HEADER,
752            None,
753            *CHAINID,
754        );
755
756        let (rem, block_header_der) = der.deserialize::<DeserializeError>(&buffer).unwrap();
757
758        assert!(rem.is_empty());
759        assert_eq!(block_header_1, block_header_der);
760    }
761
762    #[test]
763    fn test_verify_sig_batch() {
764        let (_slot, _keypair, secured_header_1, secured_header_2, secured_header_3) =
765            gen_block_headers_for_denunciation(None, None);
766
767        // Test with batch len == 1 (no // verif)
768        let batch_1 = [(
769            secured_header_1.compute_signed_hash(Some(*CHAINID)),
770            secured_header_1.signature,
771            secured_header_1.content_creator_pub_key,
772        )];
773        verify_signature_batch(&batch_1).unwrap();
774
775        // Test with batch len > 1 (// verif)
776        let batch_2 = [
777            (
778                secured_header_1.compute_signed_hash(Some(*CHAINID)),
779                secured_header_1.signature,
780                secured_header_1.content_creator_pub_key,
781            ),
782            (
783                secured_header_2.compute_signed_hash(Some(*CHAINID)),
784                secured_header_2.signature,
785                secured_header_2.content_creator_pub_key,
786            ),
787            (
788                secured_header_3.compute_signed_hash(Some(*CHAINID)),
789                secured_header_3.signature,
790                secured_header_3.content_creator_pub_key,
791            ),
792        ];
793        verify_signature_batch(&batch_2).unwrap();
794    }
795
796    #[test]
797    fn test_block_header_serde() {
798        let keypair = KeyPair::generate(0).unwrap();
799
800        let slot = Slot::new(7, 1);
801        let parents_1: Vec<BlockId> = (0..THREAD_COUNT)
802            .map(|i| BlockId::generate_from_hash(Hash::compute_from(&[i])))
803            .collect();
804
805        let endorsement_1 = Endorsement {
806            slot,
807            index: 1,
808            endorsed_block: parents_1[1],
809        };
810
811        assert_eq!(parents_1[1], endorsement_1.endorsed_block);
812
813        let s_endorsement_1: SecureShareEndorsement = Endorsement::new_verifiable(
814            endorsement_1,
815            EndorsementSerializer::new(),
816            &keypair,
817            *CHAINID,
818            Some(*CHAINID),
819        )
820        .unwrap();
821
822        let (slot_a, _, s_header_1, s_header_2, _) = gen_block_headers_for_denunciation(None, None);
823        assert!(slot_a < slot);
824        let de_a = Denunciation::try_from((&s_header_1, &s_header_2, Some(*CHAINID))).unwrap();
825        let (slot_b, _, s_endo_1, s_endo_2, _) = gen_endorsements_for_denunciation(None, None);
826        assert!(slot_b < slot);
827        let de_b = Denunciation::try_from((&s_endo_1, &s_endo_2, Some(*CHAINID))).unwrap();
828
829        let block_header_1 = BlockHeader {
830            current_version: 0,
831            announced_version: None,
832            slot,
833            parents: parents_1,
834            operation_merkle_root: Hash::compute_from("mno".as_bytes()),
835            endorsements: vec![s_endorsement_1],
836            denunciations: vec![de_a, de_b],
837        };
838
839        let serialized_block_header = serde_json::to_string(&block_header_1).unwrap();
840        let res_block_header: Value = serde_json::from_str(&serialized_block_header).unwrap();
841        // check equality
842        assert_eq!(
843            block_header_1.current_version,
844            res_block_header["current_version"]
845        );
846        assert_eq!(
847            block_header_1.slot.period,
848            res_block_header["slot"]["period"]
849        );
850        assert_eq!(
851            block_header_1.slot.thread,
852            res_block_header["slot"]["thread"]
853        );
854    }
855}