Skip to main content

light_client/
provider.rs

1//! Functionality for using the [`LightClient`] as a query service fetching [`Provider`].
2
3use async_trait::async_trait;
4use espresso_types::{Certificate2, Payload, SeqTypes};
5use hotshot_query_service::{
6    availability::{BlockQueryData, LeafId, LeafQueryData, VidCommonQueryData},
7    fetching::{
8        NonEmptyRange, Provider,
9        request::{
10            BlockRangeRequest, BlockRangesRequest, BlockRangesResponse, Certificate2Request,
11            LeafRangeRequest, LeafRangesRequest, LeafRequest, PayloadRequest,
12            VidCommonRangeRequest, VidCommonRangesRequest, VidCommonRequest,
13        },
14    },
15    node::BlockId,
16};
17use hotshot_types::data::VidCommon;
18
19use crate::{LightClient, client::Client, storage::Storage};
20
21#[async_trait]
22impl<P, S> Provider<SeqTypes, LeafRequest> for LightClient<P, S>
23where
24    P: Storage,
25    S: Client,
26{
27    async fn fetch(&self, req: LeafRequest) -> Option<LeafQueryData<SeqTypes>> {
28        match self.fetch_leaf(LeafId::Number(req.height as usize)).await {
29            Ok(leaf) => Some(leaf),
30            Err(err) => {
31                tracing::warn!(?req, "failed to fetch leaf: {err:#}");
32                None
33            },
34        }
35    }
36}
37
38#[async_trait]
39impl<P, S> Provider<SeqTypes, PayloadRequest> for LightClient<P, S>
40where
41    P: Storage,
42    S: Client,
43{
44    async fn fetch(&self, req: PayloadRequest) -> Option<Payload> {
45        match self.fetch_payload(BlockId::PayloadHash(req.0)).await {
46            Ok(payload) => Some(payload.data),
47            Err(err) => {
48                tracing::warn!(?req, "failed to fetch payload: {err:#}");
49                None
50            },
51        }
52    }
53}
54
55#[async_trait]
56impl<P, S> Provider<SeqTypes, VidCommonRequest> for LightClient<P, S>
57where
58    P: Storage,
59    S: Client,
60{
61    async fn fetch(&self, req: VidCommonRequest) -> Option<VidCommon> {
62        match self.fetch_vid_common(BlockId::PayloadHash(req.0)).await {
63            Ok(vid) => Some(vid.common),
64            Err(err) => {
65                tracing::warn!(?req, "failed to fetch VID common: {err:#}");
66                None
67            },
68        }
69    }
70}
71
72#[async_trait]
73impl<P, S> Provider<SeqTypes, LeafRangeRequest> for LightClient<P, S>
74where
75    P: Storage,
76    S: Client,
77{
78    async fn fetch(&self, req: LeafRangeRequest) -> Option<NonEmptyRange<LeafQueryData<SeqTypes>>> {
79        let leaves = match self
80            .fetch_leaves_in_range(req.start as usize, req.end as usize)
81            .await
82        {
83            Ok(leaves) => leaves,
84            Err(err) => {
85                tracing::warn!(?req, "failed to fetch leaf: {err:#}");
86                return None;
87            },
88        };
89        match leaves.try_into() {
90            Ok(leaves) => Some(leaves),
91            Err(err) => {
92                tracing::warn!(?req, "received invalid leaf range: {err:#}");
93                None
94            },
95        }
96    }
97}
98
99#[async_trait]
100impl<P, S> Provider<SeqTypes, BlockRangeRequest> for LightClient<P, S>
101where
102    P: Storage,
103    S: Client,
104{
105    async fn fetch(
106        &self,
107        req: BlockRangeRequest,
108    ) -> Option<NonEmptyRange<BlockQueryData<SeqTypes>>> {
109        let blocks = match self
110            .fetch_blocks_in_range(req.start as usize, req.end as usize)
111            .await
112        {
113            Ok(blocks) => blocks,
114            Err(err) => {
115                tracing::warn!(?req, "failed to fetch block range: {err:#}");
116                return None;
117            },
118        };
119        match blocks.try_into() {
120            Ok(blocks) => Some(blocks),
121            Err(err) => {
122                tracing::warn!(?req, "received invalid block range: {err:#}");
123                None
124            },
125        }
126    }
127}
128
129#[async_trait]
130impl<P, S> Provider<SeqTypes, VidCommonRangeRequest> for LightClient<P, S>
131where
132    P: Storage,
133    S: Client,
134{
135    async fn fetch(
136        &self,
137        req: VidCommonRangeRequest,
138    ) -> Option<NonEmptyRange<VidCommonQueryData<SeqTypes>>> {
139        let vid_common = match self
140            .fetch_vid_common_in_range(req.start as usize, req.end as usize)
141            .await
142        {
143            Ok(vid_common) => vid_common,
144            Err(err) => {
145                tracing::warn!(?req, "failed to fetch VID common range: {err:#}");
146                return None;
147            },
148        };
149        match vid_common.try_into() {
150            Ok(vid_common) => Some(vid_common),
151            Err(err) => {
152                tracing::warn!(?req, "received invalid VID common range: {err:#}");
153                None
154            },
155        }
156    }
157}
158
159#[async_trait]
160impl<P, S> Provider<SeqTypes, Certificate2Request> for LightClient<P, S>
161where
162    P: Storage,
163    S: Client,
164{
165    async fn fetch(&self, req: Certificate2Request) -> Option<Option<Certificate2<SeqTypes>>> {
166        match self.fetch_certificate2(req.height).await {
167            Ok(cert2) => Some(cert2),
168            Err(err) => {
169                tracing::info!(?req, %err, "failed to fetch cert2, will retry");
170                None
171            },
172        }
173    }
174}
175
176// Leaves are fetched in one ranges request and verified per contiguous run, since each run needs one
177// leaf whose finality is proven and the rest chain to it.
178//
179// Blocks and VID arrive as payload proofs, fetched in one ranges request too. Each proof verifies
180// the block and its VID common together, so the block ranges request returns both and the VID pass only has
181// to cover heights where the block was already present.
182#[async_trait]
183impl<P, S> Provider<SeqTypes, LeafRangesRequest> for LightClient<P, S>
184where
185    P: Storage,
186    S: Client,
187{
188    async fn fetch(&self, req: LeafRangesRequest) -> Option<Vec<LeafQueryData<SeqTypes>>> {
189        match self.fetch_leaves_for_ranges(&req.0).await {
190            Ok(leaves) => Some(leaves),
191            Err(err) => {
192                tracing::warn!(?req, "failed to fetch leaf ranges: {err:#}");
193                None
194            },
195        }
196    }
197}
198
199#[async_trait]
200impl<P, S> Provider<SeqTypes, BlockRangesRequest> for LightClient<P, S>
201where
202    P: Storage,
203    S: Client,
204{
205    async fn fetch(&self, req: BlockRangesRequest) -> Option<BlockRangesResponse<SeqTypes>> {
206        // One range is a range fetch, whose endpoints are cacheable GETs.
207        let fetched = match req.0.as_slice() {
208            [range] => {
209                self.fetch_blocks_and_vid_common_in_range(range.start as usize, range.end as usize)
210                    .await
211            },
212            _ => self.fetch_blocks_and_vid_common_for_ranges(&req.0).await,
213        };
214        match fetched {
215            Ok(fetched) => {
216                let (blocks, vid_common) = fetched.into_iter().unzip();
217                Some(BlockRangesResponse { blocks, vid_common })
218            },
219            Err(err) => {
220                tracing::warn!(?req, "failed to fetch block ranges: {err:#}");
221                None
222            },
223        }
224    }
225}
226
227#[async_trait]
228impl<P, S> Provider<SeqTypes, VidCommonRangesRequest> for LightClient<P, S>
229where
230    P: Storage,
231    S: Client,
232{
233    async fn fetch(
234        &self,
235        req: VidCommonRangesRequest,
236    ) -> Option<Vec<VidCommonQueryData<SeqTypes>>> {
237        let fetched = match req.0.as_slice() {
238            [range] => {
239                self.fetch_blocks_and_vid_common_in_range(range.start as usize, range.end as usize)
240                    .await
241            },
242            _ => self.fetch_blocks_and_vid_common_for_ranges(&req.0).await,
243        };
244        match fetched {
245            Ok(fetched) => Some(fetched.into_iter().map(|(_, common)| common).collect()),
246            Err(err) => {
247                tracing::warn!(?req, "failed to fetch VID common ranges: {err:#}");
248                None
249            },
250        }
251    }
252}
253
254#[cfg(test)]
255mod test {
256    use hotshot_query_service::types::HeightIndexed;
257
258    use super::*;
259    use crate::{storage::SqlStorage, testing::TestClient};
260
261    #[tokio::test]
262    #[test_log::test]
263    async fn test_block_ranges_carry_vid_common() {
264        let client = TestClient::default();
265        let lc = LightClient::from_genesis(
266            SqlStorage::default().await.unwrap(),
267            client.clone(),
268            client.genesis().await,
269        );
270        for height in 1..8 {
271            client.payload(height).await;
272        }
273
274        let batch = Provider::<SeqTypes, BlockRangesRequest>::fetch(
275            &lc,
276            BlockRangesRequest(vec![1..3, 5..8]),
277        )
278        .await
279        .unwrap();
280
281        assert_eq!(
282            batch
283                .blocks
284                .iter()
285                .map(|block| block.height())
286                .collect::<Vec<_>>(),
287            [1, 2, 5, 6, 7]
288        );
289        assert_eq!(batch.vid_common.len(), batch.blocks.len());
290        for (block, common) in batch.blocks.iter().zip(&batch.vid_common) {
291            assert_eq!(common.height(), block.height());
292            assert_eq!(common.payload_hash(), block.payload_hash());
293        }
294    }
295}