hotmint_storage/
evidence_store.rs1use std::collections::HashSet;
2use std::path::Path;
3
4use hotmint_consensus::evidence_store::EvidenceStore;
5use hotmint_types::evidence::EquivocationProof;
6use hotmint_types::validator::ValidatorId;
7use hotmint_types::view::ViewNumber;
8use ruc::*;
9use vsdb::MapxOrd;
10
11pub struct MemoryEvidenceStore {
13 proofs: Vec<EquivocationProof>,
14 committed: HashSet<(ViewNumber, ValidatorId)>,
15}
16
17impl MemoryEvidenceStore {
18 pub fn new() -> Self {
19 Self {
20 proofs: Vec::new(),
21 committed: HashSet::new(),
22 }
23 }
24}
25
26impl Default for MemoryEvidenceStore {
27 fn default() -> Self {
28 Self::new()
29 }
30}
31
32impl EvidenceStore for MemoryEvidenceStore {
33 fn put_evidence(&mut self, proof: EquivocationProof) {
34 let dominated = self
36 .proofs
37 .iter()
38 .any(|p| p.view == proof.view && p.validator == proof.validator);
39 if !dominated {
40 self.proofs.push(proof);
41 }
42 }
43
44 fn get_pending(&self) -> Vec<EquivocationProof> {
45 self.proofs
46 .iter()
47 .filter(|p| !self.committed.contains(&(p.view, p.validator)))
48 .cloned()
49 .collect()
50 }
51
52 fn mark_committed(&mut self, view: ViewNumber, validator: ValidatorId) {
53 self.committed.insert((view, validator));
54 self.proofs
56 .retain(|p| !self.committed.contains(&(p.view, p.validator)));
57 }
58
59 fn all(&self) -> Vec<EquivocationProof> {
60 self.proofs.clone()
61 }
62}
63
64const META_FILE: &str = "evidence_store.meta";
67
68pub struct PersistentEvidenceStore {
74 proofs: MapxOrd<u64, EquivocationProof>,
75 committed: MapxOrd<u64, u8>,
76 next_id: u64,
77 meta_path: std::path::PathBuf,
78}
79
80impl PersistentEvidenceStore {
81 pub fn open(data_dir: &Path) -> Result<Self> {
84 let meta_path = data_dir.join(META_FILE);
85 if meta_path.exists() {
86 let bytes = std::fs::read(&meta_path).c(d!("read evidence_store.meta"))?;
87 if bytes.len() != 24 {
88 return Err(eg!(
89 "corrupt evidence_store.meta: expected 24 bytes, got {}",
90 bytes.len()
91 ));
92 }
93 let proofs_id = u64::from_le_bytes(bytes[0..8].try_into().unwrap());
94 let committed_id = u64::from_le_bytes(bytes[8..16].try_into().unwrap());
95 let next_id = u64::from_le_bytes(bytes[16..24].try_into().unwrap());
96 let proofs = MapxOrd::from_meta(proofs_id).c(d!("restore proofs"))?;
97 let committed = MapxOrd::from_meta(committed_id).c(d!("restore committed"))?;
98 Ok(Self {
99 proofs,
100 committed,
101 next_id,
102 meta_path: meta_path.clone(),
103 })
104 } else {
105 let proofs: MapxOrd<u64, EquivocationProof> = MapxOrd::new();
106 let committed: MapxOrd<u64, u8> = MapxOrd::new();
107 let proofs_id = proofs.save_meta().c(d!())?;
108 let committed_id = committed.save_meta().c(d!())?;
109 let next_id = 0u64;
110 let mut meta = Vec::with_capacity(24);
111 meta.extend_from_slice(&proofs_id.to_le_bytes());
112 meta.extend_from_slice(&committed_id.to_le_bytes());
113 meta.extend_from_slice(&next_id.to_le_bytes());
114 std::fs::write(&meta_path, &meta).c(d!("write evidence_store.meta"))?;
115 Ok(Self {
116 proofs,
117 committed,
118 next_id,
119 meta_path,
120 })
121 }
122 }
123
124 fn committed_key(view: ViewNumber, validator: ValidatorId) -> u64 {
125 let mut hasher = blake3::Hasher::new();
126 hasher.update(&view.as_u64().to_le_bytes());
127 hasher.update(&validator.0.to_le_bytes());
128 let hash = hasher.finalize();
129 u64::from_le_bytes(hash.as_bytes()[..8].try_into().unwrap())
130 }
131
132 fn is_duplicate(&self, proof: &EquivocationProof) -> bool {
133 self.proofs
134 .iter()
135 .any(|(_, p)| p.view == proof.view && p.validator == proof.validator)
136 }
137
138 fn persist_next_id(&self) {
140 if let Ok(bytes) = std::fs::read(&self.meta_path)
141 && bytes.len() == 24
142 {
143 let mut meta = bytes;
144 meta[16..24].copy_from_slice(&self.next_id.to_le_bytes());
145 let _ = std::fs::write(&self.meta_path, &meta);
146 }
147 }
148}
149
150impl EvidenceStore for PersistentEvidenceStore {
151 fn put_evidence(&mut self, proof: EquivocationProof) {
152 if self.is_duplicate(&proof) {
153 return;
154 }
155 self.proofs.insert(&self.next_id, &proof);
156 self.next_id += 1;
157 self.persist_next_id();
159 }
160
161 fn get_pending(&self) -> Vec<EquivocationProof> {
162 self.proofs
163 .iter()
164 .filter_map(|(_, p)| {
165 let key = Self::committed_key(p.view, p.validator);
166 if self.committed.get(&key).is_some() {
167 None
168 } else {
169 Some(p)
170 }
171 })
172 .collect()
173 }
174
175 fn mark_committed(&mut self, view: ViewNumber, validator: ValidatorId) {
176 let key = Self::committed_key(view, validator);
177 self.committed.insert(&key, &1);
178 let to_remove: Vec<u64> = self
180 .proofs
181 .iter()
182 .filter(|(_, p)| p.view == view && p.validator == validator)
183 .map(|(id, _)| id)
184 .collect();
185 for id in to_remove {
186 self.proofs.remove(&id);
187 }
188 }
189
190 fn all(&self) -> Vec<EquivocationProof> {
191 self.proofs.iter().map(|(_, p)| p).collect()
192 }
193}
194
195#[cfg(test)]
196mod tests {
197 use super::*;
198 use hotmint_types::block::BlockHash;
199 use hotmint_types::crypto::Signature;
200 use hotmint_types::vote::VoteType;
201
202 fn dummy_proof(view: u64, validator: u64) -> EquivocationProof {
203 EquivocationProof {
204 validator: ValidatorId(validator),
205 view: ViewNumber(view),
206 vote_type: VoteType::Vote,
207 epoch: Default::default(),
208 block_hash_a: BlockHash::GENESIS,
209 signature_a: Signature(vec![1]),
210 block_hash_b: BlockHash::GENESIS,
211 signature_b: Signature(vec![2]),
212 }
213 }
214
215 #[test]
216 fn put_and_get_pending() {
217 let mut store = MemoryEvidenceStore::new();
218 store.put_evidence(dummy_proof(1, 0));
219 store.put_evidence(dummy_proof(2, 1));
220
221 assert_eq!(store.get_pending().len(), 2);
222 assert_eq!(store.all().len(), 2);
223 }
224
225 #[test]
226 fn mark_committed_filters_pending() {
227 let mut store = MemoryEvidenceStore::new();
228 store.put_evidence(dummy_proof(1, 0));
229 store.put_evidence(dummy_proof(2, 1));
230 store.mark_committed(ViewNumber(1), ValidatorId(0));
231
232 let pending = store.get_pending();
233 assert_eq!(pending.len(), 1);
234 assert_eq!(pending[0].view, ViewNumber(2));
235 assert_eq!(store.all().len(), 1);
237 }
238
239 #[test]
240 fn deduplication() {
241 let mut store = MemoryEvidenceStore::new();
242 store.put_evidence(dummy_proof(1, 0));
243 store.put_evidence(dummy_proof(1, 0)); assert_eq!(store.all().len(), 1);
245 }
246}