1use std::{
4 collections::{BTreeMap, HashMap},
5 future::Future,
6 ops::Range,
7 sync::Arc,
8};
9
10use anyhow::{Context, Result, bail, ensure};
11use async_lock::RwLock;
12use committable::Committable;
13use espresso_types::{
14 Certificate2, ChainId, DECAF_CHAIN_ID, DrbAndHeaderUpgradeVersion, Header, Leaf2, NamespaceId,
15 PubKey, SeqTypes, StakeTableState, Transaction, ValidatorSet,
16};
17use futures::future::try_join;
18use hotshot_query_service_types::{
19 HeightIndexed,
20 availability::{BlockQueryData, LeafId, LeafQueryData, PayloadQueryData, VidCommonQueryData},
21 node::BlockId,
22};
23use hotshot_types::{data::EpochNumber, stake_table::StakeTableEntry, utils::root_block_in_epoch};
24use serde::{Deserialize, Serialize};
25use vbs::version::{StaticVersionType, Version};
26
27use crate::{
28 client::Client,
29 consensus::{
30 leaf::LeafProofHint,
31 quorum::{Quorum, StakeTable, StakeTablePair, StakeTableQuorum},
32 },
33 storage::{LeafRequest, Storage},
34};
35
36#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
44#[cfg_attr(
45 feature = "rlp",
46 derive(alloy_rlp::RlpEncodable, alloy_rlp::RlpDecodable)
47)]
48pub struct Genesis {
49 pub epoch_height: u64,
51
52 pub first_epoch_with_dynamic_stake_table: EpochNumber,
55
56 pub stake_table: Vec<StakeTableEntry<PubKey>>,
58
59 pub chain_id: ChainId,
61}
62
63const MAX_FINALIZED_HINT_DISTANCE: u64 = 500;
66
67#[derive(Clone, Copy, Debug, PartialEq, Eq)]
69enum CacheLeaves {
70 Yes,
71 No,
72}
73
74#[derive(Clone, Debug, Serialize, Deserialize)]
75#[cfg_attr(feature = "clap", derive(clap::Parser))]
76pub struct LightClientOptions {
77 #[cfg_attr(
82 feature = "clap",
83 clap(
84 long = "light-client-num-stake-tables",
85 env = "LIGHT_CLIENT_NUM_STAKE_TABLES",
86 default_value = "100",
87 )
88 )]
89 pub num_stake_tables_in_memory: usize,
90}
91
92impl Default for LightClientOptions {
93 fn default() -> Self {
94 Self {
95 num_stake_tables_in_memory: 100,
96 }
97 }
98}
99
100#[derive(Debug)]
108pub struct LightClient<P, S> {
109 db: P,
110 server: S,
111 opt: LightClientOptions,
112
113 chain_id: ChainId,
114 epoch_height: u64,
115 first_epoch_with_dynamic_stake_table: EpochNumber,
116 genesis_stake_table: Arc<StakeTable>,
117
118 stake_tables: RwLock<BTreeMap<EpochNumber, Arc<StakeTable>>>,
120}
121
122impl<P, S> LightClient<P, S> {
123 pub fn into_storage(self) -> P {
124 self.db
125 }
126
127 pub fn storage(&self) -> &P {
128 &self.db
129 }
130
131 pub fn into_inner(self) -> S {
132 self.server
133 }
134
135 pub fn inner(&self) -> &S {
136 &self.server
137 }
138}
139
140impl<P, S> LightClient<P, S>
141where
142 P: Storage,
143 S: Client,
144{
145 pub fn from_genesis(db: P, server: S, genesis: Genesis) -> Self {
152 Self::from_genesis_with_options(db, server, genesis, Default::default())
153 }
154
155 pub fn from_genesis_with_options(
162 db: P,
163 server: S,
164 genesis: Genesis,
165 opt: LightClientOptions,
166 ) -> Self {
167 Self {
168 db,
169 server,
170 opt,
171 chain_id: genesis.chain_id,
172 epoch_height: genesis.epoch_height,
173 genesis_stake_table: Arc::new(genesis.stake_table.into()),
174 first_epoch_with_dynamic_stake_table: genesis.first_epoch_with_dynamic_stake_table,
175 stake_tables: Default::default(),
176 }
177 }
178
179 pub fn genesis(&self) -> Genesis {
180 Genesis {
181 epoch_height: self.epoch_height,
182 first_epoch_with_dynamic_stake_table: self.first_epoch_with_dynamic_stake_table,
183 stake_table: (*self.genesis_stake_table).clone().into(),
184 chain_id: self.chain_id,
185 }
186 }
187
188 pub async fn block_height(&self) -> Result<u64> {
197 let latest_known = self.db.block_height().await?;
198 let latest_from_server = self.server.block_height().await?;
199 if latest_from_server > latest_known {
200 if let Err(err) = self
203 .fetch_leaf(LeafId::Number(latest_from_server as usize - 1))
204 .await
205 {
206 tracing::warn!(
207 latest_known,
208 latest_from_server,
209 "failed to verify block height claimed by server: {err:#}"
210 );
211 return Ok(latest_known);
212 } else {
213 return Ok(latest_from_server);
214 }
215 }
216 Ok(latest_known)
217 }
218
219 pub async fn fetch_leaf(&self, id: LeafId<SeqTypes>) -> Result<LeafQueryData<SeqTypes>> {
221 self.fetch_leaf_with_quorum(id, |epoch| {
222 StakeTableQuorum::new((epoch, self), self.epoch_height)
223 })
224 .await
225 }
226
227 async fn fetch_leaf_with_quorum<Q>(
228 &self,
229 id: LeafId<SeqTypes>,
230 quorum: impl Send + FnOnce(EpochNumber) -> Q,
231 ) -> Result<LeafQueryData<SeqTypes>>
232 where
233 Q: Send + Quorum,
234 {
235 let upper_bound = self.db.leaf_upper_bound(id).await?;
236 let known_finalized = if let Some(upper_bound) = upper_bound {
237 if leaf_matches_id(&upper_bound, id) {
238 return Ok(upper_bound);
239 }
240
241 Some(upper_bound)
242 } else {
243 None
244 };
245 let known_finalized = known_finalized.filter(|anchor| match id {
246 LeafId::Number(n) => {
247 anchor.height().saturating_sub(n as u64) <= MAX_FINALIZED_HINT_DISTANCE
248 },
249 LeafId::Hash(_) => true,
251 });
252 let known_finalized = known_finalized.as_ref().map(LeafQueryData::leaf);
253 self.fetch_leaf_from_server(id, known_finalized, quorum)
254 .await
255 }
256
257 pub async fn fetch_leaves_in_range(
259 &self,
260 start_height: usize,
261 end_height: usize,
262 ) -> Result<Vec<LeafQueryData<SeqTypes>>> {
263 self.fetch_leaves_in_range_with(start_height, end_height, CacheLeaves::Yes)
264 .await
265 }
266
267 pub async fn fetch_leaves_in_range_uncached(
274 &self,
275 start_height: usize,
276 end_height: usize,
277 ) -> Result<Vec<LeafQueryData<SeqTypes>>> {
278 self.fetch_leaves_in_range_with(start_height, end_height, CacheLeaves::No)
279 .await
280 }
281
282 async fn fetch_leaves_in_range_with(
283 &self,
284 start_height: usize,
285 end_height: usize,
286 cache: CacheLeaves,
287 ) -> Result<Vec<LeafQueryData<SeqTypes>>> {
288 ensure!(
289 start_height < end_height,
290 "invalid range: start must be < end"
291 );
292
293 let leaves = self
295 .db
296 .get_leaves_in_range(start_height as u32, end_height as u32)
297 .await?;
298
299 if leaves.len() == end_height - start_height {
300 return Ok(leaves);
302 }
303
304 let known_end_leaf = self.fetch_leaf(LeafId::Number(end_height - 1)).await?;
306 if end_height - start_height == 1 {
307 return Ok(vec![known_end_leaf]);
308 }
309
310 let leaves = self
313 .fetch_leaves_in_range_from_server(start_height, end_height - 1, &known_end_leaf, cache)
314 .await
315 .map(|mut leaves| {
317 leaves.push(known_end_leaf);
318 leaves
319 })?;
320 Ok(leaves)
321 }
322
323 pub async fn fetch_headers_in_range(
325 &self,
326 start_height: usize,
327 end_height: usize,
328 ) -> Result<Vec<Header>> {
329 ensure!(
330 start_height < end_height,
331 "invalid range: start must be < end"
332 );
333
334 let leaves = self.fetch_leaves_in_range(start_height, end_height).await?;
336 Ok(leaves
337 .into_iter()
338 .map(|leaf| leaf.header().clone())
339 .collect())
340 }
341
342 pub async fn fetch_certificate2(&self, height: u64) -> Result<Option<Certificate2<SeqTypes>>> {
346 let Some(cert2) = self.server.cert2(height).await? else {
347 return Ok(None);
348 };
349 let header = self
350 .fetch_header(BlockId::Number(height as usize))
351 .await
352 .context("fetching header to determine cert2 version")?;
353 self.verify_certificate2(cert2, &header).await.map(Some)
354 }
355
356 pub async fn fetch_certificate2_for_header(
359 &self,
360 header: &Header,
361 ) -> Result<Option<Certificate2<SeqTypes>>> {
362 let Some(cert2) = self.server.cert2(header.height()).await? else {
363 return Ok(None);
364 };
365 self.verify_certificate2(cert2, header).await.map(Some)
366 }
367
368 async fn verify_certificate2(
369 &self,
370 cert2: Certificate2<SeqTypes>,
371 header: &Header,
372 ) -> Result<Certificate2<SeqTypes>> {
373 ensure!(
374 cert2.data.block_number == header.height(),
375 "cert2 block number {} does not match requested height {}",
376 cert2.data.block_number,
377 header.height(),
378 );
379 let quorum = StakeTableQuorum::new((cert2.data.epoch, self), self.epoch_height);
380 quorum
381 .verify_cert2(&cert2, header.version())
382 .await
383 .context("verifying cert2 signature")?;
384 Ok(cert2)
385 }
386
387 async fn fetch_leaves_in_range_from_server(
391 &self,
392 start_height: usize,
393 end_height: usize,
394 known_finalized: &LeafQueryData<SeqTypes>,
395 cache: CacheLeaves,
396 ) -> Result<Vec<LeafQueryData<SeqTypes>>> {
397 let leaves = self
399 .server
400 .get_leaves_in_range(start_height, end_height)
403 .await?;
404
405 ensure!(
406 leaves.len() == end_height.saturating_sub(start_height),
407 "server returned {} leaves for range [{}, {})",
408 leaves.len(),
409 start_height,
410 end_height
411 );
412
413 self.verify_leaf_chain(&leaves, known_finalized, cache)
414 .await?;
415 Ok(leaves)
416 }
417
418 async fn verify_leaf_chain(
424 &self,
425 leaves: &[LeafQueryData<SeqTypes>],
426 known_finalized: &LeafQueryData<SeqTypes>,
427 cache: CacheLeaves,
428 ) -> Result<()> {
429 let mut expected_parent = known_finalized.leaf().parent_commitment();
430 for leaf in leaves.iter().rev() {
431 let leaf_hash = leaf.hash();
432 ensure!(
433 leaf_hash == expected_parent,
434 "leaf hash mismatch: expected parent hash {:?}, got leaf hash {:?}",
435 expected_parent,
436 leaf_hash
437 );
438 expected_parent = leaf.leaf().parent_commitment();
439 }
440
441 if cache == CacheLeaves::No {
442 return Ok(());
443 }
444 for leaf in leaves {
446 if let Err(err) = self.db.insert_leaf(leaf.clone()).await {
447 tracing::warn!(
448 "failed to cache leaf at height {}: {:#?}",
449 leaf.height(),
450 err
451 )
452 }
453 }
454
455 Ok(())
456 }
457
458 pub async fn fetch_leaves_for_ranges(
465 &self,
466 ranges: &[Range<u64>],
467 ) -> Result<Vec<LeafQueryData<SeqTypes>>> {
468 ensure!(
469 ranges.iter().all(|range| range.start < range.end)
470 && ranges.is_sorted_by(|a, b| a.end <= b.start),
471 "each range must be non-empty, and the ranges ascending and disjoint"
472 );
473
474 if let (Some(lo), Some(hi)) = (
479 ranges.iter().map(|range| range.start).min(),
480 ranges.iter().map(|range| range.end).max(),
481 ) {
482 let wanted: u64 = ranges.iter().map(|range| range.end - range.start).sum();
483 let span = hi - lo;
484 let limit = 500;
488 if span <= limit && span <= wanted * 4 {
489 match self.fetch_leaves_in_range(lo as usize, hi as usize).await {
490 Ok(leaves) => {
491 return Ok(leaves
492 .into_iter()
493 .filter(|leaf| {
494 ranges.iter().any(|range| range.contains(&leaf.height()))
495 })
496 .collect());
497 },
498 Err(err) => {
502 tracing::info!(
503 err = %format_args!("{err:#}"),
504 lo,
505 hi,
506 "span fetch failed, fetching each run instead"
507 );
508 },
509 }
510 }
511 }
512
513 let bulk = ranges
516 .iter()
517 .filter(|range| range.end - range.start > 1)
518 .map(|range| range.start..range.end - 1)
519 .collect::<Vec<_>>();
520 let mut fetched: HashMap<u64, LeafQueryData<SeqTypes>> = HashMap::new();
521 if !bulk.is_empty() {
522 match self.server.get_leaves_for_ranges(&bulk).await {
523 Ok(leaves) => {
524 for leaf in leaves {
525 fetched.insert(leaf.height(), leaf);
526 }
527 },
528 Err(err) => {
529 tracing::info!(
533 err = %format_args!("{err:#}"),
534 "leaf ranges fetch failed, fetching each range instead"
535 );
536 let mut leaves = vec![];
537 for range in ranges {
538 leaves.extend(
539 self.fetch_leaves_in_range(range.start as usize, range.end as usize)
540 .await?,
541 );
542 }
543 return Ok(leaves);
544 },
545 }
546 }
547
548 let mut leaves = vec![];
549 for range in ranges {
550 let anchor = self
552 .fetch_leaf(LeafId::Number(range.end as usize - 1))
553 .await?;
554
555 let mut run = vec![];
556 for height in range.start..range.end - 1 {
557 let leaf = match fetched.remove(&height) {
558 Some(leaf) => leaf,
559 None => self.fetch_leaf(LeafId::Number(height as usize)).await?,
560 };
561 run.push(leaf);
562 }
563 self.verify_leaf_chain(&run, &anchor, CacheLeaves::Yes)
564 .await?;
565 leaves.extend(run);
566 leaves.push(anchor);
567 }
568
569 Ok(leaves)
570 }
571
572 pub async fn fetch_header(&self, id: BlockId<SeqTypes>) -> Result<Header> {
574 self.fetch_header_with_quorum(id, |epoch| {
575 StakeTableQuorum::new((epoch, self), self.epoch_height)
576 })
577 .await
578 }
579
580 async fn fetch_header_with_quorum<Q>(
581 &self,
582 id: BlockId<SeqTypes>,
583 quorum: impl Send + FnOnce(EpochNumber) -> Q,
584 ) -> Result<Header>
585 where
586 Q: Send + Quorum,
587 {
588 let Some(upper_bound) = self.db.leaf_upper_bound(id).await? else {
589 let leaf = self.fetch_leaf_from_server(id, None, quorum).await?;
593 return Ok(leaf.header().clone());
594 };
595
596 if leaf_matches_id(&upper_bound, id) {
599 return Ok(upper_bound.header().clone());
600 }
601
602 let proof = match self.server.header_proof(upper_bound.height(), id).await {
606 Ok(proof) => proof,
607 Err(err) => {
609 tracing::info!(
610 %id,
611 root = upper_bound.height(),
612 err = %format_args!("{err:#}"),
613 "header proof fetch failed, fetching a leaf proof instead"
614 );
615 let known_finalized = match id {
616 BlockId::Number(n) => {
617 upper_bound.height().saturating_sub(n as u64) <= MAX_FINALIZED_HINT_DISTANCE
618 },
619 BlockId::Hash(_) | BlockId::PayloadHash(_) => true,
621 }
622 .then(|| upper_bound.leaf());
623 let leaf = self
624 .fetch_leaf_from_server(id, known_finalized, quorum)
625 .await
626 .with_context(|| format!("header proof also failed: {err:#}"))?;
627 return Ok(leaf.header().clone());
628 },
629 };
630 let header = proof
631 .verify(upper_bound.header().block_merkle_tree_root())
632 .context("invalid header proof")?;
633
634 ensure!(
637 header_matches_id(&header, id),
638 "server returned a valid header proof for the wrong header (requested header {id}, \
639 got header {} with hash {})",
640 header.height(),
641 header.commit(),
642 );
643
644 Ok(header)
645 }
646
647 pub async fn fetch_payload(&self, id: BlockId<SeqTypes>) -> Result<PayloadQueryData<SeqTypes>> {
649 Ok(self.fetch_block(id).await?.into())
650 }
651
652 pub async fn fetch_payload_for_header(
654 &self,
655 header: Header,
656 ) -> Result<PayloadQueryData<SeqTypes>> {
657 Ok(self.fetch_block_for_header(header).await?.into())
658 }
659
660 pub async fn fetch_block(&self, id: BlockId<SeqTypes>) -> Result<BlockQueryData<SeqTypes>> {
662 Ok(self.fetch_block_and_vid_common(id).await?.0)
663 }
664
665 pub async fn fetch_block_for_header(&self, header: Header) -> Result<BlockQueryData<SeqTypes>> {
667 Ok(self.fetch_block_and_vid_common_for_header(header).await?.0)
668 }
669
670 pub async fn fetch_vid_common(
672 &self,
673 id: BlockId<SeqTypes>,
674 ) -> Result<VidCommonQueryData<SeqTypes>> {
675 Ok(self.fetch_block_and_vid_common(id).await?.1)
676 }
677
678 pub async fn fetch_vid_common_for_header(
680 &self,
681 header: Header,
682 ) -> Result<VidCommonQueryData<SeqTypes>> {
683 Ok(self.fetch_block_and_vid_common_for_header(header).await?.1)
684 }
685
686 pub async fn fetch_block_and_vid_common(
688 &self,
689 id: BlockId<SeqTypes>,
690 ) -> Result<(BlockQueryData<SeqTypes>, VidCommonQueryData<SeqTypes>)> {
691 let header = self.fetch_header(id).await?;
692 self.fetch_block_and_vid_common_for_header(header).await
693 }
694
695 pub async fn fetch_block_and_vid_common_for_header(
697 &self,
698 header: Header,
699 ) -> Result<(BlockQueryData<SeqTypes>, VidCommonQueryData<SeqTypes>)> {
700 let proof = self.server.payload_proof(header.height()).await?;
701 let (payload, vid_common) = proof.verify_with_vid_common(&header)?;
702 Ok((
703 BlockQueryData::new(header.clone(), payload),
704 VidCommonQueryData::new(header, vid_common),
705 ))
706 }
707
708 pub async fn fetch_blocks_in_range(
710 &self,
711 start: usize,
712 end: usize,
713 ) -> Result<Vec<BlockQueryData<SeqTypes>>> {
714 let res = self
715 .fetch_blocks_and_vid_common_in_range(start, end)
716 .await?;
717 Ok(res.into_iter().map(|(block, _)| block).collect())
718 }
719
720 pub async fn fetch_vid_common_in_range(
722 &self,
723 start: usize,
724 end: usize,
725 ) -> Result<Vec<VidCommonQueryData<SeqTypes>>> {
726 let res = self
727 .fetch_blocks_and_vid_common_in_range(start, end)
728 .await?;
729 Ok(res.into_iter().map(|(_, vid)| vid).collect())
730 }
731
732 pub async fn fetch_blocks_and_vid_common_in_range(
734 &self,
735 start: usize,
736 end: usize,
737 ) -> Result<Vec<(BlockQueryData<SeqTypes>, VidCommonQueryData<SeqTypes>)>> {
738 let (headers, proofs) = try_join(
739 self.fetch_headers_in_range(start, end),
740 self.server
741 .payload_proofs_in_range(start as u64, end as u64),
742 )
743 .await?;
744 ensure!(
745 headers.len() == proofs.len(),
746 "server returned wrong number of payload proofs for range {start}..{end} ({})",
747 proofs.len(),
748 );
749 headers
750 .into_iter()
751 .zip(proofs)
752 .map(|(header, proof)| {
753 let (payload, vid_common) = proof.verify_with_vid_common(&header)?;
754 Ok((
755 BlockQueryData::new(header.clone(), payload),
756 VidCommonQueryData::new(header, vid_common),
757 ))
758 })
759 .collect()
760 }
761
762 pub async fn fetch_blocks_and_vid_common_for_ranges(
767 &self,
768 ranges: &[Range<u64>],
769 ) -> Result<Vec<(BlockQueryData<SeqTypes>, VidCommonQueryData<SeqTypes>)>> {
770 let proofs = async {
771 match self.server.payload_proofs_for_ranges(ranges).await {
772 Ok(proofs) => Ok(proofs),
773 Err(err) => {
774 tracing::info!(
776 err = %format_args!("{err:#}"),
777 "payload proof ranges fetch failed, fetching each range instead"
778 );
779 let mut proofs = vec![];
780 for range in ranges {
781 proofs.extend(
782 self.server
783 .payload_proofs_in_range(range.start, range.end)
784 .await?,
785 );
786 }
787 Ok(proofs)
788 },
789 }
790 };
791 let (leaves, proofs) = try_join(self.fetch_leaves_for_ranges(ranges), proofs).await?;
792 ensure!(
793 leaves.len() == proofs.len(),
794 "server returned {} payload proofs for {} heights",
795 proofs.len(),
796 leaves.len(),
797 );
798
799 leaves
800 .into_iter()
801 .zip(proofs)
802 .map(|(leaf, proof)| {
803 let header = leaf.header().clone();
804 let (payload, vid_common) = proof.verify_with_vid_common(&header)?;
805 Ok((
806 BlockQueryData::new(header.clone(), payload),
807 VidCommonQueryData::new(header, vid_common),
808 ))
809 })
810 .collect()
811 }
812
813 pub async fn fetch_namespace(
815 &self,
816 id: BlockId<SeqTypes>,
817 namespace: NamespaceId,
818 ) -> Result<Vec<Transaction>> {
819 let header = self.fetch_header(id).await?;
820 self.fetch_namespace_for_header(&header, namespace).await
821 }
822
823 pub async fn fetch_namespace_for_header(
825 &self,
826 header: &Header,
827 namespace: NamespaceId,
828 ) -> Result<Vec<Transaction>> {
829 let proof = self
830 .server
831 .namespace_proof(header.height(), namespace)
832 .await?;
833 proof.verify(header, namespace)
834 }
835
836 pub async fn fetch_namespaces_in_range(
839 &self,
840 start_height: usize,
841 end_height: usize,
842 namespace: NamespaceId,
843 ) -> Result<Vec<Vec<Transaction>>> {
844 let headers = self
845 .fetch_headers_in_range(start_height, end_height)
846 .await?;
847 let proofs = self
848 .server
849 .namespace_proofs_in_range(start_height as u64, end_height as u64, namespace)
850 .await?;
851 ensure!(
852 proofs.len() == headers.len(),
853 "server returned wrong number of namespace proofs (expected {}, got {})",
854 headers.len(),
855 proofs.len()
856 );
857 proofs
858 .into_iter()
859 .zip(&headers)
860 .map(|(proof, header)| proof.verify(header, namespace))
861 .collect()
862 }
863
864 pub async fn fetch_all_namespaces_in_range(
870 &self,
871 start_height: usize,
872 end_height: usize,
873 namespaces: &[NamespaceId],
874 ) -> Result<Vec<HashMap<NamespaceId, Vec<Transaction>>>> {
875 let headers = self
876 .fetch_headers_in_range(start_height, end_height)
877 .await?;
878 let proofs = self
879 .server
880 .namespaces_proofs_in_range(start_height as u64, end_height as u64, namespaces)
881 .await?;
882 ensure!(
883 proofs.len() == headers.len(),
884 "server returned wrong number of namespace proofs (expected {}, got {})",
885 headers.len(),
886 proofs.len()
887 );
888 proofs
889 .into_iter()
890 .zip(&headers)
891 .map(|(proofs, header)| {
892 proofs
893 .into_iter()
894 .map(|(namespace, proof)| Ok((namespace, proof.verify(header, namespace)?)))
895 .collect()
896 })
897 .collect()
898 }
899
900 pub async fn quorum_for_epoch(&self, epoch: EpochNumber) -> Result<Arc<StakeTable>> {
902 if epoch < self.first_epoch_with_dynamic_stake_table {
903 return Ok(self.genesis_stake_table.clone());
904 }
905
906 {
908 let cache = self.stake_tables.read().await;
909 if let Some(stake_table) = cache.get(&epoch) {
910 tracing::debug!(%epoch, "found stake table in cache");
911 return Ok(stake_table.clone());
912 }
913 }
914
915 let (lower_bound, mut stake_table, mut prev_quorum, mut epoch_root_protocol_version) =
923 if let Some((lower_bound, stake_table, stored_version, next_stored_version)) =
924 self.db.stake_table_lower_bound(epoch).await?
925 {
926 if lower_bound == epoch {
927 tracing::debug!(%epoch, "found stake table in database");
928 let quorum = stake_table_state_to_quorum(&stake_table, stored_version)?;
929 return Ok(self.cache_stake_table(epoch, Arc::new(quorum)).await);
930 }
931
932 let quorum = stake_table_state_to_quorum(&stake_table, stored_version)?;
933 (
934 lower_bound,
935 stake_table,
936 Arc::new(quorum),
937 next_stored_version,
938 )
939 } else {
940 let bootstrap_epoch = (*self.first_epoch_with_dynamic_stake_table)
943 .checked_sub(2)
944 .context(
945 "first_epoch_with_dynamic_stake_table must be >= 2 for genesis catchup",
946 )?;
947 let bootstrap_height = root_block_in_epoch(bootstrap_epoch, self.epoch_height);
948 let bootstrap_quorum = (
949 self.genesis_stake_table.clone(),
950 self.genesis_stake_table.clone(),
951 );
952 let bootstrap_root = self
953 .fetch_header_with_quorum(
954 BlockId::Number(bootstrap_height as usize),
955 move |_epoch| StakeTableQuorum::new(bootstrap_quorum, self.epoch_height),
956 )
957 .await
958 .context("fetching snapshot root header for genesis catchup")?;
959 (
960 self.first_epoch_with_dynamic_stake_table - 1,
961 StakeTableState::default(),
962 self.genesis_stake_table.clone(),
963 bootstrap_root.version(),
964 )
965 };
966 tracing::info!(from = %lower_bound, to = %epoch, "performing stake table catchup");
967
968 for epoch in *lower_bound + 1..=*epoch {
970 let events = self
971 .server
972 .stake_table_events(EpochNumber::new(epoch))
973 .await?;
974 let num_events = events.len();
975 tracing::debug!(epoch, num_events, "reconstruct stake table");
976 for event in events {
977 tracing::debug!(epoch, ?event, "replay event");
978 if let Err(err) = stake_table.apply_event(event).context("applying event")? {
979 tracing::warn!("allowed error in event: {err:#}");
980 }
981 }
982
983 let next_quorum = Arc::new(stake_table_state_to_quorum(
984 &stake_table,
985 epoch_root_protocol_version,
986 )?);
987
988 let root_height = root_block_in_epoch(epoch - 1, self.epoch_height);
989 let root = {
990 let prev_quorum = prev_quorum.clone();
991 let next_quorum = next_quorum.clone();
992 self.fetch_header_with_quorum(
993 BlockId::Number(root_height as usize),
994 move |_epoch| {
995 StakeTableQuorum::new((prev_quorum, next_quorum), self.epoch_height)
996 },
997 )
998 .await
999 .context(format!("fetching epoch root for {epoch}"))?
1000 };
1001 if let Some(hash) = root.next_stake_table_hash() {
1002 ensure!(
1003 hash == stake_table.commit(),
1004 "epoch {epoch} root {root_height} stake table hash {hash} does not match \
1005 reconstructed hash {}",
1006 stake_table.commit(),
1007 );
1008 } else if self.decaf() && root.version() < DrbAndHeaderUpgradeVersion::VERSION {
1009 tracing::debug!(
1014 ?epoch,
1015 root_height,
1016 computed_hash = %stake_table.commit(),
1017 "trusting stake table prior to DRB upgrade on Decaf",
1018 );
1019 } else {
1020 bail!("epoch {epoch} root {root_height} does not have next stake table hash");
1021 }
1022
1023 if let Err(err) = self
1028 .db
1029 .insert_stake_table(
1030 EpochNumber::new(epoch),
1031 &stake_table,
1032 epoch_root_protocol_version,
1033 root.version(),
1034 )
1035 .await
1036 {
1037 tracing::warn!(epoch, "failed to cache stake table: {err:#}");
1040 }
1041
1042 tracing::info!(epoch, num_events, "finished stake table catchup for epoch");
1043 prev_quorum = next_quorum;
1044 epoch_root_protocol_version = root.version();
1045 }
1046
1047 Ok(self.cache_stake_table(epoch, prev_quorum).await)
1048 }
1049
1050 fn fetch_leaf_from_server<'a, 'b, Q>(
1051 &'a self,
1052 id: impl Send + Into<LeafRequest> + 'a,
1053 known_finalized: Option<&'b Leaf2>,
1054 make_quorum: impl 'a + Send + FnOnce(EpochNumber) -> Q,
1055 ) -> impl 'b + Send + Future<Output = Result<LeafQueryData<SeqTypes>>>
1056 where
1057 'a: 'b,
1058 Q: Send + Quorum,
1059 {
1060 async move {
1061 let id = id.into();
1062 let proof = self
1063 .server
1064 .leaf_proof(id, known_finalized.map(Leaf2::height))
1065 .await?;
1066 let quorum;
1067 let hint = match proof.epoch(self.epoch_height)? {
1068 Some(epoch) => {
1069 quorum = make_quorum(epoch);
1070 LeafProofHint::Quorum(&quorum)
1071 },
1072 None => LeafProofHint::Assumption(known_finalized.context(
1073 "server returned proof with assumption, but we have no finalized upper bound \
1074 to verify assumption",
1075 )?),
1076 };
1077 let leaf = proof.verify(hint).await?;
1078
1079 ensure!(
1082 leaf_matches_id(&leaf, id),
1083 "server returned a valid leaf proof for the wrong leaf (requested leaf {id}, got \
1084 leaf {} with hash {})",
1085 leaf.height(),
1086 leaf.hash(),
1087 );
1088
1089 if let Err(err) = self.db.insert_leaf(leaf.clone()).await {
1092 tracing::warn!("failed to cache fetched leaf: {err:#}");
1095 }
1096
1097 Ok(leaf)
1098 }
1099 }
1100
1101 async fn cache_stake_table(
1102 &self,
1103 epoch: EpochNumber,
1104 stake_table: Arc<StakeTable>,
1105 ) -> Arc<StakeTable> {
1106 let mut cache = self.stake_tables.write().await;
1107
1108 if cache.len() >= self.opt.num_stake_tables_in_memory {
1111 if let Some(&second_oldest_epoch) = cache.keys().nth(1) {
1117 cache.remove(&second_oldest_epoch);
1118 }
1119 }
1120
1121 cache.entry(epoch).insert_entry(stake_table).get().clone()
1122 }
1123
1124 fn decaf(&self) -> bool {
1125 self.chain_id == DECAF_CHAIN_ID
1126 }
1127}
1128
1129fn stake_table_state_to_quorum(
1130 state: &StakeTableState,
1131 protocol_version: Version,
1132) -> Result<StakeTable> {
1133 let vs = ValidatorSet::from_state(state, protocol_version)?;
1134 Ok(vs
1135 .active_validators()
1136 .values()
1137 .map(|validator| StakeTableEntry {
1138 stake_key: *validator.stake_table_key(),
1139 stake_amount: validator.stake,
1140 })
1141 .collect())
1142}
1143
1144impl<P, S> StakeTablePair for (EpochNumber, &LightClient<P, S>)
1145where
1146 P: Storage,
1147 S: Client,
1148{
1149 async fn stake_table(&self) -> Result<Arc<StakeTable>> {
1150 self.1.quorum_for_epoch(self.0).await
1151 }
1152
1153 async fn next_epoch_stake_table(&self) -> Result<Arc<StakeTable>> {
1154 self.1.quorum_for_epoch(self.0 + 1).await
1155 }
1156}
1157
1158fn leaf_matches_id(leaf: &LeafQueryData<SeqTypes>, id: impl Into<LeafRequest>) -> bool {
1159 match id.into() {
1160 LeafRequest::Leaf(LeafId::Number(h)) | LeafRequest::Header(BlockId::Number(h)) => {
1161 (h as u64) == leaf.height()
1162 },
1163 LeafRequest::Leaf(LeafId::Hash(h)) => h == leaf.hash(),
1164 LeafRequest::Header(BlockId::Hash(h)) => h == leaf.block_hash(),
1165 LeafRequest::Header(BlockId::PayloadHash(h)) => h == leaf.payload_hash(),
1166 }
1167}
1168
1169fn header_matches_id(header: &Header, id: BlockId<SeqTypes>) -> bool {
1170 match id {
1171 BlockId::Number(n) => header.height() == (n as u64),
1172 BlockId::Hash(h) => header.commit() == h,
1173 BlockId::PayloadHash(h) => header.payload_commitment() == h,
1174 }
1175}
1176
1177#[cfg(test)]
1178mod test {
1179 use std::collections::HashSet;
1180
1181 use espresso_types::{
1182 NsIndex, RegisteredValidatorMap, StakeTableState, ValidatorSet, v0_3::RegisteredValidator,
1183 };
1184 use hotshot_query_service_types::availability::TransactionIndex;
1185 use hotshot_types::{addr::NetAddr, x25519};
1186 use itertools::izip;
1187 use pretty_assertions::assert_eq;
1188 use versions::{DRB_AND_HEADER_UPGRADE_VERSION, EPOCH_VERSION, NEW_PROTOCOL_VERSION};
1189
1190 use super::*;
1191 use crate::{
1192 storage::SqlStorage,
1193 testing::{TestClient, leaf_chain, random_validator},
1194 };
1195
1196 #[tokio::test]
1197 #[test_log::test]
1198 async fn test_block_height() {
1199 let client = TestClient::default();
1200 let db = SqlStorage::default().await.unwrap();
1201
1202 let lc = LightClient::from_genesis(db.clone(), client.clone(), client.genesis().await);
1203
1204 assert_eq!(lc.block_height().await.unwrap(), 0);
1206
1207 let leaf = leaf_chain(1..2, DRB_AND_HEADER_UPGRADE_VERSION)
1209 .await
1210 .remove(0);
1211 db.insert_leaf(leaf).await.unwrap();
1212 assert_eq!(lc.block_height().await.unwrap(), 2);
1213
1214 client.leaf(2).await;
1216 assert_eq!(lc.block_height().await.unwrap(), 3);
1217 assert_eq!(db.block_height().await.unwrap(), 3);
1220
1221 client.mock_block_height(10).await;
1223 client.forget_leaf(9).await;
1224 assert_eq!(lc.block_height().await.unwrap(), 3);
1225 }
1226
1227 #[tokio::test]
1228 #[test_log::test]
1229 async fn test_fetch_leaf_twice() {
1230 let client = TestClient::default();
1231 let lc = LightClient::from_genesis(
1232 SqlStorage::default().await.unwrap(),
1233 client.clone(),
1234 client.genesis().await,
1235 );
1236
1237 let leaf = client.remember_leaf(1).await;
1239 assert_eq!(lc.fetch_leaf(LeafId::Number(1)).await.unwrap(), leaf);
1240
1241 client.forget_leaf(1).await;
1243 assert_eq!(lc.fetch_leaf(LeafId::Number(1)).await.unwrap(), leaf);
1244 }
1245
1246 #[tokio::test]
1247 #[test_log::test]
1248 async fn test_fetch_leaf_upper_bound() {
1249 let client = TestClient::default();
1250
1251 let db = SqlStorage::default().await.unwrap();
1252 db.insert_leaf(client.leaf(2).await).await.unwrap();
1253
1254 let lc = LightClient::from_genesis(db, client.clone(), client.genesis().await);
1255 assert_eq!(
1256 lc.fetch_leaf(LeafId::Number(1)).await.unwrap(),
1257 client.leaf(1).await,
1258 );
1259 }
1260
1261 #[tokio::test]
1262 #[test_log::test]
1263 async fn test_fetch_leaf_distant_upper_bound() {
1264 let client = TestClient::default();
1265 client.reject_distant_finalized_hints(16).await;
1266
1267 let distant = MAX_FINALIZED_HINT_DISTANCE + 2;
1270 let distant_leaf = leaf_chain(distant..=distant, DRB_AND_HEADER_UPGRADE_VERSION)
1271 .await
1272 .remove(0);
1273 let db = SqlStorage::default().await.unwrap();
1274 db.insert_leaf(distant_leaf).await.unwrap();
1275
1276 let lc = LightClient::from_genesis(db, client.clone(), client.genesis().await);
1277 assert_eq!(
1278 lc.fetch_leaf(LeafId::Number(1)).await.unwrap(),
1279 client.leaf(1).await,
1280 );
1281 }
1282
1283 #[tokio::test]
1284 #[test_log::test]
1285 async fn test_fetch_leaf_invalid_proof() {
1286 let client = TestClient::default();
1287 let lc = LightClient::from_genesis(
1288 SqlStorage::default().await.unwrap(),
1289 client.clone(),
1290 client.genesis().await,
1291 );
1292 client.return_invalid_proof(1).await;
1293 lc.fetch_leaf(LeafId::Number(1)).await.unwrap_err();
1294 lc.fetch_leaf(LeafId::Hash(client.leaf(1).await.hash()))
1295 .await
1296 .unwrap_err();
1297 }
1298
1299 #[tokio::test]
1300 #[test_log::test]
1301 async fn test_fetch_leaf_wrong_leaf() {
1302 let client = TestClient::default();
1303 let lc = LightClient::from_genesis(
1304 SqlStorage::default().await.unwrap(),
1305 client.clone(),
1306 client.genesis().await,
1307 );
1308 client.return_wrong_leaf(1, 2).await;
1309 lc.fetch_leaf(LeafId::Number(1)).await.unwrap_err();
1310 lc.fetch_leaf(LeafId::Hash(client.leaf(1).await.hash()))
1311 .await
1312 .unwrap_err();
1313 }
1314
1315 #[tokio::test]
1316 #[test_log::test]
1317 async fn test_fetch_header_twice() {
1318 let client = TestClient::default();
1319 let lc = LightClient::from_genesis(
1320 SqlStorage::default().await.unwrap(),
1321 client.clone(),
1322 client.genesis().await,
1323 );
1324
1325 let leaf = client.remember_leaf(1).await;
1327 assert_eq!(
1328 lc.fetch_header(BlockId::Number(1)).await.unwrap(),
1329 *leaf.header()
1330 );
1331
1332 client.forget_leaf(1).await;
1334 assert_eq!(
1335 lc.fetch_header(BlockId::Number(1)).await.unwrap(),
1336 *leaf.header()
1337 );
1338 }
1339
1340 #[tokio::test]
1341 #[test_log::test]
1342 async fn test_fetch_header_upper_bound() {
1343 let client = TestClient::default();
1344
1345 let db = SqlStorage::default().await.unwrap();
1346 db.insert_leaf(client.leaf(2).await).await.unwrap();
1347
1348 let lc = LightClient::from_genesis(db, client.clone(), client.genesis().await);
1349 assert_eq!(
1350 lc.fetch_header(BlockId::Number(1)).await.unwrap(),
1351 *client.leaf(1).await.header(),
1352 );
1353 }
1354
1355 #[tokio::test]
1356 #[test_log::test]
1357 async fn test_fetch_header_falls_back_to_leaf_proof_when_header_proof_fails() {
1358 let client = TestClient::default();
1359
1360 let db = SqlStorage::default().await.unwrap();
1361 db.insert_leaf(client.leaf(2).await).await.unwrap();
1362 client.fail_header_proofs().await;
1363
1364 let lc = LightClient::from_genesis(db, client.clone(), client.genesis().await);
1365 assert_eq!(
1366 lc.fetch_header(BlockId::Number(1)).await.unwrap(),
1367 *client.leaf(1).await.header(),
1368 );
1369 }
1370
1371 #[tokio::test]
1372 #[test_log::test]
1373 async fn test_fetch_header_fallback_anchors_on_cached_successor_without_cert2() {
1374 let client = TestClient::default();
1375 client.set_upgrade(1, NEW_PROTOCOL_VERSION).await;
1376
1377 let db = SqlStorage::default().await.unwrap();
1378 db.insert_leaf(client.leaf(2).await).await.unwrap();
1379 client.fail_header_proofs().await;
1380 client.forget_cert2(1).await;
1381
1382 let lc = LightClient::from_genesis(db, client.clone(), client.genesis().await);
1383 assert_eq!(
1384 lc.fetch_header(BlockId::Number(1)).await.unwrap(),
1385 *client.leaf(1).await.header(),
1386 );
1387 }
1388
1389 #[tokio::test]
1390 #[test_log::test]
1391 async fn test_fetch_header_reports_header_proof_error_when_fallback_fails() {
1392 let client = TestClient::default();
1393
1394 let db = SqlStorage::default().await.unwrap();
1395 db.insert_leaf(client.leaf(2).await).await.unwrap();
1396 client.fail_header_proofs().await;
1397 client.forget_leaf(1).await;
1398
1399 let lc = LightClient::from_genesis(db, client.clone(), client.genesis().await);
1400 let err = lc.fetch_header(BlockId::Number(1)).await.unwrap_err();
1401 assert!(
1402 format!("{err:#}").contains("header proof also failed: no merklized state at root 2"),
1403 "{err:#}"
1404 );
1405 }
1406
1407 #[tokio::test]
1408 #[test_log::test]
1409 async fn test_fetch_header_invalid_proof() {
1410 let client = TestClient::default();
1411 let db = SqlStorage::default().await.unwrap();
1412
1413 db.insert_leaf(client.leaf(2).await).await.unwrap();
1416
1417 let lc = LightClient::from_genesis(db, client.clone(), client.genesis().await);
1418 client.return_invalid_proof(1).await;
1419
1420 let err = lc.fetch_header(BlockId::Number(1)).await.unwrap_err();
1421 assert!(err.to_string().contains("invalid header proof"), "{err:#}");
1422 }
1423
1424 #[tokio::test]
1425 #[test_log::test]
1426 async fn test_fetch_header_wrong_header() {
1427 let client = TestClient::default();
1428 let db = SqlStorage::default().await.unwrap();
1429
1430 db.insert_leaf(client.leaf(2).await).await.unwrap();
1433
1434 let lc = LightClient::from_genesis(db, client.clone(), client.genesis().await);
1435 client.return_wrong_leaf(1, 0).await;
1436
1437 let err = lc.fetch_header(BlockId::Number(1)).await.unwrap_err();
1438 assert!(
1439 err.to_string()
1440 .contains("server returned a valid header proof for the wrong header"),
1441 "{err:#}"
1442 );
1443 }
1444
1445 #[tokio::test]
1446 #[test_log::test]
1447 async fn test_fetch_leaves_in_range() {
1448 let client = TestClient::default();
1449 let lc = LightClient::from_genesis(
1450 SqlStorage::default().await.unwrap(),
1451 client.clone(),
1452 client.genesis().await,
1453 );
1454
1455 let leaf1 = client.remember_leaf(1).await;
1457 let leaf2 = client.remember_leaf(2).await;
1458 client.remember_leaf(3).await;
1459
1460 let leaves = lc.fetch_leaves_in_range(1, 3).await.unwrap();
1461
1462 assert_eq!(leaves, vec![leaf1.clone(), leaf2.clone()]);
1463
1464 client.forget_leaf(1).await;
1466 client.forget_leaf(2).await;
1467 let leaves = lc.fetch_leaves_in_range(1, 3).await.unwrap();
1468 assert_eq!(leaves, vec![leaf1, leaf2]);
1469 }
1470
1471 #[tokio::test]
1472 #[test_log::test]
1473 async fn test_fetch_leaves_in_range_uncached() {
1474 let client = TestClient::default();
1475 let lc = LightClient::from_genesis(
1476 SqlStorage::default().await.unwrap(),
1477 client.clone(),
1478 client.genesis().await,
1479 );
1480 let leaf1 = client.remember_leaf(1).await;
1481 let leaf2 = client.remember_leaf(2).await;
1482 let leaf3 = client.remember_leaf(3).await;
1483
1484 let leaves = lc.fetch_leaves_in_range_uncached(1, 4).await.unwrap();
1485 assert_eq!(leaves, vec![leaf1, leaf2, leaf3.clone()]);
1486
1487 assert_eq!(lc.db.get_leaves_in_range(1, 4).await.unwrap(), vec![leaf3]);
1489 }
1490
1491 #[tokio::test]
1492 #[test_log::test]
1493 async fn test_fetch_headers_in_range() {
1494 let client = TestClient::default();
1495 let lc = LightClient::from_genesis(
1496 SqlStorage::default().await.unwrap(),
1497 client.clone(),
1498 client.genesis().await,
1499 );
1500
1501 let leaf1 = client.remember_leaf(1).await;
1503 let leaf2 = client.remember_leaf(2).await;
1504 client.remember_leaf(3).await;
1505
1506 let headers = lc.fetch_headers_in_range(1, 3).await.unwrap();
1507
1508 assert_eq!(
1509 headers,
1510 vec![leaf1.header().clone(), leaf2.header().clone()]
1511 );
1512
1513 client.forget_leaf(1).await;
1515 client.forget_leaf(2).await;
1516 let headers = lc.fetch_headers_in_range(1, 3).await.unwrap();
1517 assert_eq!(
1518 headers,
1519 vec![leaf1.header().clone(), leaf2.header().clone()]
1520 );
1521 }
1522
1523 #[tokio::test]
1524 #[test_log::test]
1525 async fn test_fetch_leaves_in_range_invalid_proof() {
1526 let client = TestClient::default();
1527 let lc = LightClient::from_genesis(
1528 SqlStorage::default().await.unwrap(),
1529 client.clone(),
1530 client.genesis().await,
1531 );
1532 client.return_invalid_proof(2).await;
1533 let err = lc.fetch_leaves_in_range(1, 3).await.unwrap_err();
1534 assert!(
1535 err.to_string().contains(
1536 "server returned proof with assumption, but we have no finalized upper bound to \
1537 verify assumption"
1538 ),
1539 "{err:#}"
1540 );
1541 }
1542
1543 #[tokio::test]
1544 #[test_log::test]
1545 async fn test_fetch_headers_in_range_invalid_proof() {
1546 let client = TestClient::default();
1547 let lc = LightClient::from_genesis(
1548 SqlStorage::default().await.unwrap(),
1549 client.clone(),
1550 client.genesis().await,
1551 );
1552 client.return_invalid_proof(2).await;
1553 let err = lc.fetch_headers_in_range(1, 3).await.unwrap_err();
1554 assert!(
1555 err.to_string().contains(
1556 "server returned proof with assumption, but we have no finalized upper bound to \
1557 verify assumption"
1558 ),
1559 "{err:#}"
1560 );
1561 }
1562
1563 #[tokio::test]
1564 #[test_log::test]
1565 async fn test_fetch_leaves_in_range_wrong_leaf() {
1566 let client = TestClient::default();
1567 let lc = LightClient::from_genesis(
1568 SqlStorage::default().await.unwrap(),
1569 client.clone(),
1570 client.genesis().await,
1571 );
1572 client.return_wrong_leaf(1, 2).await;
1573 let err = lc.fetch_leaves_in_range(1, 4).await.unwrap_err();
1574 assert!(err.to_string().contains("leaf hash mismatch"), "{err:#}");
1575 }
1576
1577 #[tokio::test]
1578 #[test_log::test]
1579 async fn test_fetch_leaves_for_ranges() {
1580 let client = TestClient::default();
1581 let lc = LightClient::from_genesis(
1582 SqlStorage::default().await.unwrap(),
1583 client.clone(),
1584 client.genesis().await,
1585 );
1586
1587 let mut expected = vec![];
1588 for height in [1usize, 2, 5, 6, 7] {
1589 expected.push(client.leaf(height).await);
1590 }
1591 let fetched = lc.fetch_leaves_for_ranges(&[1..3, 5..8]).await.unwrap();
1592 assert_eq!(fetched, expected);
1593 }
1594
1595 #[test]
1597 fn coalescing_cap_matches_query_service_default() {
1598 assert_eq!(
1599 hotshot_query_service::availability::Options::default().small_object_range_limit,
1600 500
1601 );
1602 }
1603
1604 #[tokio::test]
1608 #[test_log::test]
1609 async fn test_fetch_leaves_for_ranges_ordering() {
1610 let client = TestClient::default();
1611 let lc = LightClient::from_genesis(
1612 SqlStorage::default().await.unwrap(),
1613 client.clone(),
1614 client.genesis().await,
1615 );
1616
1617 let mut expected = vec![];
1618 for height in 1..5usize {
1619 expected.push(client.leaf(height).await);
1620 }
1621 assert_eq!(
1622 lc.fetch_leaves_for_ranges(&[1..3, 3..5]).await.unwrap(),
1623 expected
1624 );
1625 for ranges in [[5..8, 1..3], [1..4, 3..6]] {
1626 let err = lc.fetch_leaves_for_ranges(&ranges).await.unwrap_err();
1627 assert!(err.to_string().contains("ascending"), "{err:#}");
1628 }
1629 }
1630
1631 #[tokio::test]
1632 #[test_log::test]
1633 async fn test_fetch_leaves_for_ranges_wrong_leaf() {
1634 let client = TestClient::default();
1635 let lc = LightClient::from_genesis(
1636 SqlStorage::default().await.unwrap(),
1637 client.clone(),
1638 client.genesis().await,
1639 );
1640
1641 client.return_wrong_leaf(1, 2).await;
1645 #[allow(clippy::single_range_in_vec_init)]
1646 let err = lc.fetch_leaves_for_ranges(&[1..4]).await.unwrap_err();
1647 assert!(err.to_string().contains("wrong leaf"), "{err:#}");
1648 }
1649
1650 #[tokio::test]
1653 #[test_log::test]
1654 async fn test_fetch_leaves_for_ranges_wide_span() {
1655 let client = TestClient::default();
1656 let lc = LightClient::from_genesis(
1657 SqlStorage::default().await.unwrap(),
1658 client.clone(),
1659 client.genesis().await,
1660 );
1661
1662 let mut expected = vec![];
1663 for height in [1usize, 2, 60, 61, 62] {
1664 expected.push(client.leaf(height).await);
1665 }
1666 let fetched = lc.fetch_leaves_for_ranges(&[1..3, 60..63]).await.unwrap();
1667 assert_eq!(fetched, expected);
1668 }
1669
1670 #[tokio::test]
1673 #[test_log::test]
1674 async fn test_fetch_leaves_for_ranges_without_ranges_endpoint() {
1675 let client = TestClient::default();
1676 let lc = LightClient::from_genesis(
1677 SqlStorage::default().await.unwrap(),
1678 client.clone(),
1679 client.genesis().await,
1680 );
1681 client.fail_leaf_ranges().await;
1682
1683 let mut expected = vec![];
1684 for height in [1usize, 2, 60, 61, 62] {
1685 expected.push(client.leaf(height).await);
1686 }
1687 let fetched = lc.fetch_leaves_for_ranges(&[1..3, 60..63]).await.unwrap();
1688 assert_eq!(fetched, expected);
1689 }
1690
1691 #[tokio::test]
1692 #[test_log::test]
1693 async fn test_fetch_headers_in_range_wrong_leaf() {
1694 let client = TestClient::default();
1695 let lc = LightClient::from_genesis(
1696 SqlStorage::default().await.unwrap(),
1697 client.clone(),
1698 client.genesis().await,
1699 );
1700 client.return_wrong_leaf(1, 2).await;
1701 let err = lc.fetch_headers_in_range(1, 4).await.unwrap_err();
1702 assert!(err.to_string().contains("leaf hash mismatch"), "{err:#}");
1703 }
1704
1705 #[tokio::test]
1706 #[test_log::test]
1707 async fn test_fetch_stake_table_twice_cache() {
1708 let client = TestClient::default();
1709 let genesis = client.genesis().await;
1710 let lc = LightClient::from_genesis(
1711 SqlStorage::default().await.unwrap(),
1712 client.clone(),
1713 genesis.clone(),
1714 );
1715
1716 let epoch = genesis.first_epoch_with_dynamic_stake_table + 5;
1718 let expected = client.quorum_for_epoch(epoch).await.into();
1719 assert_eq!(*lc.quorum_for_epoch(epoch).await.unwrap(), expected);
1720
1721 client.forget_quorum(epoch).await;
1723 assert_eq!(*lc.quorum_for_epoch(epoch).await.unwrap(), expected);
1724 }
1725
1726 #[tokio::test]
1727 #[test_log::test]
1728 async fn test_fetch_stake_table_twice_storage() {
1729 let db = SqlStorage::default().await.unwrap();
1730 let client = TestClient::default();
1731 let genesis = client.genesis().await;
1732 let lc = LightClient::from_genesis(db.clone(), client.clone(), genesis.clone());
1733
1734 let epoch = genesis.first_epoch_with_dynamic_stake_table + 5;
1736 let expected = client.quorum_for_epoch(epoch).await.into();
1737 assert_eq!(*lc.quorum_for_epoch(epoch).await.unwrap(), expected);
1738
1739 client.forget_quorum(epoch).await;
1742 let lc = LightClient::from_genesis(db, client.clone(), genesis);
1743 assert_eq!(*lc.quorum_for_epoch(epoch).await.unwrap(), expected);
1744 }
1745
1746 #[tokio::test]
1747 #[test_log::test]
1748 async fn test_fetch_stake_table_catchup_from_lower_bound() {
1749 let db = SqlStorage::default().await.unwrap();
1750 let client = TestClient::default();
1751 let genesis = client.genesis().await;
1752 let lc = LightClient::from_genesis(db.clone(), client.clone(), genesis.clone());
1753
1754 let epoch = genesis.first_epoch_with_dynamic_stake_table + 5;
1757 lc.quorum_for_epoch(epoch).await.unwrap();
1758
1759 for epoch in 0..*epoch - 1 {
1762 client.forget_quorum(EpochNumber::new(epoch)).await;
1763 }
1764
1765 let expected = client.quorum_for_epoch(epoch + 5).await.into();
1767 assert_eq!(*lc.quorum_for_epoch(epoch + 5).await.unwrap(), expected);
1768 }
1769
1770 #[tokio::test]
1771 #[test_log::test]
1772 async fn test_fetch_stake_table_catchup_without_header_proofs() {
1773 let client = TestClient::with_epoch_height(10);
1774 let genesis = client.genesis().await;
1775 let epoch = genesis.first_epoch_with_dynamic_stake_table + 5;
1776
1777 let db = SqlStorage::default().await.unwrap();
1778 let above_every_root = root_block_in_epoch(*epoch + 1, 10) as usize;
1779 db.insert_leaf(client.leaf(above_every_root).await)
1780 .await
1781 .unwrap();
1782 client.fail_header_proofs().await;
1783
1784 let lc = LightClient::from_genesis(db, client.clone(), genesis);
1785 let expected = client.quorum_for_epoch(epoch).await.into();
1786 assert_eq!(*lc.quorum_for_epoch(epoch).await.unwrap(), expected);
1787 }
1788
1789 #[tokio::test]
1790 #[test_log::test]
1791 async fn test_fetch_stake_table_cache_removal() {
1792 let db = SqlStorage::default().await.unwrap();
1793 let client = TestClient::default();
1794 let genesis = client.genesis().await;
1795 let lc = LightClient::from_genesis_with_options(
1796 db.clone(),
1797 client.clone(),
1798 genesis.clone(),
1799 LightClientOptions {
1800 num_stake_tables_in_memory: 2,
1801 },
1802 );
1803
1804 for i in 0..2 {
1806 let epoch = genesis.first_epoch_with_dynamic_stake_table + 5 + i;
1807 assert_eq!(
1808 *lc.quorum_for_epoch(epoch).await.unwrap(),
1809 client.quorum_for_epoch(epoch).await.into(),
1810 );
1811 }
1812 assert_eq!(lc.stake_tables.read().await.len(), 2);
1813
1814 assert_eq!(
1816 *lc.quorum_for_epoch(genesis.first_epoch_with_dynamic_stake_table + 10)
1817 .await
1818 .unwrap(),
1819 client
1820 .quorum_for_epoch(genesis.first_epoch_with_dynamic_stake_table + 10)
1821 .await
1822 .into(),
1823 );
1824 assert_eq!(lc.stake_tables.read().await.len(), 2);
1825
1826 let epoch = genesis.first_epoch_with_dynamic_stake_table + 6;
1829 for i in 0..*epoch {
1830 client.forget_quorum(EpochNumber::new(i)).await;
1831 }
1832 assert_eq!(
1833 *lc.quorum_for_epoch(epoch).await.unwrap(),
1834 client.quorum_for_epoch(epoch).await.into(),
1835 );
1836 }
1837
1838 #[tokio::test]
1839 #[test_log::test]
1840 async fn test_fetch_stake_table_invalid() {
1841 let db = SqlStorage::default().await.unwrap();
1842 let client = TestClient::default();
1843 let genesis = client.genesis().await;
1844 let lc = LightClient::from_genesis(db.clone(), client.clone(), genesis.clone());
1845
1846 let epoch = genesis.first_epoch_with_dynamic_stake_table + 5;
1847 client.return_invalid_quorum(epoch).await;
1848 let err = lc.quorum_for_epoch(epoch).await.unwrap_err();
1849 assert!(
1850 err.to_string()
1851 .contains("does not match reconstructed hash"),
1852 "{err:#}"
1853 );
1854 }
1855
1856 #[tokio::test]
1859 #[test_log::test]
1860 async fn test_fetch_stake_table_catchup_with_view_gaps_after_root() {
1861 let client = TestClient::with_epoch_height(10);
1862 let genesis = client.genesis().await;
1863 let lc = LightClient::from_genesis(
1864 SqlStorage::default().await.unwrap(),
1865 client.clone(),
1866 genesis.clone(),
1867 );
1868
1869 let target_epoch = genesis.first_epoch_with_dynamic_stake_table + 1;
1870 let root_height = root_block_in_epoch(*target_epoch - 1, 10) as usize;
1871 for i in [0, 2, 4] {
1872 client.add_view_gap_after(root_height + i).await;
1873 }
1874
1875 let expected = client.quorum_for_epoch(target_epoch).await.into();
1876 assert_eq!(*lc.quorum_for_epoch(target_epoch).await.unwrap(), expected);
1877 }
1878
1879 #[tokio::test]
1882 #[test_log::test]
1883 async fn test_fetch_stake_table_catchup_rejects_tampered_next_epoch_qc() {
1884 let client = TestClient::with_epoch_height(10);
1885 let genesis = client.genesis().await;
1886 let lc = LightClient::from_genesis(
1887 SqlStorage::default().await.unwrap(),
1888 client.clone(),
1889 genesis.clone(),
1890 );
1891
1892 let target_epoch = genesis.first_epoch_with_dynamic_stake_table + 1;
1893 let root_height = root_block_in_epoch(*target_epoch - 1, 10) as usize;
1894 client.add_view_gap_after(root_height).await;
1895 client.corrupt_next_epoch_qc().await;
1896
1897 let err = lc.quorum_for_epoch(target_epoch).await.unwrap_err();
1898 assert!(
1899 err.to_string().contains("verifying next epoch QC")
1900 || err
1901 .chain()
1902 .any(|e| e.to_string().contains("verifying next epoch QC")),
1903 "{err:#}"
1904 );
1905 }
1906
1907 #[tokio::test]
1912 #[test_log::test]
1913 async fn test_fetch_stake_table_catchup_genesis_bootstrap_version() {
1914 let client = TestClient::with_epoch_height(10);
1915 let genesis = client.genesis().await;
1916 let db = SqlStorage::default().await.unwrap();
1917 let lc = LightClient::from_genesis(db.clone(), client.clone(), genesis.clone());
1918
1919 let epoch = genesis.first_epoch_with_dynamic_stake_table;
1920 let root_height = root_block_in_epoch(*epoch - 1, 10);
1921 client.set_upgrade(root_height, NEW_PROTOCOL_VERSION).await;
1922
1923 lc.quorum_for_epoch(epoch).await.unwrap();
1924
1925 let (cached_epoch, _, cached_version, _) =
1926 db.stake_table_lower_bound(epoch).await.unwrap().unwrap();
1927 assert_eq!(cached_epoch, epoch);
1928 assert_eq!(cached_version, DRB_AND_HEADER_UPGRADE_VERSION);
1929 }
1930
1931 #[tokio::test]
1944 #[test_log::test]
1945 async fn test_fetch_stake_table_catchup_db_load_advances_snapshot_version() {
1946 use crate::client::Client;
1947
1948 let client = TestClient::with_epoch_height(10);
1949 let genesis = client.genesis().await;
1950 let db = SqlStorage::default().await.unwrap();
1951 let lc = LightClient::from_genesis(db.clone(), client.clone(), genesis.clone());
1952
1953 let lower_bound_epoch = genesis.first_epoch_with_dynamic_stake_table;
1954 let target_epoch = lower_bound_epoch + 1;
1955 let target_epoch_root = root_block_in_epoch(*target_epoch - 2, 10);
1956
1957 let mut prev_state = StakeTableState::default();
1958 for event in client.stake_table_events(lower_bound_epoch).await.unwrap() {
1959 prev_state.apply_event(event).unwrap().unwrap();
1960 }
1961 db.insert_stake_table(
1962 lower_bound_epoch,
1963 &prev_state,
1964 DRB_AND_HEADER_UPGRADE_VERSION,
1965 NEW_PROTOCOL_VERSION,
1966 )
1967 .await
1968 .unwrap();
1969
1970 client
1971 .set_upgrade(target_epoch_root, NEW_PROTOCOL_VERSION)
1972 .await;
1973
1974 let err = lc.quorum_for_epoch(target_epoch).await.unwrap_err();
1975 assert!(
1976 err.to_string()
1977 .contains("No validators met the minimum criteria")
1978 || err.chain().any(|e| e
1979 .to_string()
1980 .contains("No validators met the minimum criteria")),
1981 "{err:#}"
1982 );
1983 }
1984
1985 #[tokio::test]
1989 #[test_log::test]
1990 async fn test_fetch_stake_table_catchup_uses_snapshot_root_version() {
1991 use crate::client::Client;
1992
1993 let client = TestClient::with_epoch_height(10);
1994 let genesis = client.genesis().await;
1995 let db = SqlStorage::default().await.unwrap();
1996 let lc = LightClient::from_genesis(db.clone(), client.clone(), genesis.clone());
1997
1998 let prev_epoch = genesis.first_epoch_with_dynamic_stake_table;
1999 let target_epoch = prev_epoch + 1;
2000 let root_height = root_block_in_epoch(*target_epoch - 1, 10);
2001
2002 let mut prev_state = StakeTableState::default();
2003 for event in client.stake_table_events(prev_epoch).await.unwrap() {
2004 prev_state.apply_event(event).unwrap().unwrap();
2005 }
2006 db.insert_stake_table(
2007 prev_epoch,
2008 &prev_state,
2009 DRB_AND_HEADER_UPGRADE_VERSION,
2010 DRB_AND_HEADER_UPGRADE_VERSION,
2011 )
2012 .await
2013 .unwrap();
2014
2015 client.set_upgrade(root_height, NEW_PROTOCOL_VERSION).await;
2016
2017 let expected = client.quorum_for_epoch(target_epoch).await.into();
2018 assert_eq!(*lc.quorum_for_epoch(target_epoch).await.unwrap(), expected);
2019 }
2020
2021 #[tokio::test]
2024 #[test_log::test]
2025 async fn test_fetch_stake_table_cliquenet_filter() {
2026 let db = SqlStorage::default().await.unwrap();
2027 let client = TestClient::default();
2028 let genesis = client.genesis().await;
2029 let lc = LightClient::from_genesis(db.clone(), client.clone(), genesis.clone());
2030
2031 let mut complete: RegisteredValidator<PubKey> = random_validator().into();
2032 complete.x25519_key = Some(x25519::PublicKey::try_from([42u8; 32].as_slice()).unwrap());
2033 complete.p2p_addr = Some("127.0.0.1:9000".parse::<NetAddr>().unwrap());
2034
2035 let incomplete: RegisteredValidator<PubKey> = random_validator().into();
2036 assert!(incomplete.x25519_key.is_none());
2037 assert!(incomplete.p2p_addr.is_none());
2038
2039 let mut validators = RegisteredValidatorMap::default();
2040 validators.insert(complete.account, complete.clone());
2041 validators.insert(incomplete.account, incomplete.clone());
2042
2043 let complete_bls = complete.stake_table_key.expect("authenticated validator");
2044 let incomplete_bls = incomplete.stake_table_key.expect("authenticated validator");
2045 let mut used_bls = HashSet::default();
2046 used_bls.insert(complete_bls);
2047 used_bls.insert(incomplete_bls);
2048
2049 let mut used_schnorr = HashSet::default();
2050 used_schnorr.insert(
2051 complete
2052 .state_ver_key
2053 .clone()
2054 .expect("random_validator has valid schnorr key"),
2055 );
2056 used_schnorr.insert(
2057 incomplete
2058 .state_ver_key
2059 .clone()
2060 .expect("random_validator has valid schnorr key"),
2061 );
2062
2063 let mut used_x25519 = HashSet::default();
2064 used_x25519.insert(complete.x25519_key.unwrap());
2065
2066 let state = StakeTableState::new(
2067 validators,
2068 Default::default(),
2069 used_bls,
2070 used_schnorr,
2071 used_x25519,
2072 );
2073
2074 let epoch = genesis.first_epoch_with_dynamic_stake_table + 1;
2075 db.insert_stake_table(epoch, &state, NEW_PROTOCOL_VERSION, NEW_PROTOCOL_VERSION)
2076 .await
2077 .unwrap();
2078
2079 let quorum = lc.quorum_for_epoch(epoch).await.unwrap();
2080 let expected: StakeTable = vec![StakeTableEntry {
2081 stake_key: complete_bls,
2082 stake_amount: complete.stake,
2083 }]
2084 .into();
2085 assert_eq!(*quorum, expected);
2086
2087 let pre_cliquenet = ValidatorSet::from_state(&state, EPOCH_VERSION).unwrap();
2090 assert_eq!(pre_cliquenet.active_validators().len(), 2);
2091 }
2092
2093 #[tokio::test]
2096 #[test_log::test]
2097 async fn test_fetch_stake_table_cliquenet_filter_reconstruction() {
2098 let db = SqlStorage::default().await.unwrap();
2099 let client = TestClient::default();
2100 let genesis = client.genesis().await;
2101 let lc = LightClient::from_genesis(db.clone(), client.clone(), genesis.clone());
2102
2103 let epoch = genesis.first_epoch_with_dynamic_stake_table + 2;
2104 assert!(db.stake_table_lower_bound(epoch).await.unwrap().is_none());
2105
2106 let expected = client.quorum_for_epoch(epoch).await.into();
2107 assert_eq!(*lc.quorum_for_epoch(epoch).await.unwrap(), expected);
2108
2109 let (cached_epoch, _, cached_version, _) =
2110 db.stake_table_lower_bound(epoch).await.unwrap().unwrap();
2111 assert_eq!(cached_epoch, epoch);
2112 assert_eq!(cached_version, DRB_AND_HEADER_UPGRADE_VERSION);
2113 }
2114
2115 #[tokio::test]
2116 #[test_log::test]
2117 async fn test_fetch_payload() {
2118 let client = TestClient::default();
2119 let lc = LightClient::from_genesis(
2120 SqlStorage::default().await.unwrap(),
2121 client.clone(),
2122 client.genesis().await,
2123 );
2124
2125 let mut hashes = vec![];
2126 for i in 1..10 {
2127 let payload = client.payload(i).await;
2128 let res = lc.fetch_payload(BlockId::Number(i)).await.unwrap();
2129 assert_eq!(res.data(), &payload);
2130 assert_eq!(res.height(), i as u64);
2131 assert_eq!(res.block_hash(), client.leaf(i).await.block_hash());
2132 assert_eq!(res.hash(), client.leaf(i).await.payload_hash());
2133 hashes.push(res.hash());
2134 }
2135
2136 let blocks = lc.fetch_blocks_in_range(1, 10).await.unwrap();
2138 let vid = lc.fetch_vid_common_in_range(1, 10).await.unwrap();
2139 assert_eq!(blocks.len(), 9);
2140 assert_eq!(vid.len(), 9);
2141 for (i, (hash, block, vid)) in izip!(hashes, blocks, vid).enumerate() {
2142 assert_eq!(block.height() as usize, i + 1);
2143 assert_eq!(vid.height() as usize, i + 1);
2144 assert_eq!(block.payload_hash(), hash);
2145 assert_eq!(vid.payload_hash(), hash);
2146 }
2147 }
2148
2149 #[tokio::test]
2150 #[test_log::test]
2151 async fn test_fetch_payload_invalid() {
2152 let client = TestClient::default();
2153 let lc = LightClient::from_genesis(
2154 SqlStorage::default().await.unwrap(),
2155 client.clone(),
2156 client.genesis().await,
2157 );
2158
2159 client.return_invalid_payload(1).await;
2160 let err = lc.fetch_payload(BlockId::Number(1)).await.unwrap_err();
2161 assert!(
2162 err.to_string()
2163 .contains("commitment of payload does not match commitment in header"),
2164 "{err:#}"
2165 );
2166 }
2167
2168 #[tokio::test]
2171 #[test_log::test]
2172 async fn test_fetch_blocks_for_ranges_without_ranges_endpoint() {
2173 let client = TestClient::default();
2174 let lc = LightClient::from_genesis(
2175 SqlStorage::default().await.unwrap(),
2176 client.clone(),
2177 client.genesis().await,
2178 );
2179 client.fail_payload_proof_ranges().await;
2180 client.payload(62).await;
2181
2182 let fetched = lc
2183 .fetch_blocks_and_vid_common_for_ranges(&[1..3, 60..63])
2184 .await
2185 .unwrap();
2186 assert_eq!(
2187 fetched
2188 .iter()
2189 .map(|(block, _)| block.height())
2190 .collect::<Vec<_>>(),
2191 [1, 2, 60, 61, 62]
2192 );
2193 }
2194
2195 #[tokio::test]
2196 #[test_log::test]
2197 async fn test_fetch_blocks_for_ranges_invalid_proof() {
2198 let client = TestClient::default();
2199 let lc = LightClient::from_genesis(
2200 SqlStorage::default().await.unwrap(),
2201 client.clone(),
2202 client.genesis().await,
2203 );
2204 client.payload(7).await;
2205
2206 client.return_invalid_payload(6).await;
2207 let err = lc
2208 .fetch_blocks_and_vid_common_for_ranges(&[1..3, 5..8])
2209 .await
2210 .unwrap_err();
2211 assert!(
2212 err.to_string()
2213 .contains("commitment of payload does not match commitment in header"),
2214 "{err:#}"
2215 );
2216 }
2217
2218 #[tokio::test]
2219 #[test_log::test]
2220 async fn test_fetch_namespace() {
2221 let client = TestClient::default();
2222 let lc = LightClient::from_genesis(
2223 SqlStorage::default().await.unwrap(),
2224 client.clone(),
2225 client.genesis().await,
2226 );
2227
2228 for i in 1..10 {
2229 let leaf = client.leaf(i).await;
2230 let payload = client.payload(i).await;
2231
2232 for id in [
2233 BlockId::Number(i),
2234 BlockId::Hash(leaf.block_hash()),
2235 BlockId::PayloadHash(leaf.payload_hash()),
2236 ] {
2237 let ns_index = NsIndex::from(0);
2239 let tx = payload
2240 .transaction(&TransactionIndex {
2241 ns_index,
2242 position: 0,
2243 })
2244 .unwrap();
2245 let txs = lc.fetch_namespace(id, tx.namespace()).await.unwrap();
2246 assert_eq!(txs, std::slice::from_ref(&tx));
2247
2248 let txs = lc
2250 .fetch_namespace(id, NamespaceId::from(u64::from(tx.namespace()) + 1))
2251 .await
2252 .unwrap();
2253 assert_eq!(txs, []);
2254 }
2255 }
2256
2257 let ns = client
2259 .payload(1)
2260 .await
2261 .transaction(&TransactionIndex {
2262 ns_index: 0.into(),
2263 position: 0,
2264 })
2265 .unwrap()
2266 .namespace();
2267 let namespaces = lc.fetch_namespaces_in_range(1, 10, ns).await.unwrap();
2268 assert_eq!(namespaces.len(), 9);
2269 }
2270
2271 #[tokio::test]
2272 #[test_log::test]
2273 async fn test_fetch_all_namespaces_in_range() {
2274 let client = TestClient::default();
2275 let lc = LightClient::from_genesis(
2276 SqlStorage::default().await.unwrap(),
2277 client.clone(),
2278 client.genesis().await,
2279 );
2280
2281 let mut requested = vec![];
2284 for height in 1..4 {
2285 requested.push(
2286 client
2287 .payload(height)
2288 .await
2289 .transaction(&TransactionIndex {
2290 ns_index: 0.into(),
2291 position: 0,
2292 })
2293 .unwrap()
2294 .namespace(),
2295 );
2296 }
2297 let absent =
2298 NamespaceId::from(requested.iter().map(|ns| u64::from(*ns)).max().unwrap() + 1);
2299 requested.push(absent);
2300
2301 let (start, end) = (1, 10);
2302 let blocks = lc
2303 .fetch_all_namespaces_in_range(start, end, &requested)
2304 .await
2305 .unwrap();
2306 assert_eq!(blocks.len(), end - start);
2307
2308 for (i, block) in blocks.iter().enumerate() {
2309 let height = i + 1;
2310 let header = client.leaf(height).await.header().clone();
2311
2312 assert!(!block.contains_key(&absent), "block {height}");
2315 for &ns in &requested {
2316 let expected = lc
2317 .fetch_namespace(BlockId::Number(height), ns)
2318 .await
2319 .unwrap();
2320 if header.ns_table().find_ns_id(&ns).is_some() {
2321 assert_eq!(block.get(&ns), Some(&expected), "block {height}, ns {ns}");
2322 } else {
2323 assert!(!block.contains_key(&ns), "block {height}, ns {ns}");
2324 }
2325 }
2326 }
2327 }
2328
2329 #[tokio::test]
2330 #[test_log::test]
2331 async fn test_fetch_namespace_invalid() {
2332 let client = TestClient::default();
2333 let lc = LightClient::from_genesis(
2334 SqlStorage::default().await.unwrap(),
2335 client.clone(),
2336 client.genesis().await,
2337 );
2338
2339 let payload = client.payload(1).await;
2340 let ns_index = NsIndex::from(0);
2341 let tx = payload
2342 .transaction(&TransactionIndex {
2343 ns_index,
2344 position: 0,
2345 })
2346 .unwrap();
2347
2348 client.return_invalid_payload(1).await;
2349 let err = lc
2350 .fetch_namespace(BlockId::Number(1), tx.namespace())
2351 .await
2352 .unwrap_err();
2353 assert!(
2354 err.to_string().contains("invalid namespace proof"),
2355 "{err:#}"
2356 );
2357 }
2358
2359 #[tokio::test]
2360 #[test_log::test]
2361 async fn test_genesis_round_trip() {
2362 let client = TestClient::default();
2363 let genesis = client.genesis().await;
2364 let lc = LightClient::from_genesis(
2365 SqlStorage::default().await.unwrap(),
2366 client.clone(),
2367 genesis.clone(),
2368 );
2369 assert_eq!(lc.genesis(), genesis);
2370 }
2371}
2372
2373#[cfg(all(test, feature = "rlp"))]
2374mod rlp_test {
2375 use alloy::primitives::U256;
2376 use alloy_rlp::{Decodable, Encodable};
2377 use hotshot_types::traits::signature_key::SignatureKey;
2378
2379 use super::*;
2380
2381 #[test_log::test]
2382 fn rlp_genesis_round_trip() {
2383 let genesis = Genesis {
2384 epoch_height: 1000,
2385 first_epoch_with_dynamic_stake_table: EpochNumber::new(3),
2386 stake_table: vec![StakeTableEntry {
2387 stake_key: PubKey::generated_from_seed_indexed(
2388 Default::default(),
2389 Default::default(),
2390 )
2391 .0,
2392 stake_amount: U256::MAX,
2393 }],
2394 chain_id: DECAF_CHAIN_ID,
2395 };
2396
2397 let mut buf = vec![];
2398 genesis.encode(&mut buf);
2399
2400 let mut buf = buf.as_slice();
2401 assert_eq!(genesis, Genesis::decode(&mut buf).unwrap());
2402 assert!(buf.is_empty());
2403 }
2404}