celestia_core/block/
header.rs

1//! Block headers
2
3use celestia_core_proto::v0_34::{
4    types::{BlockId as RawBlockId, Header as RawHeader},
5    version::Consensus as RawConsensusVersion,
6};
7use celestia_core_proto::Protobuf;
8use serde::{Deserialize, Serialize};
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: Hash,
43
44    /// Merkle root of transaction hashes
45    pub data_hash: 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: Hash,
61
62    /// Hash of evidence included in the block
63    pub evidence_hash: 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).unwrap(),
88            self.chain_id.encode_vec().unwrap(),
89            self.height.encode_vec().unwrap(),
90            self.time.encode_vec().unwrap(),
91            Protobuf::<RawBlockId>::encode_vec(&self.last_block_id.unwrap_or_default()).unwrap(),
92            self.last_commit_hash.encode_vec().unwrap(),
93            self.data_hash.encode_vec().unwrap(),
94            self.validators_hash.encode_vec().unwrap(),
95            self.next_validators_hash.encode_vec().unwrap(),
96            self.consensus_hash.encode_vec().unwrap(),
97            self.app_hash.encode_vec().unwrap(),
98            self.last_results_hash.encode_vec().unwrap(),
99            self.evidence_hash.encode_vec().unwrap(),
100            self.proposer_address.encode_vec().unwrap(),
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 height: block::Height = value.height.try_into()?;
145
146            // Todo: fix domain logic
147            // if last_block_id.is_none() && height.value() != 1 {
148            //    return Err(Kind::InvalidHeader.context("last_block_id is null on non-first
149            // height").into());
150            //}
151            if last_block_id.is_some() && height.value() == 1 {
152                return Err(Error::invalid_first_header());
153            }
154            // if last_commit_hash.is_none() && height.value() != 1 {
155            //    return Err(Kind::InvalidHeader.context("last_commit_hash is null on non-first
156            // height").into());
157            //}
158            // if height.value() == 1 && last_commit_hash.is_some() &&
159            // last_commit_hash.as_ref().unwrap() != simple_hash_from_byte_vectors(Vec::new()) {
160            //    return Err(Kind::InvalidFirstHeader.context("last_commit_hash is not empty Merkle tree
161            // on first height").into());
162            //}
163            // if last_results_hash.is_none() && height.value() != 1 {
164            //    return Err(Kind::InvalidHeader.context("last_results_hash is null on non-first
165            // height").into());
166            //}
167            // if last_results_hash.is_some() && height.value() == 1 {
168            //    return Err(Kind::InvalidFirstHeader.context("last_results_hash is not ull on first
169            // height").into());
170            //}
171            Ok(Header {
172                version: value.version.ok_or_else(Error::missing_version)?.into(),
173                chain_id: value.chain_id.try_into()?,
174                height,
175                time: value
176                    .time
177                    .ok_or_else(Error::missing_timestamp)?
178                    .try_into()?,
179                last_block_id,
180                last_commit_hash: value.last_commit_hash.try_into()?,
181                data_hash: value.data_hash.try_into()?,
182                validators_hash: value.validators_hash.try_into()?,
183                next_validators_hash: value.next_validators_hash.try_into()?,
184                consensus_hash: value.consensus_hash.try_into()?,
185                app_hash: value.app_hash.try_into()?,
186                last_results_hash: value.last_results_hash.try_into()?,
187                evidence_hash: value.evidence_hash.try_into()?, // Todo: Is it illegal to have evidence of wrongdoing in the first block?
188                proposer_address: value.proposer_address.try_into()?,
189            })
190        }
191    }
192
193    impl From<Header> for RawHeader {
194        fn from(value: Header) -> Self {
195            RawHeader {
196                version: Some(value.version.into()),
197                chain_id: value.chain_id.into(),
198                height: value.height.into(),
199                time: Some(value.time.into()),
200                last_block_id: value.last_block_id.map(Into::into),
201                last_commit_hash: value.last_commit_hash.into(),
202                data_hash: value.data_hash.into(),
203                validators_hash: value.validators_hash.into(),
204                next_validators_hash: value.next_validators_hash.into(),
205                consensus_hash: value.consensus_hash.into(),
206                app_hash: value.app_hash.into(),
207                last_results_hash: value.last_results_hash.into(),
208                evidence_hash: value.evidence_hash.into(),
209                proposer_address: value.proposer_address.into(),
210            }
211        }
212    }
213
214    impl Protobuf<RawConsensusVersion> for Version {}
215
216    impl From<RawConsensusVersion> for Version {
217        fn from(value: RawConsensusVersion) -> Self {
218            Version {
219                block: value.block,
220                app: value.app,
221            }
222        }
223    }
224
225    impl From<Version> for RawConsensusVersion {
226        fn from(value: Version) -> Self {
227            RawConsensusVersion {
228                block: value.block,
229                app: value.app,
230            }
231        }
232    }
233}