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 }
55
56 fn all(&self) -> Vec<EquivocationProof> {
57 self.proofs.clone()
58 }
59}
60
61const META_FILE: &str = "evidence_store.meta";
64
65pub struct PersistentEvidenceStore {
71 proofs: MapxOrd<u64, EquivocationProof>,
72 committed: MapxOrd<u64, u8>,
73 next_id: u64,
74}
75
76impl PersistentEvidenceStore {
77 pub fn open(data_dir: &Path) -> Result<Self> {
80 let meta_path = data_dir.join(META_FILE);
81 if meta_path.exists() {
82 let bytes = std::fs::read(&meta_path).c(d!("read evidence_store.meta"))?;
83 if bytes.len() != 24 {
84 return Err(eg!(
85 "corrupt evidence_store.meta: expected 24 bytes, got {}",
86 bytes.len()
87 ));
88 }
89 let proofs_id = u64::from_le_bytes(bytes[0..8].try_into().unwrap());
90 let committed_id = u64::from_le_bytes(bytes[8..16].try_into().unwrap());
91 let next_id = u64::from_le_bytes(bytes[16..24].try_into().unwrap());
92 let proofs = MapxOrd::from_meta(proofs_id).c(d!("restore proofs"))?;
93 let committed = MapxOrd::from_meta(committed_id).c(d!("restore committed"))?;
94 Ok(Self {
95 proofs,
96 committed,
97 next_id,
98 })
99 } else {
100 let proofs: MapxOrd<u64, EquivocationProof> = MapxOrd::new();
101 let committed: MapxOrd<u64, u8> = MapxOrd::new();
102 let proofs_id = proofs.save_meta().c(d!())?;
103 let committed_id = committed.save_meta().c(d!())?;
104 let next_id = 0u64;
105 let mut meta = Vec::with_capacity(24);
106 meta.extend_from_slice(&proofs_id.to_le_bytes());
107 meta.extend_from_slice(&committed_id.to_le_bytes());
108 meta.extend_from_slice(&next_id.to_le_bytes());
109 std::fs::write(&meta_path, &meta).c(d!("write evidence_store.meta"))?;
110 Ok(Self {
111 proofs,
112 committed,
113 next_id,
114 })
115 }
116 }
117
118 fn committed_key(view: ViewNumber, validator: ValidatorId) -> u64 {
119 let mut hasher = blake3::Hasher::new();
120 hasher.update(&view.as_u64().to_le_bytes());
121 hasher.update(&validator.0.to_le_bytes());
122 let hash = hasher.finalize();
123 u64::from_le_bytes(hash.as_bytes()[..8].try_into().unwrap())
124 }
125
126 fn is_duplicate(&self, proof: &EquivocationProof) -> bool {
127 self.proofs
128 .iter()
129 .any(|(_, p)| p.view == proof.view && p.validator == proof.validator)
130 }
131}
132
133impl EvidenceStore for PersistentEvidenceStore {
134 fn put_evidence(&mut self, proof: EquivocationProof) {
135 if self.is_duplicate(&proof) {
136 return;
137 }
138 self.proofs.insert(&self.next_id, &proof);
139 self.next_id += 1;
140 }
141
142 fn get_pending(&self) -> Vec<EquivocationProof> {
143 self.proofs
144 .iter()
145 .filter_map(|(_, p)| {
146 let key = Self::committed_key(p.view, p.validator);
147 if self.committed.get(&key).is_some() {
148 None
149 } else {
150 Some(p)
151 }
152 })
153 .collect()
154 }
155
156 fn mark_committed(&mut self, view: ViewNumber, validator: ValidatorId) {
157 let key = Self::committed_key(view, validator);
158 self.committed.insert(&key, &1);
159 }
160
161 fn all(&self) -> Vec<EquivocationProof> {
162 self.proofs.iter().map(|(_, p)| p).collect()
163 }
164}
165
166#[cfg(test)]
167mod tests {
168 use super::*;
169 use hotmint_types::block::BlockHash;
170 use hotmint_types::crypto::Signature;
171 use hotmint_types::vote::VoteType;
172
173 fn dummy_proof(view: u64, validator: u64) -> EquivocationProof {
174 EquivocationProof {
175 validator: ValidatorId(validator),
176 view: ViewNumber(view),
177 vote_type: VoteType::Vote,
178 epoch: Default::default(),
179 block_hash_a: BlockHash::GENESIS,
180 signature_a: Signature(vec![1]),
181 block_hash_b: BlockHash::GENESIS,
182 signature_b: Signature(vec![2]),
183 }
184 }
185
186 #[test]
187 fn put_and_get_pending() {
188 let mut store = MemoryEvidenceStore::new();
189 store.put_evidence(dummy_proof(1, 0));
190 store.put_evidence(dummy_proof(2, 1));
191
192 assert_eq!(store.get_pending().len(), 2);
193 assert_eq!(store.all().len(), 2);
194 }
195
196 #[test]
197 fn mark_committed_filters_pending() {
198 let mut store = MemoryEvidenceStore::new();
199 store.put_evidence(dummy_proof(1, 0));
200 store.put_evidence(dummy_proof(2, 1));
201 store.mark_committed(ViewNumber(1), ValidatorId(0));
202
203 let pending = store.get_pending();
204 assert_eq!(pending.len(), 1);
205 assert_eq!(pending[0].view, ViewNumber(2));
206 assert_eq!(store.all().len(), 2);
208 }
209
210 #[test]
211 fn deduplication() {
212 let mut store = MemoryEvidenceStore::new();
213 store.put_evidence(dummy_proof(1, 0));
214 store.put_evidence(dummy_proof(1, 0)); assert_eq!(store.all().len(), 1);
216 }
217}