Skip to main content

espresso_node/api/
light_client.rs

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
39/// Construct a proof that the requested leaf is finalized.
40///
41/// The `finalized` hint is honored when it yields a bounded proof no longer than a direct finality
42/// proof, since the client can verify hint-based proofs without signature checks.
43pub(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    // New-protocol chains cannot terminate early, so a nearby cert2 may yield a shorter proof.
60    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
84/// The earliest stored cert2 height at or after `height`. Errors are treated as no cert2: this
85/// only chooses between proof strategies, and the chosen path will surface any persistent error.
86async 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    // Grab the endpoint and the final QC chain in the same transaction, to ensure that
113    // the QC chain actually corresponds to the endpoint block (and is not subject to
114    // concurrent updates).
115    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        // HotStuff commit rules cannot terminate a chain of new-protocol leaves; switch to cert2.
137        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    // We reached the end of the range of interest without encountering a 3-chain. Thus, if the last
156    // leaf in the chain is not already assumed finalized by the client, we must prove it finalized
157    // by appending two more QCs.
158    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
166/// Build a leaf proof for the new protocol using certificate2 finality.
167///
168/// Certificate2 directly commits the leaf at `cert2.data.block_number`. The indirect commit rule then
169/// commits all of that leaf's uncommitted ancestors. If cert2 directly commits the requested leaf,
170/// the proof contains only that leaf plus cert2. Otherwise, the proof includes the chain from the
171/// requested leaf through the directly committed descendant; the verifier checks parent commitments
172/// to prove the requested leaf is on that finalized chain.
173pub(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
197/// Extend `proof` from `leaf` to the leaf directly committed by the earliest stored cert2, adding
198/// at most `chain_limit` leaves.
199async 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    // The new leaf may already complete a HotStuff chain begun before the protocol cutover.
215    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    // Extend the proof chain until we reach the leaf directly committed by cert2. Once that leaf is
257    // present and matches cert2's commitment, the requested leaf is finalized by the indirect
258    // commit rule.
259    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 we have a known-finalized block, we will not need a final 2-chain of QCs to prove
294    // the last leaf in the result finalized, since we will either terminate with a 3-chain
295    // of leaves or at the `finalized` leaf.
296    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
461/// Single-namespace version of [`get_namespaces_proof_range`].
462pub(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
540/// Construct a [`NamespaceProof`] for the namespace at `ns_index` of the given block.
541fn 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    /// Timeout for failing requests due to missing data.
559    ///
560    /// If data needed to respond to a request is missing, it can (in some cases) be fetched from an
561    /// external provider. This parameter controls how long the request handler will wait for
562    /// missing data to be fetched before giving up and failing the request.
563    pub fetch_timeout: Duration,
564
565    /// The maximum number of large objects which can be loaded in a single range query.
566    ///
567    /// Large objects include anything that _might_ contain a full payload or an object proportional
568    /// in size to a payload. Note that this limit applies to the entire class of objects: we do not
569    /// check the size of objects while loading to determine which limit to apply. If an object
570    /// belongs to a class which might contain a large payload, the large object limit always
571    /// applies.
572    pub large_object_range_limit: usize,
573
574    /// The maximum number of leaves included in a single leaf proof, bounding the memory needed
575    /// to construct and serialize it.
576    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            // Find the range of L1 block containing events for this epoch. This is determined by
657            // the `l1_finalized` field of the epoch root (from two epochs prior) and the previous
658            // epoch's epoch root.
659            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        // Insert some leaves, forming a chain.
1028        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        // Ask for the first leaf; it is proved finalized by the chain formed along with the second.
1038        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        // Insert a single leaf. We will not be able to provide proofs ending in a leaf chain, but
1063        // we can return a leaf if the leaf after it is already known to be finalized.
1064        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        // A proof for the first leaf must extend to the nearest cert2, three leaves away.
1174        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        // The cert2 at height 2 beats the hint at height 5, so the hint is ignored.
1230        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        // A hint at height 3 ties the cert2 proof length, so the hint wins.
1243        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        // With no cert2 at or after the requested height, the hint is honored.
1256        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        // Legacy leaves 1-3 upgrade to the new protocol at height 4, with a cert2 at height 5, so
1281        // both fall-through paths have a bounded proof available.
1282        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        // A hint more than `chain_limit` past a legacy leaf is ignored in favor of a QC chain.
1301        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        // A hint more than `chain_limit` past a new-protocol leaf is ignored in favor of cert2.
1320        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        // Proving the first leaf finalized requires walking the two subsequent leaves.
1351        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        // Upgrade mid-chain and skew view numbers so no HotStuff 2-chain ever forms; the proof
1419        // for the first (pre-upgrade) leaf must fall through to cert2 finality.
1420        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        // Insert a single leaf. If we request this leaf but provide a finalized leaf which is
1469        // earlier, we should fail.
1470        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        // Insert multiple leaves that don't chain. We will not be able to prove these are
1501        // finalized.
1502        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        // Even if we start from a finalized leave that extends one of the leaves we do have (4,
1522        // extends 3) we fail to generate a proof because we can't generate a chain from the
1523        // requested leaf (1) to the finalized leaf (4), since leaf 2 is missing.
1524        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        // Insert a single leaf, plus an extra QC chain proving it finalized.
1548        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        // Upgrade to epochs (and enabling HotStuff2) in the middle of a leaf chain, so that the
1586        // last leaf in the chain only requires 2 QCs to verify, even though at the start of the
1587        // chain we would have required 3.
1588        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        // Construct a chain of leaves, plus the corresponding block Merkle tree at each leaf.
1633        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        // Save all those objects in the DB.
1648        {
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        // Test happy path.
1678        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        // Test unknown leaf.
1690        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        // Test height >= root.
1696        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        // Construct a chain of blocks.
1715        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        // Save all those objects in the DB.
1721        {
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        // Test happy path: all blocks.
1742        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        // Test happy path: subset.
1758        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        // Test missing data in range.
1772        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        // Test invalid range.
1778        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        // Test large range.
1788        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}