hotmint_storage/
consensus_state.rs1use hotmint_types::{BlockHash, Epoch, Height, QuorumCertificate, ViewNumber};
2use serde::{Deserialize, Serialize};
3use vsdb::MapxOrd;
4
5const KEY_CURRENT_VIEW: u64 = 1;
7const KEY_LOCKED_QC: u64 = 2;
8const KEY_HIGHEST_QC: u64 = 3;
9const KEY_LAST_COMMITTED_HEIGHT: u64 = 4;
10const KEY_CURRENT_EPOCH: u64 = 5;
11const KEY_LAST_APP_HASH: u64 = 6;
12
13#[derive(Debug, Clone, Serialize, Deserialize)]
15enum StateValue {
16 View(ViewNumber),
17 Height(Height),
18 Qc(QuorumCertificate),
19 Epoch(Epoch),
20 AppHash(BlockHash),
21}
22
23pub struct PersistentConsensusState {
25 store: MapxOrd<u64, StateValue>,
26}
27
28impl PersistentConsensusState {
29 pub fn new() -> Self {
30 Self {
31 store: MapxOrd::new(),
32 }
33 }
34
35 pub fn save_current_view(&mut self, view: ViewNumber) {
36 self.store
37 .insert(&KEY_CURRENT_VIEW, &StateValue::View(view));
38 }
39
40 pub fn load_current_view(&self) -> Option<ViewNumber> {
41 self.store.get(&KEY_CURRENT_VIEW).and_then(|v| match v {
42 StateValue::View(view) => Some(view),
43 _ => None,
44 })
45 }
46
47 pub fn save_locked_qc(&mut self, qc: &QuorumCertificate) {
48 self.store
49 .insert(&KEY_LOCKED_QC, &StateValue::Qc(qc.clone()));
50 }
51
52 pub fn load_locked_qc(&self) -> Option<QuorumCertificate> {
53 self.store.get(&KEY_LOCKED_QC).and_then(|v| match v {
54 StateValue::Qc(qc) => Some(qc),
55 _ => None,
56 })
57 }
58
59 pub fn save_highest_qc(&mut self, qc: &QuorumCertificate) {
60 self.store
61 .insert(&KEY_HIGHEST_QC, &StateValue::Qc(qc.clone()));
62 }
63
64 pub fn load_highest_qc(&self) -> Option<QuorumCertificate> {
65 self.store.get(&KEY_HIGHEST_QC).and_then(|v| match v {
66 StateValue::Qc(qc) => Some(qc),
67 _ => None,
68 })
69 }
70
71 pub fn save_last_committed_height(&mut self, height: Height) {
72 self.store
73 .insert(&KEY_LAST_COMMITTED_HEIGHT, &StateValue::Height(height));
74 }
75
76 pub fn load_last_committed_height(&self) -> Option<Height> {
77 self.store
78 .get(&KEY_LAST_COMMITTED_HEIGHT)
79 .and_then(|v| match v {
80 StateValue::Height(h) => Some(h),
81 _ => None,
82 })
83 }
84
85 pub fn save_current_epoch(&mut self, epoch: &Epoch) {
86 self.store
87 .insert(&KEY_CURRENT_EPOCH, &StateValue::Epoch(epoch.clone()));
88 }
89
90 pub fn load_current_epoch(&self) -> Option<Epoch> {
91 self.store.get(&KEY_CURRENT_EPOCH).and_then(|v| match v {
92 StateValue::Epoch(e) => Some(e),
93 _ => None,
94 })
95 }
96
97 pub fn save_last_app_hash(&mut self, hash: BlockHash) {
98 self.store
99 .insert(&KEY_LAST_APP_HASH, &StateValue::AppHash(hash));
100 }
101
102 pub fn load_last_app_hash(&self) -> Option<BlockHash> {
103 self.store.get(&KEY_LAST_APP_HASH).and_then(|v| match v {
104 StateValue::AppHash(h) => Some(h),
105 _ => None,
106 })
107 }
108
109 pub fn flush(&self) {
110 vsdb::vsdb_flush();
111 }
112}
113
114impl hotmint_consensus::engine::StatePersistence for PersistentConsensusState {
115 fn save_current_view(&mut self, view: ViewNumber) {
116 self.save_current_view(view);
117 }
118 fn save_locked_qc(&mut self, qc: &QuorumCertificate) {
119 self.save_locked_qc(qc);
120 }
121 fn save_highest_qc(&mut self, qc: &QuorumCertificate) {
122 self.save_highest_qc(qc);
123 }
124 fn save_last_committed_height(&mut self, height: Height) {
125 self.save_last_committed_height(height);
126 }
127 fn save_current_epoch(&mut self, epoch: &Epoch) {
128 self.save_current_epoch(epoch);
129 }
130 fn save_last_app_hash(&mut self, hash: BlockHash) {
131 self.save_last_app_hash(hash);
132 }
133 fn flush(&self) {
134 self.flush();
135 }
136}
137
138impl Default for PersistentConsensusState {
139 fn default() -> Self {
140 Self::new()
141 }
142}
143
144#[cfg(test)]
145mod tests {
146 use super::*;
147 use hotmint_types::{BlockHash, Height, ViewNumber};
148
149 #[test]
150 fn last_app_hash_round_trips() {
151 let mut pcs = PersistentConsensusState::new();
152 assert_eq!(pcs.load_last_app_hash(), None);
153
154 let hash = BlockHash([0xAB; 32]);
155 pcs.save_last_app_hash(hash);
156 assert_eq!(pcs.load_last_app_hash(), Some(hash));
157
158 let hash2 = BlockHash([0xCD; 32]);
160 pcs.save_last_app_hash(hash2);
161 assert_eq!(pcs.load_last_app_hash(), Some(hash2));
162 }
163
164 #[test]
165 fn all_fields_independent() {
166 let mut pcs = PersistentConsensusState::new();
167 pcs.save_current_view(ViewNumber(42));
168 pcs.save_last_committed_height(Height(7));
169 pcs.save_last_app_hash(BlockHash([0xFF; 32]));
170
171 assert_eq!(pcs.load_current_view(), Some(ViewNumber(42)));
172 assert_eq!(pcs.load_last_committed_height(), Some(Height(7)));
173 assert_eq!(pcs.load_last_app_hash(), Some(BlockHash([0xFF; 32])));
174 }
175}