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