hotshot_query_service/availability/
query_data.rs

1// Copyright (c) 2022 Espresso Systems (espressosys.com)
2// This file is part of the HotShot Query Service library.
3//
4// This program is free software: you can redistribute it and/or modify it under the terms of the GNU
5// General Public License as published by the Free Software Foundation, either version 3 of the
6// License, or (at your option) any later version.
7// This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
8// even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
9// General Public License for more details.
10// You should have received a copy of the GNU General Public License along with this program. If not,
11// see <https://www.gnu.org/licenses/>.
12
13use std::{collections::HashMap, fmt::Debug, hash::Hash};
14
15use committable::{Commitment, Committable};
16use derive_more::derive::From;
17use hotshot_types::{
18    data::{Leaf, Leaf2, VidCommitment, VidShare},
19    simple_certificate::{
20        LightClientStateUpdateCertificateV1, LightClientStateUpdateCertificateV2,
21        QuorumCertificate2,
22    },
23    traits::{
24        self,
25        block_contents::{BlockHeader, GENESIS_VID_NUM_STORAGE_NODES},
26        node_implementation::{NodeType, Versions},
27        EncodeBytes,
28    },
29    vid::advz::{advz_scheme, ADVZCommitment, ADVZCommon},
30};
31use jf_vid::VidScheme;
32use serde::{de::DeserializeOwned, Deserialize, Serialize};
33use snafu::{ensure, Snafu};
34
35use crate::{
36    types::HeightIndexed, Header, Metadata, Payload, QuorumCertificate, Transaction, VidCommon,
37};
38
39pub type LeafHash<Types> = Commitment<Leaf2<Types>>;
40pub type LeafHashLegacy<Types> = Commitment<Leaf<Types>>;
41pub type QcHash<Types> = Commitment<QuorumCertificate2<Types>>;
42
43/// A block hash is the hash of the block header.
44///
45/// A block consists of a header and a payload. But the header itself contains a commitment to the
46/// payload, so we can commit to the entire block simply by hashing the header.
47pub type BlockHash<Types> = Commitment<Header<Types>>;
48pub type TransactionHash<Types> = Commitment<Transaction<Types>>;
49pub type TransactionInclusionProof<Types> =
50    <Payload<Types> as QueryablePayload<Types>>::InclusionProof;
51pub type NamespaceIndex<Types> = <Header<Types> as QueryableHeader<Types>>::NamespaceIndex;
52pub type NamespaceId<Types> = <Header<Types> as QueryableHeader<Types>>::NamespaceId;
53
54pub type Timestamp = time::OffsetDateTime;
55
56pub trait QueryableHeader<Types: NodeType>: BlockHeader<Types> {
57    /// Index for looking up a namespace.
58    type NamespaceIndex: Clone + Debug + Hash + PartialEq + Eq + From<i64> + Into<i64> + Send + Sync;
59
60    /// Serialized representation of a namespace.
61    type NamespaceId: Clone
62        + Debug
63        + Serialize
64        + DeserializeOwned
65        + Send
66        + Sync
67        + Hash
68        + PartialEq
69        + Eq
70        + Copy
71        + From<i64>
72        + Into<i64>;
73
74    /// Resolve a namespace index to the serialized identifier for that namespace.
75    fn namespace_id(&self, i: &Self::NamespaceIndex) -> Option<Self::NamespaceId>;
76
77    /// Get the size taken up by the given namespace in the payload.
78    fn namespace_size(&self, i: &Self::NamespaceIndex, payload_size: usize) -> u64;
79}
80
81#[derive(Clone, Debug, PartialEq, Eq)]
82pub struct TransactionIndex<Types: NodeType>
83where
84    Header<Types>: QueryableHeader<Types>,
85{
86    /// Index for looking up the namespace this transaction belongs to.
87    pub ns_index: NamespaceIndex<Types>,
88    /// Index of the transaction within its namespace in its block.
89    pub position: u32,
90}
91
92/// A block payload whose contents (e.g. individual transactions) can be examined.
93///
94/// Note to implementers: this trait has only a few required methods. The provided methods, for
95/// querying transactions in various ways, are implemented in terms of the required
96/// [`iter`](Self::iter) and [`transaction_proof`](Self::transaction_proof) methods, and
97/// the default implementations may be inefficient (e.g. performing an O(n) search, or computing an
98/// unnecessary inclusion proof). It is good practice to override these default implementations if
99/// your block type supports more efficient implementations (e.g. sublinear indexing by hash).
100pub trait QueryablePayload<Types: NodeType>: traits::BlockPayload<Types>
101where
102    Header<Types>: QueryableHeader<Types>,
103{
104    /// Enumerate the transactions in this block.
105    type Iter<'a>: Iterator<Item = TransactionIndex<Types>>
106    where
107        Self: 'a;
108
109    /// A proof that a certain transaction exists in the block.
110    ///
111    /// The proof system and the statement which is proved will vary by application, with different
112    /// applications proving stronger or weaker statements depending on the trust assumptions at
113    /// play. Some may prove a very strong statement (for example, a shared sequencer proving that
114    /// the transaction belongs not only to the block but to a section of the block dedicated to a
115    /// specific rollup), otherwise may prove something substantially weaker (for example, a trusted
116    /// query service may use `()` for the proof).
117    type InclusionProof: Clone + Debug + PartialEq + Eq + Serialize + DeserializeOwned + Send + Sync;
118
119    /// The number of transactions in the block.
120    fn len(&self, meta: &Self::Metadata) -> usize;
121
122    /// Whether this block is empty of transactions.
123    fn is_empty(&self, meta: &Self::Metadata) -> bool {
124        self.len(meta) == 0
125    }
126
127    /// List the transaction indices in the block.
128    fn iter<'a>(&'a self, meta: &'a Self::Metadata) -> Self::Iter<'a>;
129
130    /// Enumerate the transactions in the block with their indices.
131    fn enumerate<'a>(
132        &'a self,
133        meta: &'a Self::Metadata,
134    ) -> Box<dyn 'a + Iterator<Item = (TransactionIndex<Types>, Self::Transaction)>> {
135        Box::new(self.iter(meta).map(|ix| {
136            // `self.transaction` should always return `Some` if we are using an index which was
137            // yielded by `self.iter`.
138            let tx = self.transaction(meta, &ix).unwrap();
139            (ix, tx)
140        }))
141    }
142
143    /// Get a transaction by its block-specific index.
144    fn transaction(
145        &self,
146        meta: &Self::Metadata,
147        index: &TransactionIndex<Types>,
148    ) -> Option<Self::Transaction>;
149
150    /// Get an inclusion proof for the given transaction.
151    ///
152    /// This function may be slow and computationally intensive, especially for large transactions.
153    fn transaction_proof(
154        &self,
155        meta: &Self::Metadata,
156        vid: &VidCommonQueryData<Types>,
157        index: &TransactionIndex<Types>,
158    ) -> Option<Self::InclusionProof>;
159
160    /// Get the index of the `nth` transaction.
161    fn nth(&self, meta: &Self::Metadata, n: usize) -> Option<TransactionIndex<Types>> {
162        self.iter(meta).nth(n)
163    }
164
165    /// Get the `nth` transaction.
166    fn nth_transaction(&self, meta: &Self::Metadata, n: usize) -> Option<Self::Transaction> {
167        self.transaction(meta, &self.nth(meta, n)?)
168    }
169
170    /// Get the index of the transaction with a given hash, if it is in the block.
171    fn by_hash(
172        &self,
173        meta: &Self::Metadata,
174        hash: Commitment<Self::Transaction>,
175    ) -> Option<TransactionIndex<Types>> {
176        self.iter(meta).find(|i| {
177            if let Some(tx) = self.transaction(meta, i) {
178                tx.commit() == hash
179            } else {
180                false
181            }
182        })
183    }
184
185    /// Get the transaction with a given hash, if it is in the block.
186    fn transaction_by_hash(
187        &self,
188        meta: &Self::Metadata,
189        hash: Commitment<Self::Transaction>,
190    ) -> Option<Self::Transaction> {
191        self.transaction(meta, &self.by_hash(meta, hash)?)
192    }
193}
194
195#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
196#[serde(bound = "")]
197pub struct LeafQueryData<Types: NodeType> {
198    pub(crate) leaf: Leaf2<Types>,
199    pub(crate) qc: QuorumCertificate2<Types>,
200}
201
202#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
203#[serde(bound = "")]
204pub struct LeafQueryDataLegacy<Types: NodeType> {
205    pub(crate) leaf: Leaf<Types>,
206    pub(crate) qc: QuorumCertificate<Types>,
207}
208
209impl<Types: NodeType> From<LeafQueryDataLegacy<Types>> for LeafQueryData<Types> {
210    fn from(legacy: LeafQueryDataLegacy<Types>) -> Self {
211        Self {
212            leaf: legacy.leaf.into(),
213            qc: legacy.qc.to_qc2(),
214        }
215    }
216}
217
218#[derive(Clone, Debug, Snafu)]
219#[snafu(display("QC references leaf {qc_leaf}, but expected {leaf}"))]
220pub struct InconsistentLeafError<Types: NodeType> {
221    pub leaf: LeafHash<Types>,
222    pub qc_leaf: LeafHash<Types>,
223}
224
225#[derive(Clone, Debug, Snafu)]
226#[snafu(display("QC references leaf {qc_leaf}, but expected {leaf}"))]
227pub struct InconsistentLeafLegacyError<Types: NodeType> {
228    pub leaf: LeafHashLegacy<Types>,
229    pub qc_leaf: LeafHashLegacy<Types>,
230}
231
232impl<Types: NodeType> LeafQueryDataLegacy<Types> {
233    /// Collect information about a [`Leaf`].
234    ///
235    /// Returns a new [`LeafQueryData`] object populated from `leaf` and `qc`.
236    ///
237    /// # Errors
238    ///
239    /// Fails with an [`InconsistentLeafError`] if `qc` does not reference `leaf`.
240    pub fn new(
241        mut leaf: Leaf<Types>,
242        qc: QuorumCertificate<Types>,
243    ) -> Result<Self, InconsistentLeafLegacyError<Types>> {
244        // TODO: Replace with the new `commit` function in HotShot. Add an `upgrade_lock` parameter
245        // and a `HsVer: Versions` bound, then call `leaf.commit(upgrade_lock).await`. This will
246        // require updates in callers and relevant types as well.
247        let leaf_commit = <Leaf<Types> as Committable>::commit(&leaf);
248        ensure!(
249            qc.data.leaf_commit == leaf_commit,
250            InconsistentLeafLegacySnafu {
251                leaf: leaf_commit,
252                qc_leaf: qc.data.leaf_commit
253            }
254        );
255
256        // We only want the leaf for the block header and consensus metadata. The payload will be
257        // stored separately.
258        leaf.unfill_block_payload();
259
260        Ok(Self { leaf, qc })
261    }
262
263    pub async fn genesis<HsVer: Versions>(
264        validated_state: &Types::ValidatedState,
265        instance_state: &Types::InstanceState,
266    ) -> Self {
267        Self {
268            leaf: Leaf::genesis::<HsVer>(validated_state, instance_state).await,
269            qc: QuorumCertificate::genesis::<HsVer>(validated_state, instance_state).await,
270        }
271    }
272
273    pub fn leaf(&self) -> &Leaf<Types> {
274        &self.leaf
275    }
276
277    pub fn qc(&self) -> &QuorumCertificate<Types> {
278        &self.qc
279    }
280
281    pub fn header(&self) -> &Header<Types> {
282        self.leaf.block_header()
283    }
284
285    pub fn hash(&self) -> LeafHashLegacy<Types> {
286        // TODO: Replace with the new `commit` function in HotShot. Add an `upgrade_lock` parameter
287        // and a `HsVer: Versions` bound, then call `leaf.commit(upgrade_lock).await`. This will
288        // require updates in callers and relevant types as well.
289        <Leaf<Types> as Committable>::commit(&self.leaf)
290    }
291
292    pub fn block_hash(&self) -> BlockHash<Types> {
293        self.header().commit()
294    }
295
296    pub fn payload_hash(&self) -> VidCommitment {
297        self.header().payload_commitment()
298    }
299}
300
301impl<Types: NodeType> LeafQueryData<Types> {
302    /// Collect information about a [`Leaf`].
303    ///
304    /// Returns a new [`LeafQueryData`] object populated from `leaf` and `qc`.
305    ///
306    /// # Errors
307    ///
308    /// Fails with an [`InconsistentLeafError`] if `qc` does not reference `leaf`.
309    pub fn new(
310        mut leaf: Leaf2<Types>,
311        qc: QuorumCertificate2<Types>,
312    ) -> Result<Self, InconsistentLeafError<Types>> {
313        // TODO: Replace with the new `commit` function in HotShot. Add an `upgrade_lock` parameter
314        // and a `HsVer: Versions` bound, then call `leaf.commit(upgrade_lock).await`. This will
315        // require updates in callers and relevant types as well.
316        let leaf_commit = <Leaf2<Types> as Committable>::commit(&leaf);
317        ensure!(
318            qc.data.leaf_commit == leaf_commit,
319            InconsistentLeafSnafu {
320                leaf: leaf_commit,
321                qc_leaf: qc.data.leaf_commit
322            }
323        );
324
325        // We only want the leaf for the block header and consensus metadata. The payload will be
326        // stored separately.
327        leaf.unfill_block_payload();
328
329        Ok(Self { leaf, qc })
330    }
331
332    pub async fn genesis<HsVer: Versions>(
333        validated_state: &Types::ValidatedState,
334        instance_state: &Types::InstanceState,
335    ) -> Self {
336        Self {
337            leaf: Leaf2::genesis::<HsVer>(validated_state, instance_state).await,
338            qc: QuorumCertificate2::genesis::<HsVer>(validated_state, instance_state).await,
339        }
340    }
341
342    pub fn leaf(&self) -> &Leaf2<Types> {
343        &self.leaf
344    }
345
346    pub fn qc(&self) -> &QuorumCertificate2<Types> {
347        &self.qc
348    }
349
350    pub fn header(&self) -> &Header<Types> {
351        self.leaf.block_header()
352    }
353
354    pub fn hash(&self) -> LeafHash<Types> {
355        // TODO: Replace with the new `commit` function in HotShot. Add an `upgrade_lock` parameter
356        // and a `HsVer: Versions` bound, then call `leaf.commit(upgrade_lock).await`. This will
357        // require updates in callers and relevant types as well.
358        <Leaf2<Types> as Committable>::commit(&self.leaf)
359    }
360
361    pub fn block_hash(&self) -> BlockHash<Types> {
362        self.header().commit()
363    }
364
365    pub fn payload_hash(&self) -> VidCommitment {
366        self.header().payload_commitment()
367    }
368}
369
370impl<Types: NodeType> HeightIndexed for LeafQueryData<Types> {
371    fn height(&self) -> u64 {
372        self.header().block_number()
373    }
374}
375
376impl<Types: NodeType> HeightIndexed for LeafQueryDataLegacy<Types> {
377    fn height(&self) -> u64 {
378        self.header().block_number()
379    }
380}
381
382#[derive(Clone, Debug, Serialize, serde::Deserialize, PartialEq, Eq)]
383#[serde(bound = "")]
384pub struct HeaderQueryData<Types: NodeType> {
385    pub header: Header<Types>,
386}
387
388impl<Types: NodeType> HeaderQueryData<Types> {
389    pub fn new(header: Header<Types>) -> Self {
390        Self { header }
391    }
392
393    pub fn header(&self) -> &Header<Types> {
394        &self.header
395    }
396}
397
398#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
399#[serde(bound = "")]
400pub struct BlockQueryData<Types: NodeType> {
401    pub(crate) header: Header<Types>,
402    pub(crate) payload: Payload<Types>,
403    pub(crate) hash: BlockHash<Types>,
404    pub(crate) size: u64,
405    pub(crate) num_transactions: u64,
406}
407
408impl<Types: NodeType> BlockQueryData<Types> {
409    pub fn new(header: Header<Types>, payload: Payload<Types>) -> Self
410    where
411        Header<Types>: QueryableHeader<Types>,
412        Payload<Types>: QueryablePayload<Types>,
413    {
414        Self {
415            hash: header.commit(),
416            size: payload_size::<Types>(&payload),
417            num_transactions: payload.len(header.metadata()) as u64,
418            header,
419            payload,
420        }
421    }
422
423    pub async fn genesis<HsVer: Versions>(
424        validated_state: &Types::ValidatedState,
425        instance_state: &Types::InstanceState,
426    ) -> Self
427    where
428        Header<Types>: QueryableHeader<Types>,
429        Payload<Types>: QueryablePayload<Types>,
430    {
431        let leaf = Leaf2::<Types>::genesis::<HsVer>(validated_state, instance_state).await;
432        Self::new(leaf.block_header().clone(), leaf.block_payload().unwrap())
433    }
434
435    pub fn header(&self) -> &Header<Types> {
436        &self.header
437    }
438
439    pub fn metadata(&self) -> &Metadata<Types> {
440        self.header.metadata()
441    }
442
443    pub fn payload_hash(&self) -> VidCommitment {
444        self.header.payload_commitment()
445    }
446
447    pub fn payload(&self) -> &Payload<Types> {
448        &self.payload
449    }
450
451    pub fn hash(&self) -> BlockHash<Types> {
452        self.hash
453    }
454
455    pub fn size(&self) -> u64 {
456        self.size
457    }
458
459    pub fn num_transactions(&self) -> u64 {
460        self.num_transactions
461    }
462
463    pub fn namespace_info(&self) -> NamespaceMap<Types>
464    where
465        Header<Types>: QueryableHeader<Types>,
466        Payload<Types>: QueryablePayload<Types>,
467    {
468        let mut map = NamespaceMap::<Types>::new();
469        for tx in self.payload.iter(self.header.metadata()) {
470            let Some(ns_id) = self.header.namespace_id(&tx.ns_index) else {
471                continue;
472            };
473            map.entry(ns_id)
474                .or_insert_with(|| NamespaceInfo {
475                    num_transactions: 0,
476                    size: self.header.namespace_size(&tx.ns_index, self.size as usize),
477                })
478                .num_transactions += 1;
479        }
480        map
481    }
482}
483
484impl<Types: NodeType> BlockQueryData<Types>
485where
486    Header<Types>: QueryableHeader<Types>,
487    Payload<Types>: QueryablePayload<Types>,
488{
489    pub fn transaction(&self, ix: &TransactionIndex<Types>) -> Option<Transaction<Types>> {
490        self.payload().transaction(self.metadata(), ix)
491    }
492
493    pub fn transaction_by_hash(
494        &self,
495        hash: Commitment<Transaction<Types>>,
496    ) -> Option<TransactionIndex<Types>> {
497        self.payload().by_hash(self.metadata(), hash)
498    }
499
500    pub fn transaction_proof(
501        &self,
502        vid_common: &VidCommonQueryData<Types>,
503        ix: &TransactionIndex<Types>,
504    ) -> Option<TransactionInclusionProof<Types>> {
505        self.payload()
506            .transaction_proof(self.metadata(), vid_common, ix)
507    }
508
509    pub fn len(&self) -> usize {
510        self.payload.len(self.metadata())
511    }
512
513    pub fn is_empty(&self) -> bool {
514        self.len() == 0
515    }
516
517    pub fn enumerate(
518        &self,
519    ) -> impl '_ + Iterator<Item = (TransactionIndex<Types>, Transaction<Types>)> {
520        self.payload.enumerate(self.metadata())
521    }
522}
523
524impl<Types: NodeType> HeightIndexed for BlockQueryData<Types> {
525    fn height(&self) -> u64 {
526        self.header.block_number()
527    }
528}
529
530#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
531#[serde(bound = "")]
532pub struct ADVZPayloadQueryData<Types: NodeType> {
533    pub(crate) height: u64,
534    pub(crate) block_hash: BlockHash<Types>,
535    pub(crate) hash: ADVZCommitment,
536    pub(crate) size: u64,
537    pub(crate) data: Payload<Types>,
538}
539
540#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
541#[serde(bound = "")]
542pub struct PayloadQueryData<Types: NodeType> {
543    pub(crate) height: u64,
544    pub(crate) block_hash: BlockHash<Types>,
545    pub(crate) hash: VidCommitment,
546    pub(crate) size: u64,
547    pub(crate) data: Payload<Types>,
548}
549
550impl<Types: NodeType> From<BlockQueryData<Types>> for PayloadQueryData<Types> {
551    fn from(block: BlockQueryData<Types>) -> Self {
552        Self {
553            height: block.height(),
554            block_hash: block.hash(),
555            hash: block.header.payload_commitment(),
556            size: block.size(),
557            data: block.payload,
558        }
559    }
560}
561
562impl<Types: NodeType> PayloadQueryData<Types> {
563    pub fn to_legacy(&self) -> Option<ADVZPayloadQueryData<Types>> {
564        let VidCommitment::V0(advz_commit) = self.hash else {
565            return None;
566        };
567
568        Some(ADVZPayloadQueryData {
569            height: self.height,
570            block_hash: self.block_hash,
571            hash: advz_commit,
572            size: self.size,
573            data: self.data.clone(),
574        })
575    }
576
577    pub async fn genesis<HsVer: Versions>(
578        validated_state: &Types::ValidatedState,
579        instance_state: &Types::InstanceState,
580    ) -> Self
581    where
582        Header<Types>: QueryableHeader<Types>,
583        Payload<Types>: QueryablePayload<Types>,
584    {
585        BlockQueryData::genesis::<HsVer>(validated_state, instance_state)
586            .await
587            .into()
588    }
589
590    pub fn hash(&self) -> VidCommitment {
591        self.hash
592    }
593
594    pub fn block_hash(&self) -> BlockHash<Types> {
595        self.block_hash
596    }
597
598    pub fn size(&self) -> u64 {
599        self.size
600    }
601
602    pub fn data(&self) -> &Payload<Types> {
603        &self.data
604    }
605}
606
607impl<Types: NodeType> HeightIndexed for PayloadQueryData<Types> {
608    fn height(&self) -> u64 {
609        self.height
610    }
611}
612
613/// The old VidCommonQueryData, associated with ADVZ VID Scheme.
614#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
615#[serde(bound = "")]
616pub struct ADVZCommonQueryData<Types: NodeType> {
617    pub(crate) height: u64,
618    pub(crate) block_hash: BlockHash<Types>,
619    pub(crate) payload_hash: ADVZCommitment,
620    pub(crate) common: ADVZCommon,
621}
622
623impl<Types: NodeType> ADVZCommonQueryData<Types> {
624    pub fn new(header: Header<Types>, common: ADVZCommon) -> anyhow::Result<Self> {
625        let VidCommitment::V0(payload_hash) = header.payload_commitment() else {
626            return Err(anyhow::anyhow!("Inconsistent header type."));
627        };
628        Ok(Self {
629            height: header.block_number(),
630            block_hash: header.commit(),
631            payload_hash,
632            common,
633        })
634    }
635
636    pub async fn genesis<HsVer: Versions>(
637        validated_state: &Types::ValidatedState,
638        instance_state: &Types::InstanceState,
639    ) -> anyhow::Result<Self> {
640        let leaf = Leaf::<Types>::genesis::<HsVer>(validated_state, instance_state).await;
641        let payload = leaf.block_payload().unwrap();
642        let bytes = payload.encode();
643        let disperse = advz_scheme(GENESIS_VID_NUM_STORAGE_NODES)
644            .disperse(bytes)
645            .unwrap();
646
647        Self::new(leaf.block_header().clone(), disperse.common)
648    }
649
650    pub fn block_hash(&self) -> BlockHash<Types> {
651        self.block_hash
652    }
653
654    pub fn payload_hash(&self) -> ADVZCommitment {
655        self.payload_hash
656    }
657
658    pub fn common(&self) -> &ADVZCommon {
659        &self.common
660    }
661}
662
663impl<Types: NodeType> HeightIndexed for ADVZCommonQueryData<Types> {
664    fn height(&self) -> u64 {
665        self.height
666    }
667}
668
669impl<Types: NodeType> HeightIndexed for (ADVZCommonQueryData<Types>, Option<VidShare>) {
670    fn height(&self) -> u64 {
671        self.0.height
672    }
673}
674
675#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
676#[serde(bound = "")]
677pub struct VidCommonQueryData<Types: NodeType> {
678    pub(crate) height: u64,
679    pub(crate) block_hash: BlockHash<Types>,
680    pub(crate) payload_hash: VidCommitment,
681    pub(crate) common: VidCommon,
682}
683
684impl<Types: NodeType> VidCommonQueryData<Types> {
685    pub fn new(header: Header<Types>, common: VidCommon) -> Self {
686        Self {
687            height: header.block_number(),
688            block_hash: header.commit(),
689            payload_hash: header.payload_commitment(),
690            common,
691        }
692    }
693
694    pub async fn genesis<HsVer: Versions>(
695        validated_state: &Types::ValidatedState,
696        instance_state: &Types::InstanceState,
697    ) -> Self {
698        let leaf = Leaf::<Types>::genesis::<HsVer>(validated_state, instance_state).await;
699        let payload = leaf.block_payload().unwrap();
700        let bytes = payload.encode();
701        let disperse = advz_scheme(GENESIS_VID_NUM_STORAGE_NODES)
702            .disperse(bytes)
703            .unwrap();
704
705        Self::new(leaf.block_header().clone(), VidCommon::V0(disperse.common))
706    }
707
708    pub fn block_hash(&self) -> BlockHash<Types> {
709        self.block_hash
710    }
711
712    pub fn payload_hash(&self) -> VidCommitment {
713        self.payload_hash
714    }
715
716    pub fn common(&self) -> &VidCommon {
717        &self.common
718    }
719}
720
721impl<Types: NodeType> HeightIndexed for VidCommonQueryData<Types> {
722    fn height(&self) -> u64 {
723        self.height
724    }
725}
726
727impl<Types: NodeType> HeightIndexed for (VidCommonQueryData<Types>, Option<VidShare>) {
728    fn height(&self) -> u64 {
729        self.0.height
730    }
731}
732
733#[derive(Clone, Debug, PartialEq, Eq)]
734pub struct BlockWithTransaction<Types: NodeType>
735where
736    Header<Types>: QueryableHeader<Types>,
737    Payload<Types>: QueryablePayload<Types>,
738{
739    pub block: BlockQueryData<Types>,
740    pub transaction: TransactionQueryData<Types>,
741    pub index: TransactionIndex<Types>,
742}
743
744impl<Types: NodeType> BlockWithTransaction<Types>
745where
746    Header<Types>: QueryableHeader<Types>,
747    Payload<Types>: QueryablePayload<Types>,
748{
749    pub fn with_hash(block: BlockQueryData<Types>, hash: TransactionHash<Types>) -> Option<Self> {
750        let (tx, i, index) = block.enumerate().enumerate().find_map(|(i, (index, tx))| {
751            if tx.commit() == hash {
752                Some((tx, i as u64, index))
753            } else {
754                None
755            }
756        })?;
757        let transaction = TransactionQueryData::new(tx, &block, &index, i)?;
758
759        Some(BlockWithTransaction {
760            block,
761            transaction,
762            index,
763        })
764    }
765}
766
767#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
768#[serde(bound = "")]
769pub struct TransactionQueryData<Types: NodeType>
770where
771    Header<Types>: QueryableHeader<Types>,
772    Payload<Types>: QueryablePayload<Types>,
773{
774    transaction: Transaction<Types>,
775    hash: TransactionHash<Types>,
776    index: u64,
777    block_hash: BlockHash<Types>,
778    block_height: u64,
779    namespace: NamespaceId<Types>,
780    pos_in_namespace: u32,
781}
782
783#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
784#[serde(bound = "")]
785pub struct TransactionWithProofQueryData<Types: NodeType>
786where
787    Header<Types>: QueryableHeader<Types>,
788    Payload<Types>: QueryablePayload<Types>,
789{
790    // Ideally we should just have a nested `TransactionQueryData` here, with `#[serde(flatten)]`
791    // (for backwards compatibility, the serialization has to keep the fields at the top level of
792    // the response struct). Unfortunately, `#[serde(flatten)]` causes panics when serializing with
793    // bincode, so we have to manually copy in the fields from `TransactionQueryData`.
794    //
795    // Also, for backwards compatibility, the `proof` field has to be in the middle of all the other
796    // fields, which is similarly incompatible with nesting all the other fields.
797    transaction: Transaction<Types>,
798    hash: TransactionHash<Types>,
799    index: u64,
800    proof: TransactionInclusionProof<Types>,
801    block_hash: BlockHash<Types>,
802    block_height: u64,
803    namespace: NamespaceId<Types>,
804    pos_in_namespace: u32,
805}
806
807impl<Types: NodeType> TransactionQueryData<Types>
808where
809    Header<Types>: QueryableHeader<Types>,
810    Payload<Types>: QueryablePayload<Types>,
811{
812    pub fn new(
813        transaction: Transaction<Types>,
814        block: &BlockQueryData<Types>,
815        i: &TransactionIndex<Types>,
816        index: u64,
817    ) -> Option<Self> {
818        Some(Self {
819            hash: transaction.commit(),
820            transaction,
821            index,
822            block_hash: block.hash(),
823            block_height: block.height(),
824            namespace: block.header().namespace_id(&i.ns_index)?,
825            pos_in_namespace: i.position,
826        })
827    }
828
829    /// The underlying transaction data.
830    pub fn transaction(&self) -> &Transaction<Types> {
831        &self.transaction
832    }
833
834    /// The hash of this transaction.
835    pub fn hash(&self) -> TransactionHash<Types> {
836        self.hash
837    }
838
839    /// The (0-based) position of this transaction within its block.
840    pub fn index(&self) -> u64 {
841        self.index
842    }
843
844    /// The height of the block containing this transaction.
845    pub fn block_height(&self) -> u64 {
846        self.block_height
847    }
848
849    /// The hash of the block containing this transaction.
850    pub fn block_hash(&self) -> BlockHash<Types> {
851        self.block_hash
852    }
853}
854
855impl<Types: NodeType> TransactionWithProofQueryData<Types>
856where
857    Header<Types>: QueryableHeader<Types>,
858    Payload<Types>: QueryablePayload<Types>,
859{
860    pub fn new(data: TransactionQueryData<Types>, proof: TransactionInclusionProof<Types>) -> Self {
861        Self {
862            proof,
863            transaction: data.transaction,
864            hash: data.hash,
865            index: data.index,
866            block_hash: data.block_hash,
867            block_height: data.block_height,
868            namespace: data.namespace,
869            pos_in_namespace: data.pos_in_namespace,
870        }
871    }
872
873    /// A proof of inclusion of this transaction in its block.
874    pub fn proof(&self) -> &TransactionInclusionProof<Types> {
875        &self.proof
876    }
877
878    /// The underlying transaction data.
879    pub fn transaction(&self) -> &Transaction<Types> {
880        &self.transaction
881    }
882
883    /// The hash of this transaction.
884    pub fn hash(&self) -> TransactionHash<Types> {
885        self.hash
886    }
887
888    /// The (0-based) position of this transaction within its block.
889    pub fn index(&self) -> u64 {
890        self.index
891    }
892
893    /// The height of the block containing this transaction.
894    pub fn block_height(&self) -> u64 {
895        self.block_height
896    }
897
898    /// The hash of the block containing this transaction.
899    pub fn block_hash(&self) -> BlockHash<Types> {
900        self.block_hash
901    }
902}
903
904pub(crate) fn payload_size<Types: NodeType>(payload: &Payload<Types>) -> u64 {
905    payload.encode().len() as u64
906}
907
908#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
909#[serde(bound = "")]
910pub struct BlockSummaryQueryData<Types: NodeType>
911where
912    Header<Types>: QueryableHeader<Types>,
913{
914    pub(crate) header: Header<Types>,
915    pub(crate) hash: BlockHash<Types>,
916    pub(crate) size: u64,
917    pub(crate) num_transactions: u64,
918    pub(crate) namespaces: NamespaceMap<Types>,
919}
920
921// Add some basic getters to the BlockSummaryQueryData type.
922impl<Types: NodeType> BlockSummaryQueryData<Types>
923where
924    Header<Types>: QueryableHeader<Types>,
925{
926    pub fn header(&self) -> &Header<Types> {
927        &self.header
928    }
929
930    pub fn hash(&self) -> BlockHash<Types> {
931        self.hash
932    }
933
934    pub fn size(&self) -> u64 {
935        self.size
936    }
937
938    pub fn num_transactions(&self) -> u64 {
939        self.num_transactions
940    }
941
942    pub fn namespaces(&self) -> &NamespaceMap<Types> {
943        &self.namespaces
944    }
945}
946
947impl<Types: NodeType> HeightIndexed for BlockSummaryQueryData<Types>
948where
949    Header<Types>: QueryableHeader<Types>,
950{
951    fn height(&self) -> u64 {
952        self.header.block_number()
953    }
954}
955
956#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
957#[serde(bound = "")]
958pub struct TransactionSummaryQueryData<Types: NodeType> {
959    pub(crate) hash: TransactionHash<Types>,
960    pub(crate) header: Header<Types>,
961    // We want a way to determine a summary for each rollup entry, without
962    // the data directly, but rather a summary of the data.
963    // For now, we'll roll with the `Payload` itself.
964    pub(crate) transaction: Transaction<Types>,
965}
966
967// Since BlockSummaryQueryData can be derived entirely from BlockQueryData, we
968// implement the From trait to allow for a seamless conversion using rust
969// contentions.
970impl<Types: NodeType> From<BlockQueryData<Types>> for BlockSummaryQueryData<Types>
971where
972    Header<Types>: QueryableHeader<Types>,
973    Payload<Types>: QueryablePayload<Types>,
974{
975    fn from(value: BlockQueryData<Types>) -> Self {
976        BlockSummaryQueryData {
977            namespaces: value.namespace_info(),
978            header: value.header,
979            hash: value.hash,
980            size: value.size,
981            num_transactions: value.num_transactions,
982        }
983    }
984}
985
986#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Deserialize, Serialize)]
987pub struct NamespaceInfo {
988    pub num_transactions: u64,
989    pub size: u64,
990}
991
992pub type NamespaceMap<Types> = HashMap<NamespaceId<Types>, NamespaceInfo>;
993
994/// A summary of a payload without all the data.
995///
996/// This type is useful when you only want information about a payload, such as its size or
997/// transaction count, but you don't want to load the entire payload, which might be very large.
998#[derive(Clone, Debug, PartialEq, Eq)]
999pub struct PayloadMetadata<Types>
1000where
1001    Types: NodeType,
1002    Header<Types>: QueryableHeader<Types>,
1003{
1004    pub height: u64,
1005    pub block_hash: BlockHash<Types>,
1006    pub hash: VidCommitment,
1007    pub size: u64,
1008    pub num_transactions: u64,
1009    pub namespaces: NamespaceMap<Types>,
1010}
1011
1012impl<Types> HeightIndexed for PayloadMetadata<Types>
1013where
1014    Types: NodeType,
1015    Header<Types>: QueryableHeader<Types>,
1016{
1017    fn height(&self) -> u64 {
1018        self.height
1019    }
1020}
1021
1022impl<Types> From<BlockQueryData<Types>> for PayloadMetadata<Types>
1023where
1024    Types: NodeType,
1025    Header<Types>: QueryableHeader<Types>,
1026    Payload<Types>: QueryablePayload<Types>,
1027{
1028    fn from(block: BlockQueryData<Types>) -> Self {
1029        Self {
1030            height: block.height(),
1031            block_hash: block.hash(),
1032            hash: block.payload_hash(),
1033            size: block.size(),
1034            num_transactions: block.num_transactions(),
1035            namespaces: block.namespace_info(),
1036        }
1037    }
1038}
1039
1040/// A summary of a VID payload without all the data.
1041///
1042/// This is primarily useful when you want to check if a VID object exists, but not load the whole
1043/// object.
1044#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1045pub struct VidCommonMetadata<Types>
1046where
1047    Types: NodeType,
1048{
1049    pub height: u64,
1050    pub block_hash: BlockHash<Types>,
1051    pub payload_hash: VidCommitment,
1052}
1053
1054impl<Types> HeightIndexed for VidCommonMetadata<Types>
1055where
1056    Types: NodeType,
1057{
1058    fn height(&self) -> u64 {
1059        self.height
1060    }
1061}
1062
1063impl<Types> From<VidCommonQueryData<Types>> for VidCommonMetadata<Types>
1064where
1065    Types: NodeType,
1066{
1067    fn from(common: VidCommonQueryData<Types>) -> Self {
1068        Self {
1069            height: common.height(),
1070            block_hash: common.block_hash(),
1071            payload_hash: common.payload_hash(),
1072        }
1073    }
1074}
1075
1076/// A wrapper around `LightClientStateUpdateCertificateV2`.
1077///
1078/// The V2 certificate includes additional fields compared to earlier versions:
1079/// - Light client v3 signatures
1080/// - `auth_root` — used by the reward claim contract to verify that its
1081///   calculated `auth_root` matches the one in the Light Client contract.
1082///
1083/// This struct is returned by the `state-cert-v2` API.
1084#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, From)]
1085#[serde(bound = "")]
1086pub struct StateCertQueryDataV2<Types: NodeType>(pub LightClientStateUpdateCertificateV2<Types>);
1087
1088#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, Eq)]
1089pub struct Limits {
1090    pub small_object_range_limit: usize,
1091    pub large_object_range_limit: usize,
1092}
1093
1094impl<Types: NodeType> HeightIndexed for StateCertQueryDataV2<Types> {
1095    fn height(&self) -> u64 {
1096        self.0.light_client_state.block_height
1097    }
1098}
1099
1100#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, From)]
1101#[serde(bound = "")]
1102pub struct StateCertQueryDataV1<Types: NodeType>(pub LightClientStateUpdateCertificateV1<Types>);
1103
1104impl<Types> From<StateCertQueryDataV2<Types>> for StateCertQueryDataV1<Types>
1105where
1106    Types: NodeType,
1107{
1108    fn from(cert: StateCertQueryDataV2<Types>) -> Self {
1109        Self(cert.0.into())
1110    }
1111}