1#![allow(clippy::unwrap_or_default)]
8use std::collections::BTreeMap;
9
10use async_broadcast::Sender;
11use async_trait::async_trait;
12use committable::Committable;
13use hotshot_example_types::block_types::TestBlockHeader;
14use hotshot_types::{
15 data::{Leaf2, ViewNumber},
16 event::{Event, EventType},
17 message::UpgradeLock,
18 traits::node_implementation::NodeType,
19 vote::HasViewNumber,
20};
21use hotshot_utils::anytrace::*;
22use tokio::task::JoinHandle;
23use versions::{Upgrade, VERSION_0_0};
24
25use crate::{
26 overall_safety_task::OverallSafetyPropertiesDescription,
27 test_builder::TransactionValidator,
28 test_task::{TestEvent, TestResult, TestTaskState, spawn_timeout_task},
29};
30
31pub type NodeMap<TYPES> = BTreeMap<ViewNumber, Vec<Leaf2<TYPES>>>;
33
34pub type NodeMapSanitized<TYPES> = BTreeMap<ViewNumber, Leaf2<TYPES>>;
36
37fn sanitize_node_map<TYPES: NodeType>(
39 node_map: &NodeMap<TYPES>,
40) -> Result<NodeMapSanitized<TYPES>> {
41 let mut result = BTreeMap::new();
42
43 for (view, leaves) in node_map.iter() {
44 let mut reduced = leaves.clone();
45
46 reduced.dedup();
47
48 match reduced.len() {
49 0 => {},
50 1 => {
51 result.insert(*view, reduced[0].clone());
52 },
53 _ => {
54 bail!(
55 "We have received inconsistent leaves for view {view}. Leaves:\n\n{leaves:?}"
56 );
57 },
58 }
59 }
60
61 Ok(result)
62}
63
64async fn validate_node_map<TYPES: NodeType>(node_map: &NodeMapSanitized<TYPES>) -> Result<()> {
66 let leaf_triples = node_map
68 .values()
69 .zip(node_map.values().skip(1))
70 .zip(node_map.values().skip(2))
71 .map(|((a, b), c)| (a, b, c));
72
73 let mut decided_upgrade_certificate = None;
74 let mut view_decided = ViewNumber::new(0);
75
76 for (grandparent, _parent, child) in leaf_triples {
77 if let Some(cert) = grandparent.upgrade_certificate()
78 && cert.data.decide_by <= child.view_number()
79 {
80 decided_upgrade_certificate = Some(cert);
81 view_decided = child.view_number();
82
83 break;
84 }
85 }
86
87 let upgrade_lock = UpgradeLock::<TYPES>::new(Upgrade::trivial(VERSION_0_0));
92
93 let leaf_pairs = node_map.values().zip(node_map.values().skip(1));
94
95 for (parent, child) in leaf_pairs {
97 ensure!(
98 child.justify_qc().view_number >= parent.view_number(),
99 "The node has provided leaf:\n\n{child:?}\n\nbut its quorum certificate points to a \
100 view before the most recent leaf:\n\n{parent:?}"
101 );
102
103 child
104 .extends_upgrade(parent, &upgrade_lock)
105 .context(|e| error!("Leaf {child:?} does not extend its parent {parent:?}: {e}"))?;
106
107 ensure!(
108 child.height() > parent.height(),
109 "The node has decided leaf\n\n{child:?}\n\nextending leaf\n\n{parent:?}but the block \
110 height did not increase."
111 );
112
113 if child.justify_qc().view_number == parent.view_number()
116 && child.justify_qc().data.leaf_commit != parent.commit()
117 {
118 bail!(
119 "The node has provided leaf:\n\n{child:?}\n\nwhich points \
120 to:\n\n{parent:?}\n\nbut the commits do not match."
121 );
122 }
123
124 if child.view_number() == view_decided {
125 upgrade_lock.set_decided_upgrade_cert(decided_upgrade_certificate.clone());
126 }
127 }
128
129 Ok(())
130}
131
132pub type NetworkMap<TYPES> = BTreeMap<usize, NodeMap<TYPES>>;
134
135pub type NetworkMapSanitized<TYPES> = BTreeMap<usize, NodeMapSanitized<TYPES>>;
137
138fn sanitize_network_map<TYPES: NodeType>(
140 network_map: &NetworkMap<TYPES>,
141) -> Result<NetworkMapSanitized<TYPES>> {
142 let mut result = BTreeMap::new();
143
144 for (node, node_map) in network_map {
145 result.insert(
146 *node,
147 sanitize_node_map(node_map)
148 .context(|e| error!("Node {node} produced inconsistent leaves: {e}"))?,
149 );
150 }
151
152 Ok(result)
153}
154
155pub type ViewMap<TYPES> = BTreeMap<ViewNumber, BTreeMap<usize, Leaf2<TYPES>>>;
156
157async fn invert_network_map<TYPES: NodeType>(
163 network_map: &NetworkMapSanitized<TYPES>,
164) -> Result<ViewMap<TYPES>> {
165 let mut inverted_map = BTreeMap::new();
166 for (node_id, node_map) in network_map.iter() {
167 validate_node_map::<TYPES>(node_map)
168 .await
169 .context(|e| error!("Node {node_id} has an invalid leaf history: {e}"))?;
170
171 for (view, leaf) in node_map.iter() {
173 let view_map = inverted_map.entry(*view).or_insert(BTreeMap::new());
174 view_map.insert(*node_id, leaf.clone());
175 }
176 }
177
178 Ok(inverted_map)
179}
180
181pub type ViewMapSanitized<TYPES> = BTreeMap<ViewNumber, Leaf2<TYPES>>;
183
184fn sanitize_view_map<TYPES: NodeType>(
185 view_map: &ViewMap<TYPES>,
186) -> Result<ViewMapSanitized<TYPES>> {
187 let mut result = BTreeMap::new();
188
189 for (view, leaf_map) in view_map.iter() {
190 let mut node_leaves: Vec<_> = leaf_map.iter().collect();
191
192 node_leaves.dedup_by(|(_node_a, leaf_a), (_node_b, leaf_b)| leaf_a == leaf_b);
193
194 ensure!(
195 node_leaves.len() <= 1,
196 error!(
197 "The network does not agree on the following views: {}",
198 leaf_map
199 .iter()
200 .fold(format!("\n\nView {view}:"), |acc, (node, leaf)| {
201 format!("{acc}\n\nNode {node} sent us leaf:\n\n{leaf:?}")
202 })
203 )
204 );
205
206 if let Some(leaf) = node_leaves.first() {
207 result.insert(*view, leaf.1.clone());
208 }
209 }
210
211 for (parent, child) in result.values().zip(result.values().skip(1)) {
212 if child.justify_qc().data.leaf_commit != parent.commit() {
214 bail!(
215 "The network has decided:\n\n{child:?}\n\nwhich succeeds:\n\n{parent:?}\n\nbut \
216 the commits do not match. Did we miss an intermediate leaf?"
217 );
218 }
219 }
220
221 Ok(result)
222}
223
224enum TestProgress {
225 Incomplete,
226 Finished,
227}
228
229pub struct ConsistencyTask<TYPES: NodeType> {
231 pub consensus_leaves: NetworkMap<TYPES>,
233 pub safety_properties: OverallSafetyPropertiesDescription,
235 pub ensure_upgrade: bool,
237 pub errors: Vec<Error>,
239 pub test_sender: Sender<TestEvent>,
241 pub validate_transactions: TransactionValidator,
243 pub timeout_task: JoinHandle<()>,
245}
246
247impl<TYPES: NodeType<BlockHeader = TestBlockHeader>> ConsistencyTask<TYPES> {
248 pub async fn validate(&self) -> Result<()> {
249 let sanitized_network_map = sanitize_network_map(&self.consensus_leaves)?;
250
251 let inverted_map = invert_network_map::<TYPES>(&sanitized_network_map).await?;
252
253 let sanitized_view_map = sanitize_view_map(&inverted_map)?;
254 let num_successful_views = sanitized_view_map.iter().len();
255
256 ensure!(
258 num_successful_views >= self.safety_properties.num_successful_views,
259 "Not enough successful views: expected {:?} but got {:?}",
260 self.safety_properties.num_successful_views,
261 num_successful_views,
262 );
263
264 let expected_upgrade = self.ensure_upgrade;
265 let actual_upgrade = sanitized_view_map.iter().fold(false, |acc, (_view, leaf)| {
266 acc || leaf.upgrade_certificate().is_some()
267 });
268
269 let mut transactions = Vec::new();
270
271 transactions = sanitized_view_map
272 .iter()
273 .fold(transactions, |mut acc, (view, leaf)| {
274 acc.push((**view, leaf.block_header().metadata.num_transactions));
275
276 acc
277 });
278
279 (self.validate_transactions)(&transactions)?;
280
281 ensure!(
282 expected_upgrade == actual_upgrade,
283 "Mismatch between expected and actual upgrade. Expected upgrade: {expected_upgrade}. \
284 Actual upgrade: {actual_upgrade}"
285 );
286
287 Ok(())
288 }
289
290 async fn partial_validate(&self) -> Result<TestProgress> {
291 self.check_view_success().await?;
292 self.check_view_failure().await?;
293
294 self.check_total_successes().await
295 }
296
297 fn add_error(&mut self, error: Error) {
298 self.errors.push(error);
299 }
300
301 async fn handle_result(&mut self, result: Result<TestProgress>) {
302 match result {
303 Ok(TestProgress::Finished) => {
304 let _ = self.test_sender.broadcast(TestEvent::Shutdown).await;
305 },
306 Err(e) => {
307 self.add_error(e);
308 let _ = self.test_sender.broadcast(TestEvent::Shutdown).await;
309 },
310 Ok(TestProgress::Incomplete) => {},
311 }
312 }
313
314 async fn check_total_successes(&self) -> Result<TestProgress> {
315 let sanitized_network_map = sanitize_network_map(&self.consensus_leaves)?;
316
317 let inverted_map = invert_network_map::<TYPES>(&sanitized_network_map).await?;
318
319 if inverted_map.len() >= self.safety_properties.num_successful_views {
320 Ok(TestProgress::Finished)
321 } else {
322 Ok(TestProgress::Incomplete)
323 }
324 }
325 pub async fn check_view_success(&self) -> Result<()> {
326 for (node_id, node_map) in self.consensus_leaves.iter() {
327 for (view, leaf) in node_map {
328 ensure!(
329 !self.safety_properties.expected_view_failures.contains(view),
330 "Expected a view failure, but got a decided leaf for view {view} from node \
331 {node_id}.\n\nLeaf:\n\n{leaf:?}"
332 );
333 }
334 }
335
336 Ok(())
337 }
338
339 pub async fn check_view_failure(&self) -> Result<()> {
340 let sanitized_network_map = sanitize_network_map(&self.consensus_leaves)?;
341
342 let inverted_map = invert_network_map::<TYPES>(&sanitized_network_map).await?;
343
344 let decided_views: Vec<u64> = inverted_map.keys().map(|view| **view).collect();
345 if decided_views.len() < 2 {
347 return Ok(());
348 }
349
350 let unexpected_failed_views: Vec<u64> = decided_views
353 .windows(2)
354 .flat_map(|pair| (pair[0] + 1)..pair[1])
355 .filter(|view| {
356 !self.safety_properties.expected_view_failures.contains(view)
357 && !self.safety_properties.possible_view_failures.contains(view)
358 })
359 .collect();
360
361 ensure!(
362 unexpected_failed_views.len() <= self.safety_properties.max_unexpected_view_failures,
363 "Unexpected failed views: {:?} (allowed: {})",
364 unexpected_failed_views,
365 self.safety_properties.max_unexpected_view_failures
366 );
367
368 Ok(())
369 }
370}
371
372#[async_trait]
373impl<TYPES: NodeType<BlockHeader = TestBlockHeader>> TestTaskState for ConsistencyTask<TYPES> {
374 type Event = Event<TYPES>;
375 type Error = Error;
376
377 async fn handle_event(&mut self, (message, id): (Self::Event, usize)) -> Result<()> {
379 if let Event {
380 event:
381 EventType::Decide {
382 leaf_chain,
383 committing_qc,
384 deciding_qc,
385 ..
386 },
387 ..
388 } = message
389 {
390 {
391 let mut timeout_task = spawn_timeout_task(
392 self.test_sender.clone(),
393 self.safety_properties.decide_timeout,
394 );
395
396 std::mem::swap(&mut self.timeout_task, &mut timeout_task);
397
398 timeout_task.abort();
399 }
400
401 match deciding_qc {
402 Some(deciding_qc) => {
403 let last_leaf = &leaf_chain[0].leaf;
404 ensure!(committing_qc.view_number() == last_leaf.view_number());
405 ensure!(committing_qc.leaf_commit() == last_leaf.commit());
406 ensure!(deciding_qc.view_number() == committing_qc.view_number() + 1);
407 },
408 None => {
409 ensure!(
412 committing_qc.epoch().is_none(),
413 "expected 2nd deciding QC for post-epochs decide"
414 );
415 },
416 }
417
418 for leaf_info in leaf_chain.iter().rev() {
419 let map = &mut self.consensus_leaves.entry(id).or_insert(BTreeMap::new());
420
421 map.entry(leaf_info.leaf.view_number())
422 .and_modify(|vec| vec.push(leaf_info.leaf.clone()))
423 .or_insert(vec![leaf_info.leaf.clone()]);
424
425 let result = self.partial_validate().await;
426
427 self.handle_result(result).await;
428 }
429 }
430
431 Ok(())
432 }
433
434 async fn check(&self) -> TestResult {
435 self.timeout_task.abort();
436
437 let mut errors: Vec<_> = self.errors.iter().map(|e| e.to_string()).collect();
438
439 if let Err(e) = self.validate().await {
440 errors.push(e.to_string());
441 }
442
443 if !errors.is_empty() {
444 TestResult::Fail(Box::new(errors))
445 } else {
446 TestResult::Pass
447 }
448 }
449}