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
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
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 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 pub fn prefix(&self) -> String {
138 let mut result = self.to_string();
139 result.truncate(PREFIX_LENGTH);
140 result
141 }
142}
143
144impl Display for Id {
146 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
147 write!(f, "{}", &self.hash)
148 }
149}
150
151impl 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
163pub trait ParseId {
165 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}