Skip to main content

celestia_types/
block.rs

1//! Blocks within the chains of a Tendermint network
2
3#[cfg(all(target_arch = "wasm32", feature = "wasm-bindgen"))]
4use crate::evidence::JsEvidence;
5use crate::{Error, Result};
6use celestia_proto::tendermint_celestia_mods::types::Block as RawBlock;
7use serde::{Deserialize, Serialize};
8use tendermint::block::{Commit, Header};
9use tendermint::evidence;
10use tendermint_proto::Protobuf;
11#[cfg(all(target_arch = "wasm32", feature = "wasm-bindgen"))]
12use wasm_bindgen::prelude::*;
13
14mod commit;
15mod data;
16pub(crate) mod header;
17
18pub use commit::CommitExt;
19#[cfg(all(target_arch = "wasm32", feature = "wasm-bindgen"))]
20pub use commit::{JsCommit, JsCommitSig};
21pub use data::Data;
22#[cfg(all(target_arch = "wasm32", feature = "wasm-bindgen"))]
23pub use header::{JsHeader, JsHeaderVersion, JsPartsHeader, JsSignedHeader};
24
25pub(crate) const GENESIS_HEIGHT: u64 = 1;
26
27/// The height of the block in Celestia network.
28pub type Height = tendermint::block::Height;
29
30/// Blocks consist of a header, transactions, votes (the commit), and a list of
31/// evidence of malfeasance (i.e. signing conflicting votes).
32///
33/// This is a modified version of [`tendermint::block::Block`] which contains
34/// [modifications](data-mod) that Celestia introduced.
35///
36/// [data-mod]: https://github.com/celestiaorg/celestia-core/blob/a1268f7ae3e688144a613c8a439dd31818aae07d/proto/tendermint/types/types.proto#L84-L104
37#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
38#[serde(try_from = "RawBlock", into = "RawBlock")]
39#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
40#[cfg_attr(
41    all(target_arch = "wasm32", feature = "wasm-bindgen"),
42    wasm_bindgen(getter_with_clone)
43)]
44pub struct Block {
45    /// Block header
46    #[cfg_attr(
47        all(target_arch = "wasm32", feature = "wasm-bindgen"),
48        wasm_bindgen(skip)
49    )]
50    pub header: Header,
51
52    /// Transaction data
53    pub data: Data,
54
55    /// Evidence of malfeasance
56    #[cfg_attr(
57        all(target_arch = "wasm32", feature = "wasm-bindgen"),
58        wasm_bindgen(skip)
59    )]
60    pub evidence: evidence::List,
61
62    /// Last commit, should be `None` for the initial block.
63    #[cfg_attr(
64        all(target_arch = "wasm32", feature = "wasm-bindgen"),
65        wasm_bindgen(skip)
66    )]
67    pub last_commit: Option<Commit>,
68}
69
70impl Block {
71    /// Builds a new [`Block`], based on the given [`Header`], [`Data`], evidence, and last commit.
72    pub fn new(
73        header: Header,
74        data: Data,
75        evidence: evidence::List,
76        last_commit: Option<Commit>,
77    ) -> Self {
78        Block {
79            header,
80            data,
81            evidence,
82            last_commit,
83        }
84    }
85
86    /// Get header
87    pub fn header(&self) -> &Header {
88        &self.header
89    }
90
91    /// Get data
92    pub fn data(&self) -> &Data {
93        &self.data
94    }
95
96    /// Get evidence
97    pub fn evidence(&self) -> &evidence::List {
98        &self.evidence
99    }
100
101    /// Get last commit
102    pub fn last_commit(&self) -> &Option<Commit> {
103        &self.last_commit
104    }
105}
106
107impl Protobuf<RawBlock> for Block {}
108
109impl TryFrom<RawBlock> for Block {
110    type Error = Error;
111
112    fn try_from(value: RawBlock) -> Result<Self, Self::Error> {
113        let header: Header = value
114            .header
115            .ok_or_else(tendermint::Error::missing_header)?
116            .try_into()?;
117
118        // If last_commit is the default Commit, it is considered nil by Go.
119        let last_commit = value
120            .last_commit
121            .map(TryInto::try_into)
122            .transpose()?
123            .filter(|c| c != &Commit::default());
124
125        Ok(Block::new(
126            header,
127            value
128                .data
129                .ok_or_else(tendermint::Error::missing_data)?
130                .try_into()?,
131            value
132                .evidence
133                .map(TryInto::try_into)
134                .transpose()?
135                .unwrap_or_default(),
136            last_commit,
137        ))
138    }
139}
140
141impl From<Block> for RawBlock {
142    fn from(value: Block) -> Self {
143        RawBlock {
144            header: Some(value.header.into()),
145            data: Some(value.data.into()),
146            evidence: Some(value.evidence.into()),
147            last_commit: value.last_commit.map(Into::into),
148        }
149    }
150}
151
152#[cfg(all(target_arch = "wasm32", feature = "wasm-bindgen"))]
153#[wasm_bindgen]
154impl Block {
155    /// Block header
156    #[wasm_bindgen(getter, js_name = header)]
157    pub fn js_get_header(&self) -> JsHeader {
158        self.header.clone().into()
159    }
160
161    /// Evidence of malfeasance
162    #[wasm_bindgen(getter, js_name = evidence)]
163    pub fn js_get_evidence(&self) -> Vec<JsEvidence> {
164        self.evidence
165            .iter()
166            .map(|e| JsEvidence::from(e.clone()))
167            .collect()
168    }
169
170    /// Last commit, should be `None` for the initial block.
171    #[wasm_bindgen(getter, js_name = lastCommit)]
172    pub fn js_get_last_commit(&self) -> Option<JsCommit> {
173        self.last_commit.clone().map(Into::into)
174    }
175}
176
177#[cfg(all(target_arch = "wasm32", feature = "wasm-bindgen"))]
178pub use wbg::*;
179
180#[cfg(all(target_arch = "wasm32", feature = "wasm-bindgen"))]
181mod wbg {
182    use tendermint::block;
183    use wasm_bindgen::prelude::*;
184
185    use crate::block::header::JsPartsHeader;
186
187    /// Block identifiers which contain two distinct Merkle roots of the block, as well as the number of parts in the block.
188    #[derive(Clone, Debug)]
189    #[wasm_bindgen(getter_with_clone, js_name = "BlockId")]
190    pub struct JsBlockId {
191        /// The block’s main hash is the Merkle root of all the fields in the block header.
192        pub hash: String,
193        /// Parts header (if available) is used for secure gossipping of the block during
194        /// consensus. It is the Merkle root of the complete serialized block cut into parts.
195        ///
196        /// PartSet is used to split a byteslice of data into parts (pieces) for transmission.
197        /// By splitting data into smaller parts and computing a Merkle root hash on the list,
198        /// you can verify that a part is legitimately part of the complete data, and the part
199        /// can be forwarded to other peers before all the parts are known. In short, it’s
200        /// a fast way to propagate a large file over a gossip network.
201        ///
202        /// <https://github.com/tendermint/tendermint/wiki/Block-Structure#partset>
203        ///
204        /// PartSetHeader in protobuf is defined as never nil using the gogoproto annotations.
205        /// This does not translate to Rust, but we can indicate this in the domain type.
206        pub part_set_header: JsPartsHeader,
207    }
208
209    impl From<block::Id> for JsBlockId {
210        fn from(value: block::Id) -> Self {
211            JsBlockId {
212                hash: value.hash.to_string(),
213                part_set_header: value.part_set_header.into(),
214            }
215        }
216    }
217}
218
219/// uniffi conversion types
220#[cfg(feature = "uniffi")]
221pub mod uniffi_types {
222    use tendermint::block::Height as TendermintHeight;
223    use tendermint::block::Id as TendermintBlockId;
224    use tendermint::hash::Hash;
225    use uniffi::Record;
226
227    use crate::block::header::uniffi_types::PartsHeader;
228    use crate::error::UniffiConversionError;
229
230    /// Block identifiers which contain two distinct Merkle roots of the block, as well as the number of parts in the block.
231    #[derive(Record)]
232    pub struct BlockId {
233        /// The block’s main hash is the Merkle root of all the fields in the block header.
234        pub hash: Hash,
235        /// Parts header (if available) is used for secure gossipping of the block during
236        /// consensus. It is the Merkle root of the complete serialized block cut into parts.
237        ///
238        /// PartSet is used to split a byteslice of data into parts (pieces) for transmission.
239        /// By splitting data into smaller parts and computing a Merkle root hash on the list,
240        /// you can verify that a part is legitimately part of the complete data, and the part
241        /// can be forwarded to other peers before all the parts are known. In short, it’s
242        /// a fast way to propagate a large file over a gossip network.
243        ///
244        /// <https://github.com/tendermint/tendermint/wiki/Block-Structure#partset>
245        ///
246        /// PartSetHeader in protobuf is defined as never nil using the gogoproto annotations.
247        /// This does not translate to Rust, but we can indicate this in the domain type.
248        pub part_set_header: PartsHeader,
249    }
250
251    impl TryFrom<BlockId> for TendermintBlockId {
252        type Error = UniffiConversionError;
253
254        fn try_from(value: BlockId) -> Result<Self, Self::Error> {
255            Ok(TendermintBlockId {
256                hash: value.hash,
257                part_set_header: value.part_set_header.try_into()?,
258            })
259        }
260    }
261
262    impl From<TendermintBlockId> for BlockId {
263        fn from(value: TendermintBlockId) -> Self {
264            BlockId {
265                hash: value.hash,
266                part_set_header: value.part_set_header.into(),
267            }
268        }
269    }
270
271    uniffi::custom_type!(TendermintBlockId, BlockId, {
272        remote,
273        try_lift: |value| Ok(value.try_into()?),
274        lower: |value| value.into()
275    });
276
277    /// Block height for a particular chain (i.e. number of blocks created since the chain began)
278    ///
279    /// A height of 0 represents a chain which has not yet produced a block.
280    #[derive(Record)]
281    pub struct BlockHeight {
282        /// Height value
283        pub value: u64,
284    }
285
286    impl TryFrom<BlockHeight> for TendermintHeight {
287        type Error = UniffiConversionError;
288
289        fn try_from(value: BlockHeight) -> Result<Self, Self::Error> {
290            TendermintHeight::try_from(value.value)
291                .map_err(|_| UniffiConversionError::HeaderHeightOutOfRange)
292        }
293    }
294
295    impl From<TendermintHeight> for BlockHeight {
296        fn from(value: TendermintHeight) -> Self {
297            BlockHeight {
298                value: value.value(),
299            }
300        }
301    }
302
303    uniffi::custom_type!(TendermintHeight, BlockHeight, {
304        remote,
305        try_lift: |value| Ok(value.try_into()?),
306        lower: |value| value.into()
307    });
308}