1use chrono::{DateTime, Utc};
16use serde::{Deserialize, Serialize};
17use std::collections::HashMap;
18
19use crate::merkle::{Hash, MerkleTree};
20
21#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
24pub struct TreeHead {
25 pub tree_size: u64,
27 pub root_hash: Hash,
29 pub timestamp: DateTime<Utc>,
31}
32
33#[derive(Debug, Default)]
38pub struct Witness {
39 heads: HashMap<u64, TreeHead>,
41 pub witness_id: String,
43}
44
45#[derive(Debug, thiserror::Error)]
47pub enum WitnessError {
48 #[error("consistency proof failed: expected {expected:?}, got {actual:?}")]
50 ConsistencyFailed {
51 expected: Hash,
53 actual: Hash,
55 },
56 #[error("old tree head (size {0}) not known")]
58 OldHeadUnknown(u64),
59}
60
61impl Witness {
62 pub fn new(witness_id: &str) -> Self {
64 Self {
65 heads: HashMap::new(),
66 witness_id: witness_id.into(),
67 }
68 }
69
70 pub fn receive_head(&mut self, head: TreeHead, tree: &MerkleTree) -> Result<(), WitnessError> {
77 if let Some(old) = self.latest_head() {
78 if head.tree_size > old.tree_size {
79 tree.verify_consistency(
80 old.root_hash,
81 head.root_hash,
82 old.tree_size as usize,
83 head.tree_size as usize,
84 &[],
85 )
86 .map_err(|_| WitnessError::ConsistencyFailed {
87 expected: head.root_hash,
88 actual: tree.root(),
89 })?;
90 }
91 }
92 self.heads.insert(head.tree_size, head);
93 Ok(())
94 }
95
96 pub fn latest_head(&self) -> Option<&TreeHead> {
98 self.heads.values().max_by_key(|h| h.tree_size)
99 }
100
101 pub fn known_heads(&self) -> Vec<&TreeHead> {
103 let mut heads: Vec<&TreeHead> = self.heads.values().collect();
104 heads.sort_by_key(|h| h.tree_size);
105 heads
106 }
107
108 pub fn head_at(&self, tree_size: u64) -> Option<&TreeHead> {
110 self.heads.get(&tree_size)
111 }
112
113 pub fn head_count(&self) -> usize {
115 self.heads.len()
116 }
117
118 pub fn gossip_to(
121 &self,
122 peer: &mut Witness,
123 tree_size: u64,
124 tree: &MerkleTree,
125 ) -> Result<(), WitnessError> {
126 let head = self
127 .heads
128 .get(&tree_size)
129 .ok_or(WitnessError::OldHeadUnknown(tree_size))?;
130 peer.receive_head(head.clone(), tree)
131 }
132}
133
134#[cfg(test)]
135mod tests {
136 use super::*;
137 use crate::entry::{ArtifactType, MerkleEntry};
138
139 fn build_tree(n: u64) -> MerkleTree {
140 let mut tree = MerkleTree::new();
141 for i in 0..n {
142 tree.append(MerkleEntry::new(
143 i,
144 ArtifactType::ThresholdSignature,
145 [i as u8; 32],
146 ));
147 }
148 tree
149 }
150
151 #[test]
152 fn witness_starts_empty() {
153 let w = Witness::new("w1");
154 assert_eq!(w.head_count(), 0);
155 assert!(w.latest_head().is_none());
156 }
157
158 #[test]
159 fn receive_first_head() {
160 let tree = build_tree(5);
161 let mut w = Witness::new("w1");
162 let head = TreeHead {
163 tree_size: 5,
164 root_hash: tree.root(),
165 timestamp: Utc::now(),
166 };
167 w.receive_head(head, &tree).unwrap();
168 assert_eq!(w.head_count(), 1);
169 assert_eq!(w.latest_head().unwrap().tree_size, 5);
170 }
171
172 #[test]
173 fn receive_consistent_head() {
174 let mut tree = build_tree(5);
175 let mut w = Witness::new("w1");
176
177 let head1 = TreeHead {
178 tree_size: 5,
179 root_hash: tree.root(),
180 timestamp: Utc::now(),
181 };
182 w.receive_head(head1, &tree).unwrap();
183
184 tree.append(MerkleEntry::new(
185 5,
186 ArtifactType::ThresholdSignature,
187 [5; 32],
188 ));
189 let head2 = TreeHead {
190 tree_size: 6,
191 root_hash: tree.root(),
192 timestamp: Utc::now(),
193 };
194 w.receive_head(head2, &tree).unwrap();
195 assert_eq!(w.head_count(), 2);
196 assert_eq!(w.latest_head().unwrap().tree_size, 6);
197 }
198
199 #[test]
200 fn reject_inconsistent_head() {
201 let tree = build_tree(5);
202 let mut w = Witness::new("w1");
203 let head1 = TreeHead {
204 tree_size: 5,
205 root_hash: tree.root(),
206 timestamp: Utc::now(),
207 };
208 w.receive_head(head1, &tree).unwrap();
209
210 let fake_root = [0xFF; 32];
211 let head2 = TreeHead {
212 tree_size: 10,
213 root_hash: fake_root,
214 timestamp: Utc::now(),
215 };
216 let result = w.receive_head(head2, &tree);
217 assert!(result.is_err());
218 }
219
220 #[test]
221 fn known_heads_sorted_by_size() {
222 let mut tree = build_tree(1);
223 let mut w = Witness::new("w1");
224
225 let head1 = TreeHead {
226 tree_size: 1,
227 root_hash: tree.root(),
228 timestamp: Utc::now(),
229 };
230 w.receive_head(head1, &tree).unwrap();
231
232 tree.append(MerkleEntry::new(
233 1,
234 ArtifactType::ThresholdSignature,
235 [1; 32],
236 ));
237 let head2 = TreeHead {
238 tree_size: 2,
239 root_hash: tree.root(),
240 timestamp: Utc::now(),
241 };
242 w.receive_head(head2, &tree).unwrap();
243
244 tree.append(MerkleEntry::new(
245 2,
246 ArtifactType::ThresholdSignature,
247 [2; 32],
248 ));
249 let head3 = TreeHead {
250 tree_size: 3,
251 root_hash: tree.root(),
252 timestamp: Utc::now(),
253 };
254 w.receive_head(head3, &tree).unwrap();
255
256 let heads = w.known_heads();
257 assert_eq!(heads.len(), 3);
258 assert_eq!(heads[0].tree_size, 1);
259 assert_eq!(heads[1].tree_size, 2);
260 assert_eq!(heads[2].tree_size, 3);
261 }
262
263 #[test]
264 fn gossip_to_peer() {
265 let tree = build_tree(5);
266 let mut w1 = Witness::new("w1");
267 let mut w2 = Witness::new("w2");
268
269 let head = TreeHead {
270 tree_size: 5,
271 root_hash: tree.root(),
272 timestamp: Utc::now(),
273 };
274 w1.receive_head(head, &tree).unwrap();
275 w1.gossip_to(&mut w2, 5, &tree).unwrap();
276 assert_eq!(w2.head_count(), 1);
277 assert_eq!(w2.head_at(5).unwrap().root_hash, tree.root());
278 }
279
280 #[test]
281 fn gossip_unknown_head_errors() {
282 let tree = build_tree(5);
283 let w1 = Witness::new("w1");
284 let mut w2 = Witness::new("w2");
285 assert!(w1.gossip_to(&mut w2, 99, &tree).is_err());
286 }
287
288 #[test]
289 fn head_at_returns_correct_head() {
290 let tree = build_tree(5);
291 let mut w = Witness::new("w1");
292 let head = TreeHead {
293 tree_size: 5,
294 root_hash: tree.root(),
295 timestamp: Utc::now(),
296 };
297 w.receive_head(head, &tree).unwrap();
298 assert!(w.head_at(5).is_some());
299 assert!(w.head_at(3).is_none());
300 }
301}