tendermint/block/
header.rs

1//! Block headers
2
3use 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/// Block `Header` values contain metadata about the block and about the
19/// consensus, as well as commitments to the data in the current block, the
20/// previous block, and the results returned by the application.
21///
22/// <https://github.com/tendermint/spec/blob/d46cd7f573a2c6a2399fcab2cde981330aa63f37/spec/core/data_structures.md#header>
23#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
24#[serde(try_from = "RawHeader", into = "RawHeader")]
25pub struct Header {
26    /// Header version
27    pub version: Version,
28
29    /// Chain ID
30    pub chain_id: chain::Id,
31
32    /// Current block height
33    pub height: block::Height,
34
35    /// Current timestamp
36    pub time: Time,
37
38    /// Previous block info
39    pub last_block_id: Option<block::Id>,
40
41    /// Commit from validators from the last block
42    pub last_commit_hash: Option<Hash>,
43
44    /// Merkle root of transaction hashes
45    pub data_hash: Option<Hash>,
46
47    /// Validators for the current block
48    pub validators_hash: Hash,
49
50    /// Validators for the next block
51    pub next_validators_hash: Hash,
52
53    /// Consensus params for the current block
54    pub consensus_hash: Hash,
55
56    /// State after txs from the previous block
57    pub app_hash: AppHash,
58
59    /// Root hash of all results from the txs from the previous block
60    pub last_results_hash: Option<Hash>,
61
62    /// Hash of evidence included in the block
63    pub evidence_hash: Option<Hash>,
64
65    /// Original proposer of the block
66    pub proposer_address: account::Id,
67}
68
69impl Header {
70    /// Computes the hash of this header.
71    #[cfg(feature = "rust-crypto")]
72    pub fn hash(&self) -> Hash {
73        self.hash_with::<crate::crypto::default::Sha256>()
74    }
75
76    /// Hash this header with a Merkle hasher provided by a crypto provider.
77    pub fn hash_with<H>(&self) -> Hash
78    where
79        H: MerkleHash + Sha256 + Default,
80    {
81        // Note that if there is an encoding problem this will
82        // panic (as the golang code would):
83        // https://github.com/tendermint/tendermint/blob/134fe2896275bb926b49743c1e25493f6b24cc31/types/block.go#L393
84        // https://github.com/tendermint/tendermint/blob/134fe2896275bb926b49743c1e25493f6b24cc31/types/encoding_helper.go#L9:6
85
86        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/// `Version` contains the protocol version for the blockchain and the
108/// application.
109///
110/// <https://github.com/tendermint/spec/blob/d46cd7f573a2c6a2399fcab2cde981330aa63f37/spec/core/data_structures.md#version>
111#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
112pub struct Version {
113    /// Block version
114    pub block: u64,
115
116    /// App version
117    pub app: u64,
118}
119
120// =============================================================================
121// Protobuf conversions
122// =============================================================================
123
124tendermint_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            // If last block id is unfilled, it is considered nil by Go.
139            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            // Todo: fix domain logic
157            // if last_block_id.is_none() && height.value() != 1 {
158            //    return Err(Kind::InvalidHeader.context("last_block_id is null on non-first
159            // height").into());
160            //}
161            if last_block_id.is_some() && height.value() == 1 {
162                return Err(Error::invalid_first_header());
163            }
164            // if last_commit_hash.is_none() && height.value() != 1 {
165            //    return Err(Kind::InvalidHeader.context("last_commit_hash is null on non-first
166            // height").into());
167            //}
168            // if height.value() == 1 && last_commit_hash.is_some() &&
169            // last_commit_hash.as_ref().unwrap() != simple_hash_from_byte_vectors(Vec::new()) {
170            //    return Err(Kind::InvalidFirstHeader.context("last_commit_hash is not empty Merkle tree
171            // on first height").into());
172            //}
173            // if last_results_hash.is_none() && height.value() != 1 {
174            //    return Err(Kind::InvalidHeader.context("last_results_hash is null on non-first
175            // height").into());
176            //}
177            // if last_results_hash.is_some() && height.value() == 1 {
178            //    return Err(Kind::InvalidFirstHeader.context("last_results_hash is not ull on first
179            // height").into());
180            //}
181            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                }, // Todo: Is it illegal to have evidence of wrongdoing in the first block?
206                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}