1use core::{
2 fmt::{self, Display},
3 str::{self, FromStr},
4};
5
6use cometbft_proto::types::v1::BlockId as RawBlockId;
7use serde::{Deserialize, Serialize};
8
9use crate::{
10 block::parts::Header as PartSetHeader,
11 error::Error,
12 hash::{Algorithm, Hash},
13 prelude::*,
14};
15
16pub const PREFIX_LENGTH: usize = 10;
18
19#[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 pub hash: Hash,
38
39 pub part_set_header: PartSetHeader,
56}
57
58cometbft_old_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 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
135mod v1 {
136 use super::Id;
137 use crate::{prelude::*, Error};
138 use cometbft_proto::types::v1::{
139 BlockId as RawBlockId, CanonicalBlockId as RawCanonicalBlockId,
140 PartSetHeader as RawPartSetHeader,
141 };
142 use cometbft_proto::Protobuf;
143
144 impl Protobuf<RawBlockId> for Id {}
145
146 impl TryFrom<RawBlockId> for Id {
147 type Error = Error;
148
149 fn try_from(value: RawBlockId) -> Result<Self, Self::Error> {
150 if value.part_set_header.is_none() {
151 return Err(Error::invalid_part_set_header(
152 "part_set_header is None".to_string(),
153 ));
154 }
155 Ok(Self {
156 hash: value.hash.try_into()?,
157 part_set_header: value.part_set_header.unwrap().try_into()?,
158 })
159 }
160 }
161
162 impl From<Id> for RawBlockId {
163 fn from(value: Id) -> Self {
164 if value == Id::default() {
169 RawBlockId {
170 hash: vec![],
171 part_set_header: Some(RawPartSetHeader {
172 total: 0,
173 hash: vec![],
174 }),
175 }
176 } else {
177 RawBlockId {
178 hash: value.hash.into(),
179 part_set_header: Some(value.part_set_header.into()),
180 }
181 }
182 }
183 }
184
185 impl TryFrom<RawCanonicalBlockId> for Id {
186 type Error = Error;
187
188 fn try_from(value: RawCanonicalBlockId) -> Result<Self, Self::Error> {
189 if value.part_set_header.is_none() {
190 return Err(Error::invalid_part_set_header(
191 "part_set_header is None".to_string(),
192 ));
193 }
194 Ok(Self {
195 hash: value.hash.try_into()?,
196 part_set_header: value.part_set_header.unwrap().try_into()?,
197 })
198 }
199 }
200
201 impl From<Id> for RawCanonicalBlockId {
202 fn from(value: Id) -> Self {
203 RawCanonicalBlockId {
204 hash: value.hash.as_bytes().to_vec(),
205 part_set_header: Some(value.part_set_header.into()),
206 }
207 }
208 }
209}
210
211mod v1beta1 {
212 use super::Id;
213 use crate::{prelude::*, Error};
214 use cometbft_proto::types::v1beta1::{
215 BlockId as RawBlockId, CanonicalBlockId as RawCanonicalBlockId,
216 PartSetHeader as RawPartSetHeader,
217 };
218 use cometbft_proto::Protobuf;
219
220 impl Protobuf<RawBlockId> for Id {}
221
222 impl TryFrom<RawBlockId> for Id {
223 type Error = Error;
224
225 fn try_from(value: RawBlockId) -> Result<Self, Self::Error> {
226 if value.part_set_header.is_none() {
227 return Err(Error::invalid_part_set_header(
228 "part_set_header is None".to_string(),
229 ));
230 }
231 Ok(Self {
232 hash: value.hash.try_into()?,
233 part_set_header: value.part_set_header.unwrap().try_into()?,
234 })
235 }
236 }
237
238 impl From<Id> for RawBlockId {
239 fn from(value: Id) -> Self {
240 if value == Id::default() {
245 RawBlockId {
246 hash: vec![],
247 part_set_header: Some(RawPartSetHeader {
248 total: 0,
249 hash: vec![],
250 }),
251 }
252 } else {
253 RawBlockId {
254 hash: value.hash.into(),
255 part_set_header: Some(value.part_set_header.into()),
256 }
257 }
258 }
259 }
260
261 impl TryFrom<RawCanonicalBlockId> for Id {
262 type Error = Error;
263
264 fn try_from(value: RawCanonicalBlockId) -> Result<Self, Self::Error> {
265 if value.part_set_header.is_none() {
266 return Err(Error::invalid_part_set_header(
267 "part_set_header is None".to_string(),
268 ));
269 }
270 Ok(Self {
271 hash: value.hash.try_into()?,
272 part_set_header: value.part_set_header.unwrap().try_into()?,
273 })
274 }
275 }
276
277 impl From<Id> for RawCanonicalBlockId {
278 fn from(value: Id) -> Self {
279 RawCanonicalBlockId {
280 hash: value.hash.as_bytes().to_vec(),
281 part_set_header: Some(value.part_set_header.into()),
282 }
283 }
284 }
285}
286
287impl Id {
288 pub fn prefix(&self) -> String {
290 let mut result = self.to_string();
291 result.truncate(PREFIX_LENGTH);
292 result
293 }
294}
295
296impl Display for Id {
298 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
299 write!(f, "{}", &self.hash)
300 }
301}
302
303impl FromStr for Id {
305 type Err = Error;
306
307 fn from_str(s: &str) -> Result<Self, Error> {
308 Ok(Self {
309 hash: Hash::from_hex_upper(Algorithm::Sha256, s)?,
310 part_set_header: PartSetHeader::default(),
311 })
312 }
313}
314
315pub trait ParseId {
317 fn parse_block_id(&self) -> Result<Id, Error>;
319}
320
321#[cfg(test)]
322mod tests {
323 use super::*;
324
325 const EXAMPLE_SHA256_ID: &str =
326 "26C0A41F3243C6BCD7AD2DFF8A8D83A71D29D307B5326C227F734A1A512FE47D";
327
328 #[test]
329 fn parses_hex_strings() {
330 let id = Id::from_str(EXAMPLE_SHA256_ID).unwrap();
331 assert_eq!(
332 id.hash.as_bytes(),
333 b"\x26\xC0\xA4\x1F\x32\x43\xC6\xBC\xD7\xAD\x2D\xFF\x8A\x8D\x83\xA7\
334 \x1D\x29\xD3\x07\xB5\x32\x6C\x22\x7F\x73\x4A\x1A\x51\x2F\xE4\x7D"
335 .as_ref()
336 );
337 }
338
339 #[test]
340 fn serializes_hex_strings() {
341 let id = Id::from_str(EXAMPLE_SHA256_ID).unwrap();
342 assert_eq!(&id.to_string(), EXAMPLE_SHA256_ID)
343 }
344}