1use core::{fmt, str::FromStr};
4
5use cometbft::{
6 account,
7 block::{Height, Round},
8 hash, vote, Hash, Time,
9};
10use serde::{Deserialize, Deserializer, Serialize, Serializer};
11use subtle_encoding::hex;
12
13use crate::{dialect::Dialect, prelude::*, request::RequestMessage, Error, Method};
14
15const NIL_VOTE_STR: &str = "nil-Vote";
17
18#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
20pub struct Request;
21
22impl Request {
23 pub fn new() -> Self {
24 Self {}
25 }
26}
27
28impl RequestMessage for Request {
29 fn method(&self) -> Method {
30 Method::ConsensusState
31 }
32}
33
34impl<S: Dialect> crate::Request<S> for Request {
35 type Response = Response;
36}
37
38impl<S: Dialect> crate::SimpleRequest<S> for Request {
39 type Output = Response;
40}
41
42#[derive(Clone, Debug, Deserialize, Serialize)]
46pub struct Response {
47 pub round_state: RoundState,
48}
49
50impl crate::Response for Response {}
51
52#[derive(Clone, Debug, Deserialize, Serialize)]
54pub struct RoundState {
55 #[serde(alias = "height/round/step")]
56 pub height_round_step: HeightRoundStep,
57
58 #[serde(with = "cometbft::serializers::time")]
59 pub start_time: Time,
60
61 #[serde(with = "hash::allow_empty")]
62 pub proposal_block_hash: Hash,
63
64 #[serde(with = "hash::allow_empty")]
65 pub locked_block_hash: Hash,
66
67 #[serde(with = "hash::allow_empty")]
68 pub valid_block_hash: Hash,
69
70 pub height_vote_set: Vec<RoundVotes>,
71
72 pub proposer: ValidatorInfo,
73}
74
75#[derive(Clone, Debug)]
77pub struct HeightRoundStep {
78 pub height: Height,
80 pub round: Round,
82 pub step: i8,
84}
85
86impl Serialize for HeightRoundStep {
87 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
88 where
89 S: Serializer,
90 {
91 let hrs = format!(
92 "{}/{}/{}",
93 self.height.value(),
94 self.round.value(),
95 self.step
96 );
97 serializer.serialize_str(&hrs)
98 }
99}
100
101impl<'de> Deserialize<'de> for HeightRoundStep {
102 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
103 where
104 D: Deserializer<'de>,
105 {
106 let s = String::deserialize(deserializer)?;
107 let hrs: Vec<&str> = s.split('/').collect();
108 if hrs.len() != 3 {
109 return Err(serde::de::Error::custom(format!(
110 "expected 3 components to height/round/step field, but got {}",
111 hrs.len()
112 )));
113 }
114 let height = Height::from_str(hrs[0]).map_err(serde::de::Error::custom)?;
115 let round = Round::from_str(hrs[1]).map_err(serde::de::Error::custom)?;
116 let step = i8::from_str(hrs[2]).map_err(serde::de::Error::custom)?;
117 Ok(Self {
118 height,
119 round,
120 step,
121 })
122 }
123}
124
125#[derive(Debug, Clone, Serialize, Deserialize)]
127pub struct RoundVotes {
128 pub round: u32,
132 pub prevotes: Vec<RoundVote>,
133 pub prevotes_bit_array: String,
134 pub precommits: Vec<RoundVote>,
135 pub precommits_bit_array: String,
136}
137
138#[derive(Debug, Clone, PartialEq, Eq)]
140pub enum RoundVote {
141 Nil,
142 Vote(VoteSummary),
143}
144
145impl Serialize for RoundVote {
146 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
147 where
148 S: Serializer,
149 {
150 match self {
151 RoundVote::Nil => serializer.serialize_str(NIL_VOTE_STR),
152 RoundVote::Vote(summary) => serializer.serialize_str(&summary.to_string()),
153 }
154 }
155}
156
157impl<'de> Deserialize<'de> for RoundVote {
158 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
159 where
160 D: Deserializer<'de>,
161 {
162 let s = String::deserialize(deserializer)?;
163 if s == NIL_VOTE_STR {
164 Ok(Self::Nil)
165 } else {
166 Ok(Self::Vote(
167 VoteSummary::from_str(&s).map_err(serde::de::Error::custom)?,
168 ))
169 }
170 }
171}
172
173#[derive(Debug, Clone, PartialEq, Eq)]
174pub struct VoteSummary {
175 pub validator_index: i32,
176 pub validator_address_fingerprint: Fingerprint,
177 pub height: Height,
178 pub round: Round,
179 pub vote_type: vote::Type,
180 pub block_id_hash_fingerprint: Fingerprint,
181 pub signature_fingerprint: Fingerprint,
182 pub timestamp: Time,
183}
184
185impl FromStr for VoteSummary {
186 type Err = Error;
187
188 fn from_str(s: &str) -> Result<Self, Self::Err> {
189 let parts: Vec<&str> = s
190 .strip_prefix("Vote{")
191 .ok_or_else(|| {
192 Error::client_internal(
193 "invalid format for consensus state vote summary string".to_string(),
194 )
195 })?
196 .strip_suffix('}')
197 .ok_or_else(|| {
198 Error::client_internal(
199 "invalid format for consensus state vote summary string".to_string(),
200 )
201 })?
202 .split(' ')
203 .collect();
204 if parts.len() != 6 {
205 return Err(Error::client_internal(format!(
206 "expected 6 parts to a consensus state vote summary, but got {}",
207 parts.len()
208 )));
209 }
210 let validator: Vec<&str> = parts[0].split(':').collect();
211 if validator.len() != 2 {
212 return Err(Error::client_internal(format!(
213 "failed to parse validator info for consensus state vote summary: {}",
214 parts[0],
215 )));
216 }
217 let height_round_type: Vec<&str> = parts[1].split('/').collect();
218 if height_round_type.len() != 3 {
219 return Err(Error::client_internal(format!(
220 "failed to parse height/round/type for consensus state vote summary: {}",
221 parts[1]
222 )));
223 }
224
225 let validator_index = i32::from_str(validator[0]).map_err(|e| {
226 Error::client_internal(format!(
227 "failed to parse validator index from consensus state vote summary: {} ({})",
228 e, validator[0],
229 ))
230 })?;
231 let validator_address_fingerprint =
232 Fingerprint::from_str(validator[1]).map_err(|e| {
233 Error::client_internal(format!(
234 "failed to parse validator address fingerprint from consensus state vote summary: {e}"
235 ))
236 })?;
237 let height = Height::from_str(height_round_type[0]).map_err(|e| {
238 Error::client_internal(format!(
239 "failed to parse height from consensus state vote summary: {e}"
240 ))
241 })?;
242 let round = Round::from_str(height_round_type[1]).map_err(|e| {
243 Error::client_internal(format!(
244 "failed to parse round from consensus state vote summary: {e}"
245 ))
246 })?;
247 let vote_type_parts: Vec<&str> = height_round_type[2].split('(').collect();
248 if vote_type_parts.len() != 2 {
249 return Err(Error::client_internal(format!(
250 "invalid structure for vote type in consensus state vote summary: {}",
251 height_round_type[2]
252 )));
253 }
254 let vote_type_str = vote_type_parts[1].trim_end_matches(')');
255 let vote_type = vote::Type::from_str(vote_type_str).map_err(|e| {
256 Error::client_internal(format!(
257 "failed to parse vote type from consensus state vote summary: {e} ({vote_type_str})"
258 ))
259 })?;
260 let block_id_hash_fingerprint = Fingerprint::from_str(parts[2]).map_err(|e| {
261 Error::client_internal(format!(
262 "failed to parse block ID hash fingerprint from consensus state vote summary: {e}"
263 ))
264 })?;
265 let signature_fingerprint = Fingerprint::from_str(parts[3]).map_err(|e| {
266 Error::client_internal(format!(
267 "failed to parse signature fingerprint from consensus state vote summary: {e}"
268 ))
269 })?;
270 let timestamp = Time::parse_from_rfc3339(parts[5]).map_err(|e| {
271 Error::client_internal(format!(
272 "failed to parse timestamp from consensus state vote summary: {e}"
273 ))
274 })?;
275
276 Ok(Self {
277 validator_index,
278 validator_address_fingerprint,
279 height,
280 round,
281 vote_type,
282 block_id_hash_fingerprint,
283 signature_fingerprint,
284 timestamp,
285 })
286 }
287}
288
289impl fmt::Display for VoteSummary {
290 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
291 write!(
292 f,
293 "Vote{{{}:{} {}/{:02}/{}({}) {} {} @ {}}}",
294 self.validator_index,
295 self.validator_address_fingerprint,
296 self.height,
297 self.round.value(),
298 i32::from(self.vote_type),
299 self.vote_type,
300 self.block_id_hash_fingerprint,
301 self.signature_fingerprint,
302 self.timestamp,
303 )
304 }
305}
306
307#[derive(Debug, Clone, PartialEq, Eq)]
308pub struct Fingerprint(Vec<u8>);
309
310impl FromStr for Fingerprint {
311 type Err = Error;
312
313 fn from_str(s: &str) -> Result<Self, Self::Err> {
314 Ok(Self(hex::decode_upper(s).map_err(|e| {
315 Error::client_internal(format!(
316 "failed to parse fingerprint as an uppercase hexadecimal string: {e}"
317 ))
318 })?))
319 }
320}
321
322impl fmt::Display for Fingerprint {
323 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
324 let hex_bytes = hex::encode_upper(&self.0);
325 let hex_string = String::from_utf8(hex_bytes).unwrap();
326 write!(f, "{hex_string}")
327 }
328}
329
330impl AsRef<[u8]> for Fingerprint {
331 fn as_ref(&self) -> &[u8] {
332 &self.0
333 }
334}
335
336#[derive(Debug, Clone, Serialize, Deserialize)]
337pub struct ValidatorInfo {
338 pub address: account::Id,
339 pub index: i32,
340}
341
342#[cfg(test)]
343mod test {
344 use lazy_static::lazy_static;
345
346 use super::*;
347
348 lazy_static! {
349 static ref TEST_VOTE_SUMMARIES: Vec<(String, VoteSummary, String)> = vec![
351 (
352 "Vote{0:000001E443FD 1262197/00/1(Prevote) 634ADAF1F402 7BB974E1BA40 @ 2019-08-01T11:52:35.513572509Z}".to_owned(),
353 VoteSummary {
354 validator_index: 0,
355 validator_address_fingerprint: Fingerprint(vec![0, 0, 1, 228, 67, 253]),
356 height: Height::from(1262197_u32),
357 round: Round::from(0_u8),
358 vote_type: vote::Type::Prevote,
359 block_id_hash_fingerprint: Fingerprint(vec![99, 74, 218, 241, 244, 2]),
360 signature_fingerprint: Fingerprint(vec![123, 185, 116, 225, 186, 64]),
361 timestamp: "2019-08-01T11:52:35.513572509Z".parse().unwrap(),
362 },
363 "Vote{0:000001E443FD 1262197/00/1(Prevote) 634ADAF1F402 7BB974E1BA40 @ 2019-08-01T11:52:35.513572509Z}".to_owned(),
364 ),
365 (
366 "Vote{0:2DA21E474F57 384/00/SIGNED_MSG_TYPE_PREVOTE(Prevote) 8FA9FD23F590 2987C33E8F87 @ 2021-03-25T12:12:03.693870115Z}".to_owned(),
368 VoteSummary {
369 validator_index: 0,
370 validator_address_fingerprint: Fingerprint(vec![45, 162, 30, 71, 79, 87]),
371 height: Height::from(384_u32),
372 round: Round::from(0_u8),
373 vote_type: vote::Type::Prevote,
374 block_id_hash_fingerprint: Fingerprint(vec![143, 169, 253, 35, 245, 144]),
375 signature_fingerprint: Fingerprint(vec![41, 135, 195, 62, 143, 135]),
376 timestamp: "2021-03-25T12:12:03.693870115Z".parse().unwrap(),
377 },
378 "Vote{0:2DA21E474F57 384/00/1(Prevote) 8FA9FD23F590 2987C33E8F87 @ 2021-03-25T12:12:03.693870115Z}".to_owned(),
379 )
380 ];
381 }
382
383 #[test]
384 fn deserialize_vote_summary() {
385 for (vote_summary_str, expected, _) in TEST_VOTE_SUMMARIES.iter() {
386 let actual = VoteSummary::from_str(vote_summary_str);
387 assert!(actual.is_ok(), "{}", vote_summary_str);
388 let actual = actual.unwrap();
389 assert_eq!(expected.clone(), actual);
390 }
391 }
392
393 #[test]
394 fn serialize_vote_summary() {
395 for (_, vote_summary, expected) in TEST_VOTE_SUMMARIES.iter() {
396 let actual = vote_summary.to_string();
397 assert_eq!(expected.clone(), actual);
398 }
399 }
400}