celestia_types/
extended_header.rs

1use std::fmt::{Display, Formatter};
2#[cfg(any(
3    not(any(target_arch = "wasm32", target_arch = "riscv32")),
4    feature = "wasm-bindgen"
5))]
6use std::time::Duration;
7
8use celestia_proto::header::pb::ExtendedHeader as RawExtendedHeader;
9use serde::{Deserialize, Deserializer, Serialize, Serializer};
10#[cfg(all(feature = "wasm-bindgen", target_arch = "wasm32"))]
11use serde_wasm_bindgen::to_value;
12use tendermint::block::Commit;
13use tendermint::block::header::Header;
14use tendermint::chain::id::Id;
15use tendermint::{Time, validator};
16use tendermint_proto::Protobuf;
17#[cfg(all(feature = "wasm-bindgen", target_arch = "wasm32"))]
18use wasm_bindgen::prelude::*;
19
20use crate::consts::appconsts::AppVersion;
21use crate::hash::Hash;
22use crate::trust_level::DEFAULT_TRUST_LEVEL;
23use crate::validator_set::ValidatorSetExt;
24use crate::{
25    DataAvailabilityHeader, Error, Result, ValidateBasic, ValidateBasicWithAppVersion,
26    bail_validation, bail_verification,
27};
28
29/// Information about a tendermint validator.
30pub type Validator = validator::Info;
31/// A collection of the tendermint validators.
32pub type ValidatorSet = validator::Set;
33
34#[cfg(any(
35    not(any(target_arch = "wasm32", target_arch = "riscv32")),
36    feature = "wasm-bindgen"
37))]
38const VERIFY_CLOCK_DRIFT: Duration = Duration::from_secs(10);
39
40/// Block header together with the relevant Data Availability metadata.
41///
42/// [`ExtendedHeader`]s are used to announce and describe the blocks
43/// in the Celestia network.
44///
45/// Before being used, each header should be validated and verified with a header you trust.
46///
47/// # Example
48///
49/// ```
50/// # use celestia_types::ExtendedHeader;
51/// # fn trusted_genesis_header() -> ExtendedHeader {
52/// #     let s = include_str!("../test_data/chain1/extended_header_block_1.json");
53/// #     serde_json::from_str(s).unwrap()
54/// # }
55/// # fn some_untrusted_header() -> ExtendedHeader {
56/// #     let s = include_str!("../test_data/chain1/extended_header_block_27.json");
57/// #     serde_json::from_str(s).unwrap()
58/// # }
59/// let genesis_header = trusted_genesis_header();
60///
61/// // fetch new header
62/// let fetched_header = some_untrusted_header();
63///
64/// fetched_header.validate().expect("Invalid block header");
65/// genesis_header.verify(&fetched_header).expect("Malicious header received");
66/// ```
67#[derive(Debug, Clone, PartialEq, Eq)]
68#[cfg_attr(
69    all(feature = "wasm-bindgen", target_arch = "wasm32"),
70    wasm_bindgen(inspectable)
71)]
72pub struct ExtendedHeader {
73    /// Tendermint block header.
74    #[cfg_attr(
75        all(feature = "wasm-bindgen", target_arch = "wasm32"),
76        wasm_bindgen(skip)
77    )]
78    pub header: Header,
79    /// Commit metadata and signatures from validators committing the block.
80    #[cfg_attr(
81        all(feature = "wasm-bindgen", target_arch = "wasm32"),
82        wasm_bindgen(skip)
83    )]
84    pub commit: Commit,
85    /// Information about the set of validators commiting the block.
86    #[cfg_attr(
87        all(feature = "wasm-bindgen", target_arch = "wasm32"),
88        wasm_bindgen(skip)
89    )]
90    pub validator_set: ValidatorSet,
91    /// Header of the block data availability.
92    #[cfg_attr(
93        all(feature = "wasm-bindgen", target_arch = "wasm32"),
94        wasm_bindgen(getter_with_clone)
95    )]
96    pub dah: DataAvailabilityHeader,
97}
98
99impl Display for ExtendedHeader {
100    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
101        write!(f, "hash: {}; height: {}", self.hash(), self.height())
102    }
103}
104
105impl ExtendedHeader {
106    /// Decode protobuf encoded header and then validate it.
107    pub fn decode_and_validate(bytes: &[u8]) -> Result<Self> {
108        let header = ExtendedHeader::decode(bytes)?;
109        header.validate()?;
110        Ok(header)
111    }
112
113    /// Get the app version.
114    ///
115    /// # Panics
116    ///
117    /// This method can panic if an app version is not supported. Make sure to call
118    /// [`ExtendedHeader::validate`] before (this is already done for headers deserialized via
119    /// serde).
120    pub fn app_version(&self) -> AppVersion {
121        let app_version = self.header.version.app;
122        AppVersion::from_u64(app_version).expect("header should be validated before calling")
123    }
124
125    /// Get the block chain id.
126    pub fn chain_id(&self) -> &Id {
127        &self.header.chain_id
128    }
129
130    /// Get the block height.
131    pub fn height(&self) -> u64 {
132        self.header.height.value()
133    }
134
135    /// Get the block time.
136    pub fn time(&self) -> Time {
137        self.header.time
138    }
139
140    /// Get the block hash.
141    pub fn hash(&self) -> Hash {
142        self.commit.block_id.hash
143    }
144
145    /// Get the hash of the previous header.
146    pub fn last_header_hash(&self) -> Hash {
147        self.header
148            .last_block_id
149            .map(|block_id| block_id.hash)
150            .unwrap_or_default()
151    }
152
153    /// Get the block's extended data square width.
154    pub fn square_width(&self) -> u16 {
155        self.dah.square_width()
156    }
157
158    /// Validate header.
159    ///
160    /// Performs a consistency check of the data included in the header.
161    ///
162    /// # Errors
163    ///
164    /// If validation fails, this function will return an error with a reason of failure.
165    ///
166    /// ```
167    /// # use celestia_types::{ExtendedHeader, DataAvailabilityHeader};
168    /// #
169    /// # fn get_header(_: usize) -> ExtendedHeader {
170    /// #     let s = include_str!("../test_data/chain1/extended_header_block_27.json");
171    /// #     serde_json::from_str(s).unwrap()
172    /// # }
173    /// // fetch new header
174    /// let mut fetched_header = get_header(15);
175    ///
176    /// assert!(fetched_header.validate().is_ok());
177    ///
178    /// fetched_header.dah = DataAvailabilityHeader::new_unchecked(vec![], vec![]);
179    ///
180    /// assert!(fetched_header.validate().is_err());
181    /// ```
182    pub fn validate(&self) -> Result<()> {
183        self.header.validate_basic()?;
184        self.commit.validate_basic()?;
185        self.validator_set.validate_basic()?;
186
187        // make sure the validator set is consistent with the header
188        if self.validator_set.hash() != self.header.validators_hash {
189            bail_validation!(
190                "validator_set hash ({}) != header validators_hash ({})",
191                self.validator_set.hash(),
192                self.header.validators_hash,
193            )
194        }
195
196        // ensure data root from raw header matches computed root
197        if self.dah.hash() != self.header.data_hash.unwrap_or_default() {
198            bail_validation!(
199                "dah hash ({}) != header dah hash ({})",
200                self.dah.hash(),
201                self.header.data_hash.unwrap_or_default(),
202            )
203        }
204
205        // Make sure the header is consistent with the commit.
206        if self.commit.height.value() != self.height() {
207            bail_validation!(
208                "commit height ({}) != header height ({})",
209                self.commit.height,
210                self.height(),
211            )
212        }
213
214        if self.commit.block_id.hash != self.header.hash() {
215            bail_validation!(
216                "commit block_id hash ({}) != header hash ({})",
217                self.commit.block_id.hash,
218                self.header.hash(),
219            )
220        }
221
222        self.validator_set.verify_commit_light(
223            &self.header.chain_id,
224            &self.header.height,
225            &self.commit,
226        )?;
227
228        let app_version = self.header.version.app;
229        let app_version =
230            AppVersion::from_u64(app_version).ok_or(Error::UnsupportedAppVersion(app_version))?;
231
232        self.dah.validate_basic(app_version)?;
233
234        Ok(())
235    }
236
237    /// Verify an untrusted header.
238    ///
239    /// Ensures that the untrusted header can be trusted by verifying it against `self`.
240    ///
241    /// # Note
242    ///
243    /// This method does not do validation for optimization purposes.
244    /// Validation should be done from before and ideally with
245    /// [`ExtendedHeader::decode_and_validate`].
246    ///
247    /// # Errors
248    ///
249    /// If verification fails, this function will return an error with a reason of failure.
250    ///
251    /// Please note that if verifying unadjacent headers, the verification will always
252    /// fail if the validator set commiting those blocks was changed. If that is the case,
253    /// consider verifying the untrusted header with a more recent or even previous header.
254    pub fn verify(&self, untrusted: &ExtendedHeader) -> Result<()> {
255        if untrusted.height() <= self.height() {
256            bail_verification!(
257                "untrusted header height({}) <= current trusted header({})",
258                untrusted.height(),
259                self.height()
260            );
261        }
262
263        if untrusted.chain_id() != self.chain_id() {
264            bail_verification!(
265                "untrusted header has different chain {}, not {}",
266                untrusted.chain_id(),
267                self.chain_id()
268            );
269        }
270
271        if !untrusted.time().after(self.time()) {
272            bail_verification!(
273                "untrusted header time ({}) must be after current trusted header ({})",
274                untrusted.time(),
275                self.time()
276            );
277        }
278
279        #[cfg(any(
280            not(any(target_arch = "wasm32", target_arch = "riscv32")),
281            feature = "wasm-bindgen"
282        ))]
283        {
284            let now = Time::now();
285            let valid_until = now.checked_add(VERIFY_CLOCK_DRIFT).unwrap();
286
287            if !untrusted.time().before(valid_until) {
288                bail_verification!(
289                    "new untrusted header has a time from the future {} (now: {}, clock_drift: {:?})",
290                    untrusted.time(),
291                    now,
292                    VERIFY_CLOCK_DRIFT
293                );
294            }
295        }
296
297        // Optimization: If we are verifying an adjacent header we can avoid
298        // `verify_commit_light_trusting` because we can just check the hash
299        // of next validators and last header.
300        if self.height() + 1 == untrusted.height() {
301            if untrusted.header.validators_hash != self.header.next_validators_hash {
302                bail_verification!(
303                    "expected old header next validators ({}) to match those from new header ({})",
304                    self.header.next_validators_hash,
305                    untrusted.header.validators_hash,
306                );
307            }
308
309            if untrusted.last_header_hash() != self.hash() {
310                bail_verification!(
311                    "expected new header to point to last header hash ({}), but got {}",
312                    self.hash(),
313                    untrusted.last_header_hash()
314                );
315            }
316
317            return Ok(());
318        }
319
320        self.validator_set.verify_commit_light_trusting(
321            self.chain_id(),
322            &untrusted.commit,
323            DEFAULT_TRUST_LEVEL,
324        )?;
325
326        Ok(())
327    }
328
329    /// Verify an untrusted header and make sure they are adjacent to `self`.
330    ///
331    /// Ensures that the untrusted header is adjacent and can be trusted
332    /// by verifying it against `self`.
333    ///
334    /// # Note
335    ///
336    /// This method does not do validation for optimization purposes.
337    /// Validation should be done from before and ideally with
338    /// [`ExtendedHeader::decode_and_validate`].
339    ///
340    /// # Errors
341    ///
342    /// If verification fails, this function will return an error with a reason of failure.
343    pub fn verify_adjacent(&self, untrusted: &ExtendedHeader) -> Result<()> {
344        // Check is first untrusted is adjacent to `self`.
345        if self.height() + 1 != untrusted.height() {
346            bail_verification!(
347                "untrusted header height ({}) not adjacent to the current trusted ({})",
348                untrusted.height(),
349                self.height(),
350            );
351        }
352
353        self.verify(untrusted)
354    }
355
356    /// Verify a chain of adjacent untrusted headers.
357    ///
358    /// # Note
359    ///
360    /// This method does not do validation for optimization purposes.
361    /// Validation should be done from before and ideally with
362    /// [`ExtendedHeader::decode_and_validate`].
363    ///
364    /// # Errors
365    ///
366    /// If verification fails, this function will return an error with a reason of failure.
367    /// This function will also return an error if untrusted headers are not adjacent
368    /// to each other.
369    ///
370    /// # Example
371    ///
372    /// ```
373    /// # use std::ops::Range;
374    /// # use celestia_types::ExtendedHeader;
375    /// # let s = include_str!("../test_data/chain3/extended_header_block_1_to_256.json");
376    /// # let headers: Vec<ExtendedHeader> = serde_json::from_str(s).unwrap();
377    /// # let trusted_genesis = || headers[0].clone();
378    /// # // substract one as heights start from 1
379    /// # let get_headers_range = |r: Range<usize>| (&headers[r.start - 1..r.end - 1]).to_vec();
380    /// let genesis_header = trusted_genesis();
381    /// let next_headers = get_headers_range(5..50);
382    ///
383    /// assert!(genesis_header.verify_range(&next_headers).is_ok());
384    /// ```
385    pub fn verify_range(&self, untrusted: &[ExtendedHeader]) -> Result<()> {
386        let mut trusted = self;
387
388        for (i, untrusted) in untrusted.iter().enumerate() {
389            // All headers in `untrusted` must be adjacent to their previous
390            // one. However we do not check if the first untrusted is adjacent
391            // to `self`. This check is done in `verify_adjacent_range`.
392            if i != 0 && trusted.height() + 1 != untrusted.height() {
393                bail_verification!(
394                    "untrusted header height ({}) not adjacent to the current trusted ({})",
395                    untrusted.height(),
396                    trusted.height(),
397                );
398            }
399
400            trusted.verify(untrusted)?;
401            trusted = untrusted;
402        }
403
404        Ok(())
405    }
406
407    /// Verify a chain of adjacent untrusted headers and make sure
408    /// they are adjacent to `self`.
409    ///
410    /// # Note
411    ///
412    /// This method does not do validation for optimization purposes.
413    /// Validation should be done from before and ideally with
414    /// [`ExtendedHeader::decode_and_validate`].
415    ///
416    /// # Errors
417    ///
418    /// If verification fails, this function will return an error with a reason of failure.
419    /// This function will also return an error if untrusted headers and `self` don't form contiguous range
420    ///
421    /// # Example
422    ///
423    /// ```
424    /// # use std::ops::Range;
425    /// # use celestia_types::ExtendedHeader;
426    /// # let s = include_str!("../test_data/chain3/extended_header_block_1_to_256.json");
427    /// # let headers: Vec<ExtendedHeader> = serde_json::from_str(s).unwrap();
428    /// # let trusted_genesis = || headers[0].clone();
429    /// # // substract one as heights start from 1
430    /// # let get_headers_range = |r: Range<usize>| (&headers[r.start - 1..r.end - 1]).to_vec();
431    /// let genesis_header = trusted_genesis();
432    /// let next_headers = get_headers_range(5..50);
433    ///
434    /// // fails, not adjacent to genesis
435    /// assert!(genesis_header.verify_adjacent_range(&next_headers).is_err());
436    ///
437    /// let next_headers = get_headers_range(2..50);
438    ///
439    /// // succeeds
440    /// genesis_header.verify_adjacent_range(&next_headers).unwrap();
441    /// ```
442    pub fn verify_adjacent_range(&self, untrusted: &[ExtendedHeader]) -> Result<()> {
443        if untrusted.is_empty() {
444            return Ok(());
445        }
446
447        // Check is first untrusted is adjacent to `self`.
448        if self.height() + 1 != untrusted[0].height() {
449            bail_verification!(
450                "untrusted header height ({}) not adjacent to the current trusted ({})",
451                untrusted[0].height(),
452                self.height(),
453            );
454        }
455
456        self.verify_range(untrusted)
457    }
458}
459
460#[cfg(all(feature = "wasm-bindgen", target_arch = "wasm32"))]
461#[wasm_bindgen]
462impl ExtendedHeader {
463    /// Clone a header producing a deep copy of it.
464    #[wasm_bindgen(js_name = clone)]
465    pub fn js_clone(&self) -> Self {
466        self.clone()
467    }
468
469    /// Get the block height.
470    #[wasm_bindgen(js_name = height)]
471    pub fn js_height(&self) -> u64 {
472        self.height()
473    }
474
475    /// Get the block time.
476    #[wasm_bindgen(js_name = time)]
477    pub fn js_time(&self) -> Result<f64, JsValue> {
478        Ok(self
479            .time()
480            .duration_since(Time::unix_epoch())
481            .map_err(|e| JsError::new(&e.to_string()))?
482            .as_secs_f64()
483            * 1000.0)
484    }
485
486    /// Get the block hash.
487    #[wasm_bindgen(js_name = hash)]
488    pub fn js_hash(&self) -> String {
489        self.hash().to_string()
490    }
491
492    /// Get the hash of the previous header.
493    #[wasm_bindgen(js_name = previousHeaderHash)]
494    pub fn js_previous_header_hash(&self) -> String {
495        self.last_header_hash().to_string()
496    }
497
498    /// Tendermint block header.
499    #[wasm_bindgen(getter, js_name = header)]
500    pub fn js_header(&self) -> Result<JsValue, serde_wasm_bindgen::Error> {
501        to_value(&self.header)
502    }
503
504    /// Commit metadata and signatures from validators committing the block.
505    #[wasm_bindgen(getter, js_name = commit)]
506    pub fn js_commit(&self) -> Result<JsValue, serde_wasm_bindgen::Error> {
507        to_value(&self.commit)
508    }
509
510    /// Information about the set of validators commiting the block.
511    #[wasm_bindgen(getter, js_name = validatorSet)]
512    pub fn js_validator_set(&self) -> Result<JsValue, serde_wasm_bindgen::Error> {
513        to_value(&self.validator_set)
514    }
515
516    /// Decode protobuf encoded header and then validate it.
517    #[wasm_bindgen(js_name = validate)]
518    pub fn js_validate(&self) -> Result<(), JsValue> {
519        Ok(self.validate()?)
520    }
521
522    /// Verify a chain of adjacent untrusted headers and make sure
523    /// they are adjacent to `self`.
524    ///
525    /// # Errors
526    ///
527    /// If verification fails, this function will return an error with a reason of failure.
528    /// This function will also return an error if untrusted headers and `self` don't form contiguous range
529    #[wasm_bindgen(js_name = verify)]
530    pub fn js_verify(&self, untrusted: &ExtendedHeader) -> Result<(), JsValue> {
531        Ok(self.verify(untrusted)?)
532    }
533
534    /// Verify a chain of adjacent untrusted headers.
535    ///
536    /// # Note
537    ///
538    /// Provided headers will be consumed by this method, meaning
539    /// they will no longer be accessible. If this behavior is not desired,
540    /// consider using `ExtendedHeader.clone()`.
541    ///
542    /// ```js
543    /// const genesis = hdr0;
544    /// const headers = [hrd1, hdr2, hdr3];
545    /// genesis.verifyRange(headers.map(h => h.clone()));
546    /// ```
547    ///
548    /// # Errors
549    ///
550    /// If verification fails, this function will return an error with a reason of failure.
551    /// This function will also return an error if untrusted headers are not adjacent
552    /// to each other.
553    #[wasm_bindgen(js_name = verifyRange)]
554    pub fn js_verify_range(&self, untrusted: Vec<ExtendedHeader>) -> Result<(), JsValue> {
555        Ok(self.verify_range(&untrusted)?)
556    }
557
558    /// Verify a chain of adjacent untrusted headers and make sure
559    /// they are adjacent to `self`.
560    ///
561    /// # Note
562    ///
563    /// Provided headers will be consumed by this method, meaning
564    /// they will no longer be accessible. If this behavior is not desired,
565    /// consider using `ExtendedHeader.clone()`.
566    ///
567    /// ```js
568    /// const genesis = hdr0;
569    /// const headers = [hrd1, hdr2, hdr3];
570    /// genesis.verifyAdjacentRange(headers.map(h => h.clone()));
571    /// ```
572    ///
573    /// # Errors
574    ///
575    /// If verification fails, this function will return an error with a reason of failure.
576    /// This function will also return an error if untrusted headers and `self` don't form contiguous range
577    #[wasm_bindgen(js_name = verifyAdjacentRange)]
578    pub fn js_verify_adjacent_range(&self, untrusted: Vec<ExtendedHeader>) -> Result<(), JsValue> {
579        Ok(self.verify_adjacent_range(&untrusted)?)
580    }
581}
582
583impl Protobuf<RawExtendedHeader> for ExtendedHeader {}
584
585impl TryFrom<RawExtendedHeader> for ExtendedHeader {
586    type Error = Error;
587
588    fn try_from(value: RawExtendedHeader) -> Result<Self, Self::Error> {
589        let header = value.header.ok_or(Error::MissingHeader)?.try_into()?;
590        let commit = value.commit.ok_or(Error::MissingCommit)?.try_into()?;
591        let validator_set = value
592            .validator_set
593            .ok_or(Error::MissingValidatorSet)?
594            .try_into()?;
595        let dah = value
596            .dah
597            .ok_or(Error::MissingDataAvailabilityHeader)?
598            .try_into()?;
599
600        let eh = ExtendedHeader {
601            header,
602            commit,
603            validator_set,
604            dah,
605        };
606
607        eh.validate()?;
608
609        Ok(eh)
610    }
611}
612
613impl From<ExtendedHeader> for RawExtendedHeader {
614    fn from(value: ExtendedHeader) -> RawExtendedHeader {
615        RawExtendedHeader {
616            header: Some(value.header.into()),
617            commit: Some(value.commit.into()),
618            validator_set: Some(value.validator_set.into()),
619            dah: Some(value.dah.into()),
620        }
621    }
622}
623
624// TODO: get rid of this after a release or two
625// https://github.com/eigerco/lumina/issues/683
626mod custom_serde {
627    use celestia_proto::celestia::core::v1::da::DataAvailabilityHeader;
628    use celestia_proto::header::pb::ExtendedHeader as RawExtendedHeader;
629    use serde::{Deserialize, Serialize};
630    use tendermint_proto::v0_38::types::Commit as RawCommit;
631    use tendermint_proto::v0_38::types::{BlockId, CommitSig, Header, ValidatorSet};
632
633    #[derive(Deserialize, Serialize)]
634    pub(super) struct SerdeExtendedHeader {
635        header: Option<Header>,
636        commit: Option<SerdeCommit>,
637        validator_set: Option<ValidatorSet>,
638        dah: Option<DataAvailabilityHeader>,
639    }
640
641    #[derive(Deserialize, Serialize)]
642    pub(super) struct SerdeCommit {
643        #[serde(with = "celestia_proto::serializers::maybe_quoted")]
644        height: i64,
645        round: i32,
646        block_id: Option<BlockId>,
647        #[serde(with = "tendermint_proto::serializers::nullable")]
648        signatures: Vec<CommitSig>,
649    }
650
651    impl From<RawExtendedHeader> for SerdeExtendedHeader {
652        fn from(value: RawExtendedHeader) -> Self {
653            SerdeExtendedHeader {
654                header: value.header,
655                commit: value.commit.map(|commit| commit.into()),
656                validator_set: value.validator_set,
657                dah: value.dah,
658            }
659        }
660    }
661
662    impl From<SerdeExtendedHeader> for RawExtendedHeader {
663        fn from(value: SerdeExtendedHeader) -> Self {
664            RawExtendedHeader {
665                header: value.header,
666                commit: value.commit.map(|commit| commit.into()),
667                validator_set: value.validator_set,
668                dah: value.dah,
669            }
670        }
671    }
672
673    impl From<RawCommit> for SerdeCommit {
674        fn from(value: RawCommit) -> Self {
675            SerdeCommit {
676                height: value.height,
677                round: value.round,
678                block_id: value.block_id,
679                signatures: value.signatures,
680            }
681        }
682    }
683
684    impl From<SerdeCommit> for RawCommit {
685        fn from(value: SerdeCommit) -> Self {
686            RawCommit {
687                height: value.height,
688                round: value.round,
689                block_id: value.block_id,
690                signatures: value.signatures,
691            }
692        }
693    }
694
695    #[cfg(test)]
696    mod tests {
697        use super::SerdeCommit;
698
699        #[test]
700        fn deserialize_quoted_and_unquoted_commit_height() {
701            let unquoted = r#"{
702                "height": 27,
703                "round": 0,
704                "block_id": {
705                  "hash": "6F754536418C0574629379BA6F145C62C86DAEAA8F5772FA1AD5D5AEB4FE5B97",
706                  "parts": {
707                    "total": 1,
708                    "hash": "791BF8972B46DA4582779629D7E3D925510178D3930A4F6CA82FB88636FDA2C6"
709                  }
710                },
711                "signatures": [
712                  {
713                    "block_id_flag": 2,
714                    "validator_address": "F1F83230835AA69A1AD6EA68C6D894A4106B8E53",
715                    "timestamp": "2023-06-23T10:47:18.421006821Z",
716                    "signature": "/2U/PzplnCuSi2jjlOxCdwfVh2+wPQZQoWYOH/AMzwR1iQ/G68yxmamZbaen2c4Z06KUVJMcP7WtbBKtciy5AA=="
717                  }
718                ]
719            }"#;
720            serde_json::from_str::<SerdeCommit>(unquoted).unwrap();
721
722            let quoted = r#"{
723                "height": "27",
724                "round": 0,
725                "block_id": {
726                  "hash": "6F754536418C0574629379BA6F145C62C86DAEAA8F5772FA1AD5D5AEB4FE5B97",
727                  "parts": {
728                    "total": 1,
729                    "hash": "791BF8972B46DA4582779629D7E3D925510178D3930A4F6CA82FB88636FDA2C6"
730                  }
731                },
732                "signatures": [
733                  {
734                    "block_id_flag": 2,
735                    "validator_address": "F1F83230835AA69A1AD6EA68C6D894A4106B8E53",
736                    "timestamp": "2023-06-23T10:47:18.421006821Z",
737                    "signature": "/2U/PzplnCuSi2jjlOxCdwfVh2+wPQZQoWYOH/AMzwR1iQ/G68yxmamZbaen2c4Z06KUVJMcP7WtbBKtciy5AA=="
738                  }
739                ]
740            }"#;
741            serde_json::from_str::<SerdeCommit>(quoted).unwrap();
742        }
743    }
744}
745
746impl Serialize for ExtendedHeader {
747    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
748    where
749        S: Serializer,
750    {
751        let pb: RawExtendedHeader = self.clone().into();
752        let custom_ser: custom_serde::SerdeExtendedHeader = pb.into();
753        custom_ser.serialize(serializer)
754    }
755}
756
757impl<'de> Deserialize<'de> for ExtendedHeader {
758    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
759    where
760        D: Deserializer<'de>,
761    {
762        let custom_de = custom_serde::SerdeExtendedHeader::deserialize(deserializer)?;
763        let pb: RawExtendedHeader = custom_de.into();
764        ExtendedHeader::try_from(pb).map_err(serde::de::Error::custom)
765    }
766}
767
768#[cfg(test)]
769mod tests {
770    use super::*;
771    use crate::test_utils::{invalidate, unverify};
772
773    #[cfg(target_arch = "wasm32")]
774    use wasm_bindgen_test::wasm_bindgen_test as test;
775
776    fn sample_eh_chain_1_block_1() -> ExtendedHeader {
777        let s = include_str!("../test_data/chain1/extended_header_block_1.json");
778        serde_json::from_str(s).unwrap()
779    }
780
781    fn sample_eh_chain_1_block_27() -> ExtendedHeader {
782        let s = include_str!("../test_data/chain1/extended_header_block_27.json");
783        serde_json::from_str(s).unwrap()
784    }
785
786    fn sample_eh_chain_2_block_1() -> ExtendedHeader {
787        let s = include_str!("../test_data/chain2/extended_header_block_1.json");
788        serde_json::from_str(s).unwrap()
789    }
790
791    fn sample_eh_chain_2_block_27() -> ExtendedHeader {
792        let s = include_str!("../test_data/chain2/extended_header_block_27.json");
793        serde_json::from_str(s).unwrap()
794    }
795
796    fn sample_eh_chain_2_block_28() -> ExtendedHeader {
797        let s = include_str!("../test_data/chain2/extended_header_block_28.json");
798        serde_json::from_str(s).unwrap()
799    }
800
801    fn sample_eh_chain_2_block_35() -> ExtendedHeader {
802        let s = include_str!("../test_data/chain2/extended_header_block_35.json");
803        serde_json::from_str(s).unwrap()
804    }
805
806    fn sample_eh_chain_3_block_1_to_256() -> Vec<ExtendedHeader> {
807        let s = include_str!("../test_data/chain3/extended_header_block_1_to_256.json");
808        serde_json::from_str(s).unwrap()
809    }
810
811    #[test]
812    fn validate_correct() {
813        sample_eh_chain_1_block_1().validate().unwrap();
814        sample_eh_chain_1_block_27().validate().unwrap();
815
816        sample_eh_chain_2_block_1().validate().unwrap();
817        sample_eh_chain_2_block_27().validate().unwrap();
818        sample_eh_chain_2_block_28().validate().unwrap();
819        sample_eh_chain_2_block_35().validate().unwrap();
820    }
821
822    #[test]
823    fn validate_validator_hash_mismatch() {
824        let mut eh = sample_eh_chain_1_block_27();
825        eh.header.validators_hash = Hash::None;
826
827        eh.validate().unwrap_err();
828    }
829
830    #[test]
831    fn validate_dah_hash_mismatch() {
832        let mut eh = sample_eh_chain_1_block_27();
833        eh.header.data_hash = Some(Hash::Sha256([0; 32]));
834
835        eh.validate().unwrap_err();
836    }
837
838    #[test]
839    fn validate_commit_height_mismatch() {
840        let mut eh = sample_eh_chain_1_block_27();
841        eh.commit.height = 0xdeadbeefu32.into();
842
843        eh.validate().unwrap_err();
844    }
845
846    #[test]
847    fn validate_commit_block_hash_mismatch() {
848        let mut eh = sample_eh_chain_1_block_27();
849        eh.commit.block_id.hash = Hash::None;
850
851        eh.validate().unwrap_err();
852    }
853
854    #[test]
855    fn verify() {
856        let eh_block_1 = sample_eh_chain_1_block_1();
857        let eh_block_27 = sample_eh_chain_1_block_27();
858
859        eh_block_1.verify(&eh_block_27).unwrap();
860
861        let eh_block_1 = sample_eh_chain_2_block_1();
862        let eh_block_27 = sample_eh_chain_2_block_27();
863
864        eh_block_1.verify(&eh_block_27).unwrap();
865    }
866
867    #[test]
868    fn verify_adjacent() {
869        let eh_block_27 = sample_eh_chain_2_block_27();
870        let eh_block_28 = sample_eh_chain_2_block_28();
871
872        eh_block_27.verify(&eh_block_28).unwrap();
873    }
874
875    #[test]
876    fn verify_invalid_validator() {
877        let eh_block_27 = sample_eh_chain_2_block_27();
878        let mut eh_block_28 = sample_eh_chain_2_block_28();
879
880        eh_block_28.header.validators_hash = Hash::None;
881
882        eh_block_27.verify(&eh_block_28).unwrap_err();
883    }
884
885    #[test]
886    fn verify_invalid_last_block_hash() {
887        let eh_block_27 = sample_eh_chain_2_block_27();
888        let mut eh_block_28 = sample_eh_chain_2_block_28();
889
890        eh_block_28.header.last_block_id.as_mut().unwrap().hash = Hash::None;
891
892        eh_block_27.verify(&eh_block_28).unwrap_err();
893    }
894
895    #[test]
896    fn verify_invalid_adjacent() {
897        let eh_block_27 = sample_eh_chain_1_block_27();
898        let eh_block_28 = sample_eh_chain_2_block_28();
899
900        eh_block_27.verify(&eh_block_28).unwrap_err();
901    }
902
903    #[test]
904    fn verify_same_chain_id_but_different_chain() {
905        let eh_block_1 = sample_eh_chain_1_block_1();
906        let eh_block_27 = sample_eh_chain_2_block_27();
907
908        eh_block_1.verify(&eh_block_27).unwrap_err();
909    }
910
911    #[test]
912    fn verify_invalid_height() {
913        let eh_block_27 = sample_eh_chain_1_block_27();
914        eh_block_27.verify(&eh_block_27).unwrap_err();
915    }
916
917    #[test]
918    fn verify_invalid_chain_id() {
919        let eh_block_1 = sample_eh_chain_1_block_1();
920        let mut eh_block_27 = sample_eh_chain_1_block_27();
921
922        eh_block_27.header.chain_id = "1112222".parse().unwrap();
923        eh_block_1.verify(&eh_block_27).unwrap_err();
924    }
925
926    #[test]
927    fn verify_invalid_time() {
928        let eh_block_1 = sample_eh_chain_1_block_1();
929        let mut eh_block_27 = sample_eh_chain_1_block_27();
930
931        eh_block_27.header.time = eh_block_1.header.time;
932        eh_block_1.verify(&eh_block_27).unwrap_err();
933    }
934
935    #[test]
936    fn verify_time_from_the_future() {
937        let eh_block_1 = sample_eh_chain_1_block_1();
938        let mut eh_block_27 = sample_eh_chain_1_block_27();
939
940        eh_block_27.header.time = Time::now().checked_add(Duration::from_secs(60)).unwrap();
941        eh_block_1.verify(&eh_block_27).unwrap_err();
942    }
943
944    #[test]
945    fn verify_range() {
946        let eh_chain = sample_eh_chain_3_block_1_to_256();
947
948        eh_chain[0].verify_range(&eh_chain[1..]).unwrap();
949        eh_chain[0].verify_range(&eh_chain[..]).unwrap_err();
950        eh_chain[0].verify_range(&eh_chain[10..]).unwrap();
951
952        eh_chain[10].verify_range(&eh_chain[11..]).unwrap();
953        eh_chain[10].verify_range(&eh_chain[100..]).unwrap();
954        eh_chain[10].verify_range(&eh_chain[..9]).unwrap_err();
955        eh_chain[10].verify_range(&eh_chain[10..]).unwrap_err();
956    }
957
958    #[test]
959    fn verify_range_missing_height() {
960        let eh_chain = sample_eh_chain_3_block_1_to_256();
961
962        let mut headers = eh_chain[10..15].to_vec();
963        headers.remove(2);
964        eh_chain[0].verify_range(&headers).unwrap_err();
965    }
966
967    #[test]
968    fn verify_range_duplicate_height() {
969        let eh_chain = sample_eh_chain_3_block_1_to_256();
970
971        let mut headers = eh_chain[10..15].to_vec();
972        headers.insert(2, eh_chain[12].clone());
973        eh_chain[0].verify_range(&headers).unwrap_err();
974    }
975
976    #[test]
977    fn verify_range_bad_header_in_middle() {
978        let eh_chain = sample_eh_chain_3_block_1_to_256();
979
980        let mut headers = eh_chain[10..15].to_vec();
981
982        unverify(&mut headers[2]);
983
984        eh_chain[0].verify_range(&headers).unwrap_err();
985    }
986
987    #[test]
988    fn verify_range_allow_invalid_header_in_middle() {
989        let eh_chain = sample_eh_chain_3_block_1_to_256();
990
991        let mut headers = eh_chain[10..15].to_vec();
992
993        invalidate(&mut headers[2]);
994
995        eh_chain[0].verify_range(&headers).unwrap();
996    }
997
998    #[test]
999    fn verify_adjacent_range() {
1000        let eh_chain = sample_eh_chain_3_block_1_to_256();
1001
1002        eh_chain[0].verify_adjacent_range(&eh_chain[1..]).unwrap();
1003        eh_chain[0]
1004            .verify_adjacent_range(&eh_chain[..])
1005            .unwrap_err();
1006        eh_chain[0]
1007            .verify_adjacent_range(&eh_chain[10..])
1008            .unwrap_err();
1009
1010        eh_chain[10].verify_adjacent_range(&eh_chain[11..]).unwrap();
1011        eh_chain[10]
1012            .verify_adjacent_range(&eh_chain[100..])
1013            .unwrap_err();
1014        eh_chain[10]
1015            .verify_adjacent_range(&eh_chain[..9])
1016            .unwrap_err();
1017        eh_chain[10]
1018            .verify_adjacent_range(&eh_chain[10..])
1019            .unwrap_err();
1020    }
1021}