celestia_types/block/
header.rs

1use tendermint::block::Header;
2
3use crate::block::GENESIS_HEIGHT;
4use crate::consts::{genesis::MAX_CHAIN_ID_LEN, version};
5use crate::{Result, ValidateBasic, ValidationError, bail_validation};
6
7impl ValidateBasic for Header {
8    fn validate_basic(&self) -> Result<(), ValidationError> {
9        if self.version.block != version::BLOCK_PROTOCOL {
10            bail_validation!(
11                "version block ({}) != block protocol ({})",
12                self.version.block,
13                version::BLOCK_PROTOCOL,
14            )
15        }
16
17        if self.chain_id.as_str().len() > MAX_CHAIN_ID_LEN {
18            bail_validation!(
19                "chain id ({}) len > maximum ({})",
20                self.chain_id,
21                MAX_CHAIN_ID_LEN
22            )
23        }
24
25        if self.height.value() == 0 {
26            bail_validation!("height == 0")
27        }
28
29        if self.height.value() == GENESIS_HEIGHT && self.last_block_id.is_some() {
30            bail_validation!("last_block_id == Some() at height {GENESIS_HEIGHT}");
31        }
32
33        if self.height.value() != GENESIS_HEIGHT && self.last_block_id.is_none() {
34            bail_validation!("last_block_id == None at height {}", self.height)
35        }
36
37        // NOTE: We do not validate `Hash` fields because they are type safe.
38        // In Go implementation the validation passes if their length is 0 or 32.
39        //
40        // NOTE: We do not validate `app_hash` because if can be anything
41
42        Ok(())
43    }
44}
45
46#[cfg(all(target_arch = "wasm32", feature = "wasm-bindgen"))]
47pub use wbg::*;
48
49#[cfg(all(target_arch = "wasm32", feature = "wasm-bindgen"))]
50mod wbg {
51    use tendermint::block::Header;
52    use tendermint::block::header::Version;
53    use tendermint::block::parts;
54    use tendermint::block::signed_header::SignedHeader;
55    use wasm_bindgen::prelude::*;
56
57    use crate::block::JsBlockId;
58    use crate::block::commit::JsCommit;
59
60    /// Version contains the protocol version for the blockchain and the application.
61    #[derive(Clone, Copy, Debug)]
62    #[wasm_bindgen(js_name = "ProtocolVersion")]
63    pub struct JsHeaderVersion {
64        /// blockchain version
65        pub block: u64,
66        /// app version
67        pub app: u64,
68    }
69
70    impl From<Version> for JsHeaderVersion {
71        fn from(value: Version) -> Self {
72            JsHeaderVersion {
73                block: value.block,
74                app: value.app,
75            }
76        }
77    }
78    /// Block parts header
79    #[derive(Clone, Debug)]
80    #[wasm_bindgen(getter_with_clone, js_name = "PartsHeader")]
81    pub struct JsPartsHeader {
82        /// Number of parts in this block
83        pub total: u32,
84        /// Hash of the parts set header
85        pub hash: String,
86    }
87
88    impl From<parts::Header> for JsPartsHeader {
89        fn from(value: parts::Header) -> Self {
90            JsPartsHeader {
91                total: value.total,
92                hash: value.hash.to_string(),
93            }
94        }
95    }
96
97    /// Block Header values contain metadata about the block and about the consensus,
98    /// as well as commitments to the data in the current block, the previous block,
99    /// and the results returned by the application.
100    #[derive(Clone, Debug)]
101    #[wasm_bindgen(getter_with_clone, js_name = "Header")]
102    pub struct JsHeader {
103        /// Header version
104        pub version: JsHeaderVersion,
105        /// Chain ID
106        pub chain_id: String,
107        /// Current block height
108        pub height: u64,
109        /// Current timestamp encoded as rfc3339
110        pub time: String,
111        /// Previous block info
112        pub last_block_id: Option<JsBlockId>,
113        /// Commit from validators from the last block
114        pub last_commit_hash: Option<String>,
115        /// Merkle root of transaction hashes
116        pub data_hash: Option<String>,
117        /// Validators for the current block
118        pub validators_hash: String,
119        /// Validators for the next block
120        pub next_validators_hash: String,
121        /// Consensus params for the current block
122        pub consensus_hash: String,
123        /// State after txs from the previous block
124        pub app_hash: String,
125        /// Root hash of all results from the txs from the previous block
126        pub last_results_hash: Option<String>,
127        /// Hash of evidence included in the block
128        pub evidence_hash: Option<String>,
129        /// Original proposer of the block
130        pub proposer_address: String,
131    }
132
133    impl From<Header> for JsHeader {
134        fn from(value: Header) -> Self {
135            JsHeader {
136                version: value.version.into(),
137                chain_id: value.chain_id.to_string(),
138                height: value.height.value(),
139                time: value.time.to_rfc3339(),
140                last_block_id: value.last_block_id.map(Into::into),
141                last_commit_hash: value.last_commit_hash.map(|h| h.to_string()),
142                data_hash: value.data_hash.map(|h| h.to_string()),
143                validators_hash: value.validators_hash.to_string(),
144                next_validators_hash: value.next_validators_hash.to_string(),
145                consensus_hash: value.consensus_hash.to_string(),
146                app_hash: value.app_hash.to_string(),
147                last_results_hash: value.last_results_hash.map(|h| h.to_string()),
148                evidence_hash: value.evidence_hash.map(|h| h.to_string()),
149                proposer_address: value.proposer_address.to_string(),
150            }
151        }
152    }
153
154    /// Signed block headers
155    #[derive(Clone, Debug)]
156    #[wasm_bindgen(getter_with_clone, js_name = "SignedHeader")]
157    pub struct JsSignedHeader {
158        /// Signed block headers
159        pub header: JsHeader,
160        /// Commit containing signatures for the header
161        pub commit: JsCommit,
162    }
163
164    impl From<SignedHeader> for JsSignedHeader {
165        fn from(value: SignedHeader) -> Self {
166            JsSignedHeader {
167                header: value.header.into(),
168                commit: value.commit.into(),
169            }
170        }
171    }
172}
173
174#[cfg(feature = "uniffi")]
175pub mod uniffi_types {
176    use tendermint::block::parts::Header as TendermintPartsHeader;
177    use tendermint::block::signed_header::SignedHeader as TendermintSignedHeader;
178    use tendermint::block::{Header as TendermintHeader, Height};
179    use uniffi::Record;
180
181    use crate::block::commit::uniffi_types::Commit;
182    use crate::block::uniffi_types::BlockId;
183    use crate::error::UniffiConversionError;
184    use crate::hash::Hash;
185    use crate::hash::uniffi_types::AppHash;
186    use crate::state::UniffiAccountId;
187    use crate::uniffi_types::{ChainId, Time};
188
189    /// Version contains the protocol version for the blockchain and the application.
190    pub type HeaderVersion = tendermint::block::header::Version;
191
192    /// Version contains the protocol version for the blockchain and the application.
193    #[uniffi::remote(Record)]
194    pub struct HeaderVersion {
195        /// blockchain version
196        pub block: u64,
197        /// app version
198        pub app: u64,
199    }
200
201    /// Signed block headers
202    #[derive(Record)]
203    pub struct SignedHeader {
204        /// Signed block headers
205        pub header: Header,
206        /// Commit containing signatures for the header
207        pub commit: Commit,
208    }
209
210    impl TryFrom<TendermintSignedHeader> for SignedHeader {
211        type Error = UniffiConversionError;
212
213        fn try_from(value: TendermintSignedHeader) -> Result<Self, Self::Error> {
214            Ok(SignedHeader {
215                header: value.header.into(),
216                commit: value.commit.try_into()?,
217            })
218        }
219    }
220
221    impl TryFrom<SignedHeader> for TendermintSignedHeader {
222        type Error = UniffiConversionError;
223
224        fn try_from(value: SignedHeader) -> Result<Self, Self::Error> {
225            TendermintSignedHeader::new(value.header.try_into()?, value.commit.try_into()?)
226                .map_err(|_| UniffiConversionError::InvalidSignedHeader)
227        }
228    }
229
230    /// Block Header values contain metadata about the block and about the consensus,
231    /// as well as commitments to the data in the current block, the previous block,
232    /// and the results returned by the application.
233    #[derive(Record)]
234    pub struct Header {
235        /// Header version
236        pub version: HeaderVersion,
237        /// Chain ID
238        pub chain_id: ChainId,
239        /// Current block height
240        pub height: Height,
241        /// Current timestamp
242        pub time: Time,
243        /// Previous block info
244        pub last_block_id: Option<BlockId>,
245        /// Commit from validators from the last block
246        pub last_commit_hash: Option<Hash>,
247        /// Merkle root of transaction hashes
248        pub data_hash: Option<Hash>,
249        /// Validators for the current block
250        pub validators_hash: Hash,
251        /// Validators for the next block
252        pub next_validators_hash: Hash,
253        /// Consensus params for the current block
254        pub consensus_hash: Hash,
255        /// State after txs from the previous block
256        pub app_hash: AppHash,
257        /// Root hash of all results from the txs from the previous block
258        pub last_results_hash: Option<Hash>,
259        /// Hash of evidence included in the block
260        pub evidence_hash: Option<Hash>,
261        /// Original proposer of the block
262        pub proposer_address: UniffiAccountId,
263    }
264
265    impl TryFrom<Header> for TendermintHeader {
266        type Error = UniffiConversionError;
267
268        fn try_from(value: Header) -> std::result::Result<Self, Self::Error> {
269            Ok(TendermintHeader {
270                version: value.version,
271                chain_id: value.chain_id.try_into()?,
272                height: value.height,
273                time: value.time.try_into()?,
274                last_block_id: value.last_block_id.map(TryInto::try_into).transpose()?,
275                last_commit_hash: value.last_commit_hash,
276                data_hash: value.data_hash,
277                validators_hash: value.validators_hash,
278                next_validators_hash: value.next_validators_hash,
279                consensus_hash: value.consensus_hash,
280                app_hash: value.app_hash.try_into()?,
281                last_results_hash: value.last_results_hash,
282                evidence_hash: value.evidence_hash,
283                proposer_address: value.proposer_address.try_into()?,
284            })
285        }
286    }
287
288    impl From<TendermintHeader> for Header {
289        fn from(value: TendermintHeader) -> Self {
290            Header {
291                version: value.version,
292                chain_id: value.chain_id.into(),
293                height: value.height,
294                time: value.time.try_into().expect("valid time in tendermint"),
295                last_block_id: value.last_block_id.map(Into::into),
296                last_commit_hash: value.last_commit_hash,
297                data_hash: value.data_hash,
298                validators_hash: value.validators_hash,
299                next_validators_hash: value.next_validators_hash,
300                consensus_hash: value.consensus_hash,
301                app_hash: value.app_hash.into(),
302                last_results_hash: value.last_results_hash,
303                evidence_hash: value.evidence_hash,
304                proposer_address: value.proposer_address.into(),
305            }
306        }
307    }
308
309    uniffi::custom_type!(TendermintHeader, Header, {
310        remote,
311        try_lift: |value| Ok(value.try_into()?),
312        lower: |value| value.into()
313    });
314
315    /// Block parts header
316    #[derive(Record)]
317    pub struct PartsHeader {
318        /// Number of parts in this block
319        pub total: u32,
320        /// Hash of the parts set header
321        pub hash: Hash,
322    }
323
324    impl TryFrom<PartsHeader> for TendermintPartsHeader {
325        type Error = UniffiConversionError;
326
327        fn try_from(value: PartsHeader) -> Result<Self, Self::Error> {
328            TendermintPartsHeader::new(value.total, value.hash)
329                .map_err(|e| UniffiConversionError::InvalidPartsHeader { msg: e.to_string() })
330        }
331    }
332
333    impl From<TendermintPartsHeader> for PartsHeader {
334        fn from(value: TendermintPartsHeader) -> Self {
335            PartsHeader {
336                total: value.total,
337                hash: value.hash,
338            }
339        }
340    }
341}
342
343#[cfg(test)]
344mod tests {
345    use super::*;
346    use crate::hash::{Hash, HashExt};
347    use tendermint::block::Id;
348
349    #[cfg(target_arch = "wasm32")]
350    use wasm_bindgen_test::wasm_bindgen_test as test;
351
352    fn sample_header() -> Header {
353        serde_json::from_str(r#"{
354          "version": {
355            "block": "11",
356            "app": "1"
357          },
358          "chain_id": "private",
359          "height": "1",
360          "time": "2023-06-23T10:40:48.410305119Z",
361          "last_block_id": {
362            "hash": "",
363            "parts": {
364              "total": 0,
365              "hash": ""
366            }
367          },
368          "last_commit_hash": "E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855",
369          "data_hash": "3D96B7D238E7E0456F6AF8E7CDF0A67BD6CF9C2089ECB559C659DCAA1F880353",
370          "validators_hash": "64AEB6CA415A37540650FC04471974CE4FE88884CDD3300DF7BB27C1786871E9",
371          "next_validators_hash": "64AEB6CA415A37540650FC04471974CE4FE88884CDD3300DF7BB27C1786871E9",
372          "consensus_hash": "C0B6A634B72AE9687EA53B6D277A73ABA1386BA3CFC6D0F26963602F7F6FFCD6",
373          "app_hash": "E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855",
374          "last_results_hash": "E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855",
375          "evidence_hash": "E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855",
376          "proposer_address": "F1F83230835AA69A1AD6EA68C6D894A4106B8E53"
377        }"#).unwrap()
378    }
379
380    #[test]
381    fn header_validate_basic() {
382        sample_header().validate_basic().unwrap();
383    }
384
385    #[test]
386    fn header_validate_invalid_block_version() {
387        let mut header = sample_header();
388        header.version.block = 1;
389
390        header.validate_basic().unwrap_err();
391    }
392
393    #[test]
394    fn header_validate_zero_height() {
395        let mut header = sample_header();
396        header.height = 0u32.into();
397
398        header.validate_basic().unwrap_err();
399    }
400
401    #[test]
402    fn header_validate_missing_last_block_id() {
403        let mut header = sample_header();
404        header.height = 2u32.into();
405
406        header.validate_basic().unwrap_err();
407    }
408
409    #[test]
410    fn header_validate_genesis_with_last_block_id() {
411        let mut header = sample_header();
412
413        header.last_block_id = Some(Id {
414            hash: Hash::default_sha256(),
415            ..Id::default()
416        });
417
418        header.validate_basic().unwrap_err();
419    }
420}