tendermint/block/
id.rs

1use core::{
2    fmt::{self, Display},
3    str::{self, FromStr},
4};
5
6use serde::{Deserialize, Serialize};
7use tendermint_proto::v0_37::types::BlockId as RawBlockId;
8
9use crate::{
10    block::parts::Header as PartSetHeader,
11    error::Error,
12    hash::{Algorithm, Hash},
13    prelude::*,
14};
15
16/// Length of a block ID prefix displayed for debugging purposes
17pub const PREFIX_LENGTH: usize = 10;
18
19/// Block identifiers which contain two distinct Merkle roots of the block,
20/// as well as the number of parts in the block.
21///
22/// <https://github.com/tendermint/spec/blob/d46cd7f573a2c6a2399fcab2cde981330aa63f37/spec/core/data_structures.md#blockid>
23///
24/// Default implementation is an empty Id as defined by the Go implementation in
25/// <https://github.com/tendermint/tendermint/blob/1635d1339c73ae6a82e062cd2dc7191b029efa14/types/block.go#L1204>.
26///
27/// If the Hash is empty in BlockId, the BlockId should be empty (encoded to None).
28/// This is implemented outside of this struct. Use the Default trait to check for an empty BlockId.
29/// See: <https://github.com/informalsystems/tendermint-rs/issues/663>
30#[derive(
31    Serialize, Deserialize, Copy, Clone, Debug, Default, Hash, Eq, PartialEq, PartialOrd, Ord,
32)]
33#[serde(try_from = "RawBlockId", into = "RawBlockId")]
34pub struct Id {
35    /// The block's main hash is the Merkle root of all the fields in the
36    /// block header.
37    pub hash: Hash,
38
39    /// Parts header (if available) is used for secure gossipping of the block
40    /// during consensus. It is the Merkle root of the complete serialized block
41    /// cut into parts.
42    ///
43    /// PartSet is used to split a byteslice of data into parts (pieces) for
44    /// transmission. By splitting data into smaller parts and computing a
45    /// Merkle root hash on the list, you can verify that a part is
46    /// legitimately part of the complete data, and the part can be forwarded
47    /// to other peers before all the parts are known. In short, it's a fast
48    /// way to propagate a large file over a gossip network.
49    ///
50    /// <https://github.com/tendermint/tendermint/wiki/Block-Structure#partset>
51    ///
52    /// PartSetHeader in protobuf is defined as never nil using the gogoproto
53    /// annotations. This does not translate to Rust, but we can indicate this
54    /// in the domain type.
55    pub part_set_header: PartSetHeader,
56}
57
58tendermint_pb_modules! {
59    use pb::{
60        types::{
61            BlockId as RawBlockId, CanonicalBlockId as RawCanonicalBlockId,
62            PartSetHeader as RawPartSetHeader,
63        }
64    };
65    use super::Id;
66    use crate::{prelude::*, Error};
67
68    impl Protobuf<RawBlockId> for Id {}
69
70    impl TryFrom<RawBlockId> for Id {
71        type Error = Error;
72
73        fn try_from(value: RawBlockId) -> Result<Self, Self::Error> {
74            if value.part_set_header.is_none() {
75                return Err(Error::invalid_part_set_header(
76                    "part_set_header is None".to_string(),
77                ));
78            }
79            Ok(Self {
80                hash: value.hash.try_into()?,
81                part_set_header: value.part_set_header.unwrap().try_into()?,
82            })
83        }
84    }
85
86    impl From<Id> for RawBlockId {
87        fn from(value: Id) -> Self {
88            // https://github.com/tendermint/tendermint/blob/1635d1339c73ae6a82e062cd2dc7191b029efa14/types/block.go#L1204
89            // The Go implementation encodes a nil value into an empty struct. We try our best to
90            // anticipate an empty struct by using the default implementation which would otherwise be
91            // invalid.
92            if value == Id::default() {
93                RawBlockId {
94                    hash: vec![],
95                    part_set_header: Some(RawPartSetHeader {
96                        total: 0,
97                        hash: vec![],
98                    }),
99                }
100            } else {
101                RawBlockId {
102                    hash: value.hash.into(),
103                    part_set_header: Some(value.part_set_header.into()),
104                }
105            }
106        }
107    }
108
109    impl TryFrom<RawCanonicalBlockId> for Id {
110        type Error = Error;
111
112        fn try_from(value: RawCanonicalBlockId) -> Result<Self, Self::Error> {
113            if value.part_set_header.is_none() {
114                return Err(Error::invalid_part_set_header(
115                    "part_set_header is None".to_string(),
116                ));
117            }
118            Ok(Self {
119                hash: value.hash.try_into()?,
120                part_set_header: value.part_set_header.unwrap().try_into()?,
121            })
122        }
123    }
124
125    impl From<Id> for RawCanonicalBlockId {
126        fn from(value: Id) -> Self {
127            RawCanonicalBlockId {
128                hash: value.hash.as_bytes().to_vec(),
129                part_set_header: Some(value.part_set_header.into()),
130            }
131        }
132    }
133}
134
135impl Id {
136    /// Get a shortened 12-character prefix of a block ID (ala git)
137    pub fn prefix(&self) -> String {
138        let mut result = self.to_string();
139        result.truncate(PREFIX_LENGTH);
140        result
141    }
142}
143
144// TODO: match gaia serialization? e.g `D2F5991B98D708FD2C25AA2BEBED9358F24177DE:1:C37A55FB95E9`
145impl Display for Id {
146    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
147        write!(f, "{}", &self.hash)
148    }
149}
150
151// TODO: match gaia serialization?
152impl FromStr for Id {
153    type Err = Error;
154
155    fn from_str(s: &str) -> Result<Self, Error> {
156        Ok(Self {
157            hash: Hash::from_hex_upper(Algorithm::Sha256, s)?,
158            part_set_header: PartSetHeader::default(),
159        })
160    }
161}
162
163/// Parse `block::Id` from a type
164pub trait ParseId {
165    /// Parse `block::Id`, or return an `Error` if parsing failed
166    fn parse_block_id(&self) -> Result<Id, Error>;
167}
168
169#[cfg(test)]
170mod tests {
171    use super::*;
172
173    const EXAMPLE_SHA256_ID: &str =
174        "26C0A41F3243C6BCD7AD2DFF8A8D83A71D29D307B5326C227F734A1A512FE47D";
175
176    #[test]
177    fn parses_hex_strings() {
178        let id = Id::from_str(EXAMPLE_SHA256_ID).unwrap();
179        assert_eq!(
180            id.hash.as_bytes(),
181            b"\x26\xC0\xA4\x1F\x32\x43\xC6\xBC\xD7\xAD\x2D\xFF\x8A\x8D\x83\xA7\
182              \x1D\x29\xD3\x07\xB5\x32\x6C\x22\x7F\x73\x4A\x1A\x51\x2F\xE4\x7D"
183                .as_ref()
184        );
185    }
186
187    #[test]
188    fn serializes_hex_strings() {
189        let id = Id::from_str(EXAMPLE_SHA256_ID).unwrap();
190        assert_eq!(&id.to_string(), EXAMPLE_SHA256_ID)
191    }
192}