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 mut inverted_map = invert_network_map::<TYPES>(&sanitized_network_map).await?;
343
344 let (current_view, _) = inverted_map
345 .pop_last()
346 .context(error!("Leaf map is empty, which should be impossible"))?;
347 let Some((last_view, _)) = inverted_map.pop_last() else {
348 return Ok(());
350 };
351
352 let unexpected_failed_views: Vec<_> = (*(last_view + 1)..*current_view)
354 .filter(|view| {
355 !self.safety_properties.expected_view_failures.contains(view)
356 && !self.safety_properties.possible_view_failures.contains(view)
357 })
358 .collect();
359
360 ensure!(
361 unexpected_failed_views.is_empty(),
362 "Unexpected failed views: {:?}",
363 unexpected_failed_views
364 );
365
366 Ok(())
367 }
368}
369
370#[async_trait]
371impl<TYPES: NodeType<BlockHeader = TestBlockHeader>> TestTaskState for ConsistencyTask<TYPES> {
372 type Event = Event<TYPES>;
373 type Error = Error;
374
375 async fn handle_event(&mut self, (message, id): (Self::Event, usize)) -> Result<()> {
377 if let Event {
378 event:
379 EventType::Decide {
380 leaf_chain,
381 committing_qc,
382 deciding_qc,
383 ..
384 },
385 ..
386 } = message
387 {
388 {
389 let mut timeout_task = spawn_timeout_task(
390 self.test_sender.clone(),
391 self.safety_properties.decide_timeout,
392 );
393
394 std::mem::swap(&mut self.timeout_task, &mut timeout_task);
395
396 timeout_task.abort();
397 }
398
399 match deciding_qc {
400 Some(deciding_qc) => {
401 let last_leaf = &leaf_chain[0].leaf;
402 ensure!(committing_qc.view_number() == last_leaf.view_number());
403 ensure!(committing_qc.leaf_commit() == last_leaf.commit());
404 ensure!(deciding_qc.view_number() == committing_qc.view_number() + 1);
405 },
406 None => {
407 ensure!(
410 committing_qc.epoch().is_none(),
411 "expected 2nd deciding QC for post-epochs decide"
412 );
413 },
414 }
415
416 for leaf_info in leaf_chain.iter().rev() {
417 let map = &mut self.consensus_leaves.entry(id).or_insert(BTreeMap::new());
418
419 map.entry(leaf_info.leaf.view_number())
420 .and_modify(|vec| vec.push(leaf_info.leaf.clone()))
421 .or_insert(vec![leaf_info.leaf.clone()]);
422
423 let result = self.partial_validate().await;
424
425 self.handle_result(result).await;
426 }
427 }
428
429 Ok(())
430 }
431
432 async fn check(&self) -> TestResult {
433 self.timeout_task.abort();
434
435 let mut errors: Vec<_> = self.errors.iter().map(|e| e.to_string()).collect();
436
437 if let Err(e) = self.validate().await {
438 errors.push(e.to_string());
439 }
440
441 if !errors.is_empty() {
442 TestResult::Fail(Box::new(errors))
443 } else {
444 TestResult::Pass
445 }
446 }
447}