1use std::{cmp::Ordering, collections::HashMap, fmt::Display, sync::Arc, time::Duration};
2
3use anyhow::{Result, bail, ensure};
4use committable::{Commitment, Committable};
5use disco_types::status::StatusCode;
6use espresso_types::{BlockMerkleTree, Header, NsIndex, NsProof, SeqTypes};
7use futures::{TryStreamExt, future::try_join, stream::StreamExt};
8use hotshot_query_service::{
9 Error, QueryError,
10 availability::{AvailabilityDataSource, LeafQueryData, PayloadQueryData, VidCommonQueryData},
11 data_source::{VersionedDataSource, storage::NodeStorage},
12 merklized_state::{MerklizedStateDataSource, MerklizedStateHeightPersistence, Snapshot},
13 node::BlockId,
14 types::HeightIndexed,
15};
16use hotshot_types::simple_certificate::QuorumCertificate2;
17use itertools::izip;
18use jf_merkle_tree_compat::{
19 DigestAlgorithm, MerkleTreeScheme,
20 internal::MerkleNode,
21 prelude::{Sha3Digest, Sha3Node},
22};
23use light_client::{
24 client::NAMESPACES_PARAM_TAG,
25 consensus::{header::HeaderProof, leaf::LeafProof, namespace::NamespaceProof},
26};
27use tagged_base64::TaggedBase64;
28use tokio::spawn;
29use versions::NEW_PROTOCOL_VERSION;
30
31pub(crate) async fn get_leaf_proof<State>(
36 state: &State,
37 requested_leaf: LeafQueryData<SeqTypes>,
38 finalized: Option<usize>,
39 fetch_timeout: Duration,
40 chain_limit: usize,
41) -> Result<LeafProof, Error>
42where
43 State: AvailabilityDataSource<SeqTypes> + VersionedDataSource + Clone + Send + Sync + 'static,
44 for<'a> State::ReadOnly<'a>: NodeStorage<SeqTypes>,
45{
46 let requested = requested_leaf.height() as usize;
47 let new_protocol = requested_leaf.header().version() >= NEW_PROTOCOL_VERSION;
48
49 let mut hint = finalized.filter(|finalized| finalized.saturating_sub(requested) <= chain_limit);
50
51 if let Some(finalized) = hint
53 && new_protocol
54 && let Some(cert2_height) = earliest_cert2_height(state, requested as u64).await
55 && cert2_height + 1 < finalized as u64
56 {
57 hint = None;
58 }
59
60 if let Some(finalized) = hint {
61 get_leaf_proof_with_finalized_assumption(
62 state,
63 requested_leaf,
64 finalized,
65 fetch_timeout,
66 chain_limit,
67 )
68 .await
69 } else if new_protocol {
70 get_leaf_proof_with_cert2(state, requested_leaf, fetch_timeout, chain_limit).await
71 } else {
72 get_leaf_proof_with_leaf_chain(state, requested_leaf, fetch_timeout, chain_limit).await
73 }
74}
75
76async fn earliest_cert2_height<State>(state: &State, height: u64) -> Option<u64>
79where
80 State: VersionedDataSource,
81 for<'a> State::ReadOnly<'a>: NodeStorage<SeqTypes>,
82{
83 let cert2 = state
84 .read()
85 .await
86 .ok()?
87 .load_earliest_cert2(height)
88 .await
89 .ok()??;
90 Some(cert2.data.block_number)
91}
92
93pub(crate) async fn get_leaf_proof_with_leaf_chain<State>(
95 state: &State,
96 requested_leaf: LeafQueryData<SeqTypes>,
97 fetch_timeout: Duration,
98 chain_limit: usize,
99) -> Result<LeafProof, Error>
100where
101 State: AvailabilityDataSource<SeqTypes> + VersionedDataSource + Clone + Send + Sync + 'static,
102 for<'a> State::ReadOnly<'a>: NodeStorage<SeqTypes>,
103{
104 let requested = requested_leaf.height() as usize;
105 let mut tx = state.read().await.map_err(internal)?;
106 let latest_height = NodeStorage::block_height(&mut tx).await.map_err(internal)?;
107 drop(tx);
108
109 let mut leaves = state.get_leaf_range(requested + 1..latest_height).await;
110 let mut proof = LeafProof::default();
111 let requested_leaf_qc = requested_leaf.qc().clone();
112 proof.push(requested_leaf);
113
114 let mut remaining = chain_limit;
115 while let Some(leaf) = leaves.next().await {
116 let leaf = leaf
117 .with_timeout(fetch_timeout)
118 .await
119 .ok_or_else(|| not_found("missing leaves"))?;
120
121 remaining = remaining
122 .checked_sub(1)
123 .ok_or_else(|| chain_too_long(requested, chain_limit))?;
124
125 if leaf.header().version() >= NEW_PROTOCOL_VERSION {
127 complete_proof_with_cert2(
128 state,
129 &mut proof,
130 leaf,
131 requested_leaf_qc.clone(),
132 fetch_timeout,
133 remaining,
134 )
135 .await?;
136 return Ok(proof);
137 }
138
139 if proof.push(leaf) {
140 return Ok(proof);
141 }
142 }
143
144 Err(not_found("not enough leaves to prove finality"))
147}
148
149pub(crate) async fn get_leaf_proof_with_cert2<State>(
157 state: &State,
158 requested_leaf: LeafQueryData<SeqTypes>,
159 fetch_timeout: Duration,
160 chain_limit: usize,
161) -> Result<LeafProof, Error>
162where
163 State: AvailabilityDataSource<SeqTypes> + VersionedDataSource + Clone + Send + Sync + 'static,
164 for<'a> State::ReadOnly<'a>: NodeStorage<SeqTypes>,
165{
166 let mut proof = LeafProof::default();
167 let requested_leaf_qc = requested_leaf.qc().clone();
168 complete_proof_with_cert2(
169 state,
170 &mut proof,
171 requested_leaf,
172 requested_leaf_qc,
173 fetch_timeout,
174 chain_limit,
175 )
176 .await?;
177 Ok(proof)
178}
179
180async fn complete_proof_with_cert2<State>(
183 state: &State,
184 proof: &mut LeafProof,
185 leaf: LeafQueryData<SeqTypes>,
186 requested_leaf_qc: QuorumCertificate2<SeqTypes>,
187 fetch_timeout: Duration,
188 chain_limit: usize,
189) -> Result<(), Error>
190where
191 State: AvailabilityDataSource<SeqTypes> + VersionedDataSource + Clone + Send + Sync + 'static,
192 for<'a> State::ReadOnly<'a>: NodeStorage<SeqTypes>,
193{
194 let start_height = leaf.height();
195 let start_commit = leaf.leaf().commit();
196 let limit = chain_limit as u64;
197
198 if proof.push(leaf) {
200 return Ok(());
201 }
202
203 let stored = state
204 .read()
205 .await
206 .map_err(internal)?
207 .load_earliest_cert2(start_height)
208 .await
209 .map_err(internal)?;
210
211 let earliest = stored.as_ref().map(|cert2| cert2.data.block_number);
212 let Some(cert2) = stored.filter(|cert2| cert2.data.block_number <= start_height + limit) else {
213 let known_height = async {
216 let mut tx = state.read().await?;
217 anyhow::Ok(NodeStorage::block_height(&mut tx).await? as u64)
218 }
219 .await
220 .inspect_err(|err| {
222 tracing::warn!(
223 %err,
224 height = start_height,
225 "failed to load block height, skipping cert2 fetch"
226 )
227 })
228 .ok();
229 if let Some(known_height) = known_height {
230 let end = (start_height + limit + 1).min(known_height);
231 let state = state.clone();
232 spawn(async move {
233 for height in start_height..end {
234 if state
235 .get_cert2(height)
236 .await
237 .with_timeout(fetch_timeout)
238 .await
239 .is_some()
240 {
241 break;
242 }
243 }
244 });
245 }
246 return Err(not_found(match earliest {
247 None => format!("no cert2 finality proof available at or after height {start_height}"),
248 Some(height) => format!(
249 "earliest cert2 finality proof (height {height}) is more than {chain_limit} \
250 leaves past height {start_height}"
251 ),
252 }));
253 };
254
255 let cert2_height = cert2.data.block_number;
256 if cert2_height < start_height {
257 return Err(not_found(
258 "cert2 finality proof is older than requested leaf",
259 ));
260 }
261
262 if cert2_height == start_height {
263 if start_commit != cert2.data.leaf_commit {
264 return Err(internal("stored cert2 does not finalize the expected leaf"));
265 }
266 proof.add_certificate(Arc::new(cert2), requested_leaf_qc);
267 return Ok(());
268 }
269
270 let mut leaves = state
271 .get_leaf_range(start_height as usize + 1..cert2_height as usize + 1)
272 .await;
273 while let Some(leaf) = leaves.next().await {
277 let leaf = leaf
278 .with_timeout(fetch_timeout)
279 .await
280 .ok_or_else(|| not_found("missing leaves"))?;
281
282 if leaf.height() == cert2_height {
283 if leaf.leaf().commit() != cert2.data.leaf_commit {
284 return Err(internal("stored cert2 does not finalize the expected leaf"));
285 }
286 if !proof.push(leaf) {
287 proof.add_certificate(Arc::new(cert2), requested_leaf_qc);
288 }
289 return Ok(());
290 }
291 if proof.push(leaf) {
292 return Ok(());
293 }
294 }
295
296 Err(not_found("missing cert2 leaf"))
297}
298
299pub(crate) async fn get_leaf_proof_with_finalized_assumption<State>(
300 state: &State,
301 requested_leaf: LeafQueryData<SeqTypes>,
302 finalized: usize,
303 fetch_timeout: Duration,
304 chain_limit: usize,
305) -> Result<LeafProof, Error>
306where
307 State: AvailabilityDataSource<SeqTypes>,
308{
309 let requested = requested_leaf.height() as usize;
310 if finalized <= requested {
314 return Err(Error::Custom {
315 message: format!(
316 "finalized leaf height ({finalized}) must be greater than requested ({requested})"
317 ),
318 status: StatusCode::BAD_REQUEST,
319 });
320 }
321 if finalized - requested > chain_limit {
322 return Err(Error::Custom {
323 message: format!(
324 "finalized leaf height ({finalized}) is more than {chain_limit} blocks past the \
325 requested leaf ({requested}); request a proof without the finalized parameter \
326 instead"
327 ),
328 status: StatusCode::BAD_REQUEST,
329 });
330 }
331
332 let mut leaves = state.get_leaf_range(requested + 1..finalized).await;
333 let mut proof = LeafProof::default();
334 proof.push(requested_leaf);
335
336 while let Some(leaf) = leaves.next().await {
337 let leaf = leaf
338 .with_timeout(fetch_timeout)
339 .await
340 .ok_or_else(|| not_found("missing leaves"))?;
341
342 if proof.push(leaf) {
343 return Ok(proof);
344 }
345 }
346
347 Ok(proof)
348}
349
350pub(crate) async fn get_header_proof<State>(
351 state: &State,
352 root: u64,
353 requested: BlockId<SeqTypes>,
354 fetch_timeout: Duration,
355) -> Result<HeaderProof, Error>
356where
357 State: AvailabilityDataSource<SeqTypes>
358 + MerklizedStateDataSource<SeqTypes, BlockMerkleTree, { BlockMerkleTree::ARITY }>
359 + MerklizedStateHeightPersistence
360 + VersionedDataSource,
361{
362 let header = state
363 .get_header(requested)
364 .await
365 .with_timeout(fetch_timeout)
366 .await
367 .ok_or_else(|| not_found(format!("unknown header {requested}")))?;
368 if header.height() >= root {
369 return Err(Error::Custom {
370 message: format!(
371 "height ({}) must be less than root ({root})",
372 header.height()
373 ),
374 status: StatusCode::BAD_REQUEST,
375 });
376 }
377 let path = MerklizedStateDataSource::<SeqTypes, BlockMerkleTree, _>::get_path(
378 state,
379 Snapshot::Index(root),
380 header.height(),
381 )
382 .await;
383 let path = match path {
384 Ok(path) => path,
385 Err(err) if matches!(err.status(), StatusCode::NOT_FOUND | StatusCode::GONE) => {
386 let head = state.get_last_state_height().await.map_err(merkle_error)? as u64;
388 if head <= root {
389 return Err(merkle_error(err));
390 }
391 return rebuild_header_path(state, root, header, head, fetch_timeout).await;
392 },
393 Err(err) => return Err(merkle_error(err)),
394 };
395
396 Ok(HeaderProof::new(header, path))
397}
398
399fn merkle_error(source: QueryError) -> Error {
400 Error::MerklizedState {
401 source: source.into(),
402 }
403}
404
405async fn rebuild_header_path<State>(
409 state: &State,
410 root: u64,
411 header: Header,
412 head: u64,
413 fetch_timeout: Duration,
414) -> Result<HeaderProof, Error>
415where
416 State: AvailabilityDataSource<SeqTypes>
417 + MerklizedStateDataSource<SeqTypes, BlockMerkleTree, { BlockMerkleTree::ARITY }>,
418{
419 let get_path = |height| {
420 MerklizedStateDataSource::<SeqTypes, BlockMerkleTree, _>::get_path(
421 state,
422 Snapshot::Index(head),
423 height,
424 )
425 };
426 let frontier = get_path(root - 1).await.map_err(merkle_error)?;
427 let target = get_path(header.height()).await.map_err(merkle_error)?;
428 let root_header = state
429 .get_header(BlockId::Number(root as usize))
430 .await
431 .with_timeout(fetch_timeout)
432 .await
433 .ok_or_else(|| not_found(format!("unknown header {root}")))?;
434
435 let proof = rebuild_block_proof(root, &frontier, &target).map_err(internal)?;
436 let proof = HeaderProof::new(header, proof);
437 proof
438 .verify_ref(root_header.block_merkle_tree_root())
439 .map_err(|err| {
440 internal(format!(
441 "rebuilt block merkle path does not match root {root}: {err:#}"
442 ))
443 })?;
444 Ok(proof)
445}
446
447type BlockMerkleProof = <BlockMerkleTree as MerkleTreeScheme>::MembershipProof;
448type BlockMerkleNode = MerkleNode<Commitment<Header>, u64, Sha3Node>;
449
450pub(crate) fn rebuild_block_proof(
456 num_leaves: u64,
457 frontier: &BlockMerkleProof,
458 target: &BlockMerkleProof,
459) -> Result<BlockMerkleProof> {
460 ensure!(
461 frontier.pos + 1 == num_leaves,
462 "frontier proof is for leaf {}, expected {}",
463 frontier.pos,
464 num_leaves.wrapping_sub(1),
465 );
466 ensure!(target.pos < num_leaves, "target leaf is beyond the tree");
467 ensure!(
468 frontier.proof.len() == target.proof.len(),
469 "proofs have different heights"
470 );
471
472 let mut edge_values = vec![frontier.proof[0].value()];
474 for (level, node) in frontier.proof.iter().enumerate().skip(1) {
475 let edge = frontier.pos / ARITY.pow(level as u32 - 1);
476 let data = child_values(node, edge / ARITY, edge, edge_values[level - 1])?;
477 edge_values.push(<Sha3Digest as DigestAlgorithm<
478 Commitment<Header>,
479 u64,
480 _,
481 >>::digest(&data)?);
482 }
483
484 let mut proof = vec![target.proof[0].clone()];
485 for (level, node) in target.proof.iter().enumerate().skip(1) {
486 let divisor = ARITY.pow(level as u32 - 1);
487 let edge = frontier.pos / divisor;
488 let data = child_values(
489 node,
490 target.pos / divisor / ARITY,
491 edge,
492 edge_values[level - 1],
493 )?;
494 let children = data
495 .iter()
496 .enumerate()
497 .map(|(n, value)| {
498 let index = target.pos / divisor / ARITY * ARITY + n as u64;
499 Arc::new(match index.cmp(&edge) {
500 Ordering::Greater => BlockMerkleNode::Empty,
501 _ => BlockMerkleNode::ForgettenSubtree { value: *value },
502 })
503 })
504 .collect();
505 proof.push(BlockMerkleNode::Branch {
506 value: Sha3Node::default(),
507 children,
508 });
509 }
510 Ok(BlockMerkleProof {
511 pos: target.pos,
512 proof,
513 })
514}
515
516const ARITY: u64 = BlockMerkleTree::ARITY as u64;
517
518fn child_values(
521 node: &BlockMerkleNode,
522 parent: u64,
523 edge: u64,
524 edge_value: Sha3Node,
525) -> Result<Vec<Sha3Node>> {
526 let BlockMerkleNode::Branch { children, .. } = node else {
527 bail!("expected a branch node in the proof");
528 };
529 Ok(children
530 .iter()
531 .enumerate()
532 .map(|(n, child)| match (parent * ARITY + n as u64).cmp(&edge) {
533 Ordering::Less => child.value(),
534 Ordering::Equal => edge_value,
535 Ordering::Greater => Sha3Node::default(),
536 })
537 .collect())
538}
539
540pub(crate) async fn fetch_block_data_range<State>(
541 state: &State,
542 start: usize,
543 end: usize,
544 fetch_timeout: Duration,
545 large_object_range_limit: usize,
546) -> Result<
547 Vec<(
548 Header,
549 PayloadQueryData<SeqTypes>,
550 VidCommonQueryData<SeqTypes>,
551 )>,
552 Error,
553>
554where
555 State: AvailabilityDataSource<SeqTypes>,
556{
557 if end <= start {
558 return Err(Error::Custom {
559 message: format!("requested empty interval [{start}, {end})"),
560 status: StatusCode::BAD_REQUEST,
561 });
562 }
563 if end - start > large_object_range_limit {
564 return Err(Error::Custom {
565 message: format!(
566 "requested range [{start}, {end}) exceeds maximum size {large_object_range_limit}"
567 ),
568 status: StatusCode::BAD_REQUEST,
569 });
570 }
571
572 let fetch_headers = async move {
573 state
574 .get_header_range(start..end)
575 .await
576 .enumerate()
577 .then(|(i, fetch)| async move {
578 fetch
579 .with_timeout(fetch_timeout)
580 .await
581 .ok_or_else(|| Error::Custom {
582 message: format!("missing header {}", start + i),
583 status: StatusCode::NOT_FOUND,
584 })
585 })
586 .try_collect::<Vec<_>>()
587 .await
588 };
589 let fetch_payloads = async move {
590 state
591 .get_payload_range(start..end)
592 .await
593 .enumerate()
594 .then(|(i, fetch)| async move {
595 fetch
596 .with_timeout(fetch_timeout)
597 .await
598 .ok_or_else(|| Error::Custom {
599 message: format!("missing payload {}", start + i),
600 status: StatusCode::NOT_FOUND,
601 })
602 })
603 .try_collect::<Vec<_>>()
604 .await
605 };
606 let fetch_vid_commons = async move {
607 state
608 .get_vid_common_range(start..end)
609 .await
610 .enumerate()
611 .then(|(i, fetch)| async move {
612 fetch
613 .with_timeout(fetch_timeout)
614 .await
615 .ok_or_else(|| Error::Custom {
616 message: format!("missing VID common {}", start + i),
617 status: StatusCode::NOT_FOUND,
618 })
619 })
620 .try_collect::<Vec<_>>()
621 .await
622 };
623 let (headers, (payloads, vid_commons)) =
624 try_join(fetch_headers, try_join(fetch_payloads, fetch_vid_commons)).await?;
625
626 Ok(izip!(headers, payloads, vid_commons).collect())
627}
628
629pub(crate) async fn get_namespace_proof_range<State>(
631 state: &State,
632 start: usize,
633 end: usize,
634 namespace: u64,
635 fetch_timeout: Duration,
636 large_object_range_limit: usize,
637) -> Result<Vec<NamespaceProof>, Error>
638where
639 State: AvailabilityDataSource<SeqTypes>,
640{
641 let blocks = get_namespaces_proof_range(
642 state,
643 start,
644 end,
645 &[namespace],
646 fetch_timeout,
647 large_object_range_limit,
648 )
649 .await?;
650 Ok(blocks
651 .into_iter()
652 .map(|mut block| {
653 block
654 .remove(&namespace)
655 .unwrap_or_else(NamespaceProof::not_present)
656 })
657 .collect())
658}
659
660pub(crate) async fn get_namespaces_proof_range<State>(
661 state: &State,
662 start: usize,
663 end: usize,
664 namespaces: &[u64],
665 fetch_timeout: Duration,
666 large_object_range_limit: usize,
667) -> Result<Vec<HashMap<u64, NamespaceProof>>, Error>
668where
669 State: AvailabilityDataSource<SeqTypes>,
670{
671 fetch_block_data_range(state, start, end, fetch_timeout, large_object_range_limit)
672 .await?
673 .into_iter()
674 .map(|(header, payload, vid_common)| {
675 namespaces
676 .iter()
677 .filter_map(|&namespace| {
678 let ns_index = header.ns_table().find_ns_id(&namespace.into())?;
679 Some(
680 build_namespace_proof(&payload, &ns_index, &vid_common)
681 .map(|proof| (namespace, proof)),
682 )
683 })
684 .collect::<Result<HashMap<_, _>, _>>()
685 })
686 .collect()
687}
688
689pub(crate) fn parse_namespaces_str(encoded: &str) -> anyhow::Result<Vec<u64>> {
692 let encoded: TaggedBase64 = encoded
693 .parse()
694 .map_err(|err| anyhow::anyhow!("invalid namespaces parameter: {err}"))?;
695 if encoded.tag() != NAMESPACES_PARAM_TAG {
696 anyhow::bail!(
697 "invalid namespaces parameter tag: expected {NAMESPACES_PARAM_TAG}, got {}",
698 encoded.tag()
699 );
700 }
701 serde_json::from_slice(&encoded.value())
702 .map_err(|err| anyhow::anyhow!("invalid namespaces parameter: {err}"))
703}
704
705fn build_namespace_proof(
707 payload: &PayloadQueryData<SeqTypes>,
708 ns_index: &NsIndex,
709 vid_common: &VidCommonQueryData<SeqTypes>,
710) -> Result<NamespaceProof, Error> {
711 let ns_proof =
712 NsProof::new(payload.data(), ns_index, vid_common.common()).ok_or_else(|| {
713 Error::Custom {
714 message: "failed to construct namespace proof".into(),
715 status: StatusCode::INTERNAL_SERVER_ERROR,
716 }
717 })?;
718 Ok(NamespaceProof::new(ns_proof, vid_common.common().clone()))
719}
720
721fn internal(err: impl Display) -> Error {
722 Error::Custom {
723 message: err.to_string(),
724 status: StatusCode::INTERNAL_SERVER_ERROR,
725 }
726}
727
728fn not_found(msg: impl Into<String>) -> Error {
729 Error::Custom {
730 message: msg.into(),
731 status: StatusCode::NOT_FOUND,
732 }
733}
734
735fn chain_too_long(requested: usize, chain_limit: usize) -> Error {
736 not_found(format!(
737 "no finality proof found within {chain_limit} leaves of requested leaf {requested}"
738 ))
739}
740
741#[cfg(test)]
742mod test {
743 use std::{marker::PhantomData, sync::Mutex};
744
745 use axum::{Router, extract::Path, http::HeaderMap, routing::get};
746 use committable::Committable;
747 use disco_types::error::Error;
748 use espresso_types::BLOCK_MERKLE_TREE_HEIGHT;
749 use futures::future::join_all;
750 use hotshot_query_service::{
751 availability::{BlockQueryData, TransactionIndex, VidCommonQueryData},
752 data_source::{
753 Transaction,
754 storage::{AvailabilityStorage, UpdateAvailabilityStorage, pruning::PrunerCfg},
755 },
756 fetching::provider::TrustedQueryServiceProvider,
757 merklized_state::{MerklizedState, UpdateStateData},
758 };
759 use hotshot_types::{data::ViewNumber, simple_vote::Vote2Data};
760 use http_wire::{ServerError, respond};
761 use itertools::Itertools;
762 use jf_merkle_tree_compat::{
763 AppendableMerkleTreeScheme, ToTraversalPath, prelude::SHA3MerkleTree,
764 };
765 use light_client::{
766 client::{Client as _, FallbackClient, QueryServiceClient},
767 consensus::leaf::{FinalityProof, LeafProofHint},
768 testing::{
769 AlwaysTrueQuorum, LEAF_CHAIN_EPOCH_HEIGHT, TestClient, VersionCheckQuorum,
770 custom_leaf_chain_with_upgrade, custom_leaf_chain_with_upgrades, leaf_chain,
771 leaf_chain_with_upgrade,
772 },
773 };
774 use rstest_reuse::apply;
775 use tokio::{
776 net::TcpListener,
777 time::{sleep, timeout},
778 };
779 use vbs::version::Version;
780 use versions::{
781 DRB_AND_HEADER_UPGRADE_VERSION, EPOCH_REWARD_VERSION, EPOCH_VERSION, FEE_VERSION,
782 NEW_PROTOCOL_VERSION, Upgrade,
783 };
784
785 use super::*;
786 use crate::api::{
787 data_source::{Provider, testing::TestableSqlDataSource},
788 test::sql_data_sources,
789 };
790
791 const CHAIN_LIMIT: usize = 500;
792
793 fn cert2_for_leaf(leaf: &LeafQueryData<SeqTypes>) -> espresso_types::Certificate2<SeqTypes> {
794 let data = Vote2Data {
795 leaf_commit: leaf.leaf().commit(),
796 epoch: leaf.qc().data.epoch.unwrap(),
797 block_number: leaf.height(),
798 };
799 espresso_types::Certificate2::new(
800 data.clone(),
801 data.commit(),
802 leaf.leaf().view_number(),
803 None,
804 PhantomData,
805 )
806 }
807
808 #[apply(sql_data_sources)]
809 async fn test_two_chain<D: TestableSqlDataSource>(_d: PhantomData<D>) {
810 let storage = D::create_storage().await;
811 let ds = D::create(D::persistence_options(&storage), Default::default(), false)
812 .await
813 .unwrap();
814
815 let leaves = leaf_chain(1..=3, EPOCH_VERSION).await;
817 {
818 let mut tx = ds.write().await.unwrap();
819 tx.insert_leaf(&leaves[0]).await.unwrap();
820 tx.insert_leaf(&leaves[1]).await.unwrap();
821 tx.insert_leaf(&leaves[2]).await.unwrap();
822 tx.commit().await.unwrap();
823 }
824
825 let proof =
827 get_leaf_proof_with_leaf_chain(&ds, leaves[0].clone(), Duration::MAX, CHAIN_LIMIT)
828 .await
829 .unwrap();
830 assert_eq!(
831 proof
832 .verify(LeafProofHint::Quorum(&AlwaysTrueQuorum::new(
833 LEAF_CHAIN_EPOCH_HEIGHT
834 )))
835 .await
836 .unwrap(),
837 leaves[0]
838 );
839 }
840
841 #[apply(sql_data_sources)]
842 async fn test_finalized<D: TestableSqlDataSource>(_d: PhantomData<D>) {
843 let storage = D::create_storage().await;
844 let ds = D::create(D::persistence_options(&storage), Default::default(), false)
845 .await
846 .unwrap();
847
848 let leaves = leaf_chain(1..=2, EPOCH_VERSION).await;
851 {
852 let mut tx = ds.write().await.unwrap();
853 tx.insert_leaf(&leaves[0]).await.unwrap();
854 tx.commit().await.unwrap();
855 }
856
857 let proof = get_leaf_proof_with_finalized_assumption(
858 &ds,
859 leaves[0].clone(),
860 2,
861 Duration::MAX,
862 CHAIN_LIMIT,
863 )
864 .await
865 .unwrap();
866 assert_eq!(
867 proof
868 .verify(LeafProofHint::assumption(leaves[1].leaf()))
869 .await
870 .unwrap(),
871 leaves[0]
872 );
873 }
874
875 #[apply(sql_data_sources)]
876 async fn test_new_protocol_finalized_assumption<D: TestableSqlDataSource>(_d: PhantomData<D>) {
877 let storage = D::create_storage().await;
878 let ds = D::create(D::persistence_options(&storage), Default::default(), false)
879 .await
880 .unwrap();
881
882 let leaves = leaf_chain(1..=2, NEW_PROTOCOL_VERSION).await;
883 {
884 let mut tx = ds.write().await.unwrap();
885 tx.insert_leaf(&leaves[0]).await.unwrap();
886 tx.commit().await.unwrap();
887 }
888
889 let proof = get_leaf_proof_with_finalized_assumption(
890 &ds,
891 leaves[0].clone(),
892 2,
893 Duration::MAX,
894 CHAIN_LIMIT,
895 )
896 .await
897 .unwrap();
898 assert!(matches!(proof.proof(), FinalityProof::Assumption));
899 assert_eq!(
900 proof
901 .verify(LeafProofHint::assumption(leaves[1].leaf()))
902 .await
903 .unwrap(),
904 leaves[0]
905 );
906 }
907
908 #[apply(sql_data_sources)]
909 async fn test_new_protocol_cert2<D: TestableSqlDataSource>(_d: PhantomData<D>) {
910 let storage = D::create_storage().await;
911 let ds = D::create(D::persistence_options(&storage), Default::default(), false)
912 .await
913 .unwrap();
914
915 let leaves = leaf_chain(1..=2, NEW_PROTOCOL_VERSION).await;
916 let cert2_leaf = &leaves[1];
917 let cert2 = cert2_for_leaf(cert2_leaf);
918
919 {
920 let mut tx = ds.write().await.unwrap();
921 tx.insert_leaf(&leaves[0]).await.unwrap();
922 tx.insert_leaf(cert2_leaf).await.unwrap();
923 tx.insert_cert2(cert2_leaf.height(), cert2).await.unwrap();
924 tx.commit().await.unwrap();
925 }
926
927 let proof = get_leaf_proof_with_cert2(&ds, leaves[0].clone(), Duration::MAX, CHAIN_LIMIT)
928 .await
929 .unwrap();
930 assert!(matches!(proof.proof(), FinalityProof::NewProtocol { .. }));
931 assert_eq!(
932 proof
933 .verify(LeafProofHint::Quorum(&AlwaysTrueQuorum::new(
934 LEAF_CHAIN_EPOCH_HEIGHT
935 )))
936 .await
937 .unwrap(),
938 leaves[0]
939 );
940 }
941
942 #[apply(sql_data_sources)]
943 async fn test_new_protocol_cert2_chain_limit<D: TestableSqlDataSource>(_d: PhantomData<D>) {
944 let storage = D::create_storage().await;
945 let ds = D::create(D::persistence_options(&storage), Default::default(), false)
946 .await
947 .unwrap();
948
949 let leaves = leaf_chain(1..=4, NEW_PROTOCOL_VERSION).await;
951 let cert2_leaf = &leaves[3];
952 let cert2 = cert2_for_leaf(cert2_leaf);
953
954 {
955 let mut tx = ds.write().await.unwrap();
956 for leaf in &leaves {
957 tx.insert_leaf(leaf).await.unwrap();
958 }
959 tx.insert_cert2(cert2_leaf.height(), cert2).await.unwrap();
960 tx.commit().await.unwrap();
961 }
962
963 let err = get_leaf_proof_with_cert2(&ds, leaves[0].clone(), Duration::MAX, 2)
964 .await
965 .unwrap_err();
966 assert_eq!(err.status(), StatusCode::NOT_FOUND);
967
968 let proof = get_leaf_proof_with_cert2(&ds, leaves[0].clone(), Duration::MAX, 3)
969 .await
970 .unwrap();
971 assert!(matches!(proof.proof(), FinalityProof::NewProtocol { .. }));
972 assert_eq!(
973 proof
974 .verify(LeafProofHint::Quorum(&AlwaysTrueQuorum::new(
975 LEAF_CHAIN_EPOCH_HEIGHT
976 )))
977 .await
978 .unwrap(),
979 leaves[0]
980 );
981 }
982
983 #[apply(sql_data_sources)]
984 async fn test_new_protocol_hint_vs_cert2<D: TestableSqlDataSource>(_d: PhantomData<D>) {
985 let storage = D::create_storage().await;
986 let ds = D::create(D::persistence_options(&storage), Default::default(), false)
987 .await
988 .unwrap();
989
990 let leaves = leaf_chain(1..=5, NEW_PROTOCOL_VERSION).await;
991 let cert2_leaf = &leaves[1];
992 let cert2 = cert2_for_leaf(cert2_leaf);
993
994 {
995 let mut tx = ds.write().await.unwrap();
996 for leaf in &leaves {
997 tx.insert_leaf(leaf).await.unwrap();
998 }
999 tx.insert_cert2(cert2_leaf.height(), cert2).await.unwrap();
1000 tx.commit().await.unwrap();
1001 }
1002
1003 let proof = get_leaf_proof(&ds, leaves[0].clone(), Some(5), Duration::MAX, CHAIN_LIMIT)
1005 .await
1006 .unwrap();
1007 assert!(matches!(proof.proof(), FinalityProof::NewProtocol { .. }));
1008 assert_eq!(
1009 proof
1010 .verify(LeafProofHint::Quorum(&AlwaysTrueQuorum::new(
1011 LEAF_CHAIN_EPOCH_HEIGHT
1012 )))
1013 .await
1014 .unwrap(),
1015 leaves[0]
1016 );
1017
1018 let proof = get_leaf_proof(&ds, leaves[0].clone(), Some(3), Duration::MAX, CHAIN_LIMIT)
1020 .await
1021 .unwrap();
1022 assert!(matches!(proof.proof(), FinalityProof::Assumption));
1023 assert_eq!(
1024 proof
1025 .verify(LeafProofHint::assumption(leaves[2].leaf()))
1026 .await
1027 .unwrap(),
1028 leaves[0]
1029 );
1030
1031 let proof = get_leaf_proof(&ds, leaves[2].clone(), Some(5), Duration::MAX, CHAIN_LIMIT)
1033 .await
1034 .unwrap();
1035 assert!(matches!(proof.proof(), FinalityProof::Assumption));
1036 assert_eq!(
1037 proof
1038 .verify(LeafProofHint::assumption(leaves[4].leaf()))
1039 .await
1040 .unwrap(),
1041 leaves[2]
1042 );
1043 }
1044
1045 #[apply(sql_data_sources)]
1046 async fn test_distant_hint_falls_through<D: TestableSqlDataSource>(_d: PhantomData<D>) {
1047 let storage = D::create_storage().await;
1048 let ds = D::create(D::persistence_options(&storage), Default::default(), false)
1049 .await
1050 .unwrap();
1051
1052 let leaves = leaf_chain_with_upgrade(
1055 1..=5,
1056 4,
1057 Upgrade::new(DRB_AND_HEADER_UPGRADE_VERSION, NEW_PROTOCOL_VERSION),
1058 )
1059 .await;
1060 let cert2_leaf = &leaves[4];
1061 let cert2 = cert2_for_leaf(cert2_leaf);
1062
1063 {
1064 let mut tx = ds.write().await.unwrap();
1065 for leaf in &leaves {
1066 tx.insert_leaf(leaf).await.unwrap();
1067 }
1068 tx.insert_cert2(cert2_leaf.height(), cert2).await.unwrap();
1069 tx.commit().await.unwrap();
1070 }
1071
1072 let proof = get_leaf_proof(
1074 &ds,
1075 leaves[0].clone(),
1076 Some(1000),
1077 Duration::MAX,
1078 CHAIN_LIMIT,
1079 )
1080 .await
1081 .unwrap();
1082 assert!(matches!(proof.proof(), FinalityProof::HotStuff2 { .. }));
1083 assert_eq!(
1084 proof
1085 .verify(LeafProofHint::Quorum(&AlwaysTrueQuorum::new(
1086 LEAF_CHAIN_EPOCH_HEIGHT
1087 )))
1088 .await
1089 .unwrap(),
1090 leaves[0]
1091 );
1092
1093 let proof = get_leaf_proof(
1095 &ds,
1096 leaves[3].clone(),
1097 Some(1000),
1098 Duration::MAX,
1099 CHAIN_LIMIT,
1100 )
1101 .await
1102 .unwrap();
1103 assert!(matches!(proof.proof(), FinalityProof::NewProtocol { .. }));
1104 assert_eq!(
1105 proof
1106 .verify(LeafProofHint::Quorum(&AlwaysTrueQuorum::new(
1107 LEAF_CHAIN_EPOCH_HEIGHT
1108 )))
1109 .await
1110 .unwrap(),
1111 leaves[3]
1112 );
1113 }
1114
1115 #[apply(sql_data_sources)]
1116 async fn test_qc_chain_chain_limit<D: TestableSqlDataSource>(_d: PhantomData<D>) {
1117 let storage = D::create_storage().await;
1118 let ds = D::create(D::persistence_options(&storage), Default::default(), false)
1119 .await
1120 .unwrap();
1121
1122 let leaves = leaf_chain(1..=3, EPOCH_VERSION).await;
1124 {
1125 let mut tx = ds.write().await.unwrap();
1126 for leaf in &leaves {
1127 tx.insert_leaf(leaf).await.unwrap();
1128 }
1129 tx.commit().await.unwrap();
1130 }
1131
1132 let err = get_leaf_proof_with_leaf_chain(&ds, leaves[0].clone(), Duration::MAX, 1)
1133 .await
1134 .unwrap_err();
1135 assert_eq!(err.status(), StatusCode::NOT_FOUND);
1136
1137 let proof = get_leaf_proof_with_leaf_chain(&ds, leaves[0].clone(), Duration::MAX, 2)
1138 .await
1139 .unwrap();
1140 assert_eq!(
1141 proof
1142 .verify(LeafProofHint::Quorum(&AlwaysTrueQuorum::new(
1143 LEAF_CHAIN_EPOCH_HEIGHT
1144 )))
1145 .await
1146 .unwrap(),
1147 leaves[0]
1148 );
1149 }
1150
1151 #[apply(sql_data_sources)]
1152 async fn test_finalized_hint_too_far<D: TestableSqlDataSource>(_d: PhantomData<D>) {
1153 let storage = D::create_storage().await;
1154 let ds = D::create(D::persistence_options(&storage), Default::default(), false)
1155 .await
1156 .unwrap();
1157
1158 let leaves = leaf_chain(1..=2, EPOCH_VERSION).await;
1159 {
1160 let mut tx = ds.write().await.unwrap();
1161 tx.insert_leaf(&leaves[0]).await.unwrap();
1162 tx.commit().await.unwrap();
1163 }
1164
1165 let err = get_leaf_proof_with_finalized_assumption(
1166 &ds,
1167 leaves[0].clone(),
1168 1000,
1169 Duration::MAX,
1170 10,
1171 )
1172 .await
1173 .unwrap_err();
1174 assert_eq!(err.status(), StatusCode::BAD_REQUEST);
1175 }
1176
1177 #[apply(sql_data_sources)]
1178 async fn test_qc_chain_new_protocol_cutover<D: TestableSqlDataSource>(_d: PhantomData<D>) {
1179 let storage = D::create_storage().await;
1180 let ds = D::create(D::persistence_options(&storage), Default::default(), false)
1181 .await
1182 .unwrap();
1183
1184 let leaves = custom_leaf_chain_with_upgrade(
1187 1..=4,
1188 2,
1189 Upgrade::new(DRB_AND_HEADER_UPGRADE_VERSION, NEW_PROTOCOL_VERSION),
1190 |proposal| {
1191 proposal.view_number = ViewNumber::new(proposal.block_header.height() * 2);
1192 },
1193 )
1194 .await;
1195 assert_eq!(leaves[0].header().version(), DRB_AND_HEADER_UPGRADE_VERSION);
1196 assert_eq!(leaves[1].header().version(), NEW_PROTOCOL_VERSION);
1197 let cert2_leaf = &leaves[3];
1198 let cert2 = cert2_for_leaf(cert2_leaf);
1199
1200 {
1201 let mut tx = ds.write().await.unwrap();
1202 for leaf in &leaves {
1203 tx.insert_leaf(leaf).await.unwrap();
1204 }
1205 tx.insert_cert2(cert2_leaf.height(), cert2).await.unwrap();
1206 tx.commit().await.unwrap();
1207 }
1208
1209 let proof =
1210 get_leaf_proof_with_leaf_chain(&ds, leaves[0].clone(), Duration::MAX, CHAIN_LIMIT)
1211 .await
1212 .unwrap();
1213 assert!(matches!(proof.proof(), FinalityProof::NewProtocol { .. }));
1214 assert_eq!(
1215 proof
1216 .verify(LeafProofHint::Quorum(&AlwaysTrueQuorum::new(
1217 LEAF_CHAIN_EPOCH_HEIGHT
1218 )))
1219 .await
1220 .unwrap(),
1221 leaves[0]
1222 );
1223 }
1224
1225 #[apply(sql_data_sources)]
1226 async fn test_qc_chain_new_protocol_cutover_two_chain<D: TestableSqlDataSource>(
1227 _d: PhantomData<D>,
1228 ) {
1229 let storage = D::create_storage().await;
1230 let ds = D::create(D::persistence_options(&storage), Default::default(), false)
1231 .await
1232 .unwrap();
1233
1234 let leaves = leaf_chain_with_upgrade(
1237 1..=3,
1238 3,
1239 Upgrade::new(DRB_AND_HEADER_UPGRADE_VERSION, NEW_PROTOCOL_VERSION),
1240 )
1241 .await;
1242 assert_eq!(leaves[1].header().version(), DRB_AND_HEADER_UPGRADE_VERSION);
1243 assert_eq!(leaves[2].header().version(), NEW_PROTOCOL_VERSION);
1244
1245 {
1246 let mut tx = ds.write().await.unwrap();
1247 for leaf in &leaves {
1248 tx.insert_leaf(leaf).await.unwrap();
1249 }
1250 tx.commit().await.unwrap();
1251 }
1252
1253 let proof =
1254 get_leaf_proof_with_leaf_chain(&ds, leaves[0].clone(), Duration::MAX, CHAIN_LIMIT)
1255 .await
1256 .unwrap();
1257 assert!(matches!(proof.proof(), FinalityProof::HotStuff2 { .. }));
1258 assert_eq!(
1259 proof
1260 .verify(LeafProofHint::Quorum(&VersionCheckQuorum::new(
1261 leaves.iter().map(|leaf| leaf.leaf().clone()),
1262 LEAF_CHAIN_EPOCH_HEIGHT
1263 )))
1264 .await
1265 .unwrap(),
1266 leaves[0]
1267 );
1268 }
1269
1270 #[apply(sql_data_sources)]
1271 async fn test_qc_chain_last_legacy_leaf_uses_cert2<D: TestableSqlDataSource>(
1272 _d: PhantomData<D>,
1273 ) {
1274 let storage = D::create_storage().await;
1275 let ds = D::create(D::persistence_options(&storage), Default::default(), false)
1276 .await
1277 .unwrap();
1278
1279 let leaves = leaf_chain_with_upgrade(
1282 1..=4,
1283 2,
1284 Upgrade::new(DRB_AND_HEADER_UPGRADE_VERSION, NEW_PROTOCOL_VERSION),
1285 )
1286 .await;
1287 assert_eq!(leaves[0].header().version(), DRB_AND_HEADER_UPGRADE_VERSION);
1288 assert_eq!(leaves[1].header().version(), NEW_PROTOCOL_VERSION);
1289
1290 let cert2_leaf = &leaves[2];
1291 let cert2 = cert2_for_leaf(cert2_leaf);
1292
1293 {
1294 let mut tx = ds.write().await.unwrap();
1295 for leaf in &leaves {
1296 tx.insert_leaf(leaf).await.unwrap();
1297 }
1298 tx.insert_cert2(cert2_leaf.height(), cert2).await.unwrap();
1299 tx.commit().await.unwrap();
1300 }
1301
1302 let proof =
1303 get_leaf_proof_with_leaf_chain(&ds, leaves[0].clone(), Duration::MAX, CHAIN_LIMIT)
1304 .await
1305 .unwrap();
1306 assert!(matches!(proof.proof(), FinalityProof::NewProtocol { .. }));
1307 assert_eq!(
1308 proof
1309 .verify(LeafProofHint::Quorum(&VersionCheckQuorum::new(
1310 leaves.iter().map(|leaf| leaf.leaf().clone()),
1311 LEAF_CHAIN_EPOCH_HEIGHT
1312 )))
1313 .await
1314 .unwrap(),
1315 leaves[0]
1316 );
1317 }
1318
1319 #[apply(sql_data_sources)]
1324 async fn test_leaf_before_cutover_uses_hotstuff2<D: TestableSqlDataSource>(_d: PhantomData<D>) {
1325 let storage = D::create_storage().await;
1326 let ds = D::create(D::persistence_options(&storage), Default::default(), false)
1327 .await
1328 .unwrap();
1329
1330 let leaves = leaf_chain_with_upgrade(
1332 1..=5,
1333 4,
1334 Upgrade::new(DRB_AND_HEADER_UPGRADE_VERSION, NEW_PROTOCOL_VERSION),
1335 )
1336 .await;
1337 assert!(leaves[1].header().version() < NEW_PROTOCOL_VERSION);
1338 assert!(leaves[3].header().version() >= NEW_PROTOCOL_VERSION);
1339
1340 {
1341 let mut tx = ds.write().await.unwrap();
1342 for leaf in &leaves {
1343 tx.insert_leaf(leaf).await.unwrap();
1344 }
1345 tx.commit().await.unwrap();
1346 }
1347
1348 let proof =
1349 get_leaf_proof_with_leaf_chain(&ds, leaves[1].clone(), Duration::MAX, CHAIN_LIMIT)
1350 .await
1351 .unwrap();
1352 assert!(matches!(proof.proof(), FinalityProof::HotStuff2 { .. }));
1353 assert_eq!(
1354 proof
1355 .verify(LeafProofHint::Quorum(&VersionCheckQuorum::new(
1356 leaves.iter().map(|leaf| leaf.leaf().clone()),
1357 LEAF_CHAIN_EPOCH_HEIGHT
1358 )))
1359 .await
1360 .unwrap(),
1361 leaves[1]
1362 );
1363 }
1364
1365 #[apply(sql_data_sources)]
1371 async fn test_leaf_before_cutover_uses_cert2<D: TestableSqlDataSource>(_d: PhantomData<D>) {
1372 let storage = D::create_storage().await;
1373 let ds = D::create(D::persistence_options(&storage), Default::default(), false)
1374 .await
1375 .unwrap();
1376
1377 let leaves = custom_leaf_chain_with_upgrade(
1380 1..=5,
1381 4,
1382 Upgrade::new(DRB_AND_HEADER_UPGRADE_VERSION, NEW_PROTOCOL_VERSION),
1383 |proposal| {
1384 proposal.view_number = ViewNumber::new(proposal.block_header.height() * 2);
1385 },
1386 )
1387 .await;
1388 assert!(leaves[1].header().version() < NEW_PROTOCOL_VERSION);
1389 assert!(leaves[3].header().version() >= NEW_PROTOCOL_VERSION);
1390 let cert2_leaf = &leaves[3];
1391 let cert2 = cert2_for_leaf(cert2_leaf);
1392
1393 {
1394 let mut tx = ds.write().await.unwrap();
1395 for leaf in &leaves {
1396 tx.insert_leaf(leaf).await.unwrap();
1397 }
1398 tx.insert_cert2(cert2_leaf.height(), cert2).await.unwrap();
1399 tx.commit().await.unwrap();
1400 }
1401
1402 let proof =
1403 get_leaf_proof_with_leaf_chain(&ds, leaves[1].clone(), Duration::MAX, CHAIN_LIMIT)
1404 .await
1405 .unwrap();
1406 assert!(matches!(proof.proof(), FinalityProof::NewProtocol { .. }));
1407 assert_eq!(
1408 proof
1409 .verify(LeafProofHint::Quorum(&AlwaysTrueQuorum::new(
1410 LEAF_CHAIN_EPOCH_HEIGHT
1411 )))
1412 .await
1413 .unwrap(),
1414 leaves[1]
1415 );
1416 }
1417
1418 #[apply(sql_data_sources)]
1421 async fn test_leaf_proofs_across_history<D: TestableSqlDataSource>(_d: PhantomData<D>) {
1422 const UPGRADES: [(u64, Version); 4] = [
1424 (6, EPOCH_VERSION),
1425 (11, DRB_AND_HEADER_UPGRADE_VERSION),
1426 (16, EPOCH_REWARD_VERSION),
1427 (21, NEW_PROTOCOL_VERSION),
1428 ];
1429 const FAILED_VIEWS_BEFORE: [u64; 2] = [3, 13];
1431 const CERT2_HEIGHTS: [u64; 3] = [22, 24, 25];
1432
1433 let view_of = |height: u64| {
1434 height + FAILED_VIEWS_BEFORE.iter().filter(|&&h| h <= height).count() as u64
1435 };
1436 let leaves = custom_leaf_chain_with_upgrades(1..=25, FEE_VERSION, &UPGRADES, |proposal| {
1437 proposal.view_number = ViewNumber::new(view_of(proposal.block_header.height()));
1438 if let Some(cert) = &mut proposal.upgrade_certificate {
1440 let (height, _) = UPGRADES
1441 .into_iter()
1442 .find(|(_, version)| *version == cert.data.new_version)
1443 .unwrap();
1444 let first_view = ViewNumber::new(view_of(height));
1445 cert.data.old_version_last_view = first_view - 1;
1446 cert.data.new_version_first_view = first_view;
1447 cert.data.decide_by = first_view;
1448 }
1449 })
1450 .await;
1451 let leaf_at = |height: u64| &leaves[height as usize - 1];
1452
1453 let storage = D::create_storage().await;
1454 let ds = D::create(D::persistence_options(&storage), Default::default(), false)
1455 .await
1456 .unwrap();
1457 {
1458 let mut tx = ds.write().await.unwrap();
1459 for leaf in &leaves {
1460 tx.insert_leaf(leaf).await.unwrap();
1461 }
1462 for height in CERT2_HEIGHTS {
1463 tx.insert_cert2(height, cert2_for_leaf(leaf_at(height)))
1464 .await
1465 .unwrap();
1466 }
1467 tx.commit().await.unwrap();
1468 }
1469
1470 let quorum = VersionCheckQuorum::new(
1471 leaves.iter().map(|leaf| leaf.leaf().clone()),
1472 LEAF_CHAIN_EPOCH_HEIGHT,
1473 );
1474 for leaf in &leaves {
1475 let height = leaf.height();
1476 let proof = get_leaf_proof(&ds, leaf.clone(), None, Duration::MAX, CHAIN_LIMIT)
1477 .await
1478 .unwrap_or_else(|err| panic!("no proof for height {height}: {err:#}"));
1479 match height {
1480 1..=4 => assert!(matches!(proof.proof(), FinalityProof::HotStuff { .. })),
1482 5..=19 => assert!(matches!(proof.proof(), FinalityProof::HotStuff2 { .. })),
1484 _ => assert!(matches!(proof.proof(), FinalityProof::NewProtocol { .. })),
1486 }
1487 assert_eq!(
1488 proof.verify(LeafProofHint::Quorum(&quorum)).await.unwrap(),
1489 *leaf,
1490 "height {height}"
1491 );
1492 }
1493
1494 for (leaf, finalized) in leaves.iter().tuple_windows() {
1496 let height = leaf.height();
1497 let proof = get_leaf_proof(
1498 &ds,
1499 leaf.clone(),
1500 Some(finalized.height() as usize),
1501 Duration::MAX,
1502 CHAIN_LIMIT,
1503 )
1504 .await
1505 .unwrap_or_else(|err| panic!("no proof for height {height}: {err:#}"));
1506 assert!(
1507 matches!(proof.proof(), FinalityProof::Assumption),
1508 "height {height}"
1509 );
1510 assert_eq!(
1511 proof
1512 .verify(LeafProofHint::<AlwaysTrueQuorum>::Assumption(
1513 finalized.leaf()
1514 ))
1515 .await
1516 .unwrap(),
1517 *leaf,
1518 "height {height}"
1519 );
1520 }
1521 }
1522
1523 #[apply(sql_data_sources)]
1524 async fn test_bad_finalized<D: TestableSqlDataSource>(_d: PhantomData<D>) {
1525 let storage = D::create_storage().await;
1526 let ds = D::create(D::persistence_options(&storage), Default::default(), false)
1527 .await
1528 .unwrap();
1529
1530 let leaves = leaf_chain(1..2, EPOCH_VERSION).await;
1533 {
1534 let mut tx = ds.write().await.unwrap();
1535 tx.insert_leaf(&leaves[0]).await.unwrap();
1536 tx.commit().await.unwrap();
1537 }
1538
1539 let err = get_leaf_proof_with_finalized_assumption(
1540 &ds,
1541 leaves[0].clone(),
1542 0,
1543 Duration::MAX,
1544 CHAIN_LIMIT,
1545 )
1546 .await
1547 .unwrap_err();
1548 assert_eq!(err.status(), StatusCode::BAD_REQUEST);
1549 }
1550
1551 #[apply(sql_data_sources)]
1552 async fn test_no_chain<D: TestableSqlDataSource>(_d: PhantomData<D>) {
1553 let storage = D::create_storage().await;
1554 let ds = D::create(D::persistence_options(&storage), Default::default(), false)
1555 .await
1556 .unwrap();
1557
1558 let leaves = leaf_chain(1..=4, EPOCH_VERSION).await;
1561 {
1562 let mut tx = ds.write().await.unwrap();
1563 tx.insert_leaf(&leaves[0]).await.unwrap();
1564 tx.insert_leaf(&leaves[2]).await.unwrap();
1565 tx.insert_leaf(&leaves[3]).await.unwrap();
1566 tx.commit().await.unwrap();
1567 }
1568
1569 let err = get_leaf_proof_with_leaf_chain(
1570 &ds,
1571 leaves[0].clone(),
1572 Duration::from_secs(1),
1573 CHAIN_LIMIT,
1574 )
1575 .await
1576 .unwrap_err();
1577 assert_eq!(err.status(), StatusCode::NOT_FOUND);
1578
1579 let err = get_leaf_proof_with_finalized_assumption(
1583 &ds,
1584 leaves[0].clone(),
1585 4,
1586 Duration::from_secs(1),
1587 CHAIN_LIMIT,
1588 )
1589 .await
1590 .unwrap_err();
1591 assert_eq!(err.status(), StatusCode::NOT_FOUND);
1592 }
1593
1594 #[apply(sql_data_sources)]
1595 async fn test_chain_ends_before_finality<D: TestableSqlDataSource>(_d: PhantomData<D>) {
1596 let storage = D::create_storage().await;
1597 let ds = D::create(D::persistence_options(&storage), Default::default(), false)
1598 .await
1599 .unwrap();
1600
1601 let leaves = leaf_chain(1..=3, EPOCH_REWARD_VERSION).await;
1604 {
1605 let mut tx = ds.write().await.unwrap();
1606 tx.insert_leaf(&leaves[0]).await.unwrap();
1607 tx.insert_leaf(&leaves[1]).await.unwrap();
1608 tx.commit().await.unwrap();
1609 }
1610
1611 let err = get_leaf_proof_with_leaf_chain(
1612 &ds,
1613 leaves[0].clone(),
1614 Duration::from_secs(1),
1615 CHAIN_LIMIT,
1616 )
1617 .await
1618 .unwrap_err();
1619 assert_eq!(err.status(), StatusCode::NOT_FOUND);
1620 assert!(
1621 matches!(&err, hotshot_query_service::Error::Custom { message, .. } if message.contains("not enough leaves")),
1622 "{err}"
1623 );
1624
1625 {
1627 let mut tx = ds.write().await.unwrap();
1628 tx.insert_leaf(&leaves[2]).await.unwrap();
1629 tx.commit().await.unwrap();
1630 }
1631 let proof =
1632 get_leaf_proof_with_leaf_chain(&ds, leaves[0].clone(), Duration::MAX, CHAIN_LIMIT)
1633 .await
1634 .unwrap();
1635 assert_eq!(
1636 proof
1637 .verify(LeafProofHint::Quorum(&AlwaysTrueQuorum::new(
1638 LEAF_CHAIN_EPOCH_HEIGHT
1639 )))
1640 .await
1641 .unwrap(),
1642 leaves[0]
1643 );
1644 }
1645
1646 #[apply(sql_data_sources)]
1647 async fn test_header_proof<D: TestableSqlDataSource>(_d: PhantomData<D>) {
1648 let storage = D::create_storage().await;
1649 let ds = D::create(D::persistence_options(&storage), Default::default(), false)
1650 .await
1651 .unwrap();
1652
1653 let leaves = leaf_chain(0..=2, EPOCH_VERSION).await;
1655 let mts = leaves
1656 .iter()
1657 .scan(
1658 BlockMerkleTree::new(BLOCK_MERKLE_TREE_HEIGHT),
1659 |mt, leaf| {
1660 assert_eq!(mt.commitment(), leaf.header().block_merkle_tree_root());
1661 let item = mt.clone();
1662 mt.push(leaf.block_hash()).unwrap();
1663 Some(item)
1664 },
1665 )
1666 .collect::<Vec<_>>();
1667
1668 {
1670 let mut tx = ds.write().await.unwrap();
1671 for (leaf, mt) in leaves.iter().zip(&mts) {
1672 tx.insert_leaf(leaf).await.unwrap();
1673
1674 if leaf.height() > 0 {
1675 let merkle_path = mt.lookup(leaf.height() - 1).expect_ok().unwrap().1;
1676 UpdateStateData::<SeqTypes, BlockMerkleTree, _>::insert_merkle_nodes(
1677 &mut tx,
1678 merkle_path,
1679 ToTraversalPath::<{ BlockMerkleTree::ARITY }>::to_traversal_path(
1680 &(leaf.height() - 1),
1681 BLOCK_MERKLE_TREE_HEIGHT,
1682 ),
1683 leaf.height(),
1684 )
1685 .await
1686 .unwrap();
1687 UpdateStateData::<SeqTypes, BlockMerkleTree, _>::set_last_state_height(
1688 &mut tx,
1689 leaf.height() as usize,
1690 )
1691 .await
1692 .unwrap();
1693 }
1694 }
1695 tx.commit().await.unwrap();
1696 }
1697
1698 for (root, mt) in mts.iter().enumerate().skip(1) {
1700 for (height, leaf) in leaves.iter().enumerate().take(root) {
1701 tracing::info!(root, height, "test happy path");
1702 let proof =
1703 get_header_proof(&ds, root as u64, BlockId::Number(height), Duration::MAX)
1704 .await
1705 .unwrap();
1706 assert_eq!(proof.verify_ref(mt.commitment()).unwrap(), leaf.header());
1707 }
1708 }
1709
1710 let err = get_header_proof(&ds, 5, BlockId::Number(4), Duration::from_secs(1))
1712 .await
1713 .unwrap_err();
1714 assert_eq!(err.status(), StatusCode::NOT_FOUND);
1715
1716 let err = get_header_proof(&ds, 1, BlockId::Number(1), Duration::MAX)
1718 .await
1719 .unwrap_err();
1720 assert_eq!(err.status(), StatusCode::BAD_REQUEST);
1721 }
1722
1723 type FullTree = SHA3MerkleTree<Commitment<Header>>;
1725
1726 fn fake_tree(num_leaves: u64) -> FullTree {
1727 FullTree::from_elems(
1728 Some(BLOCK_MERKLE_TREE_HEIGHT),
1729 (0..num_leaves).map(|i| Commitment::<Header>::from_raw([i as u8 + 1; 32])),
1730 )
1731 .unwrap()
1732 }
1733
1734 fn lookup(tree: &FullTree, pos: u64) -> BlockMerkleProof {
1735 tree.lookup(pos).expect_ok().unwrap().1
1736 }
1737
1738 fn check_rebuild(head_leaves: u64, rs: impl IntoIterator<Item = u64>) {
1740 let head = fake_tree(head_leaves);
1741 for r in rs {
1742 let tree = fake_tree(r);
1743 let frontier = lookup(&head, r - 1);
1744 for i in 0..r {
1745 let proof = rebuild_block_proof(r, &frontier, &lookup(&head, i)).unwrap();
1746 assert_eq!(proof, lookup(&tree, i), "r={r} i={i}");
1747 assert!(
1748 BlockMerkleTree::verify(tree.commitment(), i, &proof)
1749 .unwrap()
1750 .is_ok()
1751 );
1752 }
1753 }
1754 }
1755
1756 #[test]
1757 fn test_rebuild_block_proof_matches_snapshot() {
1758 check_rebuild(100, [2, 4, 5, 10, 26, 28, 50, 99]);
1759 }
1760
1761 #[test]
1762 fn test_rebuild_block_proof_last_leaf() {
1763 let head = fake_tree(40);
1764 let tree = fake_tree(13);
1765 let proof = rebuild_block_proof(13, &lookup(&head, 12), &lookup(&head, 12)).unwrap();
1766 assert_eq!(proof, lookup(&tree, 12));
1767 }
1768
1769 #[test]
1770 fn test_rebuild_block_proof_full_subtree() {
1771 check_rebuild(100, [3, 9, 27, 81]);
1772 }
1773
1774 #[test]
1775 fn test_rebuild_block_proof_single_leaf() {
1776 check_rebuild(5, [1]);
1777 }
1778
1779 #[test]
1780 fn test_rebuild_block_proof_wrong_frontier() {
1781 let head = fake_tree(40);
1782 let err = rebuild_block_proof(13, &lookup(&head, 11), &lookup(&head, 3)).unwrap_err();
1783 assert!(err.to_string().contains("frontier"), "{err:#}");
1784 }
1785
1786 async fn pruned_data_source<D: TestableSqlDataSource>(
1790 num_leaves: usize,
1791 pruned: u64,
1792 ) -> (impl Sized, D, Vec<BlockMerkleTree>) {
1793 let storage = D::create_storage().await;
1794 let ds = D::create(D::persistence_options(&storage), Default::default(), false)
1795 .await
1796 .unwrap();
1797
1798 let leaves = leaf_chain(0..num_leaves as u64, EPOCH_VERSION).await;
1799 let mts = leaves
1800 .iter()
1801 .scan(
1802 BlockMerkleTree::new(BLOCK_MERKLE_TREE_HEIGHT),
1803 |mt, leaf| {
1804 let item = mt.clone();
1805 mt.push(leaf.block_hash()).unwrap();
1806 Some(item)
1807 },
1808 )
1809 .collect::<Vec<_>>();
1810
1811 let mut tx = ds.write().await.unwrap();
1812 for (leaf, mt) in leaves.iter().zip(&mts) {
1813 tx.insert_leaf(leaf).await.unwrap();
1814 if leaf.height() > 0 {
1815 let merkle_path = mt.lookup(leaf.height() - 1).expect_ok().unwrap().1;
1816 UpdateStateData::<SeqTypes, BlockMerkleTree, _>::insert_merkle_nodes(
1817 &mut tx,
1818 merkle_path,
1819 ToTraversalPath::<{ BlockMerkleTree::ARITY }>::to_traversal_path(
1820 &(leaf.height() - 1),
1821 BLOCK_MERKLE_TREE_HEIGHT,
1822 ),
1823 leaf.height(),
1824 )
1825 .await
1826 .unwrap();
1827 UpdateStateData::<SeqTypes, BlockMerkleTree, _>::set_last_state_height(
1828 &mut tx,
1829 leaf.height() as usize,
1830 )
1831 .await
1832 .unwrap();
1833 }
1834 }
1835 tx.commit().await.unwrap();
1836
1837 let cfg =
1838 PrunerCfg::default().with_state_tables(vec![BlockMerkleTree::state_type().into()]);
1839 D::prune_state_below(&ds, pruned, &cfg).await.unwrap();
1840 (storage, ds, mts)
1841 }
1842
1843 #[apply(sql_data_sources)]
1844 async fn test_header_proof_pruned_root<D: TestableSqlDataSource>(_d: PhantomData<D>) {
1845 let num_leaves = 40;
1846 let (_storage, ds, mts) = pruned_data_source::<D>(num_leaves, 30).await;
1847
1848 for root in [1, 2, 4, 10, 13, 27, 28] {
1850 for height in 0..root {
1851 let proof = get_header_proof(
1852 &ds,
1853 root as u64,
1854 BlockId::Number(height),
1855 Duration::from_secs(10),
1856 )
1857 .await
1858 .unwrap();
1859 proof.verify(mts[root].commitment()).unwrap();
1860 }
1861 }
1862 }
1863
1864 #[apply(sql_data_sources)]
1865 async fn test_header_proof_root_beyond_head_fails<D: TestableSqlDataSource>(
1866 _d: PhantomData<D>,
1867 ) {
1868 let (_storage, ds, _) = pruned_data_source::<D>(10, 5).await;
1869 let err = get_header_proof(&ds, 20, BlockId::Number(3), Duration::from_secs(10))
1871 .await
1872 .unwrap_err();
1873 assert_eq!(err.status(), StatusCode::NOT_FOUND);
1874 }
1875
1876 #[apply(sql_data_sources)]
1877 async fn test_namespace_proof<D: TestableSqlDataSource>(_d: PhantomData<D>) {
1878 let storage = D::create_storage().await;
1879 let ds = D::create(D::persistence_options(&storage), Default::default(), false)
1880 .await
1881 .unwrap();
1882
1883 let client = TestClient::default();
1885 let leaves = join_all((0..=2).map(|i| client.leaf(i))).await;
1886 let payloads = join_all((0..=2).map(|i| client.payload(i))).await;
1887 let vid_commons = join_all((0..=2).map(|i| client.vid_common(i))).await;
1888
1889 {
1891 let mut tx = ds.write().await.unwrap();
1892 for (leaf, payload, vid_common) in izip!(&leaves, &payloads, &vid_commons) {
1893 tx.insert_leaf(leaf).await.unwrap();
1894 tx.insert_block(&BlockQueryData::<SeqTypes>::new(
1895 leaf.header().clone(),
1896 payload.clone(),
1897 ))
1898 .await
1899 .unwrap();
1900 tx.insert_vid(
1901 &VidCommonQueryData::<SeqTypes>::new(leaf.header().clone(), vid_common.clone()),
1902 None,
1903 )
1904 .await
1905 .unwrap();
1906 }
1907 tx.commit().await.unwrap();
1908 }
1909
1910 let ns = payloads[0]
1912 .transaction(&TransactionIndex {
1913 ns_index: 0.into(),
1914 position: 0,
1915 })
1916 .unwrap()
1917 .namespace();
1918 let proofs = get_namespace_proof_range(&ds, 0, 3, ns.into(), Duration::MAX, 100)
1919 .await
1920 .unwrap();
1921 assert_eq!(proofs.len(), 3);
1922 for (leaf, proof) in leaves.iter().zip(proofs) {
1923 proof.verify(leaf.header(), ns).unwrap();
1924 }
1925
1926 let tx = payloads[1]
1928 .transaction(&TransactionIndex {
1929 ns_index: 0.into(),
1930 position: 0,
1931 })
1932 .unwrap();
1933 let ns = tx.namespace();
1934 let proofs = get_namespace_proof_range(&ds, 1, 2, ns.into(), Duration::MAX, 100)
1935 .await
1936 .unwrap();
1937 assert_eq!(proofs.len(), 1);
1938 assert_eq!(proofs[0].verify(leaves[1].header(), ns).unwrap(), [tx]);
1939
1940 let err = get_namespace_proof_range(&ds, 0, 4, ns.into(), Duration::from_secs(1), 100)
1942 .await
1943 .unwrap_err();
1944 assert_eq!(err.status(), StatusCode::NOT_FOUND);
1945
1946 let err = get_namespace_proof_range(&ds, 1, 0, ns.into(), Duration::from_secs(1), 100)
1948 .await
1949 .unwrap_err();
1950 assert_eq!(err.status(), StatusCode::BAD_REQUEST);
1951 assert!(
1952 err.to_string().contains("requested empty interval"),
1953 "{err:#}"
1954 );
1955
1956 let err = get_namespace_proof_range(&ds, 0, 10_000, ns.into(), Duration::from_secs(1), 100)
1958 .await
1959 .unwrap_err();
1960 assert_eq!(err.status(), StatusCode::BAD_REQUEST);
1961 assert!(err.to_string().contains("exceeds maximum size"), "{err:#}");
1962 }
1963
1964 async fn serve_cert2(
1967 cert2: espresso_types::Certificate2<SeqTypes>,
1968 ) -> (TrustedQueryServiceProvider, Arc<Mutex<Vec<u64>>>) {
1969 let requested = Arc::new(Mutex::new(Vec::new()));
1970 let seen = requested.clone();
1971 let router = Router::new().route(
1972 "/availability/cert2/{height}",
1973 get(move |headers: HeaderMap, Path(height): Path<u64>| {
1974 seen.lock().unwrap().push(height);
1975 let served = (height == cert2.data.block_number).then(|| cert2.clone());
1976 async move {
1977 respond(
1978 &headers,
1979 served.ok_or_else(|| ServerError {
1980 status: axum::http::StatusCode::NOT_FOUND,
1981 message: format!("no cert2 at height {height}"),
1982 }),
1983 )
1984 }
1985 }),
1986 );
1987 let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
1988 let addr = listener.local_addr().unwrap();
1989 tokio::spawn(async move { axum::serve(listener, router).await.unwrap() });
1990 let provider = TrustedQueryServiceProvider::new(format!("http://{addr}").parse().unwrap());
1991 (provider, requested)
1992 }
1993
1994 async fn serve_catchup_cert2(
1997 cert2: Option<espresso_types::Certificate2<SeqTypes>>,
1998 ) -> (QueryServiceClient, Arc<Mutex<Vec<u64>>>) {
1999 let requested = Arc::new(Mutex::new(Vec::new()));
2000 let seen = requested.clone();
2001 let router = Router::new().route(
2002 "/catchup/{height}/cert2",
2003 get(move |headers: HeaderMap, Path(height): Path<u64>| {
2004 seen.lock().unwrap().push(height);
2005 let served = cert2.clone();
2006 async move {
2007 respond(
2008 &headers,
2009 served.ok_or_else(|| ServerError {
2010 status: axum::http::StatusCode::NOT_FOUND,
2011 message: format!("no cert2 available for height {height}"),
2012 }),
2013 )
2014 }
2015 }),
2016 );
2017 let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
2018 let addr = listener.local_addr().unwrap();
2019 tokio::spawn(async move { axum::serve(listener, router).await.unwrap() });
2020 let client = QueryServiceClient::new(format!("http://{addr}").parse().unwrap());
2021 (client, requested)
2022 }
2023
2024 #[apply(sql_data_sources)]
2027 async fn test_cert2_recovery_from_peer<D: TestableSqlDataSource>(_d: PhantomData<D>) {
2028 let leaves = leaf_chain(1..=4, NEW_PROTOCOL_VERSION).await;
2029 let (peer, requested) = serve_cert2(cert2_for_leaf(&leaves[1])).await;
2030
2031 for stored_out_of_limit in [false, true] {
2033 requested.lock().unwrap().clear();
2034 let storage = D::create_storage().await;
2035 let ds = D::on_request_data_source(
2036 &storage,
2037 Provider::default().with_cert2_provider(peer.clone()),
2038 )
2039 .await
2040 .unwrap();
2041 {
2042 let mut tx = ds.write().await.unwrap();
2043 for leaf in &leaves {
2044 tx.insert_leaf(leaf).await.unwrap();
2045 }
2046 if stored_out_of_limit {
2047 tx.insert_cert2(leaves[3].height(), cert2_for_leaf(&leaves[3]))
2048 .await
2049 .unwrap();
2050 }
2051 tx.commit().await.unwrap();
2052 }
2053
2054 let err = get_leaf_proof_with_cert2(&ds, leaves[0].clone(), Duration::from_secs(1), 2)
2055 .await
2056 .unwrap_err();
2057 assert_eq!(err.status(), StatusCode::NOT_FOUND);
2058
2059 timeout(Duration::from_secs(30), async {
2060 loop {
2061 let mut tx = ds.read().await.unwrap();
2062 let stored: Option<espresso_types::Certificate2<SeqTypes>> =
2063 tx.load_cert2(leaves[1].height()).await.unwrap();
2064 if stored.is_some() {
2065 break;
2066 }
2067 drop(tx);
2068 sleep(Duration::from_millis(100)).await;
2069 }
2070 })
2071 .await
2072 .expect("descendant cert2 was not recovered from the peer");
2073
2074 let asked = requested.lock().unwrap().clone();
2075 assert_eq!(
2076 asked,
2077 [1, 2],
2078 "the walk did not stop at the first cert2 it found"
2079 );
2080
2081 let proof =
2082 get_leaf_proof_with_cert2(&ds, leaves[0].clone(), Duration::from_secs(1), 2)
2083 .await
2084 .unwrap();
2085 assert_eq!(
2086 proof
2087 .verify(LeafProofHint::Quorum(&AlwaysTrueQuorum::new(
2088 LEAF_CHAIN_EPOCH_HEIGHT
2089 )))
2090 .await
2091 .unwrap(),
2092 leaves[0]
2093 );
2094 }
2095 }
2096
2097 #[apply(sql_data_sources)]
2099 async fn test_cert2_recovery_stops_at_the_window<D: TestableSqlDataSource>(_d: PhantomData<D>) {
2100 let leaves = leaf_chain(1..=4, NEW_PROTOCOL_VERSION).await;
2101 let (peer, requested) = serve_cert2(cert2_for_leaf(&leaves[3])).await;
2103
2104 let storage = D::create_storage().await;
2105 let ds = D::on_request_data_source(&storage, Provider::default().with_cert2_provider(peer))
2106 .await
2107 .unwrap();
2108 {
2109 let mut tx = ds.write().await.unwrap();
2110 for leaf in &leaves {
2111 tx.insert_leaf(leaf).await.unwrap();
2112 }
2113 tx.commit().await.unwrap();
2114 }
2115
2116 let err = get_leaf_proof_with_cert2(&ds, leaves[0].clone(), Duration::from_secs(1), 2)
2117 .await
2118 .unwrap_err();
2119 assert_eq!(err.status(), StatusCode::NOT_FOUND);
2120
2121 timeout(Duration::from_secs(30), async {
2123 while !(1..=3).all(|h| requested.lock().unwrap().contains(&h)) {
2124 sleep(Duration::from_millis(100)).await;
2125 }
2126 })
2127 .await
2128 .expect("recovery did not reach every height within the limit");
2129 sleep(Duration::from_secs(1)).await;
2130 let asked = requested.lock().unwrap().clone();
2131 assert!(
2132 asked.iter().all(|h| (1..=3).contains(h)),
2133 "recovery reached past the limit: {asked:?}"
2134 );
2135 }
2136
2137 #[tokio::test]
2138 async fn test_fallback_client_asks_every_peer_for_cert2() {
2139 let leaves = leaf_chain(1..=2, NEW_PROTOCOL_VERSION).await;
2140 let cert2 = cert2_for_leaf(&leaves[1]);
2141 let height = leaves[1].height();
2142
2143 let mut peers = Vec::new();
2144 let mut requests = Vec::new();
2145 for holds_cert2 in [false, false, false, false, true] {
2146 let (peer, requested) = serve_catchup_cert2(holds_cert2.then(|| cert2.clone())).await;
2147 peers.push(peer);
2148 requests.push(requested);
2149 }
2150 let client = FallbackClient::new(peers).unwrap();
2151
2152 assert_eq!(client.cert2(height).await.unwrap(), Some(cert2));
2153 for (i, requested) in requests.iter().enumerate() {
2154 assert_eq!(
2155 *requested.lock().unwrap(),
2156 [height],
2157 "peer {i} was not asked exactly once"
2158 );
2159 }
2160 }
2161}