tendermint/consensus/
state.rs

1//! Tendermint consensus state
2
3pub use core::{cmp::Ordering, fmt};
4
5use serde::{Deserialize, Serialize};
6
7pub use crate::block;
8use crate::prelude::*;
9
10/// Placeholder string to show when block ID is absent. Syntax from:
11/// <https://tendermint.com/docs/spec/consensus/consensus.html>
12pub const NIL_PLACEHOLDER: &str = "<nil>";
13
14/// Tendermint consensus state
15// Serde serialization for KMS state file read/write.
16// https://github.com/informalsystems/tendermint-rs/issues/675
17#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
18pub struct State {
19    /// Current block height
20    pub height: block::Height,
21
22    /// Current consensus round
23    pub round: block::Round,
24
25    /// Current consensus step
26    pub step: i8,
27
28    /// Block ID being proposed (if available)
29    #[serde(with = "tendermint_proto::serializers::optional")]
30    pub block_id: Option<block::Id>,
31}
32
33impl State {
34    /// Get short prefix of the block ID for debugging purposes (ala git)
35    pub fn block_id_prefix(&self) -> String {
36        self.block_id
37            .as_ref()
38            .map(block::Id::prefix)
39            .unwrap_or_else(|| NIL_PLACEHOLDER.to_owned())
40    }
41}
42
43impl fmt::Display for State {
44    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
45        write!(f, "{}/{}/{}", self.height, self.round, self.step)
46    }
47}
48
49impl Ord for State {
50    fn cmp(&self, other: &State) -> Ordering {
51        match self.height.cmp(&other.height) {
52            Ordering::Greater => Ordering::Greater,
53            Ordering::Less => Ordering::Less,
54            Ordering::Equal => match self.round.cmp(&other.round) {
55                Ordering::Greater => Ordering::Greater,
56                Ordering::Less => Ordering::Less,
57                Ordering::Equal => self.step.cmp(&other.step),
58            },
59        }
60    }
61}
62
63impl PartialOrd for State {
64    fn partial_cmp(&self, other: &State) -> Option<Ordering> {
65        Some(self.cmp(other))
66    }
67}
68
69#[cfg(test)]
70mod tests {
71    use core::str::FromStr;
72
73    use super::State;
74    use crate::{block, Hash};
75
76    #[test]
77    fn state_ord_test() {
78        let new = State {
79            height: block::Height::from(9001_u32),
80            round: block::Round::default(),
81            step: 0,
82            block_id: None,
83        };
84
85        let old = State {
86            height: block::Height::from(1001_u32),
87            round: block::Round::from(1_u16),
88            step: 0,
89            block_id: None,
90        };
91
92        let older = State {
93            height: block::Height::from(1001_u32),
94            round: block::Round::default(),
95            step: 0,
96            block_id: None,
97        };
98
99        let oldest = State {
100            height: block::Height::default(),
101            round: block::Round::default(),
102            step: 0,
103            block_id: None,
104        };
105
106        assert!(old < new);
107        assert!(older < old);
108        assert!(oldest < older);
109        assert!(oldest < new);
110    }
111
112    #[test]
113    fn state_deser_update_null_test() {
114        // Testing that block_id == null is correctly deserialized.
115        let state_json_string = r#"{
116            "height": "5",
117            "round": "1",
118            "step": 6,
119            "block_id": null
120        }"#;
121        let state: State = State {
122            height: block::Height::from(5_u32),
123            round: block::Round::from(1_u16),
124            step: 6,
125            block_id: None,
126        };
127        let state_from_json: State = serde_json::from_str(state_json_string).unwrap();
128        assert_eq!(state_from_json, state);
129    }
130
131    #[test]
132    fn state_deser_update_total_test() {
133        // Testing, if total is correctly deserialized from string.
134        // Note that we use 'parts' to test backwards compatibility.
135        let state_json_string = r#"{
136            "height": "5",
137            "round": "1",
138            "step": 6,
139            "block_id": {
140              "hash": "1234567890123456789012345678901234567890123456789012345678901234",
141              "parts": {
142                  "total": "1",
143                  "hash": "1234567890123456789012345678901234567890123456789012345678901234"
144              }
145            }
146        }"#;
147        let state: State = State {
148            height: block::Height::from(5_u32),
149            round: block::Round::from(1_u16),
150            step: 6,
151            block_id: Some(block::Id {
152                hash: Hash::from_str(
153                    "1234567890123456789012345678901234567890123456789012345678901234",
154                )
155                .unwrap(),
156                part_set_header: block::parts::Header::new(
157                    1,
158                    Hash::from_str(
159                        "1234567890123456789012345678901234567890123456789012345678901234",
160                    )
161                    .unwrap(),
162                )
163                .unwrap(),
164            }),
165        };
166        let state_from_json: State = serde_json::from_str(state_json_string).unwrap();
167        assert_eq!(state_from_json, state);
168    }
169}