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 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 #[derive(Clone, Copy, Debug)]
62 #[wasm_bindgen(js_name = "ProtocolVersion")]
63 pub struct JsHeaderVersion {
64 pub block: u64,
66 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 #[derive(Clone, Debug)]
80 #[wasm_bindgen(getter_with_clone, js_name = "PartsHeader")]
81 pub struct JsPartsHeader {
82 pub total: u32,
84 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 #[derive(Clone, Debug)]
101 #[wasm_bindgen(getter_with_clone, js_name = "Header")]
102 pub struct JsHeader {
103 pub version: JsHeaderVersion,
105 pub chain_id: String,
107 pub height: u64,
109 pub time: String,
111 pub last_block_id: Option<JsBlockId>,
113 pub last_commit_hash: Option<String>,
115 pub data_hash: Option<String>,
117 pub validators_hash: String,
119 pub next_validators_hash: String,
121 pub consensus_hash: String,
123 pub app_hash: String,
125 pub last_results_hash: Option<String>,
127 pub evidence_hash: Option<String>,
129 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 #[derive(Clone, Debug)]
156 #[wasm_bindgen(getter_with_clone, js_name = "SignedHeader")]
157 pub struct JsSignedHeader {
158 pub header: JsHeader,
160 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 pub type HeaderVersion = tendermint::block::header::Version;
191
192 #[uniffi::remote(Record)]
194 pub struct HeaderVersion {
195 pub block: u64,
197 pub app: u64,
199 }
200
201 #[derive(Record)]
203 pub struct SignedHeader {
204 pub header: Header,
206 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 #[derive(Record)]
234 pub struct Header {
235 pub version: HeaderVersion,
237 pub chain_id: ChainId,
239 pub height: Height,
241 pub time: Time,
243 pub last_block_id: Option<BlockId>,
245 pub last_commit_hash: Option<Hash>,
247 pub data_hash: Option<Hash>,
249 pub validators_hash: Hash,
251 pub next_validators_hash: Hash,
253 pub consensus_hash: Hash,
255 pub app_hash: AppHash,
257 pub last_results_hash: Option<Hash>,
259 pub evidence_hash: Option<Hash>,
261 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 #[derive(Record)]
317 pub struct PartsHeader {
318 pub total: u32,
320 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}