1use serde::{Deserialize, Serialize};
4use tendermint_proto::v0_37::{
5 types::{BlockId as RawBlockId, Header as RawHeader},
6 version::Consensus as RawConsensusVersion,
7};
8use tendermint_proto::Protobuf;
9
10use crate::{
11 account, block, chain,
12 crypto::Sha256,
13 merkle::{self, MerkleHash},
14 prelude::*,
15 AppHash, Hash, Time,
16};
17
18#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
24#[serde(try_from = "RawHeader", into = "RawHeader")]
25pub struct Header {
26 pub version: Version,
28
29 pub chain_id: chain::Id,
31
32 pub height: block::Height,
34
35 pub time: Time,
37
38 pub last_block_id: Option<block::Id>,
40
41 pub last_commit_hash: Option<Hash>,
43
44 pub data_hash: Option<Hash>,
46
47 pub validators_hash: Hash,
49
50 pub next_validators_hash: Hash,
52
53 pub consensus_hash: Hash,
55
56 pub app_hash: AppHash,
58
59 pub last_results_hash: Option<Hash>,
61
62 pub evidence_hash: Option<Hash>,
64
65 pub proposer_address: account::Id,
67}
68
69impl Header {
70 #[cfg(feature = "rust-crypto")]
72 pub fn hash(&self) -> Hash {
73 self.hash_with::<crate::crypto::default::Sha256>()
74 }
75
76 pub fn hash_with<H>(&self) -> Hash
78 where
79 H: MerkleHash + Sha256 + Default,
80 {
81 let fields_bytes = vec![
87 Protobuf::<RawConsensusVersion>::encode_vec(self.version),
88 self.chain_id.clone().encode_vec(),
89 self.height.encode_vec(),
90 self.time.encode_vec(),
91 Protobuf::<RawBlockId>::encode_vec(self.last_block_id.unwrap_or_default()),
92 self.last_commit_hash.unwrap_or_default().encode_vec(),
93 self.data_hash.unwrap_or_default().encode_vec(),
94 self.validators_hash.encode_vec(),
95 self.next_validators_hash.encode_vec(),
96 self.consensus_hash.encode_vec(),
97 self.app_hash.clone().encode_vec(),
98 self.last_results_hash.unwrap_or_default().encode_vec(),
99 self.evidence_hash.unwrap_or_default().encode_vec(),
100 self.proposer_address.encode_vec(),
101 ];
102
103 Hash::Sha256(merkle::simple_hash_from_byte_vectors::<H>(&fields_bytes))
104 }
105}
106
107#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
112pub struct Version {
113 pub block: u64,
115
116 pub app: u64,
118}
119
120tendermint_pb_modules! {
125 use super::{Header, Version};
126 use crate::{block, Error};
127 use pb::{
128 types::Header as RawHeader,
129 version::Consensus as RawConsensusVersion,
130 };
131
132 impl Protobuf<RawHeader> for Header {}
133
134 impl TryFrom<RawHeader> for Header {
135 type Error = Error;
136
137 fn try_from(value: RawHeader) -> Result<Self, Self::Error> {
138 let last_block_id = value
140 .last_block_id
141 .map(TryInto::try_into)
142 .transpose()?
143 .filter(|l| l != &block::Id::default());
144 let last_commit_hash = if value.last_commit_hash.is_empty() {
145 None
146 } else {
147 Some(value.last_commit_hash.try_into()?)
148 };
149 let last_results_hash = if value.last_results_hash.is_empty() {
150 None
151 } else {
152 Some(value.last_results_hash.try_into()?)
153 };
154 let height: block::Height = value.height.try_into()?;
155
156 if last_block_id.is_some() && height.value() == 1 {
162 return Err(Error::invalid_first_header());
163 }
164 Ok(Header {
182 version: value.version.ok_or_else(Error::missing_version)?.into(),
183 chain_id: value.chain_id.try_into()?,
184 height,
185 time: value
186 .time
187 .ok_or_else(Error::missing_timestamp)?
188 .try_into()?,
189 last_block_id,
190 last_commit_hash,
191 data_hash: if value.data_hash.is_empty() {
192 None
193 } else {
194 Some(value.data_hash.try_into()?)
195 },
196 validators_hash: value.validators_hash.try_into()?,
197 next_validators_hash: value.next_validators_hash.try_into()?,
198 consensus_hash: value.consensus_hash.try_into()?,
199 app_hash: value.app_hash.try_into()?,
200 last_results_hash,
201 evidence_hash: if value.evidence_hash.is_empty() {
202 None
203 } else {
204 Some(value.evidence_hash.try_into()?)
205 }, proposer_address: value.proposer_address.try_into()?,
207 })
208 }
209 }
210
211 impl From<Header> for RawHeader {
212 fn from(value: Header) -> Self {
213 RawHeader {
214 version: Some(value.version.into()),
215 chain_id: value.chain_id.into(),
216 height: value.height.into(),
217 time: Some(value.time.into()),
218 last_block_id: value.last_block_id.map(Into::into),
219 last_commit_hash: value.last_commit_hash.unwrap_or_default().into(),
220 data_hash: value.data_hash.unwrap_or_default().into(),
221 validators_hash: value.validators_hash.into(),
222 next_validators_hash: value.next_validators_hash.into(),
223 consensus_hash: value.consensus_hash.into(),
224 app_hash: value.app_hash.into(),
225 last_results_hash: value.last_results_hash.unwrap_or_default().into(),
226 evidence_hash: value.evidence_hash.unwrap_or_default().into(),
227 proposer_address: value.proposer_address.into(),
228 }
229 }
230 }
231
232 impl Protobuf<RawConsensusVersion> for Version {}
233
234 impl From<RawConsensusVersion> for Version {
235 fn from(value: RawConsensusVersion) -> Self {
236 Version {
237 block: value.block,
238 app: value.app,
239 }
240 }
241 }
242
243 impl From<Version> for RawConsensusVersion {
244 fn from(value: Version) -> Self {
245 RawConsensusVersion {
246 block: value.block,
247 app: value.app,
248 }
249 }
250 }
251}
252
253#[cfg(test)]
254mod tests {
255 use super::Header;
256 use crate::test::test_serialization_roundtrip;
257
258 #[test]
259 fn serialization_roundtrip() {
260 let json_data = include_str!("../../tests/support/serialization/block/header.json");
261 test_serialization_roundtrip::<Header>(json_data);
262 }
263
264 #[cfg(feature = "rust-crypto")]
265 mod crypto {
266 use super::*;
267 use crate::{hash::Algorithm, Hash};
268
269 #[test]
270 fn header_hashing() {
271 let expected_hash = Hash::from_hex_upper(
272 Algorithm::Sha256,
273 "F30A71F2409FB15AACAEDB6CC122DFA2525BEE9CAE521721B06BFDCA291B8D56",
274 )
275 .unwrap();
276 let header: Header = serde_json::from_str(include_str!(
277 "../../tests/support/serialization/block/header_with_known_hash.json"
278 ))
279 .unwrap();
280 assert_eq!(expected_hash, header.hash());
281 }
282 }
283}