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