1use std::{
55 cmp::{max, min},
56 fmt::{Debug, Display},
57 iter::repeat_with,
58 marker::PhantomData,
59 ops::{Bound, Range, RangeBounds},
60 sync::Arc,
61 time::{Duration, Instant},
62};
63
64use anyhow::{Context, bail};
65use async_lock::Semaphore;
66use async_trait::async_trait;
67use backoff::{ExponentialBackoff, ExponentialBackoffBuilder, backoff::Backoff};
68use chrono::{DateTime, Utc};
69use derivative::Derivative;
70use futures::{
71 channel::oneshot,
72 future::{self, BoxFuture, Either, Future, FutureExt, join_all},
73 stream::{self, BoxStream, StreamExt},
74};
75use hotshot_types::{
76 data::VidShare,
77 simple_certificate::CertificatePair,
78 traits::{
79 metrics::{Counter, Gauge, Histogram, Metrics},
80 node_implementation::NodeType,
81 },
82};
83use jf_merkle_tree_compat::{MerkleTreeScheme, prelude::MerkleProof};
84use tagged_base64::TaggedBase64;
85use tokio::{
86 spawn,
87 sync::Mutex,
88 time::{sleep, timeout},
89};
90use tracing::Instrument;
91
92use super::{
93 Transaction, VersionedDataSource,
94 notifier::Notifier,
95 storage::{
96 Aggregate, AggregatesStorage, AvailabilityStorage, ExplorerStorage,
97 MerklizedStateHeightStorage, MerklizedStateStorage, NodeStorage, SerializableRetry,
98 UpdateAggregatesStorage, UpdateAvailabilityStorage,
99 pruning::{PruneStorage, PrunedHeightDataSource, PrunedHeightStorage},
100 },
101};
102use crate::{
103 Header, Payload, QueryError, QueryResult,
104 availability::{
105 AvailabilityDataSource, BlockId, BlockInfo, BlockQueryData, BlockWithTransaction,
106 Certificate2, Fetch, FetchStream, HeaderQueryData, LeafId, LeafQueryData, NamespaceId,
107 PayloadMetadata, PayloadQueryData, QueryableHeader, QueryablePayload, TransactionHash,
108 UpdateAvailabilityData, VidCommonMetadata, VidCommonQueryData,
109 },
110 data_source::fetching::{leaf::RangeRequest, vid::VidCommonRangeFetcher},
111 explorer::{self, ExplorerDataSource},
112 fetching::{self, NonEmptyRange, Provider, request},
113 merklized_state::{
114 MerklizedState, MerklizedStateDataSource, MerklizedStateHeightPersistence, Snapshot,
115 },
116 metrics::PrometheusMetrics,
117 node::{
118 NodeDataSource, SyncStatus, SyncStatusQueryData, SyncStatusRange, TimeWindowQueryData,
119 WindowStart,
120 },
121 serializable_retry,
122 status::{HasMetrics, StatusDataSource},
123 task::BackgroundTask,
124 types::HeightIndexed,
125};
126
127mod block;
128mod header;
129mod leaf;
130mod transaction;
131mod vid;
132
133use self::{
134 block::{PayloadFetcher, PayloadRangeFetcher},
135 leaf::{LeafFetcher, LeafRangeFetcher},
136 transaction::TransactionRequest,
137 vid::{VidCommonFetcher, VidCommonRequest},
138};
139
140pub struct Builder<Types, S, P> {
142 storage: S,
143 provider: P,
144 backoff: ExponentialBackoffBuilder,
145 rate_limit: usize,
146 range_chunk_size: usize,
147 proactive_interval: Duration,
148 proactive_range_chunk_size: usize,
149 sync_status_chunk_size: usize,
150 active_fetch_delay: Duration,
151 chunk_fetch_delay: Duration,
152 proactive_fetching: bool,
153 aggregator: bool,
154 aggregator_chunk_size: Option<usize>,
155 leaf_only: bool,
156 sync_status_ttl: Duration,
157 _types: PhantomData<Types>,
158}
159
160impl<Types, S, P> Builder<Types, S, P> {
161 pub fn new(storage: S, provider: P) -> Self {
163 let mut default_backoff = ExponentialBackoffBuilder::default();
164 default_backoff
165 .with_initial_interval(Duration::from_secs(1))
166 .with_multiplier(2.)
167 .with_max_interval(Duration::from_secs(32))
168 .with_max_elapsed_time(Some(Duration::from_secs(64)));
169
170 Self {
171 storage,
172 provider,
173 backoff: default_backoff,
174 rate_limit: 32,
175 range_chunk_size: 25,
176 proactive_interval: Duration::from_hours(8),
177 proactive_range_chunk_size: 100,
178 sync_status_chunk_size: 100_000,
179 active_fetch_delay: Duration::from_millis(50),
180 chunk_fetch_delay: Duration::from_millis(100),
181 proactive_fetching: true,
182 aggregator: true,
183 aggregator_chunk_size: None,
184 leaf_only: false,
185 sync_status_ttl: Duration::from_mins(5),
186 _types: Default::default(),
187 }
188 }
189
190 pub fn leaf_only(mut self) -> Self {
191 self.leaf_only = true;
192 self
193 }
194
195 pub fn with_min_retry_interval(mut self, interval: Duration) -> Self {
197 self.backoff.with_initial_interval(interval);
198 self
199 }
200
201 pub fn with_max_retry_interval(mut self, interval: Duration) -> Self {
203 self.backoff.with_max_interval(interval);
204 self
205 }
206
207 pub fn with_retry_multiplier(mut self, multiplier: f64) -> Self {
209 self.backoff.with_multiplier(multiplier);
210 self
211 }
212
213 pub fn with_retry_randomization_factor(mut self, factor: f64) -> Self {
215 self.backoff.with_randomization_factor(factor);
216 self
217 }
218
219 pub fn with_retry_timeout(mut self, timeout: Duration) -> Self {
221 self.backoff.with_max_elapsed_time(Some(timeout));
222 self
223 }
224
225 pub fn with_rate_limit(mut self, with_rate_limit: usize) -> Self {
227 self.rate_limit = with_rate_limit;
228 self
229 }
230
231 pub fn with_range_chunk_size(mut self, range_chunk_size: usize) -> Self {
238 self.range_chunk_size = range_chunk_size;
239 self
240 }
241
242 pub fn with_proactive_interval(mut self, interval: Duration) -> Self {
246 self.proactive_interval = interval;
247 self
248 }
249
250 pub fn with_proactive_range_chunk_size(mut self, range_chunk_size: usize) -> Self {
256 self.proactive_range_chunk_size = range_chunk_size;
257 self
258 }
259
260 pub fn with_sync_status_chunk_size(mut self, chunk_size: usize) -> Self {
263 self.sync_status_chunk_size = chunk_size;
264 self
265 }
266
267 pub fn with_sync_status_ttl(mut self, ttl: Duration) -> Self {
273 self.sync_status_ttl = ttl;
274 self
275 }
276
277 pub fn with_active_fetch_delay(mut self, active_fetch_delay: Duration) -> Self {
283 self.active_fetch_delay = active_fetch_delay;
284 self
285 }
286
287 pub fn with_chunk_fetch_delay(mut self, chunk_fetch_delay: Duration) -> Self {
299 self.chunk_fetch_delay = chunk_fetch_delay;
300 self
301 }
302
303 pub fn disable_proactive_fetching(mut self) -> Self {
312 self.proactive_fetching = false;
313 self
314 }
315
316 pub fn disable_aggregator(mut self) -> Self {
321 self.aggregator = false;
322 self
323 }
324
325 pub fn with_aggregator_chunk_size(mut self, chunk_size: usize) -> Self {
334 self.aggregator_chunk_size = Some(chunk_size);
335 self
336 }
337
338 pub fn is_leaf_only(&self) -> bool {
339 self.leaf_only
340 }
341}
342
343impl<Types, S, P> Builder<Types, S, P>
344where
345 Types: NodeType,
346 Payload<Types>: QueryablePayload<Types>,
347 Header<Types>: QueryableHeader<Types>,
348 S: PruneStorage + VersionedDataSource + HasMetrics + 'static,
349 for<'a> S::ReadOnly<'a>: AvailabilityStorage<Types>
350 + PrunedHeightStorage
351 + NodeStorage<Types>
352 + AggregatesStorage<Types>,
353 for<'a> S::Transaction<'a>: UpdateAvailabilityStorage<Types> + UpdateAggregatesStorage<Types>,
354 P: AvailabilityProvider<Types>,
355{
356 pub async fn build(self) -> anyhow::Result<FetchingDataSource<Types, S, P>> {
358 FetchingDataSource::new(self).await
359 }
360}
361
362#[derive(Derivative)]
379#[derivative(Clone(bound = ""), Debug(bound = "S: Debug, P: Debug"))]
380pub struct FetchingDataSource<Types, S, P>
381where
382 Types: NodeType,
383{
384 fetcher: Arc<Fetcher<Types, S, P>>,
389 scanner: Option<BackgroundTask>,
391 aggregator: Option<BackgroundTask>,
393 pruner: Pruner<Types, S>,
394}
395
396#[derive(Derivative)]
397#[derivative(Clone(bound = ""), Debug(bound = "S: Debug, "))]
398pub struct Pruner<Types, S>
399where
400 Types: NodeType,
401{
402 handle: Option<BackgroundTask>,
403 _types: PhantomData<(Types, S)>,
404}
405
406impl<Types, S> Pruner<Types, S>
407where
408 Types: NodeType,
409 Header<Types>: QueryableHeader<Types>,
410 Payload<Types>: QueryablePayload<Types>,
411 S: PruneStorage + Send + Sync + 'static,
412{
413 async fn new(storage: Arc<S>, backoff: ExponentialBackoff) -> Self {
414 let cfg = storage.get_pruning_config();
415 let Some(cfg) = cfg else {
416 return Self {
417 handle: None,
418 _types: Default::default(),
419 };
420 };
421
422 let future = async move {
423 for i in 1.. {
424 sleep(cfg.interval()).await;
427
428 tracing::warn!("starting pruner run {i} ");
429 Self::prune(storage.clone(), &backoff).await;
430 }
431 };
432
433 let task = BackgroundTask::spawn("pruner", future);
434
435 Self {
436 handle: Some(task),
437 _types: Default::default(),
438 }
439 }
440
441 async fn prune(storage: Arc<S>, backoff: &ExponentialBackoff) {
442 let mut pruner = S::Pruner::default();
444 'run: loop {
445 let mut backoff = backoff.clone();
446 backoff.reset();
447 'batch: loop {
448 match storage.prune(&mut pruner).await {
449 Ok(Some(height)) => {
450 tracing::warn!("Pruned to height {height}");
451 break 'batch;
452 },
453 Ok(None) => {
454 tracing::warn!("pruner run complete.");
455 break 'run;
456 },
457 Err(e) => {
458 tracing::warn!("error pruning batch: {e:#}");
459 if let Some(delay) = backoff.next_backoff() {
460 sleep(delay).await;
461 } else {
462 tracing::error!("pruning run failed after too many errors: {e:#}");
463 break 'run;
464 }
465 },
466 }
467 }
468 }
469 }
470}
471
472impl<Types, S, P> FetchingDataSource<Types, S, P>
473where
474 Types: NodeType,
475 Payload<Types>: QueryablePayload<Types>,
476 Header<Types>: QueryableHeader<Types>,
477 S: VersionedDataSource + PruneStorage + HasMetrics + 'static,
478 for<'a> S::Transaction<'a>: UpdateAvailabilityStorage<Types> + UpdateAggregatesStorage<Types>,
479 for<'a> S::ReadOnly<'a>: AvailabilityStorage<Types>
480 + NodeStorage<Types>
481 + PrunedHeightStorage
482 + AggregatesStorage<Types>,
483 P: AvailabilityProvider<Types>,
484{
485 pub fn builder(storage: S, provider: P) -> Builder<Types, S, P> {
487 Builder::new(storage, provider)
488 }
489
490 async fn new(builder: Builder<Types, S, P>) -> anyhow::Result<Self> {
491 let leaf_only = builder.is_leaf_only();
492 let aggregator = builder.aggregator;
493 let aggregator_chunk_size = builder
494 .aggregator_chunk_size
495 .unwrap_or(builder.range_chunk_size);
496 let proactive_fetching = builder.proactive_fetching;
497 let proactive_interval = builder.proactive_interval;
498 let proactive_range_chunk_size = builder.proactive_range_chunk_size;
499 let backoff = builder.backoff.build();
500 let scanner_metrics = ScannerMetrics::new(builder.storage.metrics());
501 let aggregator_metrics = AggregatorMetrics::new(builder.storage.metrics());
502
503 let fetcher = Arc::new(Fetcher::new(builder).await?);
504 let scanner = if proactive_fetching && !leaf_only {
505 Some(BackgroundTask::spawn(
506 "proactive scanner",
507 fetcher.clone().proactive_scan(
508 proactive_interval,
509 proactive_range_chunk_size,
510 scanner_metrics,
511 ),
512 ))
513 } else {
514 None
515 };
516
517 let aggregator = if aggregator && !leaf_only {
518 Some(BackgroundTask::spawn(
519 "aggregator",
520 fetcher
521 .clone()
522 .aggregate(aggregator_chunk_size, aggregator_metrics),
523 ))
524 } else {
525 None
526 };
527
528 let storage = fetcher.storage.clone();
529
530 let pruner = Pruner::new(storage, backoff).await;
531 let ds = Self {
532 fetcher,
533 scanner,
534 pruner,
535 aggregator,
536 };
537
538 Ok(ds)
539 }
540
541 pub fn inner(&self) -> Arc<S> {
543 self.fetcher.storage.clone()
544 }
545}
546
547impl<Types, S, P> AsRef<S> for FetchingDataSource<Types, S, P>
548where
549 Types: NodeType,
550{
551 fn as_ref(&self) -> &S {
552 &self.fetcher.storage
553 }
554}
555
556impl<Types, S, P> HasMetrics for FetchingDataSource<Types, S, P>
557where
558 Types: NodeType,
559 S: HasMetrics,
560{
561 fn metrics(&self) -> &PrometheusMetrics {
562 self.as_ref().metrics()
563 }
564}
565
566#[async_trait]
567impl<Types, S, P> StatusDataSource for FetchingDataSource<Types, S, P>
568where
569 Types: NodeType,
570 Header<Types>: QueryableHeader<Types>,
571 S: VersionedDataSource + HasMetrics + SerializableRetry + Send + Sync + 'static,
572 for<'a> S::ReadOnly<'a>: NodeStorage<Types>,
573 P: Send + Sync,
574{
575 async fn block_height(&self) -> QueryResult<usize> {
576 serializable_retry!(self.fetcher.storage, || async {
577 let mut tx = self.read().await.map_err(|err| QueryError::Error {
578 message: err.to_string(),
579 })?;
580 tx.block_height().await
581 })
582 .await
583 }
584}
585
586#[async_trait]
587impl<Types, S, P> PrunedHeightDataSource for FetchingDataSource<Types, S, P>
588where
589 Types: NodeType,
590 S: VersionedDataSource + HasMetrics + SerializableRetry + Send + Sync + 'static,
591 for<'a> S::ReadOnly<'a>: PrunedHeightStorage,
592 P: Send + Sync,
593{
594 async fn load_pruned_height(&self) -> anyhow::Result<Option<u64>> {
595 serializable_retry!(self.fetcher.storage, || async {
596 let mut tx = self.read().await?;
597 tx.load_pruned_height().await
598 })
599 .await
600 }
601
602 async fn load_state_pruned_height(&self) -> anyhow::Result<Option<u64>> {
603 let mut tx = self.read().await?;
604 tx.load_state_pruned_height().await
605 }
606}
607
608#[async_trait]
609impl<Types, S, P> AvailabilityDataSource<Types> for FetchingDataSource<Types, S, P>
610where
611 Types: NodeType,
612 Header<Types>: QueryableHeader<Types>,
613 Payload<Types>: QueryablePayload<Types>,
614 S: VersionedDataSource + 'static,
615 for<'a> S::Transaction<'a>: UpdateAvailabilityStorage<Types>,
616 for<'a> S::ReadOnly<'a>: AvailabilityStorage<Types> + NodeStorage<Types> + PrunedHeightStorage,
617 P: AvailabilityProvider<Types>,
618{
619 async fn get_leaf<ID>(&self, id: ID) -> Fetch<LeafQueryData<Types>>
620 where
621 ID: Into<LeafId<Types>> + Send + Sync,
622 {
623 self.fetcher.get(id.into()).await
624 }
625
626 async fn get_header<ID>(&self, id: ID) -> Fetch<Header<Types>>
627 where
628 ID: Into<BlockId<Types>> + Send + Sync,
629 {
630 self.fetcher
631 .get::<HeaderQueryData<_>>(id.into())
632 .await
633 .map(|h| h.header)
634 }
635
636 async fn get_block<ID>(&self, id: ID) -> Fetch<BlockQueryData<Types>>
637 where
638 ID: Into<BlockId<Types>> + Send + Sync,
639 {
640 self.fetcher.get(id.into()).await
641 }
642
643 async fn get_payload<ID>(&self, id: ID) -> Fetch<PayloadQueryData<Types>>
644 where
645 ID: Into<BlockId<Types>> + Send + Sync,
646 {
647 self.fetcher.get(id.into()).await
648 }
649
650 async fn get_payload_metadata<ID>(&self, id: ID) -> Fetch<PayloadMetadata<Types>>
651 where
652 ID: Into<BlockId<Types>> + Send + Sync,
653 {
654 self.fetcher.get(id.into()).await
655 }
656
657 async fn get_vid_common<ID>(&self, id: ID) -> Fetch<VidCommonQueryData<Types>>
658 where
659 ID: Into<BlockId<Types>> + Send + Sync,
660 {
661 self.fetcher.get(VidCommonRequest::from(id.into())).await
662 }
663
664 async fn get_vid_common_metadata<ID>(&self, id: ID) -> Fetch<VidCommonMetadata<Types>>
665 where
666 ID: Into<BlockId<Types>> + Send + Sync,
667 {
668 self.fetcher.get(VidCommonRequest::from(id.into())).await
669 }
670
671 async fn get_leaf_range<R>(&self, range: R) -> FetchStream<LeafQueryData<Types>>
672 where
673 R: RangeBounds<usize> + Send + 'static,
674 {
675 self.fetcher.clone().get_range(range)
676 }
677
678 async fn get_block_range<R>(&self, range: R) -> FetchStream<BlockQueryData<Types>>
679 where
680 R: RangeBounds<usize> + Send + 'static,
681 {
682 self.fetcher.clone().get_range(range)
683 }
684
685 async fn get_header_range<R>(&self, range: R) -> FetchStream<Header<Types>>
686 where
687 R: RangeBounds<usize> + Send + 'static,
688 {
689 let leaves: FetchStream<LeafQueryData<Types>> = self.fetcher.clone().get_range(range);
690
691 leaves
692 .map(|fetch| fetch.map(|leaf| leaf.leaf.block_header().clone()))
693 .boxed()
694 }
695
696 async fn get_payload_range<R>(&self, range: R) -> FetchStream<PayloadQueryData<Types>>
697 where
698 R: RangeBounds<usize> + Send + 'static,
699 {
700 self.fetcher.clone().get_range(range)
701 }
702
703 async fn get_payload_metadata_range<R>(&self, range: R) -> FetchStream<PayloadMetadata<Types>>
704 where
705 R: RangeBounds<usize> + Send + 'static,
706 {
707 self.fetcher.clone().get_range(range)
708 }
709
710 async fn get_vid_common_range<R>(&self, range: R) -> FetchStream<VidCommonQueryData<Types>>
711 where
712 R: RangeBounds<usize> + Send + 'static,
713 {
714 self.fetcher.clone().get_range(range)
715 }
716
717 async fn get_vid_common_metadata_range<R>(
718 &self,
719 range: R,
720 ) -> FetchStream<VidCommonMetadata<Types>>
721 where
722 R: RangeBounds<usize> + Send + 'static,
723 {
724 self.fetcher.clone().get_range(range)
725 }
726
727 async fn get_leaf_range_rev(
728 &self,
729 start: Bound<usize>,
730 end: usize,
731 ) -> FetchStream<LeafQueryData<Types>> {
732 self.fetcher.clone().get_range_rev(start, end)
733 }
734
735 async fn get_block_range_rev(
736 &self,
737 start: Bound<usize>,
738 end: usize,
739 ) -> FetchStream<BlockQueryData<Types>> {
740 self.fetcher.clone().get_range_rev(start, end)
741 }
742
743 async fn get_payload_range_rev(
744 &self,
745 start: Bound<usize>,
746 end: usize,
747 ) -> FetchStream<PayloadQueryData<Types>> {
748 self.fetcher.clone().get_range_rev(start, end)
749 }
750
751 async fn get_payload_metadata_range_rev(
752 &self,
753 start: Bound<usize>,
754 end: usize,
755 ) -> FetchStream<PayloadMetadata<Types>> {
756 self.fetcher.clone().get_range_rev(start, end)
757 }
758
759 async fn get_vid_common_range_rev(
760 &self,
761 start: Bound<usize>,
762 end: usize,
763 ) -> FetchStream<VidCommonQueryData<Types>> {
764 self.fetcher.clone().get_range_rev(start, end)
765 }
766
767 async fn get_vid_common_metadata_range_rev(
768 &self,
769 start: Bound<usize>,
770 end: usize,
771 ) -> FetchStream<VidCommonMetadata<Types>> {
772 self.fetcher.clone().get_range_rev(start, end)
773 }
774
775 async fn get_block_containing_transaction(
776 &self,
777 h: TransactionHash<Types>,
778 ) -> Fetch<BlockWithTransaction<Types>> {
779 self.fetcher.clone().get(TransactionRequest::from(h)).await
780 }
781
782 async fn get_cert2(&self, height: u64) -> QueryResult<Option<Certificate2<Types>>> {
783 self.fetcher.get_cert2(height).await
784 }
785}
786
787impl<Types, S, P> UpdateAvailabilityData<Types> for FetchingDataSource<Types, S, P>
788where
789 Types: NodeType,
790 Header<Types>: QueryableHeader<Types>,
791 Payload<Types>: QueryablePayload<Types>,
792 S: VersionedDataSource + 'static,
793 for<'a> S::Transaction<'a>: UpdateAvailabilityStorage<Types>,
794 for<'a> S::ReadOnly<'a>: AvailabilityStorage<Types> + NodeStorage<Types> + PrunedHeightStorage,
795 P: AvailabilityProvider<Types>,
796{
797 async fn append(&self, info: BlockInfo<Types>) -> anyhow::Result<()> {
798 let height = info.height() as usize;
799
800 self.fetcher
802 .store(&(info.leaf.clone(), info.qc_chain, info.cert2.clone()))
803 .await;
804
805 leaf::trigger_fetch_for_parent(&self.fetcher, &info.leaf);
807
808 let block = match info.block {
822 Some(block) => Some(block),
823 None => match self.fetcher.get::<BlockQueryData<Types>>(height).await {
824 Fetch::Ready(block) => Some(block),
825 Fetch::Pending(fut) => {
826 let span = tracing::info_span!("fetch missing block", height);
827 spawn(
828 async move {
829 tracing::info!("fetching missing block");
830 fut.await;
831 }
832 .instrument(span),
833 );
834 None
835 },
836 },
837 };
838 if let Some(block) = &block {
839 self.fetcher.store(block).await;
840 }
841 let vid = match info.vid_common {
842 Some(vid) => Some(vid),
843 None => match self.fetcher.get::<VidCommonQueryData<Types>>(height).await {
844 Fetch::Ready(vid) => Some(vid),
845 Fetch::Pending(fut) => {
846 let span = tracing::info_span!("fetch missing VID common", height);
847 spawn(
848 async move {
849 tracing::info!("fetching missing VID common");
850 fut.await;
851 }
852 .instrument(span),
853 );
854 None
855 },
856 },
857 };
858 if let Some(vid) = &vid {
859 self.fetcher.store(&(vid.clone(), info.vid_share)).await;
860 }
861
862 info.leaf.notify(&self.fetcher.notifiers).await;
868 if let Some(block) = &block {
869 block.notify(&self.fetcher.notifiers).await;
870 }
871 if let Some(vid) = &vid {
872 vid.notify(&self.fetcher.notifiers).await;
873 }
874
875 Ok(())
876 }
877
878 async fn append_payload(&self, block: BlockQueryData<Types>) -> anyhow::Result<()> {
891 let height = block.height();
892 let leaf = {
893 let mut tx = self.read().await.context("opening read transaction")?;
894 match tx.get_leaf(LeafId::Number(height as usize)).await {
895 Ok(leaf) => leaf,
896 Err(QueryError::Missing | QueryError::NotFound) => {
897 tracing::info!(
898 height,
899 "dropping reconstructed payload; leaf not yet available"
900 );
901 return Ok(());
902 },
903 Err(err) => {
904 return Err(err).context(format!(
905 "loading leaf {height} to verify reconstructed payload"
906 ));
907 },
908 }
909 };
910 if leaf.block_hash() != block.hash() {
911 tracing::warn!(
912 height,
913 decided = %leaf.block_hash(),
914 reconstructed = %block.hash(),
915 "reconstructed payload does not match decided block; discarding"
916 );
917 return Ok(());
918 }
919 self.fetcher.store_and_notify(&block).await;
920 Ok(())
921 }
922}
923
924impl<Types, S, P> VersionedDataSource for FetchingDataSource<Types, S, P>
925where
926 Types: NodeType,
927 S: VersionedDataSource + Send + Sync,
928 P: Send + Sync,
929{
930 type Transaction<'a>
931 = S::Transaction<'a>
932 where
933 Self: 'a;
934 type ReadOnly<'a>
935 = S::ReadOnly<'a>
936 where
937 Self: 'a;
938
939 async fn write(&self) -> anyhow::Result<Self::Transaction<'_>> {
940 self.fetcher.write().await
941 }
942
943 async fn read(&self) -> anyhow::Result<Self::ReadOnly<'_>> {
944 self.fetcher.read().await
945 }
946}
947
948#[derive(Debug)]
950struct Fetcher<Types, S, P>
951where
952 Types: NodeType,
953{
954 storage: Arc<S>,
955 notifiers: Notifiers<Types>,
956 provider: Arc<P>,
957 leaf_fetcher: Arc<LeafFetcher<Types, S, P>>,
958 leaf_range_fetcher: Arc<LeafRangeFetcher<Types, S, P>>,
959 payload_fetcher: Option<Arc<PayloadFetcher<Types, S, P>>>,
960 payload_range_fetcher: Option<Arc<PayloadRangeFetcher<Types, S, P>>>,
961 vid_common_fetcher: Option<Arc<VidCommonFetcher<Types, S, P>>>,
962 vid_common_range_fetcher: Option<Arc<VidCommonRangeFetcher<Types, S, P>>>,
963 range_chunk_size: usize,
964 sync_status_chunk_size: usize,
965 active_fetch_delay: Duration,
967 chunk_fetch_delay: Duration,
969 backoff: ExponentialBackoff,
971 retry_semaphore: Arc<Semaphore>,
974 leaf_only: bool,
975 sync_status_metrics: SyncStatusMetrics,
976 sync_status: Mutex<CachedSyncStatus>,
977}
978
979impl<Types, S, P> VersionedDataSource for Fetcher<Types, S, P>
980where
981 Types: NodeType,
982 S: VersionedDataSource + Send + Sync,
983 P: Send + Sync,
984{
985 type Transaction<'a>
986 = S::Transaction<'a>
987 where
988 Self: 'a;
989 type ReadOnly<'a>
990 = S::ReadOnly<'a>
991 where
992 Self: 'a;
993
994 async fn write(&self) -> anyhow::Result<Self::Transaction<'_>> {
995 self.storage.write().await
996 }
997
998 async fn read(&self) -> anyhow::Result<Self::ReadOnly<'_>> {
999 self.storage.read().await
1000 }
1001}
1002
1003impl<Types, S, P> Fetcher<Types, S, P>
1004where
1005 Types: NodeType,
1006 Header<Types>: QueryableHeader<Types>,
1007 S: VersionedDataSource + HasMetrics + Sync,
1008 for<'a> S::ReadOnly<'a>: PrunedHeightStorage + NodeStorage<Types>,
1009{
1010 pub async fn new(builder: Builder<Types, S, P>) -> anyhow::Result<Self> {
1011 let retry_semaphore = Arc::new(Semaphore::new(builder.rate_limit));
1012 let backoff = builder.backoff.build();
1013
1014 let (payload_fetcher, payload_range_fetcher, vid_common_fetcher, vid_common_range_fetcher) =
1015 if builder.is_leaf_only() {
1016 (None, None, None, None)
1017 } else {
1018 (
1019 Some(Arc::new(fetching::Fetcher::new(
1020 retry_semaphore.clone(),
1021 backoff.clone(),
1022 ))),
1023 Some(Arc::new(fetching::Fetcher::new(
1024 retry_semaphore.clone(),
1025 backoff.clone(),
1026 ))),
1027 Some(Arc::new(fetching::Fetcher::new(
1028 retry_semaphore.clone(),
1029 backoff.clone(),
1030 ))),
1031 Some(Arc::new(fetching::Fetcher::new(
1032 retry_semaphore.clone(),
1033 backoff.clone(),
1034 ))),
1035 )
1036 };
1037 let leaf_fetcher = fetching::Fetcher::new(retry_semaphore.clone(), backoff.clone());
1038 let leaf_range_fetcher = fetching::Fetcher::new(retry_semaphore.clone(), backoff.clone());
1039
1040 let leaf_only = builder.leaf_only;
1041 let sync_status_metrics =
1042 SyncStatusMetrics::new(builder.storage.metrics(), builder.sync_status_chunk_size);
1043
1044 Ok(Self {
1045 storage: Arc::new(builder.storage),
1046 notifiers: Default::default(),
1047 provider: Arc::new(builder.provider),
1048 leaf_fetcher: Arc::new(leaf_fetcher),
1049 leaf_range_fetcher: Arc::new(leaf_range_fetcher),
1050 payload_fetcher,
1051 payload_range_fetcher,
1052 vid_common_fetcher,
1053 vid_common_range_fetcher,
1054 range_chunk_size: builder.range_chunk_size,
1055 sync_status_chunk_size: builder.sync_status_chunk_size,
1056 active_fetch_delay: builder.active_fetch_delay,
1057 chunk_fetch_delay: builder.chunk_fetch_delay,
1058 backoff,
1059 retry_semaphore,
1060 leaf_only,
1061 sync_status_metrics,
1062 sync_status: Mutex::new(CachedSyncStatus::new(builder.sync_status_ttl)),
1063 })
1064 }
1065}
1066
1067impl<Types, S, P> Fetcher<Types, S, P>
1068where
1069 Types: NodeType,
1070 Header<Types>: QueryableHeader<Types>,
1071 Payload<Types>: QueryablePayload<Types>,
1072 S: VersionedDataSource + 'static,
1073 for<'a> S::Transaction<'a>: UpdateAvailabilityStorage<Types>,
1074 for<'a> S::ReadOnly<'a>: AvailabilityStorage<Types> + NodeStorage<Types> + PrunedHeightStorage,
1075 P: AvailabilityProvider<Types>,
1076{
1077 async fn get<T>(self: &Arc<Self>, req: impl Into<T::Request> + Send) -> Fetch<T>
1078 where
1079 T: Fetchable<Types>,
1080 {
1081 let req = req.into();
1082
1083 let passive_fetch = T::passive_fetch(&self.notifiers, req).await;
1093
1094 match self.try_get(req).await {
1095 Ok(Some(obj)) => return Fetch::Ready(obj),
1096 Ok(None) => return passive(req, passive_fetch),
1097 Err(err) => {
1098 tracing::warn!(
1099 ?req,
1100 "unable to fetch object; spawning a task to retry: {err:#}"
1101 );
1102 },
1103 }
1104
1105 let (send, recv) = oneshot::channel();
1107
1108 let fetcher = self.clone();
1109 let mut backoff = fetcher.backoff.clone();
1110 let span = tracing::warn_span!("get retry", ?req);
1111 spawn(
1112 async move {
1113 backoff.reset();
1114 let mut delay = backoff.next_backoff().unwrap_or(Duration::from_secs(1));
1115 loop {
1116 let res = {
1117 let _guard = fetcher.retry_semaphore.acquire().await;
1121 fetcher.try_get(req).await
1122 };
1123 match res {
1124 Ok(Some(obj)) => {
1125 tracing::info!(?req, "object was ready after retries");
1129 send.send(obj).ok();
1130 break;
1131 },
1132 Ok(None) => {
1133 tracing::info!(?req, "spawned fetch after retries");
1137 break;
1138 },
1139 Err(err) => {
1140 tracing::warn!(
1141 ?req,
1142 ?delay,
1143 "unable to fetch object, will retry: {err:#}"
1144 );
1145 sleep(delay).await;
1146 if let Some(next_delay) = backoff.next_backoff() {
1147 delay = next_delay;
1148 }
1149 },
1150 }
1151 }
1152 }
1153 .instrument(span),
1154 );
1155
1156 passive(req, select_some(passive_fetch, recv.map(Result::ok)))
1159 }
1160
1161 async fn try_get<T>(self: &Arc<Self>, req: T::Request) -> anyhow::Result<Option<T>>
1172 where
1173 T: Fetchable<Types>,
1174 {
1175 let mut tx = self.read().await.context("opening read transaction")?;
1176 match T::load(&mut tx, req).await {
1177 Ok(t) => Ok(Some(t)),
1178 Err(QueryError::Missing | QueryError::NotFound) => {
1179 tracing::debug!(?req, "object missing from local storage, will try to fetch");
1182 self.fetch::<T>(&mut tx, req).await?;
1183 Ok(None)
1184 },
1185 Err(err) => {
1186 bail!("failed to fetch resource {req:?} from local storage: {err:#}");
1189 },
1190 }
1191 }
1192
1193 fn get_range<R, T>(self: Arc<Self>, range: R) -> BoxStream<'static, Fetch<T>>
1205 where
1206 R: RangeBounds<usize> + Send + 'static,
1207 T: RangedFetchable<Types>,
1208 {
1209 let chunk_size = self.range_chunk_size;
1210 self.get_range_with_chunk_size(chunk_size, range)
1211 }
1212
1213 fn get_range_with_chunk_size<R, T>(
1215 self: Arc<Self>,
1216 chunk_size: usize,
1217 range: R,
1218 ) -> BoxStream<'static, Fetch<T>>
1219 where
1220 R: RangeBounds<usize> + Send + 'static,
1221 T: RangedFetchable<Types>,
1222 {
1223 let chunk_fetch_delay = self.chunk_fetch_delay;
1224 let active_fetch_delay = self.active_fetch_delay;
1225
1226 stream::iter(range_chunks(range, chunk_size))
1227 .then(move |chunk| {
1228 let self_clone = self.clone();
1229 async move {
1230 {
1231 let chunk = self_clone.get_chunk(chunk).await;
1232
1233 sleep(chunk_fetch_delay).await;
1238 stream::iter(chunk)
1239 }
1240 }
1241 })
1242 .flatten()
1243 .then(move |f| async move {
1244 match f {
1245 Fetch::Pending(_) => sleep(active_fetch_delay).await,
1249 Fetch::Ready(_) => (),
1250 };
1251 f
1252 })
1253 .boxed()
1254 }
1255
1256 fn get_range_rev<T>(
1263 self: Arc<Self>,
1264 start: Bound<usize>,
1265 end: usize,
1266 ) -> BoxStream<'static, Fetch<T>>
1267 where
1268 T: RangedFetchable<Types>,
1269 {
1270 let chunk_size = self.range_chunk_size;
1271 self.get_range_with_chunk_size_rev(chunk_size, start, end)
1272 }
1273
1274 fn get_range_with_chunk_size_rev<T>(
1276 self: Arc<Self>,
1277 chunk_size: usize,
1278 start: Bound<usize>,
1279 end: usize,
1280 ) -> BoxStream<'static, Fetch<T>>
1281 where
1282 T: RangedFetchable<Types>,
1283 {
1284 let chunk_fetch_delay = self.chunk_fetch_delay;
1285 let active_fetch_delay = self.active_fetch_delay;
1286
1287 stream::iter(range_chunks_rev(start, end, chunk_size))
1288 .then(move |chunk| {
1289 let self_clone = self.clone();
1290 async move {
1291 {
1292 let chunk = self_clone.get_chunk(chunk).await;
1293
1294 sleep(chunk_fetch_delay).await;
1299 stream::iter(chunk.into_iter().rev())
1300 }
1301 }
1302 })
1303 .flatten()
1304 .then(move |f| async move {
1305 match f {
1306 Fetch::Pending(_) => sleep(active_fetch_delay).await,
1310 Fetch::Ready(_) => (),
1311 };
1312 f
1313 })
1314 .boxed()
1315 }
1316
1317 async fn get_chunk<T>(self: &Arc<Self>, chunk: Range<usize>) -> Vec<Fetch<T>>
1324 where
1325 T: RangedFetchable<Types>,
1326 {
1327 let passive_fetches = join_all(
1331 chunk
1332 .clone()
1333 .map(|i| T::passive_fetch(&self.notifiers, i.into())),
1334 )
1335 .await;
1336
1337 match self.try_get_chunk(&chunk).await {
1338 Ok(objs) => {
1339 return objs
1342 .into_iter()
1343 .zip(passive_fetches)
1344 .enumerate()
1345 .map(move |(i, (obj, passive_fetch))| match obj {
1346 Some(obj) => Fetch::Ready(obj),
1347 None => passive(T::Request::from(chunk.start + i), passive_fetch),
1348 })
1349 .collect();
1350 },
1351 Err(err) => {
1352 tracing::warn!(
1353 ?chunk,
1354 "unable to fetch chunk; spawning a task to retry: {err:#}"
1355 );
1356 },
1357 }
1358
1359 let (send, recv): (Vec<_>, Vec<_>) =
1361 repeat_with(oneshot::channel).take(chunk.len()).unzip();
1362
1363 {
1364 let fetcher = self.clone();
1365 let mut backoff = fetcher.backoff.clone();
1366 let chunk = chunk.clone();
1367 let span = tracing::warn_span!("get_chunk retry", ?chunk);
1368 spawn(
1369 async move {
1370 backoff.reset();
1371 let mut delay = backoff.next_backoff().unwrap_or(Duration::from_secs(1));
1372 loop {
1373 let res = {
1374 let _guard = fetcher.retry_semaphore.acquire().await;
1379 fetcher.try_get_chunk(&chunk).await
1380 };
1381 match res {
1382 Ok(objs) => {
1383 for (i, (obj, sender)) in objs.into_iter().zip(send).enumerate() {
1384 if let Some(obj) = obj {
1385 tracing::info!(?chunk, i, "object was ready after retries");
1389 sender.send(obj).ok();
1390 } else {
1391 tracing::info!(?chunk, i, "spawned fetch after retries");
1396 }
1397 }
1398 break;
1399 },
1400 Err(err) => {
1401 tracing::warn!(
1402 ?chunk,
1403 ?delay,
1404 "unable to fetch chunk, will retry: {err:#}"
1405 );
1406 sleep(delay).await;
1407 if let Some(next_delay) = backoff.next_backoff() {
1408 delay = next_delay;
1409 }
1410 },
1411 }
1412 }
1413 }
1414 .instrument(span),
1415 );
1416 }
1417
1418 passive_fetches
1421 .into_iter()
1422 .zip(recv)
1423 .enumerate()
1424 .map(move |(i, (passive_fetch, recv))| {
1425 passive(
1426 T::Request::from(chunk.start + i),
1427 select_some(passive_fetch, recv.map(Result::ok)),
1428 )
1429 })
1430 .collect()
1431 }
1432
1433 async fn try_get_chunk<T>(
1446 self: &Arc<Self>,
1447 chunk: &Range<usize>,
1448 ) -> anyhow::Result<Vec<Option<T>>>
1449 where
1450 T: RangedFetchable<Types>,
1451 {
1452 let mut tx = self.read().await.context("opening read transaction")?;
1453 let ts = T::load_range(&mut tx, chunk.clone())
1454 .await
1455 .context(format!("when fetching items in range {chunk:?}"))?;
1456
1457 let ts = ts.into_iter().filter_map(ResultExt::ok_or_trace);
1464
1465 let mut results = Vec::with_capacity(chunk.len());
1467 for t in ts {
1468 while chunk.start + results.len() < t.height() as usize {
1470 tracing::debug!(
1471 "item {} in chunk not available, will be fetched",
1472 results.len()
1473 );
1474 self.fetch::<T>(&mut tx, (chunk.start + results.len()).into())
1475 .await?;
1476 results.push(None);
1477 }
1478
1479 results.push(Some(t));
1480 }
1481 while results.len() < chunk.len() {
1483 self.fetch::<T>(&mut tx, (chunk.start + results.len()).into())
1484 .await?;
1485 results.push(None);
1486 }
1487
1488 Ok(results)
1489 }
1490
1491 async fn fetch<T>(
1497 self: &Arc<Self>,
1498 tx: &mut <Self as VersionedDataSource>::ReadOnly<'_>,
1499 req: T::Request,
1500 ) -> anyhow::Result<()>
1501 where
1502 T: Fetchable<Types>,
1503 {
1504 tracing::debug!("fetching resource {req:?}");
1505
1506 let heights = Heights::load(tx)
1508 .await
1509 .context("failed to load heights; cannot definitively say object might exist")?;
1510 if req.might_exist(heights) {
1511 T::active_fetch(tx, self.clone(), req).await?;
1512 } else {
1513 tracing::debug!("not fetching object {req:?} that cannot exist at {heights:?}");
1514 }
1515 Ok(())
1516 }
1517
1518 async fn proactive_scan(
1524 self: Arc<Self>,
1525 interval: Duration,
1526 chunk_size: usize,
1527 metrics: ScannerMetrics,
1528 ) {
1529 for i in 0.. {
1530 let span = tracing::warn_span!("proactive scan", i);
1531 metrics.running.set(1);
1532 metrics.current_scan.set(i);
1533 async {
1534 let sync_status = {
1535 match self.sync_status().await {
1536 Ok(st) => st,
1537 Err(err) => {
1538 tracing::warn!(
1539 "unable to load sync status, scan will be skipped: {err:#}"
1540 );
1541 return;
1542 },
1543 }
1544 };
1545 tracing::info!(?sync_status, "starting scan");
1546 metrics.missing_blocks.set(sync_status.blocks.missing);
1547 metrics.missing_vid.set(sync_status.vid_common.missing);
1548
1549 for range in sync_status.blocks.ranges {
1552 metrics.scanned_blocks.set(range.start);
1553 if range.status != SyncStatus::Missing {
1554 metrics.scanned_blocks.set(range.end);
1555 continue;
1556 }
1557
1558 tracing::info!(?range, "fetching missing block range");
1559
1560 for chunk in range_chunks_aligned(range.start..range.end, chunk_size) {
1563 tracing::info!(?chunk, "fetching missing block chunk");
1564
1565 self.get::<NonEmptyRange<BlockQueryData<Types>>>(RangeRequest {
1568 start: chunk.start as u64,
1569 end: chunk.end as u64,
1570 })
1571 .await
1572 .await;
1573
1574 metrics
1575 .missing_blocks
1576 .update((chunk.start as i64) - (chunk.end as i64));
1577 metrics.scanned_blocks.set(chunk.end);
1578 }
1579 }
1580
1581 for range in sync_status.vid_common.ranges {
1583 metrics.scanned_vid.set(range.start);
1584 if range.status != SyncStatus::Missing {
1585 metrics.scanned_vid.set(range.end);
1586 continue;
1587 }
1588
1589 tracing::info!(?range, "fetching missing VID range");
1590 for chunk in range_chunks_aligned(range.start..range.end, chunk_size) {
1591 tracing::info!(?chunk, "fetching missing VID chunk");
1592 self.get::<NonEmptyRange<VidCommonQueryData<Types>>>(RangeRequest {
1593 start: chunk.start as u64,
1594 end: chunk.end as u64,
1595 })
1596 .await
1597 .await;
1598
1599 metrics
1600 .missing_vid
1601 .update((chunk.start as i64) - (chunk.end as i64));
1602 metrics.scanned_vid.set(chunk.end);
1603 }
1604 }
1605
1606 tracing::info!("completed proactive scan, will scan again in {interval:?}");
1607
1608 metrics.running.set(0);
1610 }
1611 .instrument(span)
1612 .await;
1613
1614 sleep(interval).await;
1615 }
1616 }
1617}
1618
1619impl<Types, S, P> Fetcher<Types, S, P>
1620where
1621 Types: NodeType,
1622 Header<Types>: QueryableHeader<Types>,
1623 S: VersionedDataSource + 'static,
1624 for<'a> S::ReadOnly<'a>: NodeStorage<Types> + PrunedHeightStorage,
1625 P: Send + Sync,
1626{
1627 async fn sync_status(&self) -> anyhow::Result<SyncStatusQueryData> {
1628 let mut cache = self.sync_status.lock().await;
1633 if let Some(sync_status) = cache.try_get() {
1634 return Ok(sync_status.clone());
1635 }
1636 tracing::debug!("updating sync status");
1637
1638 let heights = {
1639 let mut tx = self
1640 .read()
1641 .await
1642 .context("opening transaction to load heights")?;
1643 Heights::load(&mut tx).await.context("loading heights")?
1644 };
1645
1646 let mut res = SyncStatusQueryData {
1647 pruned_height: heights.pruned_height.map(|h| h as usize),
1648 ..Default::default()
1649 };
1650 let start = if let Some(height) = res.pruned_height {
1651 let range = SyncStatusRange {
1653 status: SyncStatus::Pruned,
1654 start: 0,
1655 end: height + 1,
1656 };
1657 res.blocks.ranges.push(range);
1658 res.leaves.ranges.push(range);
1659 res.vid_common.ranges.push(range);
1660
1661 height + 1
1662 } else {
1663 0
1664 };
1665
1666 for chunk in range_chunks(
1669 start..(heights.height as usize),
1670 self.sync_status_chunk_size,
1671 ) {
1672 tracing::debug!(chunk.start, chunk.end, "checking sync status in sub-range");
1673 let metrics = self.sync_status_metrics.start_range(&chunk);
1674 let mut tx = self
1675 .read()
1676 .await
1677 .context("opening transaction to sync status range")?;
1678 let range_status = tx
1679 .sync_status_for_range(chunk.start, chunk.end)
1680 .await
1681 .context(format!("checking sync status in sub-range {chunk:?}"))?;
1682 tracing::debug!(
1683 chunk.start,
1684 chunk.end,
1685 ?range_status,
1686 "found sync status for range"
1687 );
1688
1689 res.blocks.extend(range_status.blocks);
1690 res.leaves.extend(range_status.leaves);
1691 res.vid_common.extend(range_status.vid_common);
1692 metrics.end();
1693 }
1694
1695 cache.update(res.clone());
1696 Ok(res)
1697 }
1698}
1699
1700pub(crate) const AGGREGATOR_RETRY_INTERVAL: Duration = Duration::from_secs(30);
1708
1709impl<Types, S, P> Fetcher<Types, S, P>
1710where
1711 Types: NodeType,
1712 Header<Types>: QueryableHeader<Types>,
1713 Payload<Types>: QueryablePayload<Types>,
1714 S: VersionedDataSource + 'static,
1715 for<'a> S::Transaction<'a>: UpdateAvailabilityStorage<Types> + UpdateAggregatesStorage<Types>,
1716 for<'a> S::ReadOnly<'a>: AvailabilityStorage<Types>
1717 + NodeStorage<Types>
1718 + PrunedHeightStorage
1719 + AggregatesStorage<Types>,
1720 P: AvailabilityProvider<Types>,
1721{
1722 #[tracing::instrument(skip_all)]
1723 async fn aggregate(self: Arc<Self>, chunk_size: usize, metrics: AggregatorMetrics) {
1724 loop {
1725 let prev_aggregate = loop {
1726 let mut tx = match self.read().await {
1727 Ok(tx) => tx,
1728 Err(err) => {
1729 tracing::error!("unable to open read tx: {err:#}");
1730 sleep(Duration::from_secs(5)).await;
1731 continue;
1732 },
1733 };
1734 match tx.load_prev_aggregate().await {
1735 Ok(agg) => break agg,
1736 Err(err) => {
1737 tracing::error!("unable to load previous aggregate: {err:#}");
1738 sleep(Duration::from_secs(5)).await;
1739 continue;
1740 },
1741 }
1742 };
1743
1744 let (start, mut prev_aggregate) = match prev_aggregate {
1745 Some(aggregate) => (aggregate.height as usize + 1, aggregate),
1746 None => (0, Aggregate::default()),
1747 };
1748
1749 tracing::debug!(start, "starting aggregator");
1750 metrics.height.set(start);
1751
1752 let mut blocks = self
1753 .clone()
1754 .get_range_with_chunk_size::<_, PayloadMetadata<Types>>(chunk_size, start..)
1755 .then(Fetch::resolve)
1756 .ready_chunks(chunk_size)
1757 .boxed();
1758 loop {
1759 match timeout(AGGREGATOR_RETRY_INTERVAL, blocks.next()).await {
1760 Ok(Some(chunk)) => {
1761 let Some(last) = chunk.last() else {
1762 tracing::warn!("ready_chunks returned an empty chunk");
1764 continue;
1765 };
1766 let height = last.height();
1767 let num_blocks = chunk.len();
1768 tracing::debug!(
1769 num_blocks,
1770 height,
1771 "updating aggregate statistics for chunk"
1772 );
1773 loop {
1774 let res = async {
1775 let mut tx = self.write().await.context("opening transaction")?;
1776 let aggregate =
1777 tx.update_aggregates(prev_aggregate.clone(), &chunk).await?;
1778 tx.commit().await.context("committing transaction")?;
1779 prev_aggregate = aggregate;
1780 anyhow::Result::<_>::Ok(())
1781 }
1782 .await;
1783 match res {
1784 Ok(()) => {
1785 break;
1786 },
1787 Err(err) => {
1788 tracing::warn!(
1789 num_blocks,
1790 height,
1791 "failed to update aggregates for chunk: {err:#}"
1792 );
1793 sleep(Duration::from_secs(1)).await;
1794 },
1795 }
1796 }
1797 metrics.height.set(height as usize);
1798 },
1799 Ok(None) => break, Err(_elapsed) => break,
1805 }
1806 }
1807 tracing::debug!("aggregator stream restarting");
1808 }
1809 }
1810}
1811
1812impl<Types, S, P> Fetcher<Types, S, P>
1813where
1814 Types: NodeType,
1815 Header<Types>: QueryableHeader<Types>,
1816 Payload<Types>: QueryablePayload<Types>,
1817 S: VersionedDataSource + 'static,
1818 for<'a> S::ReadOnly<'a>: NodeStorage<Types>,
1819 for<'a> S::Transaction<'a>: UpdateAvailabilityStorage<Types>,
1820 P: AvailabilityProvider<Types>,
1821{
1822 async fn get_cert2(self: &Arc<Self>, height: u64) -> QueryResult<Option<Certificate2<Types>>> {
1824 let mut tx = self.read().await.map_err(|err| QueryError::Error {
1825 message: err.to_string(),
1826 })?;
1827
1828 if let Some(cert2) = tx.load_cert2(height).await? {
1829 return Ok(Some(cert2));
1830 }
1831
1832 drop(tx);
1833
1834 let Some(cert2) = self
1835 .provider
1836 .fetch(request::Certificate2Request { height })
1837 .await
1838 .flatten()
1839 else {
1840 return Ok(None);
1841 };
1842
1843 self.store(&(height, cert2.clone())).await;
1844 Ok(Some(cert2))
1845 }
1846}
1847
1848impl<Types, S, P> Fetcher<Types, S, P>
1849where
1850 Types: NodeType,
1851 S: VersionedDataSource,
1852 for<'a> S::Transaction<'a>: UpdateAvailabilityStorage<Types>,
1853{
1854 async fn store_and_notify<T>(&self, obj: &T)
1856 where
1857 T: Storable<Types>,
1858 {
1859 self.store(obj).await;
1860
1861 obj.notify(&self.notifiers).await;
1886 }
1887
1888 async fn store<T>(&self, obj: &T)
1889 where
1890 T: Storable<Types>,
1891 {
1892 let try_store = || async {
1893 let mut tx = self.storage.write().await?;
1894 obj.clone().store(&mut tx, self.leaf_only).await?;
1895 tx.commit().await
1896 };
1897
1898 let mut backoff = self.backoff.clone();
1900 backoff.reset();
1901 loop {
1902 let Err(err) = try_store().await else {
1903 break;
1904 };
1905 tracing::warn!(
1909 obj = obj.debug_name(),
1910 "failed to store fetched object: {err:#}"
1911 );
1912
1913 let Some(delay) = backoff.next_backoff() else {
1914 break;
1915 };
1916 tracing::info!(?delay, "retrying failed operation");
1917 sleep(delay).await;
1918 }
1919 }
1920}
1921
1922#[derive(Debug)]
1923struct Notifiers<Types>
1924where
1925 Types: NodeType,
1926{
1927 block: Notifier<BlockQueryData<Types>>,
1928 leaf: Notifier<LeafQueryData<Types>>,
1929 vid_common: Notifier<VidCommonQueryData<Types>>,
1930}
1931
1932impl<Types> Default for Notifiers<Types>
1933where
1934 Types: NodeType,
1935{
1936 fn default() -> Self {
1937 Self {
1938 block: Notifier::new(),
1939 leaf: Notifier::new(),
1940 vid_common: Notifier::new(),
1941 }
1942 }
1943}
1944
1945#[derive(Clone, Copy, Debug)]
1946struct Heights {
1947 height: u64,
1948 pruned_height: Option<u64>,
1949}
1950
1951impl Heights {
1952 async fn load<Types, T>(tx: &mut T) -> anyhow::Result<Self>
1953 where
1954 Types: NodeType,
1955 Header<Types>: QueryableHeader<Types>,
1956 T: NodeStorage<Types> + PrunedHeightStorage + Send,
1957 {
1958 let height = tx.block_height().await.context("loading block height")? as u64;
1959 let pruned_height = tx
1960 .load_pruned_height()
1961 .await
1962 .context("loading pruned height")?;
1963 Ok(Self {
1964 height,
1965 pruned_height,
1966 })
1967 }
1968
1969 fn might_exist(self, h: u64) -> bool {
1970 h < self.height && self.pruned_height.is_none_or(|ph| h > ph)
1971 }
1972}
1973
1974#[async_trait]
1975impl<Types, S, P, State, const ARITY: usize> MerklizedStateDataSource<Types, State, ARITY>
1976 for FetchingDataSource<Types, S, P>
1977where
1978 Types: NodeType,
1979 S: VersionedDataSource + 'static,
1980 for<'a> S::ReadOnly<'a>: MerklizedStateStorage<Types, State, ARITY>,
1981 P: Send + Sync,
1982 State: MerklizedState<Types, ARITY> + 'static,
1983 <State as MerkleTreeScheme>::Commitment: Send,
1984{
1985 async fn get_path(
1986 &self,
1987 snapshot: Snapshot<Types, State, ARITY>,
1988 key: State::Key,
1989 ) -> QueryResult<MerkleProof<State::Entry, State::Key, State::T, ARITY>> {
1990 let mut tx = self.read().await.map_err(|err| QueryError::Error {
1991 message: err.to_string(),
1992 })?;
1993 tx.get_path(snapshot, key).await
1994 }
1995}
1996
1997#[async_trait]
1998impl<Types, S, P> MerklizedStateHeightPersistence for FetchingDataSource<Types, S, P>
1999where
2000 Types: NodeType,
2001 Header<Types>: QueryableHeader<Types>,
2002 Payload<Types>: QueryablePayload<Types>,
2003 S: VersionedDataSource + SerializableRetry + 'static,
2004 for<'a> S::ReadOnly<'a>: MerklizedStateHeightStorage,
2005 P: Send + Sync,
2006{
2007 async fn get_last_state_height(&self) -> QueryResult<usize> {
2008 serializable_retry!(self.fetcher.storage, || async {
2009 let mut tx = self.read().await.map_err(|err| QueryError::Error {
2010 message: err.to_string(),
2011 })?;
2012 tx.get_last_state_height().await
2013 })
2014 .await
2015 }
2016}
2017
2018#[async_trait]
2019impl<Types, S, P> NodeDataSource<Types> for FetchingDataSource<Types, S, P>
2020where
2021 Types: NodeType,
2022 Header<Types>: QueryableHeader<Types>,
2023 S: VersionedDataSource + SerializableRetry + 'static,
2024 for<'a> S::ReadOnly<'a>: NodeStorage<Types> + PrunedHeightStorage,
2025 P: Send + Sync,
2026{
2027 async fn block_height(&self) -> QueryResult<usize> {
2028 serializable_retry!(self.fetcher.storage, || async {
2029 let mut tx = self.read().await.map_err(|err| QueryError::Error {
2030 message: err.to_string(),
2031 })?;
2032 tx.block_height().await
2033 })
2034 .await
2035 }
2036
2037 async fn count_transactions_in_range(
2038 &self,
2039 range: impl RangeBounds<usize> + Send + Sync + Clone,
2040 namespace: Option<NamespaceId<Types>>,
2041 ) -> QueryResult<usize> {
2042 serializable_retry!(self.fetcher.storage, || async {
2043 let mut tx = self.read().await.map_err(|err| QueryError::Error {
2044 message: err.to_string(),
2045 })?;
2046 tx.count_transactions_in_range(range.clone(), namespace)
2047 .await
2048 })
2049 .await
2050 }
2051
2052 async fn payload_size_in_range(
2053 &self,
2054 range: impl RangeBounds<usize> + Send + Sync + Clone,
2055 namespace: Option<NamespaceId<Types>>,
2056 ) -> QueryResult<usize> {
2057 serializable_retry!(self.fetcher.storage, || async {
2058 let mut tx = self.read().await.map_err(|err| QueryError::Error {
2059 message: err.to_string(),
2060 })?;
2061 tx.payload_size_in_range(range.clone(), namespace).await
2062 })
2063 .await
2064 }
2065
2066 async fn vid_share<ID>(&self, id: ID) -> QueryResult<VidShare>
2067 where
2068 ID: Into<BlockId<Types>> + Send + Sync,
2069 {
2070 let id: BlockId<Types> = id.into();
2071 serializable_retry!(self.fetcher.storage, || async {
2072 let mut tx = self.read().await.map_err(|err| QueryError::Error {
2073 message: err.to_string(),
2074 })?;
2075 tx.vid_share(id).await
2076 })
2077 .await
2078 }
2079
2080 async fn sync_status(&self) -> QueryResult<SyncStatusQueryData> {
2081 self.fetcher
2082 .sync_status()
2083 .await
2084 .map_err(|err| QueryError::Error {
2085 message: format!("{err:#}"),
2086 })
2087 }
2088
2089 async fn get_header_window(
2090 &self,
2091 start: impl Into<WindowStart<Types>> + Send + Sync,
2092 end: u64,
2093 limit: usize,
2094 ) -> QueryResult<TimeWindowQueryData<Header<Types>>> {
2095 let start: WindowStart<Types> = start.into();
2096 serializable_retry!(self.fetcher.storage, || async {
2097 let mut tx = self.read().await.map_err(|err| QueryError::Error {
2098 message: err.to_string(),
2099 })?;
2100 tx.get_header_window(start, end, limit).await
2101 })
2102 .await
2103 }
2104}
2105
2106#[async_trait]
2107impl<Types, S, P> ExplorerDataSource<Types> for FetchingDataSource<Types, S, P>
2108where
2109 Types: NodeType,
2110 Payload<Types>: QueryablePayload<Types>,
2111 Header<Types>: QueryableHeader<Types> + explorer::traits::ExplorerHeader<Types>,
2112 crate::Transaction<Types>: explorer::traits::ExplorerTransaction<Types>,
2113 S: VersionedDataSource + SerializableRetry + 'static,
2114 for<'a> S::ReadOnly<'a>: ExplorerStorage<Types>,
2115 P: Send + Sync,
2116{
2117 async fn get_block_summaries(
2118 &self,
2119 request: explorer::query_data::GetBlockSummariesRequest<Types>,
2120 ) -> Result<
2121 Vec<explorer::query_data::BlockSummary<Types>>,
2122 explorer::query_data::GetBlockSummariesError,
2123 > {
2124 serializable_retry!(self.fetcher.storage, || async {
2125 let mut tx = self.read().await.map_err(|err| QueryError::Error {
2126 message: err.to_string(),
2127 })?;
2128 tx.get_block_summaries(request.clone()).await
2129 })
2130 .await
2131 }
2132
2133 async fn get_block_detail(
2134 &self,
2135 request: explorer::query_data::BlockIdentifier<Types>,
2136 ) -> Result<explorer::query_data::BlockDetail<Types>, explorer::query_data::GetBlockDetailError>
2137 {
2138 serializable_retry!(self.fetcher.storage, || async {
2139 let mut tx = self.read().await.map_err(|err| QueryError::Error {
2140 message: err.to_string(),
2141 })?;
2142 tx.get_block_detail(request.clone()).await
2143 })
2144 .await
2145 }
2146
2147 async fn get_transaction_summaries(
2148 &self,
2149 request: explorer::query_data::GetTransactionSummariesRequest<Types>,
2150 ) -> Result<
2151 Vec<explorer::query_data::TransactionSummary<Types>>,
2152 explorer::query_data::GetTransactionSummariesError,
2153 > {
2154 serializable_retry!(self.fetcher.storage, || async {
2155 let mut tx = self.read().await.map_err(|err| QueryError::Error {
2156 message: err.to_string(),
2157 })?;
2158 tx.get_transaction_summaries(request.clone()).await
2159 })
2160 .await
2161 }
2162
2163 async fn get_transaction_detail(
2164 &self,
2165 request: explorer::query_data::TransactionIdentifier<Types>,
2166 ) -> Result<
2167 explorer::query_data::TransactionDetailResponse<Types>,
2168 explorer::query_data::GetTransactionDetailError,
2169 > {
2170 serializable_retry!(self.fetcher.storage, || async {
2171 let mut tx = self.read().await.map_err(|err| QueryError::Error {
2172 message: err.to_string(),
2173 })?;
2174 tx.get_transaction_detail(request.clone()).await
2175 })
2176 .await
2177 }
2178
2179 async fn get_explorer_summary(
2180 &self,
2181 ) -> Result<
2182 explorer::query_data::ExplorerSummary<Types>,
2183 explorer::query_data::GetExplorerSummaryError,
2184 > {
2185 serializable_retry!(self.fetcher.storage, || async {
2186 let mut tx = self.read().await.map_err(|err| QueryError::Error {
2187 message: err.to_string(),
2188 })?;
2189 tx.get_explorer_summary().await
2190 })
2191 .await
2192 }
2193
2194 async fn get_search_results(
2195 &self,
2196 query: TaggedBase64,
2197 ) -> Result<
2198 explorer::query_data::SearchResult<Types>,
2199 explorer::query_data::GetSearchResultsError,
2200 > {
2201 serializable_retry!(self.fetcher.storage, || async {
2202 let mut tx = self.read().await.map_err(|err| QueryError::Error {
2203 message: err.to_string(),
2204 })?;
2205 tx.get_search_results(query.clone()).await
2206 })
2207 .await
2208 }
2209}
2210
2211pub trait AvailabilityProvider<Types: NodeType>:
2213 Provider<Types, request::LeafRequest>
2214 + Provider<Types, request::LeafRangeRequest>
2215 + Provider<Types, request::PayloadRequest>
2216 + Provider<Types, request::BlockRangeRequest>
2217 + Provider<Types, request::VidCommonRequest>
2218 + Provider<Types, request::VidCommonRangeRequest>
2219 + Provider<Types, request::Certificate2Request>
2220 + Sync
2221 + 'static
2222{
2223}
2224impl<Types: NodeType, P> AvailabilityProvider<Types> for P where
2225 P: Provider<Types, request::LeafRequest>
2226 + Provider<Types, request::LeafRangeRequest>
2227 + Provider<Types, request::PayloadRequest>
2228 + Provider<Types, request::BlockRangeRequest>
2229 + Provider<Types, request::VidCommonRequest>
2230 + Provider<Types, request::VidCommonRangeRequest>
2231 + Provider<Types, request::Certificate2Request>
2232 + Sync
2233 + 'static
2234{
2235}
2236
2237trait FetchRequest: Copy + Debug + Send + Sync + 'static {
2238 fn might_exist(self, _heights: Heights) -> bool {
2249 true
2250 }
2251}
2252
2253#[async_trait]
2259trait Fetchable<Types>: Clone + Send + Sync + 'static
2260where
2261 Types: NodeType,
2262 Header<Types>: QueryableHeader<Types>,
2263 Payload<Types>: QueryablePayload<Types>,
2264{
2265 type Request: FetchRequest;
2267
2268 fn satisfies(&self, req: Self::Request) -> bool;
2270
2271 async fn active_fetch<S, P>(
2290 tx: &mut impl AvailabilityStorage<Types>,
2291 fetcher: Arc<Fetcher<Types, S, P>>,
2292 req: Self::Request,
2293 ) -> anyhow::Result<()>
2294 where
2295 S: VersionedDataSource + 'static,
2296 for<'a> S::Transaction<'a>: UpdateAvailabilityStorage<Types>,
2297 for<'a> S::ReadOnly<'a>:
2298 AvailabilityStorage<Types> + NodeStorage<Types> + PrunedHeightStorage,
2299 P: AvailabilityProvider<Types>;
2300
2301 async fn passive_fetch(notifiers: &Notifiers<Types>, req: Self::Request) -> PassiveFetch<Self>;
2303
2304 async fn load<S>(storage: &mut S, req: Self::Request) -> QueryResult<Self>
2309 where
2310 S: AvailabilityStorage<Types>;
2311}
2312
2313type PassiveFetch<T> = BoxFuture<'static, Option<T>>;
2314
2315#[async_trait]
2316trait RangedFetchable<Types>: Fetchable<Types, Request = Self::RangedRequest> + HeightIndexed
2317where
2318 Types: NodeType,
2319 Header<Types>: QueryableHeader<Types>,
2320 Payload<Types>: QueryablePayload<Types>,
2321{
2322 type RangedRequest: FetchRequest + From<usize> + Send;
2323
2324 async fn load_range<S, R>(storage: &mut S, range: R) -> QueryResult<Vec<QueryResult<Self>>>
2326 where
2327 S: AvailabilityStorage<Types>,
2328 R: RangeBounds<usize> + Send + 'static;
2329}
2330
2331trait Storable<Types: NodeType>: Clone {
2333 fn debug_name(&self) -> String;
2335
2336 fn notify(&self, notifiers: &Notifiers<Types>) -> impl Send + Future<Output = ()>;
2338
2339 fn store(
2341 &self,
2342 storage: &mut impl UpdateAvailabilityStorage<Types>,
2343 leaf_only: bool,
2344 ) -> impl Send + Future<Output = anyhow::Result<()>>;
2345}
2346
2347impl<Types: NodeType> Storable<Types>
2348 for (
2349 LeafQueryData<Types>,
2350 Option<[CertificatePair<Types>; 2]>,
2351 Option<Certificate2<Types>>,
2352 )
2353{
2354 fn debug_name(&self) -> String {
2355 format!("leaf {} with QC chain", self.0.height())
2356 }
2357
2358 async fn notify(&self, notifiers: &Notifiers<Types>) {
2359 self.0.notify(notifiers).await;
2360 }
2361
2362 async fn store(
2363 &self,
2364 storage: &mut impl UpdateAvailabilityStorage<Types>,
2365 _leaf_only: bool,
2366 ) -> anyhow::Result<()> {
2367 storage
2368 .insert_leaf_with_qc_chain(&self.0, self.1.clone())
2369 .await
2370 .context("inserting leaf with QC chain")?;
2371 if let Some(cert2) = &self.2 {
2372 storage.insert_cert2(self.0.height(), cert2.clone()).await?;
2373 }
2374 Ok(())
2375 }
2376}
2377
2378impl<Types: NodeType> Storable<Types> for (u64, Certificate2<Types>) {
2379 fn debug_name(&self) -> String {
2380 format!("cert2 at height {}", self.0)
2381 }
2382
2383 async fn notify(&self, _notifiers: &Notifiers<Types>) {
2384 }
2386
2387 async fn store(
2388 &self,
2389 storage: &mut impl UpdateAvailabilityStorage<Types>,
2390 _leaf_only: bool,
2391 ) -> anyhow::Result<()> {
2392 storage.insert_cert2(self.0, self.1.clone()).await
2393 }
2394}
2395
2396fn range_chunks<R>(range: R, chunk_size: usize) -> impl Iterator<Item = Range<usize>>
2398where
2399 R: RangeBounds<usize>,
2400{
2401 let Range { mut start, end } = range_to_bounds(range);
2403 std::iter::from_fn(move || {
2404 let chunk_end = min(start + chunk_size, end);
2405 if chunk_end == start {
2406 return None;
2407 }
2408
2409 let chunk = start..chunk_end;
2410 start = chunk_end;
2411 Some(chunk)
2412 })
2413}
2414
2415fn range_chunks_aligned<R>(range: R, alignment: usize) -> impl Iterator<Item = Range<usize>>
2421where
2422 R: RangeBounds<usize>,
2423{
2424 let Range { mut start, end } = range_to_bounds(range);
2426
2427 let first = if start.is_multiple_of(alignment) {
2429 None
2430 } else {
2431 let chunk_end = min(start.next_multiple_of(alignment), end);
2434 let chunk = start..chunk_end;
2435
2436 start = chunk_end;
2438 Some(chunk)
2439 };
2440
2441 first.into_iter().chain(range_chunks(start..end, alignment))
2442}
2443
2444fn range_to_bounds(range: impl RangeBounds<usize>) -> Range<usize> {
2446 let start = match range.start_bound() {
2447 Bound::Included(i) => *i,
2448 Bound::Excluded(i) => *i + 1,
2449 Bound::Unbounded => 0,
2450 };
2451 let end = match range.end_bound() {
2452 Bound::Included(i) => *i + 1,
2453 Bound::Excluded(i) => *i,
2454 Bound::Unbounded => usize::MAX,
2455 };
2456 Range { start, end }
2457}
2458
2459fn range_chunks_rev(
2470 start: Bound<usize>,
2471 end: usize,
2472 chunk_size: usize,
2473) -> impl Iterator<Item = Range<usize>> {
2474 let start = match start {
2476 Bound::Included(i) => i,
2477 Bound::Excluded(i) => i + 1,
2478 Bound::Unbounded => 0,
2479 };
2480 let mut end = end + 1;
2482
2483 std::iter::from_fn(move || {
2484 let chunk_start = max(start, end.saturating_sub(chunk_size));
2485 if end <= chunk_start {
2486 return None;
2487 }
2488
2489 let chunk = chunk_start..end;
2490 end = chunk_start;
2491 Some(chunk)
2492 })
2493}
2494
2495trait ResultExt<T, E> {
2496 fn ok_or_trace(self) -> Option<T>
2497 where
2498 E: Display;
2499}
2500
2501impl<T, E> ResultExt<T, E> for Result<T, E> {
2502 fn ok_or_trace(self) -> Option<T>
2503 where
2504 E: Display,
2505 {
2506 match self {
2507 Ok(t) => Some(t),
2508 Err(err) => {
2509 tracing::info!(
2510 "error loading resource from local storage, will try to fetch: {err:#}"
2511 );
2512 None
2513 },
2514 }
2515 }
2516}
2517
2518#[derive(Debug)]
2519struct ScannerMetrics {
2520 running: Box<dyn Gauge>,
2522 current_scan: Box<dyn Gauge>,
2524 scanned_blocks: Box<dyn Gauge>,
2526 scanned_vid: Box<dyn Gauge>,
2528 missing_blocks: Box<dyn Gauge>,
2530 missing_vid: Box<dyn Gauge>,
2532}
2533
2534impl ScannerMetrics {
2535 fn new(metrics: &PrometheusMetrics) -> Self {
2536 let group = metrics.subgroup("scanner".into());
2537 Self {
2538 running: group.create_gauge("running".into(), None),
2539 current_scan: group.create_gauge("current".into(), None),
2540 scanned_blocks: group.create_gauge("scanned_blocks".into(), None),
2541 scanned_vid: group.create_gauge("scanned_vid".into(), None),
2542 missing_blocks: group.create_gauge("missing_blocks".into(), None),
2543 missing_vid: group.create_gauge("missing_vid".into(), None),
2544 }
2545 }
2546}
2547
2548#[derive(Debug)]
2549struct AggregatorMetrics {
2550 height: Box<dyn Gauge>,
2552}
2553
2554impl AggregatorMetrics {
2555 fn new(metrics: &PrometheusMetrics) -> Self {
2556 let group = metrics.subgroup("aggregator".into());
2557 Self {
2558 height: group.create_gauge("height".into(), None),
2559 }
2560 }
2561}
2562
2563#[derive(Debug)]
2564struct SyncStatusMetrics {
2565 current_range_start: Box<dyn Gauge>,
2566 current_range_end: Box<dyn Gauge>,
2567 current_start_time: Box<dyn Gauge>,
2568 avg_rate: Box<dyn Histogram>,
2569 ranges_scanned: Box<dyn Counter>,
2570 running: Box<dyn Gauge>,
2571}
2572
2573impl SyncStatusMetrics {
2574 fn new(metrics: &PrometheusMetrics, size: usize) -> Self {
2575 let group = metrics.subgroup("sync_status".into());
2576 group.create_gauge("range_size".into(), None).set(size);
2577
2578 Self {
2579 current_range_start: group.create_gauge("current_range_start".into(), None),
2580 current_range_end: group.create_gauge("current_range_end".into(), None),
2581 current_start_time: group
2582 .create_gauge("current_range_start_time".into(), Some("s".into())),
2583 avg_rate: group
2584 .create_histogram("avg_time_per_block_scanned".into(), Some("ms".into())),
2585 ranges_scanned: group.create_counter("ranges_scanned".into(), None),
2586 running: group.create_gauge("running".into(), None),
2587 }
2588 }
2589
2590 fn start_range(&self, range: &Range<usize>) -> SyncStatusRangeMetrics<'_> {
2591 let start = Utc::now();
2592 self.current_range_start.set(range.start);
2593 self.current_range_end.set(range.end);
2594 self.current_start_time.set(start.timestamp() as usize);
2595 self.running.set(1);
2596 SyncStatusRangeMetrics {
2597 size: range.end - range.start,
2598 start,
2599 metrics: self,
2600 }
2601 }
2602}
2603
2604#[must_use]
2605#[derive(Debug)]
2606struct SyncStatusRangeMetrics<'a> {
2607 size: usize,
2608 start: DateTime<Utc>,
2609 metrics: &'a SyncStatusMetrics,
2610}
2611
2612impl<'a> SyncStatusRangeMetrics<'a> {
2613 fn end(self) {
2614 let elapsed = Utc::now() - self.start;
2615 self.metrics
2616 .avg_rate
2617 .add_point((elapsed.num_milliseconds() as f64) / (self.size as f64));
2618 self.metrics.ranges_scanned.add(1);
2619 self.metrics.running.set(0);
2620 }
2621}
2622
2623#[derive(Debug)]
2624struct CachedSyncStatus {
2625 last_updated: Instant,
2626 ttl: Duration,
2627 cached: Option<SyncStatusQueryData>,
2628}
2629
2630impl CachedSyncStatus {
2631 fn new(ttl: Duration) -> Self {
2632 Self {
2633 last_updated: Instant::now(),
2634 ttl,
2635 cached: None,
2636 }
2637 }
2638
2639 fn try_get(&self) -> Option<&SyncStatusQueryData> {
2641 if self.last_updated.elapsed() > self.ttl {
2642 return None;
2644 }
2645 self.cached.as_ref()
2646 }
2647
2648 fn update(&mut self, value: SyncStatusQueryData) {
2650 self.last_updated = Instant::now();
2651 self.cached = Some(value);
2652 }
2653}
2654
2655fn passive<T>(
2659 req: impl Debug + Send + 'static,
2660 fut: impl Future<Output = Option<T>> + Send + 'static,
2661) -> Fetch<T>
2662where
2663 T: Send + 'static,
2664{
2665 Fetch::Pending(
2666 fut.then(move |opt| async move {
2667 match opt {
2668 Some(t) => t,
2669 None => {
2670 panic!("notifier dropped without satisfying request {req:?}");
2685 },
2686 }
2687 })
2688 .boxed(),
2689 )
2690}
2691
2692async fn select_some<T>(
2694 a: impl Future<Output = Option<T>> + Unpin,
2695 b: impl Future<Output = Option<T>> + Unpin,
2696) -> Option<T> {
2697 match future::select(a, b).await {
2698 Either::Left((Some(a), _)) => Some(a),
2700 Either::Right((Some(b), _)) => Some(b),
2701
2702 Either::Left((None, b)) => b.await,
2704 Either::Right((None, a)) => a.await,
2705 }
2706}
2707
2708#[cfg(test)]
2709mod test {
2710 use hotshot::traits::BlockPayload;
2711 use hotshot_example_types::{
2712 block_types::{TestBlockHeader, TestMetadata},
2713 node_types::TEST_VERSIONS,
2714 state_types::{TestInstanceState, TestValidatedState},
2715 };
2716 use hotshot_types::{
2717 data::vid_commitment, traits::block_contents::EncodeBytes, utils::BuilderCommitment,
2718 };
2719
2720 use super::*;
2721 use crate::{
2722 data_source::{
2723 sql::testing::TmpDb,
2724 storage::{SqlStorage, StorageConnectionType},
2725 },
2726 fetching::provider::NoFetching,
2727 testing::{
2728 consensus::MockSqlDataSource,
2729 mocks::{MockPayload, MockTypes, mock_transaction},
2730 },
2731 };
2732
2733 #[test]
2734 fn test_range_chunks() {
2735 assert_eq!(
2737 range_chunks(0..=4, 2).collect::<Vec<_>>(),
2738 [0..2, 2..4, 4..5]
2739 );
2740
2741 assert_eq!(
2743 range_chunks(0..=5, 2).collect::<Vec<_>>(),
2744 [0..2, 2..4, 4..6]
2745 );
2746
2747 assert_eq!(
2749 range_chunks(0..5, 2).collect::<Vec<_>>(),
2750 [0..2, 2..4, 4..5]
2751 );
2752
2753 assert_eq!(
2755 range_chunks(0..6, 2).collect::<Vec<_>>(),
2756 [0..2, 2..4, 4..6]
2757 );
2758
2759 assert_eq!(
2761 range_chunks(0.., 2).take(5).collect::<Vec<_>>(),
2762 [0..2, 2..4, 4..6, 6..8, 8..10]
2763 );
2764 }
2765
2766 #[test]
2767 fn test_range_chunks_aligned() {
2768 #![allow(clippy::single_range_in_vec_init)]
2769
2770 assert_eq!(
2772 range_chunks_aligned(2..5, 2).collect::<Vec<_>>(),
2773 [2..4, 4..5]
2774 );
2775
2776 assert_eq!(
2778 range_chunks_aligned(1..4, 2).collect::<Vec<_>>(),
2779 [1..2, 2..4]
2780 );
2781
2782 assert_eq!(range_chunks_aligned(1..3, 10).collect::<Vec<_>>(), [1..3]);
2784
2785 assert_eq!(
2787 range_chunks_aligned(1.., 2).take(5).collect::<Vec<_>>(),
2788 [1..2, 2..4, 4..6, 6..8, 8..10]
2789 );
2790 }
2791
2792 #[test]
2793 fn test_range_chunks_rev() {
2794 assert_eq!(
2796 range_chunks_rev(Bound::Included(0), 4, 2).collect::<Vec<_>>(),
2797 [3..5, 1..3, 0..1]
2798 );
2799
2800 assert_eq!(
2802 range_chunks_rev(Bound::Included(0), 5, 2).collect::<Vec<_>>(),
2803 [4..6, 2..4, 0..2]
2804 );
2805
2806 assert_eq!(
2808 range_chunks_rev(Bound::Excluded(0), 5, 2).collect::<Vec<_>>(),
2809 [4..6, 2..4, 1..2]
2810 );
2811
2812 assert_eq!(
2814 range_chunks_rev(Bound::Excluded(0), 4, 2).collect::<Vec<_>>(),
2815 [3..5, 1..3]
2816 );
2817 }
2818
2819 async fn test_sync_status(chunk_size: usize, present_ranges: &[(usize, usize)]) {
2820 let block_height = present_ranges.last().unwrap().1;
2821 let storage = TmpDb::init().await;
2822 let db = SqlStorage::connect(storage.config(), StorageConnectionType::Query)
2823 .await
2824 .unwrap();
2825 let ds = MockSqlDataSource::builder(db, NoFetching)
2826 .with_sync_status_chunk_size(chunk_size)
2827 .with_sync_status_ttl(Duration::ZERO)
2828 .build()
2829 .await
2830 .unwrap();
2831
2832 let mut leaves: Vec<LeafQueryData<MockTypes>> = vec![
2834 LeafQueryData::<MockTypes>::genesis(
2835 &Default::default(),
2836 &Default::default(),
2837 TEST_VERSIONS.test,
2838 )
2839 .await,
2840 ];
2841 for i in 1..block_height {
2842 let mut leaf = leaves[i - 1].clone();
2843 leaf.leaf.block_header_mut().block_number = i as u64;
2844 leaves.push(leaf);
2845 }
2846
2847 {
2849 let mut tx = ds.write().await.unwrap();
2850
2851 for &(start, end) in present_ranges {
2852 for leaf in &leaves[start..end] {
2853 tracing::info!(height = leaf.height(), "insert leaf");
2854 tx.insert_leaf(leaf).await.unwrap();
2855 }
2856 }
2857
2858 if present_ranges[0].0 > 0 {
2859 tx.save_pruned_height((present_ranges[0].0 - 1) as u64)
2860 .await
2861 .unwrap();
2862 }
2863
2864 tx.commit().await.unwrap();
2865 }
2866
2867 let sync_status = ds.sync_status().await.unwrap().leaves;
2868
2869 let present: usize = present_ranges.iter().map(|(start, end)| end - start).sum();
2871 assert_eq!(
2872 sync_status.missing,
2873 block_height - present - present_ranges[0].0
2874 );
2875
2876 let mut ranges = sync_status.ranges.into_iter();
2878 let mut prev = 0;
2879 for &(start, end) in present_ranges {
2880 if start != prev {
2881 let range = ranges.next().unwrap();
2882 assert_eq!(
2883 range,
2884 SyncStatusRange {
2885 start: prev,
2886 end: start,
2887 status: if prev == 0 {
2888 SyncStatus::Pruned
2889 } else {
2890 SyncStatus::Missing
2891 },
2892 }
2893 );
2894 }
2895 let range = ranges.next().unwrap();
2896 assert_eq!(
2897 range,
2898 SyncStatusRange {
2899 start,
2900 end,
2901 status: SyncStatus::Present,
2902 }
2903 );
2904 prev = end;
2905 }
2906
2907 if prev != block_height {
2908 let range = ranges.next().unwrap();
2909 assert_eq!(
2910 range,
2911 SyncStatusRange {
2912 start: prev,
2913 end: block_height,
2914 status: SyncStatus::Missing,
2915 }
2916 );
2917 }
2918
2919 assert_eq!(ranges.next(), None);
2920 }
2921
2922 #[tokio::test]
2923 #[test_log::test]
2924 async fn test_sync_status_multiple_chunks() {
2925 test_sync_status(10, &[(0, 1), (3, 5), (8, 10)]).await;
2926 }
2927
2928 #[tokio::test]
2929 #[test_log::test]
2930 async fn test_sync_status_multiple_chunks_present_range_overlapping_chunk() {
2931 test_sync_status(5, &[(1, 4)]).await;
2932 }
2933
2934 #[tokio::test]
2935 #[test_log::test]
2936 async fn test_sync_status_multiple_chunks_missing_range_overlapping_chunk() {
2937 test_sync_status(5, &[(0, 1), (4, 5)]).await;
2938 }
2939
2940 #[tokio::test]
2941 #[test_log::test]
2942 async fn test_load_range_incomplete() {
2943 let storage = TmpDb::init().await;
2944 let db = SqlStorage::connect(storage.config(), StorageConnectionType::Query)
2945 .await
2946 .unwrap();
2947 {
2948 let mut tx = db.write().await.unwrap();
2949 tx.insert_leaf(
2950 &LeafQueryData::<MockTypes>::genesis(
2951 &Default::default(),
2952 &Default::default(),
2953 TEST_VERSIONS.test,
2954 )
2955 .await,
2956 )
2957 .await
2958 .unwrap();
2959 tx.insert_block(
2960 &BlockQueryData::<MockTypes>::genesis(
2961 &Default::default(),
2962 &Default::default(),
2963 TEST_VERSIONS.test.base,
2964 )
2965 .await,
2966 )
2967 .await
2968 .unwrap();
2969 tx.insert_vid(
2970 &VidCommonQueryData::<MockTypes>::genesis(
2971 &Default::default(),
2972 &Default::default(),
2973 TEST_VERSIONS.test.base,
2974 )
2975 .await,
2976 None,
2977 )
2978 .await
2979 .unwrap();
2980 tx.commit().await.unwrap();
2981 }
2982
2983 let mut tx = db.read().await.unwrap();
2984 let req = RangeRequest { start: 0, end: 100 };
2985
2986 let err = <NonEmptyRange<BlockQueryData<MockTypes>>>::load(&mut tx, req)
2987 .await
2988 .unwrap_err();
2989 tracing::info!("loading partial block range failed as expected: {err:#}");
2990 assert!(matches!(err, QueryError::Missing));
2991
2992 let err =
2993 <NonEmptyRange<LeafQueryData<MockTypes>> as Fetchable<MockTypes>>::load(&mut tx, req)
2994 .await
2995 .unwrap_err();
2996 tracing::info!("loading partial leaf range failed as expected: {err:#}");
2997 assert!(matches!(err, QueryError::Missing));
2998
2999 let err = <NonEmptyRange<VidCommonQueryData<MockTypes>>>::load(&mut tx, req)
3000 .await
3001 .unwrap_err();
3002 tracing::info!("loading partial VID common range failed as expected: {err:#}");
3003 assert!(matches!(err, QueryError::Missing));
3004 }
3005
3006 async fn poll_until<T, F, Fut>(max_wait: Duration, interval: Duration, f: F) -> T
3008 where
3009 F: Fn() -> Fut,
3010 Fut: std::future::Future<Output = Option<T>>,
3011 {
3012 let deadline = std::time::Instant::now() + max_wait;
3013 loop {
3014 if let Some(v) = f().await {
3015 return v;
3016 }
3017 assert!(
3018 std::time::Instant::now() < deadline,
3019 "timed out waiting for condition"
3020 );
3021 sleep(interval).await;
3022 }
3023 }
3024
3025 #[test_log::test(tokio::test(flavor = "multi_thread"))]
3040 #[cfg(not(target_os = "windows"))]
3041 async fn test_aggregator_recovery_after_missed_notification() {
3042 let storage = TmpDb::init().await;
3043 let db = SqlStorage::connect(storage.config(), StorageConnectionType::Query)
3044 .await
3045 .unwrap();
3046
3047 let ds = MockSqlDataSource::builder(db, NoFetching)
3048 .build()
3049 .await
3050 .unwrap();
3051
3052 let n = 5usize;
3057 let expected_total = n * (n + 1) / 2;
3058
3059 let genesis_leaf = LeafQueryData::<MockTypes>::genesis(
3060 &Default::default(),
3061 &Default::default(),
3062 TEST_VERSIONS.test,
3063 )
3064 .await;
3065
3066 let mut leaves = vec![genesis_leaf];
3067 let mut blocks: Vec<BlockQueryData<MockTypes>> = Vec::new();
3068
3069 for i in 0..n {
3070 let txs = (0..=i).map(|j| mock_transaction(vec![j as u8]));
3072 let (payload, metadata) = <MockPayload as BlockPayload<MockTypes>>::from_transactions(
3073 txs,
3074 &TestValidatedState::default(),
3075 &TestInstanceState::default(),
3076 )
3077 .await
3078 .unwrap();
3079 let encoded = payload.encode();
3080 let payload_commitment =
3081 vid_commitment(&encoded, &metadata.encode(), 1, TEST_VERSIONS.test.base);
3082 let header = TestBlockHeader {
3083 block_number: i as u64,
3084 payload_commitment,
3085 builder_commitment: BuilderCommitment::from_bytes([]),
3086 metadata: TestMetadata {
3087 num_transactions: metadata.num_transactions,
3088 },
3089 timestamp: i as u64,
3090 timestamp_millis: i as u64 * 1_000,
3091 random: 1,
3092 version: TEST_VERSIONS.test.base,
3093 };
3094 if i > 0 {
3095 let mut leaf = leaves[i - 1].clone();
3096 *leaf.leaf.block_header_mut() = header.clone();
3097 leaves.push(leaf);
3098 } else {
3099 *leaves[0].leaf.block_header_mut() = header.clone();
3100 }
3101 blocks.push(BlockQueryData::new(header, payload));
3102 }
3103
3104 {
3108 let inner = ds.inner();
3109 let mut tx = inner.write().await.unwrap();
3110 for leaf in &leaves {
3111 tx.insert_leaf(leaf).await.unwrap();
3112 }
3113 for block in &blocks {
3114 tx.insert_block(block).await.unwrap();
3115 }
3116 tx.commit().await.unwrap();
3117 }
3118
3119 poll_until(
3122 AGGREGATOR_RETRY_INTERVAL * 4,
3123 Duration::from_millis(500),
3124 || async {
3125 ds.count_transactions_in_range(..=n - 1, None)
3126 .await
3127 .ok()
3128 .map(|_| ())
3129 },
3130 )
3131 .await;
3132
3133 let total = ds
3135 .count_transactions_in_range(.., None)
3136 .await
3137 .expect("aggregate should be available after recovery");
3138 assert_eq!(total, expected_total);
3139 }
3140}