1use std::{collections::HashMap, fmt::Display, sync::Arc, time::Duration};
2
3use anyhow::Result;
4use committable::Committable;
5use espresso_types::{BlockMerkleTree, Header, NsIndex, NsProof, SeqTypes};
6use futures::{
7 TryStreamExt,
8 future::{FutureExt, join, try_join},
9 stream::StreamExt,
10};
11use hotshot_query_service::{
12 Error,
13 availability::{
14 self, AvailabilityDataSource, LeafId, LeafQueryData, PayloadQueryData, VidCommonQueryData,
15 },
16 data_source::{VersionedDataSource, storage::NodeStorage},
17 merklized_state::{MerklizedStateDataSource, Snapshot},
18 node::BlockId,
19 types::HeightIndexed,
20};
21use hotshot_types::{
22 simple_certificate::QuorumCertificate2,
23 utils::{epoch_from_block_number, root_block_in_epoch},
24};
25use itertools::izip;
26use jf_merkle_tree_compat::MerkleTreeScheme;
27use light_client::{
28 client::NAMESPACES_PARAM_TAG,
29 consensus::{
30 header::HeaderProof, leaf::LeafProof, namespace::NamespaceProof, payload::PayloadProof,
31 },
32};
33use tide_disco::{Api, RequestParams, StatusCode, method::ReadState};
34use vbs::version::StaticVersionType;
35use versions::NEW_PROTOCOL_VERSION;
36
37use crate::api::data_source::{NodeStateDataSource, StakeTableDataSource};
38
39pub(crate) async fn get_leaf_proof<State>(
44 state: &State,
45 requested_leaf: LeafQueryData<SeqTypes>,
46 finalized: Option<usize>,
47 fetch_timeout: Duration,
48 chain_limit: usize,
49) -> Result<LeafProof, Error>
50where
51 State: AvailabilityDataSource<SeqTypes> + VersionedDataSource,
52 for<'a> State::ReadOnly<'a>: NodeStorage<SeqTypes>,
53{
54 let requested = requested_leaf.height() as usize;
55 let new_protocol = requested_leaf.header().version() >= NEW_PROTOCOL_VERSION;
56
57 let mut hint = finalized.filter(|finalized| finalized.saturating_sub(requested) <= chain_limit);
58
59 if let Some(finalized) = hint
61 && new_protocol
62 && let Some(cert2_height) = earliest_cert2_height(state, requested as u64).await
63 && cert2_height + 1 < finalized as u64
64 {
65 hint = None;
66 }
67
68 if let Some(finalized) = hint {
69 get_leaf_proof_with_finalized_assumption(
70 state,
71 requested_leaf,
72 finalized,
73 fetch_timeout,
74 chain_limit,
75 )
76 .await
77 } else if new_protocol {
78 get_leaf_proof_with_cert2(state, requested_leaf, fetch_timeout, chain_limit).await
79 } else {
80 get_leaf_proof_with_qc_chain(state, requested_leaf, fetch_timeout, chain_limit).await
81 }
82}
83
84async fn earliest_cert2_height<State>(state: &State, height: u64) -> Option<u64>
87where
88 State: VersionedDataSource,
89 for<'a> State::ReadOnly<'a>: NodeStorage<SeqTypes>,
90{
91 let cert2 = state
92 .read()
93 .await
94 .ok()?
95 .load_earliest_cert2(height)
96 .await
97 .ok()??;
98 Some(cert2.data.block_number)
99}
100
101pub(crate) async fn get_leaf_proof_with_qc_chain<State>(
102 state: &State,
103 requested_leaf: LeafQueryData<SeqTypes>,
104 fetch_timeout: Duration,
105 chain_limit: usize,
106) -> Result<LeafProof, Error>
107where
108 State: AvailabilityDataSource<SeqTypes> + VersionedDataSource,
109 for<'a> State::ReadOnly<'a>: NodeStorage<SeqTypes>,
110{
111 let requested = requested_leaf.height() as usize;
112 let mut tx = state.read().await.map_err(internal)?;
116 let latest_height = NodeStorage::block_height(&mut tx).await.map_err(internal)?;
117 let qc_chain = tx.latest_qc_chain().await.map_err(internal)?;
118 drop(tx);
119
120 let mut leaves = state.get_leaf_range(requested + 1..latest_height).await;
121 let mut proof = LeafProof::default();
122 let requested_leaf_qc = requested_leaf.qc().clone();
123 proof.push(requested_leaf);
124
125 let mut remaining = chain_limit;
126 while let Some(leaf) = leaves.next().await {
127 let leaf = leaf
128 .with_timeout(fetch_timeout)
129 .await
130 .ok_or_else(|| not_found("missing leaves"))?;
131
132 remaining = remaining
133 .checked_sub(1)
134 .ok_or_else(|| chain_too_long(requested, chain_limit))?;
135
136 if leaf.header().version() >= NEW_PROTOCOL_VERSION {
138 complete_proof_with_cert2(
139 state,
140 &mut proof,
141 leaf,
142 requested_leaf_qc.clone(),
143 fetch_timeout,
144 remaining,
145 )
146 .await?;
147 return Ok(proof);
148 }
149
150 if proof.push(leaf) {
151 return Ok(proof);
152 }
153 }
154
155 let Some([committing_qc, deciding_qc]) = qc_chain else {
159 return Err(not_found("missing QC 2-chain to prove finality"));
160 };
161 proof.add_qc_chain(Arc::new(committing_qc), Arc::new(deciding_qc));
162
163 Ok(proof)
164}
165
166pub(crate) async fn get_leaf_proof_with_cert2<State>(
174 state: &State,
175 requested_leaf: LeafQueryData<SeqTypes>,
176 fetch_timeout: Duration,
177 chain_limit: usize,
178) -> Result<LeafProof, Error>
179where
180 State: AvailabilityDataSource<SeqTypes> + VersionedDataSource,
181 for<'a> State::ReadOnly<'a>: NodeStorage<SeqTypes>,
182{
183 let mut proof = LeafProof::default();
184 let requested_leaf_qc = requested_leaf.qc().clone();
185 complete_proof_with_cert2(
186 state,
187 &mut proof,
188 requested_leaf,
189 requested_leaf_qc,
190 fetch_timeout,
191 chain_limit,
192 )
193 .await?;
194 Ok(proof)
195}
196
197async fn complete_proof_with_cert2<State>(
200 state: &State,
201 proof: &mut LeafProof,
202 leaf: LeafQueryData<SeqTypes>,
203 requested_leaf_qc: QuorumCertificate2<SeqTypes>,
204 fetch_timeout: Duration,
205 chain_limit: usize,
206) -> Result<(), Error>
207where
208 State: AvailabilityDataSource<SeqTypes> + VersionedDataSource,
209 for<'a> State::ReadOnly<'a>: NodeStorage<SeqTypes>,
210{
211 let start_height = leaf.height();
212 let start_commit = leaf.leaf().commit();
213
214 if proof.push(leaf) {
216 return Ok(());
217 }
218
219 let cert2 = state
220 .read()
221 .await
222 .map_err(internal)?
223 .load_earliest_cert2(start_height)
224 .await
225 .map_err(internal)?
226 .ok_or_else(|| {
227 not_found(format!(
228 "no cert2 finality proof available at or after height {start_height}"
229 ))
230 })?;
231
232 let cert2_height = cert2.data.block_number;
233 if cert2_height < start_height {
234 return Err(not_found(
235 "cert2 finality proof is older than requested leaf",
236 ));
237 }
238 if cert2_height - start_height > chain_limit as u64 {
239 return Err(not_found(format!(
240 "earliest cert2 finality proof (height {cert2_height}) is more than {chain_limit} \
241 leaves past height {start_height}"
242 )));
243 }
244
245 if cert2_height == start_height {
246 if start_commit != cert2.data.leaf_commit {
247 return Err(internal("stored cert2 does not finalize the expected leaf"));
248 }
249 proof.add_certificate(Arc::new(cert2), requested_leaf_qc);
250 return Ok(());
251 }
252
253 let mut leaves = state
254 .get_leaf_range(start_height as usize + 1..cert2_height as usize + 1)
255 .await;
256 while let Some(leaf) = leaves.next().await {
260 let leaf = leaf
261 .with_timeout(fetch_timeout)
262 .await
263 .ok_or_else(|| not_found("missing leaves"))?;
264
265 if leaf.height() == cert2_height {
266 if leaf.leaf().commit() != cert2.data.leaf_commit {
267 return Err(internal("stored cert2 does not finalize the expected leaf"));
268 }
269 if !proof.push(leaf) {
270 proof.add_certificate(Arc::new(cert2), requested_leaf_qc);
271 }
272 return Ok(());
273 }
274 if proof.push(leaf) {
275 return Ok(());
276 }
277 }
278
279 Err(not_found("missing cert2 leaf"))
280}
281
282pub(crate) async fn get_leaf_proof_with_finalized_assumption<State>(
283 state: &State,
284 requested_leaf: LeafQueryData<SeqTypes>,
285 finalized: usize,
286 fetch_timeout: Duration,
287 chain_limit: usize,
288) -> Result<LeafProof, Error>
289where
290 State: AvailabilityDataSource<SeqTypes>,
291{
292 let requested = requested_leaf.height() as usize;
293 if finalized <= requested {
297 return Err(Error::Custom {
298 message: format!(
299 "finalized leaf height ({finalized}) must be greater than requested ({requested})"
300 ),
301 status: StatusCode::BAD_REQUEST,
302 });
303 }
304 if finalized - requested > chain_limit {
305 return Err(Error::Custom {
306 message: format!(
307 "finalized leaf height ({finalized}) is more than {chain_limit} blocks past the \
308 requested leaf ({requested}); request a proof without the finalized parameter \
309 instead"
310 ),
311 status: StatusCode::BAD_REQUEST,
312 });
313 }
314
315 let mut leaves = state.get_leaf_range(requested + 1..finalized).await;
316 let mut proof = LeafProof::default();
317 proof.push(requested_leaf);
318
319 while let Some(leaf) = leaves.next().await {
320 let leaf = leaf
321 .with_timeout(fetch_timeout)
322 .await
323 .ok_or_else(|| not_found("missing leaves"))?;
324
325 if proof.push(leaf) {
326 return Ok(proof);
327 }
328 }
329
330 Ok(proof)
331}
332
333pub(crate) async fn get_header_proof<State>(
334 state: &State,
335 root: u64,
336 requested: BlockId<SeqTypes>,
337 fetch_timeout: Duration,
338) -> Result<HeaderProof, Error>
339where
340 State: AvailabilityDataSource<SeqTypes>
341 + MerklizedStateDataSource<SeqTypes, BlockMerkleTree, { BlockMerkleTree::ARITY }>
342 + VersionedDataSource,
343{
344 let header = state
345 .get_header(requested)
346 .await
347 .with_timeout(fetch_timeout)
348 .await
349 .ok_or_else(|| not_found(format!("unknown header {requested}")))?;
350 if header.height() >= root {
351 return Err(Error::Custom {
352 message: format!(
353 "height ({}) must be less than root ({root})",
354 header.height()
355 ),
356 status: StatusCode::BAD_REQUEST,
357 });
358 }
359 let path = MerklizedStateDataSource::<SeqTypes, BlockMerkleTree, _>::get_path(
360 state,
361 Snapshot::Index(root),
362 header.height(),
363 )
364 .await
365 .map_err(|source| Error::MerklizedState {
366 source: source.into(),
367 })?;
368
369 Ok(HeaderProof::new(header, path))
370}
371
372pub(crate) async fn fetch_block_data_range<State>(
373 state: &State,
374 start: usize,
375 end: usize,
376 fetch_timeout: Duration,
377 large_object_range_limit: usize,
378) -> Result<
379 Vec<(
380 Header,
381 PayloadQueryData<SeqTypes>,
382 VidCommonQueryData<SeqTypes>,
383 )>,
384 Error,
385>
386where
387 State: AvailabilityDataSource<SeqTypes>,
388{
389 if end <= start {
390 return Err(Error::Custom {
391 message: format!("requested empty interval [{start}, {end})"),
392 status: StatusCode::BAD_REQUEST,
393 });
394 }
395 if end - start > large_object_range_limit {
396 return Err(Error::Custom {
397 message: format!(
398 "requested range [{start}, {end}) exceeds maximum size {large_object_range_limit}"
399 ),
400 status: StatusCode::BAD_REQUEST,
401 });
402 }
403
404 let fetch_headers = async move {
405 state
406 .get_header_range(start..end)
407 .await
408 .enumerate()
409 .then(|(i, fetch)| async move {
410 fetch
411 .with_timeout(fetch_timeout)
412 .await
413 .ok_or_else(|| Error::Custom {
414 message: format!("missing header {}", start + i),
415 status: StatusCode::NOT_FOUND,
416 })
417 })
418 .try_collect::<Vec<_>>()
419 .await
420 };
421 let fetch_payloads = async move {
422 state
423 .get_payload_range(start..end)
424 .await
425 .enumerate()
426 .then(|(i, fetch)| async move {
427 fetch
428 .with_timeout(fetch_timeout)
429 .await
430 .ok_or_else(|| Error::Custom {
431 message: format!("missing payload {}", start + i),
432 status: StatusCode::NOT_FOUND,
433 })
434 })
435 .try_collect::<Vec<_>>()
436 .await
437 };
438 let fetch_vid_commons = async move {
439 state
440 .get_vid_common_range(start..end)
441 .await
442 .enumerate()
443 .then(|(i, fetch)| async move {
444 fetch
445 .with_timeout(fetch_timeout)
446 .await
447 .ok_or_else(|| Error::Custom {
448 message: format!("missing VID common {}", start + i),
449 status: StatusCode::NOT_FOUND,
450 })
451 })
452 .try_collect::<Vec<_>>()
453 .await
454 };
455 let (headers, (payloads, vid_commons)) =
456 try_join(fetch_headers, try_join(fetch_payloads, fetch_vid_commons)).await?;
457
458 Ok(izip!(headers, payloads, vid_commons).collect())
459}
460
461pub(crate) async fn get_namespace_proof_range<State>(
463 state: &State,
464 start: usize,
465 end: usize,
466 namespace: u64,
467 fetch_timeout: Duration,
468 large_object_range_limit: usize,
469) -> Result<Vec<NamespaceProof>, Error>
470where
471 State: AvailabilityDataSource<SeqTypes>,
472{
473 let blocks = get_namespaces_proof_range(
474 state,
475 start,
476 end,
477 &[namespace],
478 fetch_timeout,
479 large_object_range_limit,
480 )
481 .await?;
482 Ok(blocks
483 .into_iter()
484 .map(|mut block| {
485 block
486 .remove(&namespace)
487 .unwrap_or_else(NamespaceProof::not_present)
488 })
489 .collect())
490}
491
492async fn get_namespaces_proof_range<State>(
493 state: &State,
494 start: usize,
495 end: usize,
496 namespaces: &[u64],
497 fetch_timeout: Duration,
498 large_object_range_limit: usize,
499) -> Result<Vec<HashMap<u64, NamespaceProof>>, Error>
500where
501 State: AvailabilityDataSource<SeqTypes>,
502{
503 fetch_block_data_range(state, start, end, fetch_timeout, large_object_range_limit)
504 .await?
505 .into_iter()
506 .map(|(header, payload, vid_common)| {
507 namespaces
508 .iter()
509 .filter_map(|&namespace| {
510 let ns_index = header.ns_table().find_ns_id(&namespace.into())?;
511 Some(
512 build_namespace_proof(&payload, &ns_index, &vid_common)
513 .map(|proof| (namespace, proof)),
514 )
515 })
516 .collect::<Result<HashMap<_, _>, _>>()
517 })
518 .collect()
519}
520
521fn parse_namespaces_param(req: &RequestParams) -> Result<Vec<u64>, Error> {
522 let encoded = req
523 .tagged_base64_param("namespaces")
524 .map_err(bad_param("namespaces"))?;
525 if encoded.tag() != NAMESPACES_PARAM_TAG {
526 return Err(Error::Custom {
527 message: format!(
528 "invalid namespaces parameter tag: expected {NAMESPACES_PARAM_TAG}, got {}",
529 encoded.tag()
530 ),
531 status: StatusCode::BAD_REQUEST,
532 });
533 }
534 serde_json::from_slice(&encoded.value()).map_err(|err| Error::Custom {
535 message: format!("invalid namespaces parameter: {err}"),
536 status: StatusCode::BAD_REQUEST,
537 })
538}
539
540fn build_namespace_proof(
542 payload: &PayloadQueryData<SeqTypes>,
543 ns_index: &NsIndex,
544 vid_common: &VidCommonQueryData<SeqTypes>,
545) -> Result<NamespaceProof, Error> {
546 let ns_proof =
547 NsProof::new(payload.data(), ns_index, vid_common.common()).ok_or_else(|| {
548 Error::Custom {
549 message: "failed to construct namespace proof".into(),
550 status: StatusCode::INTERNAL_SERVER_ERROR,
551 }
552 })?;
553 Ok(NamespaceProof::new(ns_proof, vid_common.common().clone()))
554}
555
556#[derive(Debug)]
557pub(super) struct Options {
558 pub fetch_timeout: Duration,
564
565 pub large_object_range_limit: usize,
573
574 pub leaf_proof_chain_limit: usize,
577}
578
579impl Default for Options {
580 fn default() -> Self {
581 Self {
582 fetch_timeout: Duration::from_millis(500),
583 large_object_range_limit: availability::Options::default().large_object_range_limit,
584 leaf_proof_chain_limit: availability::Options::default().small_object_range_limit,
585 }
586 }
587}
588
589pub(super) fn define_api<S, ApiVer: StaticVersionType + 'static>(
590 opt: Options,
591 api_ver: semver::Version,
592) -> Result<Api<S, Error, ApiVer>>
593where
594 S: ReadState + Send + Sync + 'static,
595 S::State: AvailabilityDataSource<SeqTypes>
596 + MerklizedStateDataSource<SeqTypes, BlockMerkleTree, { BlockMerkleTree::ARITY }>
597 + NodeStateDataSource
598 + StakeTableDataSource<SeqTypes>
599 + VersionedDataSource,
600 for<'a> <S::State as VersionedDataSource>::ReadOnly<'a>: NodeStorage<SeqTypes>,
601{
602 let toml = toml::from_str::<toml::Value>(include_str!("../../api/light-client.toml"))?;
603 let mut api = Api::<S, Error, ApiVer>::new(toml)?;
604 api.with_version(api_ver);
605
606 let Options {
607 fetch_timeout,
608 large_object_range_limit,
609 leaf_proof_chain_limit,
610 } = opt;
611
612 api.get("leaf", move |req, state| {
613 async move {
614 let requested_leaf = leaf_from_req(&req, state, fetch_timeout).await?;
615 let finalized = req
616 .opt_integer_param("finalized")
617 .map_err(bad_param("finalized"))?;
618
619 get_leaf_proof(
620 state,
621 requested_leaf,
622 finalized,
623 fetch_timeout,
624 leaf_proof_chain_limit,
625 )
626 .await
627 }
628 .boxed()
629 })?
630 .get("header", move |req, state| {
631 async move {
632 let root = req.integer_param("root").map_err(bad_param("root"))?;
633 let requested = block_id_from_req(&req)?;
634 get_header_proof(state, root, requested, fetch_timeout).await
635 }
636 .boxed()
637 })?
638 .get("stake_table", move |req, state| {
639 async move {
640 let epoch: u64 = req.integer_param("epoch").map_err(bad_param("epoch"))?;
641
642 let node_state = state.node_state().await;
643 let epoch_height = node_state.epoch_height.ok_or_else(|| Error::Custom {
644 message: "epoch state not set".into(),
645 status: StatusCode::INTERNAL_SERVER_ERROR,
646 })?;
647 let first_epoch = epoch_from_block_number(node_state.epoch_start_block, epoch_height);
648
649 if epoch < first_epoch + 2 {
650 return Err(Error::Custom {
651 message: format!("epoch must be at least {}", first_epoch + 2),
652 status: StatusCode::BAD_REQUEST,
653 });
654 }
655
656 let epoch_root_height = root_block_in_epoch(epoch - 2, epoch_height) as usize;
660 let epoch_root = state
661 .get_header(epoch_root_height)
662 .await
663 .with_timeout(fetch_timeout)
664 .await
665 .ok_or_else(|| {
666 not_found(format!("missing epoch root header {epoch_root_height}"))
667 })?;
668 let to_l1_block = epoch_root
669 .l1_finalized()
670 .ok_or_else(|| Error::Custom {
671 message: "epoch root header is missing L1 finalized block".into(),
672 status: StatusCode::INTERNAL_SERVER_ERROR,
673 })?
674 .number();
675
676 let from_l1_block = if epoch >= first_epoch + 3 {
677 let prev_epoch_root_height = root_block_in_epoch(epoch - 3, epoch_height) as usize;
678 let prev_epoch_root = state
679 .get_header(prev_epoch_root_height)
680 .await
681 .with_timeout(fetch_timeout)
682 .await
683 .ok_or_else(|| {
684 not_found(format!(
685 "missing previous epoch root header {prev_epoch_root_height}"
686 ))
687 })?;
688 prev_epoch_root
689 .l1_finalized()
690 .ok_or_else(|| Error::Custom {
691 message: "previous epoch root header is missing L1 finalized block".into(),
692 status: StatusCode::INTERNAL_SERVER_ERROR,
693 })?
694 .number()
695 + 1
696 } else {
697 0
698 };
699
700 state
701 .stake_table_events(from_l1_block, to_l1_block)
702 .await
703 .map_err(|err| Error::Custom {
704 message: format!("failed to load stake table events: {err:#}"),
705 status: StatusCode::INTERNAL_SERVER_ERROR,
706 })
707 }
708 .boxed()
709 })?
710 .get("payload", move |req, state| {
711 async move {
712 let height: usize = req.integer_param("height").map_err(bad_param("height"))?;
713 let fetch_payload = async move {
714 state
715 .get_payload(height)
716 .await
717 .with_timeout(fetch_timeout)
718 .await
719 .ok_or_else(|| Error::Custom {
720 message: format!("missing payload {height}"),
721 status: StatusCode::NOT_FOUND,
722 })
723 };
724 let fetch_vid_common = async move {
725 state
726 .get_vid_common(height)
727 .await
728 .with_timeout(fetch_timeout)
729 .await
730 .ok_or_else(|| Error::Custom {
731 message: format!("missing VID common {height}"),
732 status: StatusCode::NOT_FOUND,
733 })
734 };
735 let (payload, vid_common) = try_join(fetch_payload, fetch_vid_common).await?;
736 Ok(PayloadProof::new(
737 payload.data().clone(),
738 vid_common.common().clone(),
739 ))
740 }
741 .boxed()
742 })?
743 .get("payload_range", move |req, state| {
744 async move {
745 let start: usize = req.integer_param("start").map_err(bad_param("start"))?;
746 let end: usize = req.integer_param("end").map_err(bad_param("end"))?;
747 let fetch_payloads = async move {
748 state.get_payload_range(start..end).await.enumerate().then(
749 move |(i, fetch)| async move {
750 fetch
751 .with_timeout(fetch_timeout)
752 .await
753 .ok_or_else(|| Error::Custom {
754 message: format!("missing payload {}", start + i),
755 status: StatusCode::NOT_FOUND,
756 })
757 },
758 )
759 };
760 let fetch_vid_commons = async move {
761 state
762 .get_vid_common_range(start..end)
763 .await
764 .enumerate()
765 .then(move |(i, fetch)| async move {
766 fetch
767 .with_timeout(fetch_timeout)
768 .await
769 .ok_or_else(|| Error::Custom {
770 message: format!("missing VID common {}", start + i),
771 status: StatusCode::NOT_FOUND,
772 })
773 })
774 };
775 let (payloads, vid_commons) = join(fetch_payloads, fetch_vid_commons).await;
776 payloads
777 .zip(vid_commons)
778 .map(|(payload, vid_common)| {
779 Ok(PayloadProof::new(
780 payload?.data().clone(),
781 vid_common?.common().clone(),
782 ))
783 })
784 .try_collect::<Vec<_>>()
785 .await
786 }
787 .boxed()
788 })?
789 .get("namespace", move |req, state| {
790 async move {
791 let height = req.integer_param("height").map_err(bad_param("height"))?;
792 let namespace = req
793 .integer_param("namespace")
794 .map_err(bad_param("namespace"))?;
795 let mut proofs = get_namespace_proof_range(
796 state,
797 height,
798 height + 1,
799 namespace,
800 fetch_timeout,
801 large_object_range_limit,
802 )
803 .await?;
804 if proofs.len() != 1 {
805 tracing::error!(
806 height,
807 namespace,
808 ?proofs,
809 "get_namespace_proof_range should have returned exactly one proof"
810 );
811 return Err(Error::Custom {
812 message: "internal consistency error".into(),
813 status: StatusCode::INTERNAL_SERVER_ERROR,
814 });
815 }
816 Ok(proofs.remove(0))
817 }
818 .boxed()
819 })?
820 .get("namespace_range", move |req, state| {
821 async move {
822 let start = req.integer_param("start").map_err(bad_param("start"))?;
823 let end = req.integer_param("end").map_err(bad_param("end"))?;
824 let namespace = req
825 .integer_param("namespace")
826 .map_err(bad_param("namespace"))?;
827 get_namespace_proof_range(
828 state,
829 start,
830 end,
831 namespace,
832 fetch_timeout,
833 large_object_range_limit,
834 )
835 .await
836 }
837 .boxed()
838 })?
839 .get("namespaces_range", move |req, state| {
840 async move {
841 let start = req.integer_param("start").map_err(bad_param("start"))?;
842 let end = req.integer_param("end").map_err(bad_param("end"))?;
843 let namespaces = parse_namespaces_param(&req)?;
844 get_namespaces_proof_range(
845 state,
846 start,
847 end,
848 &namespaces,
849 fetch_timeout,
850 large_object_range_limit,
851 )
852 .await
853 }
854 .boxed()
855 })?;
856
857 Ok(api)
858}
859
860async fn leaf_from_req<S>(
861 req: &RequestParams,
862 state: &S,
863 fetch_timeout: Duration,
864) -> Result<LeafQueryData<SeqTypes>, Error>
865where
866 S: AvailabilityDataSource<SeqTypes>,
867{
868 let requested = if let Some(height) = req
869 .opt_integer_param::<_, usize>("height")
870 .map_err(bad_param("height"))?
871 {
872 LeafId::Number(height)
873 } else if let Some(hash) = req.opt_blob_param("hash").map_err(bad_param("hash"))? {
874 LeafId::Hash(hash)
875 } else if let Some(hash) = req
876 .opt_blob_param("block-hash")
877 .map_err(bad_param("block-hash"))?
878 {
879 let header = state
880 .get_header(BlockId::Hash(hash))
881 .await
882 .with_timeout(fetch_timeout)
883 .await
884 .ok_or_else(|| not_found(format!("unknown block hash {hash}")))?;
885 LeafId::Number(header.height() as usize)
886 } else if let Some(hash) = req
887 .opt_blob_param("payload-hash")
888 .map_err(bad_param("payload-hash"))?
889 {
890 let header = state
891 .get_header(BlockId::PayloadHash(hash))
892 .await
893 .with_timeout(fetch_timeout)
894 .await
895 .ok_or_else(|| not_found(format!("unknown payload hash {hash}")))?;
896 LeafId::Number(header.height() as usize)
897 } else {
898 return Err(Error::Custom {
899 message: "missing parameter: requested leaf must be identified by height, hash, block \
900 hash, or payload hash"
901 .into(),
902 status: StatusCode::BAD_REQUEST,
903 });
904 };
905
906 state
907 .get_leaf(requested)
908 .await
909 .with_timeout(fetch_timeout)
910 .await
911 .ok_or_else(|| not_found(format!("unknown leaf {requested}")))
912}
913
914fn block_id_from_req(req: &RequestParams) -> Result<BlockId<SeqTypes>, Error> {
915 if let Some(height) = req
916 .opt_integer_param("height")
917 .map_err(bad_param("height"))?
918 {
919 Ok(BlockId::Number(height))
920 } else if let Some(hash) = req.opt_blob_param("hash").map_err(bad_param("hash"))? {
921 Ok(BlockId::Hash(hash))
922 } else if let Some(hash) = req
923 .opt_blob_param("payload-hash")
924 .map_err(bad_param("payload-hash"))?
925 {
926 Ok(BlockId::PayloadHash(hash))
927 } else {
928 Err(Error::Custom {
929 message: "missing parameter: requested header must be identified by height, hash, or \
930 payload hash"
931 .into(),
932 status: StatusCode::BAD_REQUEST,
933 })
934 }
935}
936
937fn bad_param<E>(name: &'static str) -> impl FnOnce(E) -> Error
938where
939 E: Display,
940{
941 move |err| Error::Custom {
942 message: format!("{name}: {err:#}"),
943 status: StatusCode::BAD_REQUEST,
944 }
945}
946
947fn internal(err: impl Display) -> Error {
948 Error::Custom {
949 message: err.to_string(),
950 status: StatusCode::INTERNAL_SERVER_ERROR,
951 }
952}
953
954fn not_found(msg: impl Into<String>) -> Error {
955 Error::Custom {
956 message: msg.into(),
957 status: StatusCode::NOT_FOUND,
958 }
959}
960
961fn chain_too_long(requested: usize, chain_limit: usize) -> Error {
962 not_found(format!(
963 "no finality proof found within {chain_limit} leaves of requested leaf {requested}"
964 ))
965}
966
967#[cfg(test)]
968mod test {
969 use std::marker::PhantomData;
970
971 use committable::Committable;
972 use espresso_types::BLOCK_MERKLE_TREE_HEIGHT;
973 use futures::future::join_all;
974 use hotshot_query_service::{
975 availability::{BlockQueryData, TransactionIndex, VidCommonQueryData},
976 data_source::{Transaction, storage::UpdateAvailabilityStorage},
977 merklized_state::UpdateStateData,
978 };
979 use hotshot_types::{
980 data::ViewNumber, simple_certificate::CertificatePair, simple_vote::Vote2Data,
981 };
982 use jf_merkle_tree_compat::{AppendableMerkleTreeScheme, ToTraversalPath};
983 use light_client::{
984 consensus::leaf::{FinalityProof, LeafProofHint},
985 testing::{
986 AlwaysTrueQuorum, ENABLE_EPOCHS, LEGACY_VERSION, TestClient, VersionCheckQuorum,
987 custom_leaf_chain_with_upgrade, leaf_chain, leaf_chain_with_upgrade,
988 },
989 };
990 use tide_disco::Error;
991 use versions::{DRB_AND_HEADER_UPGRADE_VERSION, EPOCH_VERSION, NEW_PROTOCOL_VERSION, Upgrade};
992
993 use super::*;
994 use crate::api::{
995 data_source::{SequencerDataSource, testing::TestableSequencerDataSource},
996 sql::DataSource,
997 };
998
999 const CHAIN_LIMIT: usize = 500;
1000
1001 fn cert2_for_leaf(leaf: &LeafQueryData<SeqTypes>) -> espresso_types::Certificate2<SeqTypes> {
1002 let data = Vote2Data {
1003 leaf_commit: leaf.leaf().commit(),
1004 epoch: leaf.qc().data.epoch.unwrap(),
1005 block_number: leaf.height(),
1006 };
1007 espresso_types::Certificate2::new(
1008 data.clone(),
1009 data.commit(),
1010 leaf.leaf().view_number(),
1011 None,
1012 PhantomData,
1013 )
1014 }
1015
1016 #[test_log::test(tokio::test(flavor = "multi_thread"))]
1017 async fn test_two_chain() {
1018 let storage = <DataSource as TestableSequencerDataSource>::create_storage().await;
1019 let ds = DataSource::create(
1020 DataSource::persistence_options(&storage),
1021 Default::default(),
1022 false,
1023 )
1024 .await
1025 .unwrap();
1026
1027 let leaves = leaf_chain(1..=3, EPOCH_VERSION).await;
1029 {
1030 let mut tx = ds.write().await.unwrap();
1031 tx.insert_leaf(&leaves[0]).await.unwrap();
1032 tx.insert_leaf(&leaves[1]).await.unwrap();
1033 tx.insert_leaf(&leaves[2]).await.unwrap();
1034 tx.commit().await.unwrap();
1035 }
1036
1037 let proof =
1039 get_leaf_proof_with_qc_chain(&ds, leaves[0].clone(), Duration::MAX, CHAIN_LIMIT)
1040 .await
1041 .unwrap();
1042 assert_eq!(
1043 proof
1044 .verify(LeafProofHint::Quorum(&AlwaysTrueQuorum))
1045 .await
1046 .unwrap(),
1047 leaves[0]
1048 );
1049 }
1050
1051 #[test_log::test(tokio::test(flavor = "multi_thread"))]
1052 async fn test_finalized() {
1053 let storage = <DataSource as TestableSequencerDataSource>::create_storage().await;
1054 let ds = DataSource::create(
1055 DataSource::persistence_options(&storage),
1056 Default::default(),
1057 false,
1058 )
1059 .await
1060 .unwrap();
1061
1062 let leaves = leaf_chain(1..=2, EPOCH_VERSION).await;
1065 {
1066 let mut tx = ds.write().await.unwrap();
1067 tx.insert_leaf(&leaves[0]).await.unwrap();
1068 tx.commit().await.unwrap();
1069 }
1070
1071 let proof = get_leaf_proof_with_finalized_assumption(
1072 &ds,
1073 leaves[0].clone(),
1074 2,
1075 Duration::MAX,
1076 CHAIN_LIMIT,
1077 )
1078 .await
1079 .unwrap();
1080 assert_eq!(
1081 proof
1082 .verify(LeafProofHint::assumption(leaves[1].leaf()))
1083 .await
1084 .unwrap(),
1085 leaves[0]
1086 );
1087 }
1088
1089 #[test_log::test(tokio::test(flavor = "multi_thread"))]
1090 async fn test_new_protocol_finalized_assumption() {
1091 let storage = <DataSource as TestableSequencerDataSource>::create_storage().await;
1092 let ds = DataSource::create(
1093 DataSource::persistence_options(&storage),
1094 Default::default(),
1095 false,
1096 )
1097 .await
1098 .unwrap();
1099
1100 let leaves = leaf_chain(1..=2, NEW_PROTOCOL_VERSION).await;
1101 {
1102 let mut tx = ds.write().await.unwrap();
1103 tx.insert_leaf(&leaves[0]).await.unwrap();
1104 tx.commit().await.unwrap();
1105 }
1106
1107 let proof = get_leaf_proof_with_finalized_assumption(
1108 &ds,
1109 leaves[0].clone(),
1110 2,
1111 Duration::MAX,
1112 CHAIN_LIMIT,
1113 )
1114 .await
1115 .unwrap();
1116 assert!(matches!(proof.proof(), FinalityProof::Assumption));
1117 assert_eq!(
1118 proof
1119 .verify(LeafProofHint::assumption(leaves[1].leaf()))
1120 .await
1121 .unwrap(),
1122 leaves[0]
1123 );
1124 }
1125
1126 #[test_log::test(tokio::test(flavor = "multi_thread"))]
1127 async fn test_new_protocol_cert2() {
1128 let storage = <DataSource as TestableSequencerDataSource>::create_storage().await;
1129 let ds = DataSource::create(
1130 DataSource::persistence_options(&storage),
1131 Default::default(),
1132 false,
1133 )
1134 .await
1135 .unwrap();
1136
1137 let leaves = leaf_chain(1..=2, NEW_PROTOCOL_VERSION).await;
1138 let cert2_leaf = &leaves[1];
1139 let cert2 = cert2_for_leaf(cert2_leaf);
1140
1141 {
1142 let mut tx = ds.write().await.unwrap();
1143 tx.insert_leaf(&leaves[0]).await.unwrap();
1144 tx.insert_leaf(cert2_leaf).await.unwrap();
1145 tx.insert_cert2(cert2_leaf.height(), cert2).await.unwrap();
1146 tx.commit().await.unwrap();
1147 }
1148
1149 let proof = get_leaf_proof_with_cert2(&ds, leaves[0].clone(), Duration::MAX, CHAIN_LIMIT)
1150 .await
1151 .unwrap();
1152 assert!(matches!(proof.proof(), FinalityProof::NewProtocol { .. }));
1153 assert_eq!(
1154 proof
1155 .verify(LeafProofHint::Quorum(&AlwaysTrueQuorum))
1156 .await
1157 .unwrap(),
1158 leaves[0]
1159 );
1160 }
1161
1162 #[test_log::test(tokio::test(flavor = "multi_thread"))]
1163 async fn test_new_protocol_cert2_chain_limit() {
1164 let storage = <DataSource as TestableSequencerDataSource>::create_storage().await;
1165 let ds = DataSource::create(
1166 DataSource::persistence_options(&storage),
1167 Default::default(),
1168 false,
1169 )
1170 .await
1171 .unwrap();
1172
1173 let leaves = leaf_chain(1..=4, NEW_PROTOCOL_VERSION).await;
1175 let cert2_leaf = &leaves[3];
1176 let cert2 = cert2_for_leaf(cert2_leaf);
1177
1178 {
1179 let mut tx = ds.write().await.unwrap();
1180 for leaf in &leaves {
1181 tx.insert_leaf(leaf).await.unwrap();
1182 }
1183 tx.insert_cert2(cert2_leaf.height(), cert2).await.unwrap();
1184 tx.commit().await.unwrap();
1185 }
1186
1187 let err = get_leaf_proof_with_cert2(&ds, leaves[0].clone(), Duration::MAX, 2)
1188 .await
1189 .unwrap_err();
1190 assert_eq!(err.status(), StatusCode::NOT_FOUND);
1191
1192 let proof = get_leaf_proof_with_cert2(&ds, leaves[0].clone(), Duration::MAX, 3)
1193 .await
1194 .unwrap();
1195 assert!(matches!(proof.proof(), FinalityProof::NewProtocol { .. }));
1196 assert_eq!(
1197 proof
1198 .verify(LeafProofHint::Quorum(&AlwaysTrueQuorum))
1199 .await
1200 .unwrap(),
1201 leaves[0]
1202 );
1203 }
1204
1205 #[test_log::test(tokio::test(flavor = "multi_thread"))]
1206 async fn test_new_protocol_hint_vs_cert2() {
1207 let storage = <DataSource as TestableSequencerDataSource>::create_storage().await;
1208 let ds = DataSource::create(
1209 DataSource::persistence_options(&storage),
1210 Default::default(),
1211 false,
1212 )
1213 .await
1214 .unwrap();
1215
1216 let leaves = leaf_chain(1..=5, NEW_PROTOCOL_VERSION).await;
1217 let cert2_leaf = &leaves[1];
1218 let cert2 = cert2_for_leaf(cert2_leaf);
1219
1220 {
1221 let mut tx = ds.write().await.unwrap();
1222 for leaf in &leaves {
1223 tx.insert_leaf(leaf).await.unwrap();
1224 }
1225 tx.insert_cert2(cert2_leaf.height(), cert2).await.unwrap();
1226 tx.commit().await.unwrap();
1227 }
1228
1229 let proof = get_leaf_proof(&ds, leaves[0].clone(), Some(5), Duration::MAX, CHAIN_LIMIT)
1231 .await
1232 .unwrap();
1233 assert!(matches!(proof.proof(), FinalityProof::NewProtocol { .. }));
1234 assert_eq!(
1235 proof
1236 .verify(LeafProofHint::Quorum(&AlwaysTrueQuorum))
1237 .await
1238 .unwrap(),
1239 leaves[0]
1240 );
1241
1242 let proof = get_leaf_proof(&ds, leaves[0].clone(), Some(3), Duration::MAX, CHAIN_LIMIT)
1244 .await
1245 .unwrap();
1246 assert!(matches!(proof.proof(), FinalityProof::Assumption));
1247 assert_eq!(
1248 proof
1249 .verify(LeafProofHint::assumption(leaves[2].leaf()))
1250 .await
1251 .unwrap(),
1252 leaves[0]
1253 );
1254
1255 let proof = get_leaf_proof(&ds, leaves[2].clone(), Some(5), Duration::MAX, CHAIN_LIMIT)
1257 .await
1258 .unwrap();
1259 assert!(matches!(proof.proof(), FinalityProof::Assumption));
1260 assert_eq!(
1261 proof
1262 .verify(LeafProofHint::assumption(leaves[4].leaf()))
1263 .await
1264 .unwrap(),
1265 leaves[2]
1266 );
1267 }
1268
1269 #[test_log::test(tokio::test(flavor = "multi_thread"))]
1270 async fn test_distant_hint_falls_through() {
1271 let storage = <DataSource as TestableSequencerDataSource>::create_storage().await;
1272 let ds = DataSource::create(
1273 DataSource::persistence_options(&storage),
1274 Default::default(),
1275 false,
1276 )
1277 .await
1278 .unwrap();
1279
1280 let leaves = leaf_chain_with_upgrade(
1283 1..=5,
1284 4,
1285 Upgrade::new(DRB_AND_HEADER_UPGRADE_VERSION, NEW_PROTOCOL_VERSION),
1286 )
1287 .await;
1288 let cert2_leaf = &leaves[4];
1289 let cert2 = cert2_for_leaf(cert2_leaf);
1290
1291 {
1292 let mut tx = ds.write().await.unwrap();
1293 for leaf in &leaves {
1294 tx.insert_leaf(leaf).await.unwrap();
1295 }
1296 tx.insert_cert2(cert2_leaf.height(), cert2).await.unwrap();
1297 tx.commit().await.unwrap();
1298 }
1299
1300 let proof = get_leaf_proof(
1302 &ds,
1303 leaves[0].clone(),
1304 Some(1000),
1305 Duration::MAX,
1306 CHAIN_LIMIT,
1307 )
1308 .await
1309 .unwrap();
1310 assert!(matches!(proof.proof(), FinalityProof::HotStuff2 { .. }));
1311 assert_eq!(
1312 proof
1313 .verify(LeafProofHint::Quorum(&AlwaysTrueQuorum))
1314 .await
1315 .unwrap(),
1316 leaves[0]
1317 );
1318
1319 let proof = get_leaf_proof(
1321 &ds,
1322 leaves[3].clone(),
1323 Some(1000),
1324 Duration::MAX,
1325 CHAIN_LIMIT,
1326 )
1327 .await
1328 .unwrap();
1329 assert!(matches!(proof.proof(), FinalityProof::NewProtocol { .. }));
1330 assert_eq!(
1331 proof
1332 .verify(LeafProofHint::Quorum(&AlwaysTrueQuorum))
1333 .await
1334 .unwrap(),
1335 leaves[3]
1336 );
1337 }
1338
1339 #[test_log::test(tokio::test(flavor = "multi_thread"))]
1340 async fn test_qc_chain_chain_limit() {
1341 let storage = <DataSource as TestableSequencerDataSource>::create_storage().await;
1342 let ds = DataSource::create(
1343 DataSource::persistence_options(&storage),
1344 Default::default(),
1345 false,
1346 )
1347 .await
1348 .unwrap();
1349
1350 let leaves = leaf_chain(1..=3, EPOCH_VERSION).await;
1352 {
1353 let mut tx = ds.write().await.unwrap();
1354 for leaf in &leaves {
1355 tx.insert_leaf(leaf).await.unwrap();
1356 }
1357 tx.commit().await.unwrap();
1358 }
1359
1360 let err = get_leaf_proof_with_qc_chain(&ds, leaves[0].clone(), Duration::MAX, 1)
1361 .await
1362 .unwrap_err();
1363 assert_eq!(err.status(), StatusCode::NOT_FOUND);
1364
1365 let proof = get_leaf_proof_with_qc_chain(&ds, leaves[0].clone(), Duration::MAX, 2)
1366 .await
1367 .unwrap();
1368 assert_eq!(
1369 proof
1370 .verify(LeafProofHint::Quorum(&AlwaysTrueQuorum))
1371 .await
1372 .unwrap(),
1373 leaves[0]
1374 );
1375 }
1376
1377 #[test_log::test(tokio::test(flavor = "multi_thread"))]
1378 async fn test_finalized_hint_too_far() {
1379 let storage = <DataSource as TestableSequencerDataSource>::create_storage().await;
1380 let ds = DataSource::create(
1381 DataSource::persistence_options(&storage),
1382 Default::default(),
1383 false,
1384 )
1385 .await
1386 .unwrap();
1387
1388 let leaves = leaf_chain(1..=2, EPOCH_VERSION).await;
1389 {
1390 let mut tx = ds.write().await.unwrap();
1391 tx.insert_leaf(&leaves[0]).await.unwrap();
1392 tx.commit().await.unwrap();
1393 }
1394
1395 let err = get_leaf_proof_with_finalized_assumption(
1396 &ds,
1397 leaves[0].clone(),
1398 1000,
1399 Duration::MAX,
1400 10,
1401 )
1402 .await
1403 .unwrap_err();
1404 assert_eq!(err.status(), StatusCode::BAD_REQUEST);
1405 }
1406
1407 #[test_log::test(tokio::test(flavor = "multi_thread"))]
1408 async fn test_qc_chain_new_protocol_cutover() {
1409 let storage = <DataSource as TestableSequencerDataSource>::create_storage().await;
1410 let ds = DataSource::create(
1411 DataSource::persistence_options(&storage),
1412 Default::default(),
1413 false,
1414 )
1415 .await
1416 .unwrap();
1417
1418 let leaves = custom_leaf_chain_with_upgrade(
1421 1..=4,
1422 2,
1423 Upgrade::new(DRB_AND_HEADER_UPGRADE_VERSION, NEW_PROTOCOL_VERSION),
1424 |proposal| {
1425 proposal.view_number = ViewNumber::new(proposal.block_header.height() * 2);
1426 },
1427 )
1428 .await;
1429 assert_eq!(leaves[0].header().version(), DRB_AND_HEADER_UPGRADE_VERSION);
1430 assert_eq!(leaves[1].header().version(), NEW_PROTOCOL_VERSION);
1431 let cert2_leaf = &leaves[3];
1432 let cert2 = cert2_for_leaf(cert2_leaf);
1433
1434 {
1435 let mut tx = ds.write().await.unwrap();
1436 for leaf in &leaves {
1437 tx.insert_leaf(leaf).await.unwrap();
1438 }
1439 tx.insert_cert2(cert2_leaf.height(), cert2).await.unwrap();
1440 tx.commit().await.unwrap();
1441 }
1442
1443 let proof =
1444 get_leaf_proof_with_qc_chain(&ds, leaves[0].clone(), Duration::MAX, CHAIN_LIMIT)
1445 .await
1446 .unwrap();
1447 assert!(matches!(proof.proof(), FinalityProof::NewProtocol { .. }));
1448 assert_eq!(
1449 proof
1450 .verify(LeafProofHint::Quorum(&AlwaysTrueQuorum))
1451 .await
1452 .unwrap(),
1453 leaves[0]
1454 );
1455 }
1456
1457 #[test_log::test(tokio::test(flavor = "multi_thread"))]
1458 async fn test_bad_finalized() {
1459 let storage = <DataSource as TestableSequencerDataSource>::create_storage().await;
1460 let ds = DataSource::create(
1461 DataSource::persistence_options(&storage),
1462 Default::default(),
1463 false,
1464 )
1465 .await
1466 .unwrap();
1467
1468 let leaves = leaf_chain(1..2, EPOCH_VERSION).await;
1471 {
1472 let mut tx = ds.write().await.unwrap();
1473 tx.insert_leaf(&leaves[0]).await.unwrap();
1474 tx.commit().await.unwrap();
1475 }
1476
1477 let err = get_leaf_proof_with_finalized_assumption(
1478 &ds,
1479 leaves[0].clone(),
1480 0,
1481 Duration::MAX,
1482 CHAIN_LIMIT,
1483 )
1484 .await
1485 .unwrap_err();
1486 assert_eq!(err.status(), StatusCode::BAD_REQUEST);
1487 }
1488
1489 #[test_log::test(tokio::test(flavor = "multi_thread"))]
1490 async fn test_no_chain() {
1491 let storage = <DataSource as TestableSequencerDataSource>::create_storage().await;
1492 let ds = DataSource::create(
1493 DataSource::persistence_options(&storage),
1494 Default::default(),
1495 false,
1496 )
1497 .await
1498 .unwrap();
1499
1500 let leaves = leaf_chain(1..=4, EPOCH_VERSION).await;
1503 {
1504 let mut tx = ds.write().await.unwrap();
1505 tx.insert_leaf(&leaves[0]).await.unwrap();
1506 tx.insert_leaf(&leaves[2]).await.unwrap();
1507 tx.insert_leaf(&leaves[3]).await.unwrap();
1508 tx.commit().await.unwrap();
1509 }
1510
1511 let err = get_leaf_proof_with_qc_chain(
1512 &ds,
1513 leaves[0].clone(),
1514 Duration::from_secs(1),
1515 CHAIN_LIMIT,
1516 )
1517 .await
1518 .unwrap_err();
1519 assert_eq!(err.status(), StatusCode::NOT_FOUND);
1520
1521 let err = get_leaf_proof_with_finalized_assumption(
1525 &ds,
1526 leaves[0].clone(),
1527 4,
1528 Duration::from_secs(1),
1529 CHAIN_LIMIT,
1530 )
1531 .await
1532 .unwrap_err();
1533 assert_eq!(err.status(), StatusCode::NOT_FOUND);
1534 }
1535
1536 #[test_log::test(tokio::test(flavor = "multi_thread"))]
1537 async fn test_final_qcs() {
1538 let storage = <DataSource as TestableSequencerDataSource>::create_storage().await;
1539 let ds = DataSource::create(
1540 DataSource::persistence_options(&storage),
1541 Default::default(),
1542 false,
1543 )
1544 .await
1545 .unwrap();
1546
1547 let leaves = leaf_chain(1..=3, EPOCH_VERSION).await;
1549 let qcs = [
1550 CertificatePair::for_parent(leaves[1].leaf()),
1551 CertificatePair::for_parent(leaves[2].leaf()),
1552 ];
1553 {
1554 let mut tx = ds.write().await.unwrap();
1555 tx.insert_leaf_with_qc_chain(&leaves[0], Some(qcs.clone()))
1556 .await
1557 .unwrap();
1558 tx.commit().await.unwrap();
1559 }
1560
1561 let proof =
1562 get_leaf_proof_with_qc_chain(&ds, leaves[0].clone(), Duration::MAX, CHAIN_LIMIT)
1563 .await
1564 .unwrap();
1565 assert_eq!(
1566 proof
1567 .verify(LeafProofHint::Quorum(&AlwaysTrueQuorum))
1568 .await
1569 .unwrap(),
1570 leaves[0]
1571 );
1572 }
1573
1574 #[test_log::test(tokio::test(flavor = "multi_thread"))]
1575 async fn test_upgrade_to_epochs() {
1576 let storage = <DataSource as TestableSequencerDataSource>::create_storage().await;
1577 let ds = DataSource::create(
1578 DataSource::persistence_options(&storage),
1579 Default::default(),
1580 false,
1581 )
1582 .await
1583 .unwrap();
1584
1585 let leaves = leaf_chain_with_upgrade(1..=4, 2, ENABLE_EPOCHS).await;
1589 assert_eq!(leaves[0].header().version(), LEGACY_VERSION);
1590 assert_eq!(leaves[1].header().version(), DRB_AND_HEADER_UPGRADE_VERSION);
1591 let qcs = [
1592 CertificatePair::for_parent(leaves[2].leaf()),
1593 CertificatePair::for_parent(leaves[3].leaf()),
1594 ];
1595 {
1596 let mut tx = ds.write().await.unwrap();
1597 tx.insert_leaf(&leaves[0]).await.unwrap();
1598 tx.insert_leaf_with_qc_chain(&leaves[1], Some(qcs.clone()))
1599 .await
1600 .unwrap();
1601 tx.commit().await.unwrap();
1602 }
1603
1604 let proof =
1605 get_leaf_proof_with_qc_chain(&ds, leaves[0].clone(), Duration::MAX, CHAIN_LIMIT)
1606 .await
1607 .unwrap();
1608 assert_eq!(
1609 proof
1610 .verify(LeafProofHint::Quorum(&VersionCheckQuorum::new(
1611 leaves.iter().map(|leaf| leaf.leaf().clone())
1612 )))
1613 .await
1614 .unwrap(),
1615 leaves[0]
1616 );
1617 assert!(matches!(proof.proof(), FinalityProof::HotStuff2 { .. }))
1618 }
1619
1620 #[tokio::test]
1621 #[test_log::test]
1622 async fn test_header_proof() {
1623 let storage = <DataSource as TestableSequencerDataSource>::create_storage().await;
1624 let ds = DataSource::create(
1625 DataSource::persistence_options(&storage),
1626 Default::default(),
1627 false,
1628 )
1629 .await
1630 .unwrap();
1631
1632 let leaves = leaf_chain(0..=2, EPOCH_VERSION).await;
1634 let mts = leaves
1635 .iter()
1636 .scan(
1637 BlockMerkleTree::new(BLOCK_MERKLE_TREE_HEIGHT),
1638 |mt, leaf| {
1639 assert_eq!(mt.commitment(), leaf.header().block_merkle_tree_root());
1640 let item = mt.clone();
1641 mt.push(leaf.block_hash()).unwrap();
1642 Some(item)
1643 },
1644 )
1645 .collect::<Vec<_>>();
1646
1647 {
1649 let mut tx = ds.write().await.unwrap();
1650 for (leaf, mt) in leaves.iter().zip(&mts) {
1651 tx.insert_leaf(leaf).await.unwrap();
1652
1653 if leaf.height() > 0 {
1654 let merkle_path = mt.lookup(leaf.height() - 1).expect_ok().unwrap().1;
1655 UpdateStateData::<SeqTypes, BlockMerkleTree, _>::insert_merkle_nodes(
1656 &mut tx,
1657 merkle_path,
1658 ToTraversalPath::<{ BlockMerkleTree::ARITY }>::to_traversal_path(
1659 &(leaf.height() - 1),
1660 BLOCK_MERKLE_TREE_HEIGHT,
1661 ),
1662 leaf.height(),
1663 )
1664 .await
1665 .unwrap();
1666 UpdateStateData::<SeqTypes, BlockMerkleTree, _>::set_last_state_height(
1667 &mut tx,
1668 leaf.height() as usize,
1669 )
1670 .await
1671 .unwrap();
1672 }
1673 }
1674 tx.commit().await.unwrap();
1675 }
1676
1677 for (root, mt) in mts.iter().enumerate().skip(1) {
1679 for (height, leaf) in leaves.iter().enumerate().take(root) {
1680 tracing::info!(root, height, "test happy path");
1681 let proof =
1682 get_header_proof(&ds, root as u64, BlockId::Number(height), Duration::MAX)
1683 .await
1684 .unwrap();
1685 assert_eq!(proof.verify_ref(mt.commitment()).unwrap(), leaf.header());
1686 }
1687 }
1688
1689 let err = get_header_proof(&ds, 5, BlockId::Number(4), Duration::from_secs(1))
1691 .await
1692 .unwrap_err();
1693 assert_eq!(err.status(), StatusCode::NOT_FOUND);
1694
1695 let err = get_header_proof(&ds, 1, BlockId::Number(1), Duration::MAX)
1697 .await
1698 .unwrap_err();
1699 assert_eq!(err.status(), StatusCode::BAD_REQUEST);
1700 }
1701
1702 #[tokio::test]
1703 #[test_log::test]
1704 async fn test_namespace_proof() {
1705 let storage = <DataSource as TestableSequencerDataSource>::create_storage().await;
1706 let ds = DataSource::create(
1707 DataSource::persistence_options(&storage),
1708 Default::default(),
1709 false,
1710 )
1711 .await
1712 .unwrap();
1713
1714 let client = TestClient::default();
1716 let leaves = join_all((0..=2).map(|i| client.leaf(i))).await;
1717 let payloads = join_all((0..=2).map(|i| client.payload(i))).await;
1718 let vid_commons = join_all((0..=2).map(|i| client.vid_common(i))).await;
1719
1720 {
1722 let mut tx = ds.write().await.unwrap();
1723 for (leaf, payload, vid_common) in izip!(&leaves, &payloads, &vid_commons) {
1724 tx.insert_leaf(leaf).await.unwrap();
1725 tx.insert_block(&BlockQueryData::<SeqTypes>::new(
1726 leaf.header().clone(),
1727 payload.clone(),
1728 ))
1729 .await
1730 .unwrap();
1731 tx.insert_vid(
1732 &VidCommonQueryData::<SeqTypes>::new(leaf.header().clone(), vid_common.clone()),
1733 None,
1734 )
1735 .await
1736 .unwrap();
1737 }
1738 tx.commit().await.unwrap();
1739 }
1740
1741 let ns = payloads[0]
1743 .transaction(&TransactionIndex {
1744 ns_index: 0.into(),
1745 position: 0,
1746 })
1747 .unwrap()
1748 .namespace();
1749 let proofs = get_namespace_proof_range(&ds, 0, 3, ns.into(), Duration::MAX, 100)
1750 .await
1751 .unwrap();
1752 assert_eq!(proofs.len(), 3);
1753 for (leaf, proof) in leaves.iter().zip(proofs) {
1754 proof.verify(leaf.header(), ns).unwrap();
1755 }
1756
1757 let tx = payloads[1]
1759 .transaction(&TransactionIndex {
1760 ns_index: 0.into(),
1761 position: 0,
1762 })
1763 .unwrap();
1764 let ns = tx.namespace();
1765 let proofs = get_namespace_proof_range(&ds, 1, 2, ns.into(), Duration::MAX, 100)
1766 .await
1767 .unwrap();
1768 assert_eq!(proofs.len(), 1);
1769 assert_eq!(proofs[0].verify(leaves[1].header(), ns).unwrap(), [tx]);
1770
1771 let err = get_namespace_proof_range(&ds, 0, 4, ns.into(), Duration::from_secs(1), 100)
1773 .await
1774 .unwrap_err();
1775 assert_eq!(err.status(), StatusCode::NOT_FOUND);
1776
1777 let err = get_namespace_proof_range(&ds, 1, 0, ns.into(), Duration::from_secs(1), 100)
1779 .await
1780 .unwrap_err();
1781 assert_eq!(err.status(), StatusCode::BAD_REQUEST);
1782 assert!(
1783 err.to_string().contains("requested empty interval"),
1784 "{err:#}"
1785 );
1786
1787 let err = get_namespace_proof_range(&ds, 0, 10_000, ns.into(), Duration::from_secs(1), 100)
1789 .await
1790 .unwrap_err();
1791 assert_eq!(err.status(), StatusCode::BAD_REQUEST);
1792 assert!(err.to_string().contains("exceeds maximum size"), "{err:#}");
1793 }
1794}