Skip to main content

hotshot_query_service/data_source/
fetching.rs

1// Copyright (c) 2022 Espresso Systems (espressosys.com)
2// This file is part of the HotShot Query Service library.
3//
4// This program is free software: you can redistribute it and/or modify it under the terms of the GNU
5// General Public License as published by the Free Software Foundation, either version 3 of the
6// License, or (at your option) any later version.
7// This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
8// even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
9// General Public License for more details.
10// You should have received a copy of the GNU General Public License along with this program. If not,
11// see <https://www.gnu.org/licenses/>.
12
13//! Asynchronous retrieval of missing data.
14//!
15//! [`FetchingDataSource`] combines a local storage implementation with a remote data availability
16//! provider to create a data sources which caches data locally, but which is capable of fetching
17//! missing data from a remote source, either proactively or on demand.
18//!
19//! This implementation supports three kinds of data fetching.
20//!
21//! # Proactive Fetching
22//!
23//! Proactive fetching means actively scanning the local database for missing objects and
24//! proactively retrieving them from a remote provider, even if those objects have yet to be
25//! requested by a client. Doing this increases the chance of success and decreases latency when a
26//! client does eventually ask for those objects. This is also the mechanism by which a query
27//! service joining a network late, or having been offline for some time, is able to catch up with
28//! the events on the network that it missed.
29//!
30//! Proactive fetching is implemented by a background task which performs periodic scans of the
31//! database, identifying and retrieving missing objects. This task is generally low priority, since
32//! missing objects are rare, and it will take care not to monopolize resources that could be used
33//! to serve requests.
34//!
35//! # Active Fetching
36//!
37//! Active fetching means reaching out to a remote data availability provider to retrieve a missing
38//! resource, upon receiving a request for that resource from a client. Not every request for a
39//! missing resource triggers an active fetch. To avoid spamming peers with requests for missing
40//! data, we only actively fetch resources that are known to exist somewhere. This means we can
41//! actively fetch leaves and headers when we are requested a leaf or header by height, whose height
42//! is less than the current chain height. We can fetch a block when the corresponding header exists
43//! (corresponding based on height, hash, or payload hash) or can be actively fetched.
44//!
45//! # Passive Fetching
46//!
47//! For requests that cannot be actively fetched (for example, a block requested by hash, where we
48//! do not have a header proving that a block with that hash exists), we use passive fetching. This
49//! essentially means waiting passively until the query service receives an object that satisfies
50//! the request. This object may be received because it was actively fetched in responsive to a
51//! different request for the same object, one that permitted an active fetch. Or it may have been
52//! fetched [proactively](#proactive-fetching).
53
54use 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
140/// Builder for [`FetchingDataSource`] with configuration.
141pub 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    /// Construct a new builder with the given storage and fetcher and the default options.
162    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    /// Set the minimum delay between retries of failed operations.
196    pub fn with_min_retry_interval(mut self, interval: Duration) -> Self {
197        self.backoff.with_initial_interval(interval);
198        self
199    }
200
201    /// Set the maximum delay between retries of failed operations.
202    pub fn with_max_retry_interval(mut self, interval: Duration) -> Self {
203        self.backoff.with_max_interval(interval);
204        self
205    }
206
207    /// Set the multiplier for exponential backoff when retrying failed requests.
208    pub fn with_retry_multiplier(mut self, multiplier: f64) -> Self {
209        self.backoff.with_multiplier(multiplier);
210        self
211    }
212
213    /// Set the randomization factor for randomized backoff when retrying failed requests.
214    pub fn with_retry_randomization_factor(mut self, factor: f64) -> Self {
215        self.backoff.with_randomization_factor(factor);
216        self
217    }
218
219    /// Set the maximum time to retry failed operations before giving up.
220    pub fn with_retry_timeout(mut self, timeout: Duration) -> Self {
221        self.backoff.with_max_elapsed_time(Some(timeout));
222        self
223    }
224
225    /// Set the maximum number of simultaneous fetches.
226    pub fn with_rate_limit(mut self, with_rate_limit: usize) -> Self {
227        self.rate_limit = with_rate_limit;
228        self
229    }
230
231    /// Set the number of items to process at a time when loading a range or stream.
232    ///
233    /// This determines:
234    /// * The number of objects to load from storage in a single request
235    /// * The number of objects to buffer in memory per request/stream
236    /// * The number of concurrent notification subscriptions per request/stream
237    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    /// Set the time interval between proactive fetching scans.
243    ///
244    /// See [proactive fetching](self#proactive-fetching).
245    pub fn with_proactive_interval(mut self, interval: Duration) -> Self {
246        self.proactive_interval = interval;
247        self
248    }
249
250    /// Set the number of items to process at a time when scanning for proactive fetching.
251    ///
252    /// This is similar to [`Self::with_range_chunk_size`], but only affects the chunk size for
253    /// proactive fetching scans, not for normal subscription streams. This can be useful to tune
254    /// the proactive scanner to be more or less greedy with persistent storage resources.
255    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    /// Set the number of items to process in a single transaction when scanning the database for
261    /// missing objects.
262    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    /// Duration to cache sync status results for.
268    ///
269    /// Computing the sync status is expensive, and it typically doesn't change that quickly. Thus,
270    /// it makes sense to cache the results whenever we do compute it, and return those cached
271    /// results if they are not too old.
272    pub fn with_sync_status_ttl(mut self, ttl: Duration) -> Self {
273        self.sync_status_ttl = ttl;
274        self
275    }
276
277    /// Add a delay between active fetches in proactive scans.
278    ///
279    /// This can be used to limit the rate at which this query service makes requests to other query
280    /// services during proactive scans. This is useful if the query service has a lot of blocks to
281    /// catch up on, as without a delay, scanning can be extremely burdensome on the peer.
282    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    /// Adds a delay between chunk fetches during proactive scans.
288    ///
289    /// In a proactive scan, we retrieve a range of objects from a provider or local storage (e.g., a database).
290    /// Without a delay between fetching these chunks, the process can become very CPU-intensive, especially
291    /// when chunks are retrieved from local storage. While there is already a delay for active fetches
292    /// (`active_fetch_delay`), situations may arise when subscribed to an old stream that fetches most of the data
293    /// from local storage.
294    ///
295    /// This additional delay helps to limit constant maximum CPU usage
296    /// and ensures that local storage remains accessible to all processes,
297    /// not just the proactive scanner.
298    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    /// Run without [proactive fetching](self#proactive-fetching).
304    ///
305    /// This can reduce load on the CPU and the database, but increases the probability that
306    /// requests will fail due to missing resources. If resources are constrained, it is recommended
307    /// to run with rare proactive fetching (see
308    /// [`with_major_scan_interval`](Self::with_major_scan_interval),
309    /// [`with_minor_scan_interval`](Self::with_minor_scan_interval)), rather than disabling it
310    /// entirely.
311    pub fn disable_proactive_fetching(mut self) -> Self {
312        self.proactive_fetching = false;
313        self
314    }
315
316    /// Run without an aggregator.
317    ///
318    /// This can reduce load on the CPU and the database, but it will cause aggregate statistics
319    /// (such as transaction counts) not to update.
320    pub fn disable_aggregator(mut self) -> Self {
321        self.aggregator = false;
322        self
323    }
324
325    /// Set the number of items to process at a time when computing aggregate statistics.
326    ///
327    /// This is similar to [`Self::with_range_chunk_size`], but only affects the chunk size for
328    /// the aggregator task, not for normal subscription streams. This can be useful to tune
329    /// the aggregator to be more or less greedy with persistent storage resources.
330    ///
331    /// By default (i.e. if this method is not called) the proactive range chunk size will be set to
332    /// whatever the normal range chunk size is.
333    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    /// Build a [`FetchingDataSource`] with these options.
357    pub async fn build(self) -> anyhow::Result<FetchingDataSource<Types, S, P>> {
358        FetchingDataSource::new(self).await
359    }
360}
361
362/// The most basic kind of data source.
363///
364/// A data source is constructed modularly by combining a [storage](super::storage) implementation
365/// with a [Fetcher](crate::fetching::Fetcher). The former allows the query service to store the
366/// data it has persistently in an easily accessible storage medium, such as the local file system
367/// or a database. This allows it to answer queries efficiently and to maintain its state across
368/// restarts. The latter allows the query service to fetch data that is missing from its storage
369/// from an external data availability provider, such as the Tiramisu DA network or another instance
370/// of the query service.
371///
372/// These two components of a data source are combined in [`FetchingDataSource`], which is the
373/// lowest level kind of data source available. It simply uses the storage implementation to fetch
374/// data when available, and fills in everything else using the fetcher. Various kinds of data
375/// sources can be constructed out of [`FetchingDataSource`] by changing the storage and fetcher
376/// implementations used, and more complex data sources can be built on top using data source
377/// combinators.
378#[derive(Derivative)]
379#[derivative(Clone(bound = ""), Debug(bound = "S: Debug, P: Debug"))]
380pub struct FetchingDataSource<Types, S, P>
381where
382    Types: NodeType,
383{
384    // The fetcher manages retrieval of resources from both local storage and a remote provider. It
385    // encapsulates the data which may need to be shared with a long-lived task or future that
386    // implements the asynchronous fetching of a particular object. This is why it gets its own
387    // type, wrapped in an [`Arc`] for easy, efficient cloning.
388    fetcher: Arc<Fetcher<Types, S, P>>,
389    // The proactive scanner task. This is only saved here so that we can cancel it on drop.
390    scanner: Option<BackgroundTask>,
391    // The aggregator task, which derives aggregate statistics from a block stream.
392    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                // Delay before we start the pruner run to avoid a useless and expensive prune
425                // immediately on startup.
426                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        // We loop until the whole run pruner run is complete
443        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    /// Build a [`FetchingDataSource`] with the given `storage` and `provider`.
486    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    /// Get a copy of the (shared) inner storage
542    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        // Save the new decided leaf.
801        self.fetcher
802            .store(&(info.leaf.clone(), info.qc_chain, info.cert2.clone()))
803            .await;
804
805        // Trigger a fetch of the parent leaf, if we don't already have it.
806        leaf::trigger_fetch_for_parent(&self.fetcher, &info.leaf);
807
808        // Store and notify the block data and VID common, if available. Spawn a fetch to retrieve
809        // it, if not.
810        //
811        // Note a special case here: if the data was not available in the decide event, but _is_
812        // available locally in the database, without having to spawn a fetch for it, we _must_
813        // notify now. Thus, we must pattern match to distinguish `Fetch::Ready`/`Fetch::Pending`.
814        //
815        // Why? As soon as we inserted the leaf, the corresponding object may become available, if
816        // we already had an identical payload/VID common in the database, from a different block.
817        // Then calling `get()` will not spawn a fetch/notification, and existing fetches waiting
818        // for the newly decided object to arrive will miss it. Thus, if `get()` returned a `Ready`
819        // object, it is our responsibility, as the task processing newly decided objects, to make
820        // sure those fetches get notified.
821        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        // Send notifications for the new objects after storing all of them. This ensures that as
863        // soon as a fetch for any of these objects resolves, the corresponding data will
864        // immediately be available. This isn't strictly required for correctness; after all,
865        // objects can generally be fetched as asynchronously as we want. But this is the most
866        // intuitive behavior to provide when possible.
867        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    /// Append a payload for a block whose leaf was already decided without one.
879    ///
880    /// In the new protocol, decide events can arrive before VID reconstruction
881    /// has produced the block payload, so [`append`](Self::append) may persist
882    /// a leaf with no payload attached. The payload is then back-filled here
883    /// once it becomes available, leaving the rest of the block info untouched.
884    ///
885    /// Reconstruction runs on views that are not yet (and may never be)
886    /// decided, so the block is only stored if it matches the decided leaf at
887    /// the same height. If that leaf hasn't been ingested yet the payload is
888    /// dropped: when the decide arrives, [`append`](Self::append) spawns a
889    /// fetch that back-fills the payload from a peer.
890    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/// Asynchronous retrieval and storage of [`Fetchable`] resources.
949#[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    // Duration to sleep after each active fetch,
966    active_fetch_delay: Duration,
967    // Duration to sleep after each chunk fetched
968    chunk_fetch_delay: Duration,
969    // Exponential backoff when retrying failed operations.
970    backoff: ExponentialBackoff,
971    // Semaphore limiting the number of simultaneous DB accesses we can have from tasks spawned to
972    // retry failed loads.
973    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        // Subscribe to notifications before we check storage for the requested object. This ensures
1084        // that this operation will always eventually succeed as long as the requested object
1085        // actually exists (or will exist). We will either find it in our local storage and succeed
1086        // immediately, or (if it exists) someone will *later* come and add it to storage, at which
1087        // point we will get a notification causing this passive fetch to resolve.
1088        //
1089        // Note the "someone" who later fetches the object and adds it to storage may be an active
1090        // fetch triggered by this very requests, in cases where that is possible, but it need not
1091        // be.
1092        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        // We'll use this channel to get the object back if we successfully load it on retry.
1106        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                        // Limit the number of simultaneous retry tasks hitting the database. When
1118                        // the database is down, we might have a lot of these tasks running, and if
1119                        // they all hit the DB at once, they are only going to make things worse.
1120                        let _guard = fetcher.retry_semaphore.acquire().await;
1121                        fetcher.try_get(req).await
1122                    };
1123                    match res {
1124                        Ok(Some(obj)) => {
1125                            // If the object was immediately available after all, signal the
1126                            // original fetch. We probably just temporarily couldn't access it due
1127                            // to database errors.
1128                            tracing::info!(?req, "object was ready after retries");
1129                            send.send(obj).ok();
1130                            break;
1131                        },
1132                        Ok(None) => {
1133                            // The object was not immediately available after all, but we have
1134                            // successfully spawned a fetch for it if possible. The spawned fetch
1135                            // will notify the original request once it completes.
1136                            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        // Wait for the object to be fetched, either from the local database on retry or from
1157        // another provider eventually.
1158        passive(req, select_some(passive_fetch, recv.map(Result::ok)))
1159    }
1160
1161    /// Try to get an object from local storage or initialize a fetch if it is missing.
1162    ///
1163    /// There are three possible scenarios in this function, indicated by the return type:
1164    /// * `Ok(Some(obj))`: the requested object was available locally and successfully retrieved
1165    ///   from the database; no fetch was spawned
1166    /// * `Ok(None)`: the requested object was not available locally, but a fetch was successfully
1167    ///   spawned if possible (in other words, if a fetch was not spawned, it was determined that
1168    ///   the requested object is not fetchable)
1169    /// * `Err(_)`: it could not be determined whether the object was available locally or whether
1170    ///   it could be fetched; no fetch was spawned even though the object may be fetchable
1171    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                // We successfully queried the database, but the object wasn't there. Try to
1180                // fetch it.
1181                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                // An error occurred while querying the database. We don't know if we need to fetch
1187                // the object or not. Return an error so we can try again.
1188                bail!("failed to fetch resource {req:?} from local storage: {err:#}");
1189            },
1190        }
1191    }
1192
1193    /// Get a range of objects from local storage or a provider.
1194    ///
1195    /// Convert a finite stream of fallible local storage lookups into a (possibly infinite) stream
1196    /// of infallible fetches. Objects in `range` are loaded from local storage. Any gaps or missing
1197    /// objects are filled by fetching from a provider. Items in the resulting stream are futures
1198    /// that will never fail to produce a resource, although they may block indefinitely if the
1199    /// resource needs to be fetched.
1200    ///
1201    /// Objects are loaded and fetched in chunks, which strikes a good balance of limiting the total
1202    /// number of storage and network requests, while also keeping the amount of simultaneous
1203    /// resource consumption bounded.
1204    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    /// Same as [`Self::get_range`], but uses the given chunk size instead of the default.
1214    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                        // Introduce a delay (`chunk_fetch_delay`) between fetching chunks. This
1234                        // helps to limit constant high CPU usage when fetching long range of data,
1235                        // especially for older streams that fetch most of the data from local
1236                        // storage.
1237                        sleep(chunk_fetch_delay).await;
1238                        stream::iter(chunk)
1239                    }
1240                }
1241            })
1242            .flatten()
1243            .then(move |f| async move {
1244                match f {
1245                    // Introduce a delay (`active_fetch_delay`) for active fetches to reduce load on
1246                    // the catchup provider. The delay applies between pending fetches, not between
1247                    // chunks.
1248                    Fetch::Pending(_) => sleep(active_fetch_delay).await,
1249                    Fetch::Ready(_) => (),
1250                };
1251                f
1252            })
1253            .boxed()
1254    }
1255
1256    /// Same as [`Self::get_range`], but yields objects in reverse order by height.
1257    ///
1258    /// Note that unlike [`Self::get_range`], which accepts any range and yields an infinite stream
1259    /// if the range has no upper bound, this function requires there to be a defined upper bound,
1260    /// otherwise we don't know where the reversed stream should _start_. The `end` bound given here
1261    /// is inclusive; i.e. the first item yielded by the stream will have height `end`.
1262    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    /// Same as [`Self::get_range_rev`], but uses the given chunk size instead of the default.
1275    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                        // Introduce a delay (`chunk_fetch_delay`) between fetching chunks. This
1295                        // helps to limit constant high CPU usage when fetching long range of data,
1296                        // especially for older streams that fetch most of the data from local
1297                        // storage
1298                        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                    // Introduce a delay (`active_fetch_delay`) for active fetches to reduce load on
1307                    // the catchup provider. The delay applies between pending fetches, not between
1308                    // chunks.
1309                    Fetch::Pending(_) => sleep(active_fetch_delay).await,
1310                    Fetch::Ready(_) => (),
1311                };
1312                f
1313            })
1314            .boxed()
1315    }
1316
1317    /// Get a range of objects from local storage or a provider.
1318    ///
1319    /// This method is similar to `get_range`, except that:
1320    /// * It fetches all desired objects together, as a single chunk
1321    /// * It loads the object or triggers fetches right now rather than providing a lazy stream
1322    ///   which only fetches objects when polled.
1323    async fn get_chunk<T>(self: &Arc<Self>, chunk: Range<usize>) -> Vec<Fetch<T>>
1324    where
1325        T: RangedFetchable<Types>,
1326    {
1327        // Subscribe to notifications first. As in [`get`](Self::get), this ensures we won't miss
1328        // any notifications sent in between checking local storage and triggering a fetch if
1329        // necessary.
1330        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                // Convert to fetches. Objects which are not immediately available (`None` in the
1340                // chunk) become passive fetches awaiting a notification of availability.
1341                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        // We'll use these channels to get the objects back that we successfully load on retry.
1360        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                            // Limit the number of simultaneous retry tasks hitting the database.
1375                            // When the database is down, we might have a lot of these tasks
1376                            // running, and if they all hit the DB at once, they are only going to
1377                            // make things worse.
1378                            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                                        // If the object was immediately available after all, signal
1386                                        // the original fetch. We probably just temporarily couldn't
1387                                        // access it due to database errors.
1388                                        tracing::info!(?chunk, i, "object was ready after retries");
1389                                        sender.send(obj).ok();
1390                                    } else {
1391                                        // The object was not immediately available after all, but
1392                                        // we have successfully spawned a fetch for it if possible.
1393                                        // The spawned fetch will notify the original request once
1394                                        // it completes.
1395                                        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        // Wait for the objects to be fetched, either from the local database on retry or from
1419        // another provider eventually.
1420        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    /// Try to get a range of objects from local storage, initializing fetches if any are missing.
1434    ///
1435    /// If this function succeeded, then for each object in the requested range, either:
1436    /// * the object was available locally, and corresponds to `Some(_)` object in the result
1437    /// * the object was not available locally (and corresponds to `None` in the result), but a
1438    ///   fetch was successfully spawned if possible (in other words, if a fetch was not spawned, it
1439    ///   was determined that the requested object is not fetchable)
1440    ///
1441    /// This function will fail if it could not be determined which objects in the requested range
1442    /// are available locally, or if, for any missing object, it could not be determined whether
1443    /// that object is fetchable. In this case, there may be no fetch spawned for certain objects in
1444    /// the requested range, even if those objects are actually fetchable.
1445    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        // Log and discard error information; we want a list of Option where None indicates an
1458        // object that needs to be fetched. Note that we don't use `FetchRequest::might_exist` to
1459        // silence the logs here when an object is missing that is not expected to exist at all.
1460        // When objects are not expected to exist, `load_range` should just return a truncated list
1461        // rather than returning `Err` objects, so if there are errors in here they are unexpected
1462        // and we do want to log them.
1463        let ts = ts.into_iter().filter_map(ResultExt::ok_or_trace);
1464
1465        // Kick off a fetch for each missing object.
1466        let mut results = Vec::with_capacity(chunk.len());
1467        for t in ts {
1468            // Fetch missing objects that should come before `t`.
1469            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        // Fetch missing objects from the end of the range.
1482        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    /// Spawn an active fetch for the requested object, if possible.
1492    ///
1493    /// On success, either an active fetch for `req` has been spawned, or it has been determined
1494    /// that `req` is not fetchable. Fails if it cannot be determined (e.g. due to errors in the
1495    /// local database) whether `req` is fetchable or not.
1496    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        // Trigger an active fetch from a remote provider if possible.
1507        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    /// Proactively search for and retrieve missing objects.
1519    ///
1520    /// This function will proactively identify and retrieve blocks and leaves which are missing
1521    /// from storage. It will run until cancelled, thus, it is meant to be spawned as a background
1522    /// task rather than called synchronously.
1523    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                // Fetch missing blocks. This will also trigger a fetch for the corresponding
1550                // missing leaves.
1551                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                    // Break the range into manageable, aligned chunks (which improves cacheability
1561                    // for the upstream server).
1562                    for chunk in range_chunks_aligned(range.start..range.end, chunk_size) {
1563                        tracing::info!(?chunk, "fetching missing block chunk");
1564
1565                        // Fetching the payload metadata is enough to trigger an active fetch of the
1566                        // corresponding leaf and the full block if they are missing.
1567                        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                // Do the same for VID.
1582                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                // Reset metrics.
1609                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        // Check the cache first. This prevents the expensive sync_status queries from being run too
1629        // often, and also ensures that if two tasks try to get the sync status at the same time,
1630        // only one will actually compute it; the other will find the cache populated by the time it
1631        // gets a lock on the mutex.
1632        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            // Add an initial range for pruned data.
1652            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        // Break the range into manageable chunks, so we don't hold any one database transaction
1667        // open for too long.
1668        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
1700/// How long the aggregator waits for the next block before rebuilding the stream.
1701///
1702/// The aggregator drives an in-order stream of passive fetches. A single missed availability
1703/// notification would otherwise park the stream until the 8h proactive scan. On this interval
1704/// the aggregator breaks the inner loop and rebuilds the stream from the committed aggregate
1705/// height, which re-evaluates `might_exist` against current storage and loads now-present
1706/// heights as `Ready`.
1707pub(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                            // This is not supposed to happen, but if the chunk is empty, skip it.
1763                            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, // stream ended (unbounded range: should not happen)
1800                    // A legitimately slow in-progress fetch is intentionally abandoned here:
1801                    // we cannot distinguish "slow fetch" from "missed-notification stall", so
1802                    // we rebuild the stream, which re-loads now-present heights as Ready and
1803                    // re-issues any genuinely-missing fetch from the committed aggregate height.
1804                    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    /// Load a cert2 from availability storage
1823    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    /// Store an object and notify anyone waiting on this object that it is available.
1855    async fn store_and_notify<T>(&self, obj: &T)
1856    where
1857        T: Storable<Types>,
1858    {
1859        self.store(obj).await;
1860
1861        // Send a notification about the newly received object. It is important that we do this
1862        // _after_ our attempt to store the object in local storage, otherwise there is a potential
1863        // missed notification deadlock:
1864        // * we send the notification
1865        // * a task calls [`get`](Self::get) or [`get_chunk`](Self::get_chunk), finds that the
1866        //   requested object is not in storage, and begins waiting for a notification
1867        // * we store the object. This ensures that no other task will be triggered to fetch it,
1868        //   which means no one will ever notify the waiting task.
1869        //
1870        // Note that we send the notification regardless of whether the store actually succeeded or
1871        // not. This is to avoid _another_ subtle deadlock: if we failed to notify just because we
1872        // failed to store, some fetches might not resolve, even though the object in question has
1873        // actually been fetched. This should actually be ok, because as long as the object is not
1874        // in storage, eventually some other task will come along and fetch, store, and notify about
1875        // it. However, this is certainly not ideal, since we could resolve those pending fetches
1876        // right now, and it causes bigger problems when the fetch that fails to resolve is the
1877        // proactive scanner task, who is often the one that would eventually come along and
1878        // re-fetch the object.
1879        //
1880        // The key thing to note is that it does no harm to notify even if we fail to store: at best
1881        // we wake some tasks up sooner; at worst, anyone who misses the notification still
1882        // satisfies the invariant that we only wait on notifications for objects which are not in
1883        // storage, and eventually some other task will come along, find the object missing from
1884        // storage, and re-fetch it.
1885        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        // Store the object in local storage, so we can avoid fetching it in the future.
1899        let mut backoff = self.backoff.clone();
1900        backoff.reset();
1901        loop {
1902            let Err(err) = try_store().await else {
1903                break;
1904            };
1905            // It is unfortunate if this fails, but we can still proceed by notifying with the
1906            // object that we fetched, keeping it in memory. Log the error, retry a few times, and
1907            // eventually move on.
1908            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
2211/// A provider which can be used as a fetcher by the availability service.
2212pub 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    /// Indicate whether it is possible this object could exist.
2239    ///
2240    /// This can filter out requests quickly for objects that cannot possibly exist, such as
2241    /// requests for objects with a height greater than the current block height. Not only does this
2242    /// let us fail faster for such requests (without touching storage at all), it also helps keep
2243    /// logging quieter when we fail to fetch an object because the user made a bad request, while
2244    /// still being fairly loud when we fail to fetch an object that might have really existed.
2245    ///
2246    /// This method is conservative: it returns `true` if it cannot tell whether the given object
2247    /// could exist or not.
2248    fn might_exist(self, _heights: Heights) -> bool {
2249        true
2250    }
2251}
2252
2253/// Objects which can be fetched from a remote DA provider and cached in local storage.
2254///
2255/// This trait lets us abstract over leaves, blocks, and other types that can be fetched. Thus, the
2256/// logistics of fetching are shared between all objects, and only the low-level particulars are
2257/// type-specific.
2258#[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    /// A succinct specification of the object to be fetched.
2266    type Request: FetchRequest;
2267
2268    /// Does this object satisfy the given request?
2269    fn satisfies(&self, req: Self::Request) -> bool;
2270
2271    /// Spawn a task to fetch the object from a remote provider, if possible.
2272    ///
2273    /// An active fetch will only be triggered if:
2274    /// * There is not already an active fetch in progress for the same object
2275    /// * The requested object is known to exist. For example, we will fetch a leaf by height but
2276    ///   not by hash, since we can't guarantee that a leaf with an arbitrary hash exists. Note that
2277    ///   this function assumes `req.might_exist()` has already been checked before calling it, and
2278    ///   so may do unnecessary work if the caller does not ensure this.
2279    ///
2280    /// If we do trigger an active fetch for an object, any passive listeners for the object will be
2281    /// notified once it has been retrieved. If we do not trigger an active fetch for an object,
2282    /// this function does nothing. In either case, as long as the requested object does in fact
2283    /// exist, we will eventually receive it passively, since we will eventually receive all blocks
2284    /// and leaves that are ever produced. Active fetching merely helps us receive certain objects
2285    /// sooner.
2286    ///
2287    /// This function fails if it _might_ be possible to actively fetch the requested object, but we
2288    /// were unable to do so (e.g. due to errors in the database).
2289    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    /// Wait for someone else to fetch the object.
2302    async fn passive_fetch(notifiers: &Notifiers<Types>, req: Self::Request) -> PassiveFetch<Self>;
2303
2304    /// Load an object from local storage.
2305    ///
2306    /// This function assumes `req.might_exist()` has already been checked before calling it, and so
2307    /// may do unnecessary work if the caller does not ensure this.
2308    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    /// Load a range of these objects from local storage.
2325    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
2331/// An object which can be stored in the database.
2332trait Storable<Types: NodeType>: Clone {
2333    /// The name of this object, for debugging purposes.
2334    fn debug_name(&self) -> String;
2335
2336    /// Notify anyone waiting for this object that it has become available.
2337    fn notify(&self, notifiers: &Notifiers<Types>) -> impl Send + Future<Output = ()>;
2338
2339    /// Store the object in the local database.
2340    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        // No passive listeners for cert2.
2385    }
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
2396/// Break a range into fixed-size chunks.
2397fn range_chunks<R>(range: R, chunk_size: usize) -> impl Iterator<Item = Range<usize>>
2398where
2399    R: RangeBounds<usize>,
2400{
2401    // Transform range to explicit start (inclusive) and end (exclusive) bounds.
2402    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
2415/// Break a range into fixed-alignment chunks.
2416///
2417/// Each chunk is of size `alignment`, and starts on a multiple of `alignment`, with the possible
2418/// exception of the first chunk (which may be misaligned and small) and the last (which may be
2419/// small).
2420fn range_chunks_aligned<R>(range: R, alignment: usize) -> impl Iterator<Item = Range<usize>>
2421where
2422    R: RangeBounds<usize>,
2423{
2424    // Transform range to explicit start (inclusive) and end (exclusive) bounds.
2425    let Range { mut start, end } = range_to_bounds(range);
2426
2427    // If necessary, generate a partial first chunk to force the remaining chunks into alignment.
2428    let first = if start.is_multiple_of(alignment) {
2429        None
2430    } else {
2431        // The partial first chunk ends at the next multiple of the alignment, or at the end of the
2432        // overall range, whichever comes first.
2433        let chunk_end = min(start.next_multiple_of(alignment), end);
2434        let chunk = start..chunk_end;
2435
2436        // Start the series of aligned chunks at the end of the partial first chunk.
2437        start = chunk_end;
2438        Some(chunk)
2439    };
2440
2441    first.into_iter().chain(range_chunks(start..end, alignment))
2442}
2443
2444/// Transform a range to explicit start (inclusive) and end (exclusive) bounds.
2445fn 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
2459/// Break a range into fixed-size chunks, starting from the end and moving towards the start.
2460///
2461/// While the chunks are yielded in reverse order, from `end` to `start`, each individual chunk is
2462/// in the usual ascending order. That is, the first chunk ends with `end` and the last chunk starts
2463/// with `start`.
2464///
2465/// Note that unlike [`range_chunks`], which accepts any range and yields an infinite iterator if
2466/// the range has no upper bound, this function requires there to be a defined upper bound,
2467/// otherwise we don't know where the reversed iterator should _start_. The `end` bound given here
2468/// is inclusive; i.e. the end of the first chunk yielded by the stream will be exactly `end`.
2469fn range_chunks_rev(
2470    start: Bound<usize>,
2471    end: usize,
2472    chunk_size: usize,
2473) -> impl Iterator<Item = Range<usize>> {
2474    // Transform the start bound to be inclusive.
2475    let start = match start {
2476        Bound::Included(i) => i,
2477        Bound::Excluded(i) => i + 1,
2478        Bound::Unbounded => 0,
2479    };
2480    // Transform the end bound to be exclusive.
2481    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    /// Whether a scan is currently running (1) or not (0).
2521    running: Box<dyn Gauge>,
2522    /// The current number that is running.
2523    current_scan: Box<dyn Gauge>,
2524    /// Number of blocks processed in the current scan.
2525    scanned_blocks: Box<dyn Gauge>,
2526    /// Number of VID entries processed in the current scan.
2527    scanned_vid: Box<dyn Gauge>,
2528    /// The number of missing blocks discovered and not yet resolved in the current scan.
2529    missing_blocks: Box<dyn Gauge>,
2530    /// The number of missing VID entries discovered and not yet resolved in the current scan.
2531    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    /// The block height for which aggregate statistics are currently available.
2551    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    /// Return the cached sync status, if present and fresh.
2640    fn try_get(&self) -> Option<&SyncStatusQueryData> {
2641        if self.last_updated.elapsed() > self.ttl {
2642            // Cached value is stale.
2643            return None;
2644        }
2645        self.cached.as_ref()
2646    }
2647
2648    /// Refresh the cache with an updated value.
2649    fn update(&mut self, value: SyncStatusQueryData) {
2650        self.last_updated = Instant::now();
2651        self.cached = Some(value);
2652    }
2653}
2654
2655/// Turn a fallible passive fetch future into an infallible "fetch".
2656///
2657/// Basically, we ignore failures due to a channel sender being dropped, which should never happen.
2658fn 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                    // If `passive_fetch` returns `None`, it means the notifier was dropped without
2671                    // ever sending a notification. In this case, the correct behavior is actually
2672                    // to block forever (unless the `Fetch` itself is dropped), since the semantics
2673                    // of `Fetch` are to never fail. This is analogous to fetching an object which
2674                    // doesn't actually exist: the `Fetch` will never return.
2675                    //
2676                    // However, for ease of debugging, and since this is never expected to happen in
2677                    // normal usage, we panic instead. This should only happen in two cases:
2678                    // * The server was shut down (dropping the notifier) without cleaning up some
2679                    //   background tasks. This will not affect runtime behavior, but should be
2680                    //   fixed if it happens.
2681                    // * There is a very unexpected runtime bug resulting in the notifier being
2682                    //   dropped. If this happens, things are very broken in any case, and it is
2683                    //   better to panic loudly than simply block forever.
2684                    panic!("notifier dropped without satisfying request {req:?}");
2685                },
2686            }
2687        })
2688        .boxed(),
2689    )
2690}
2691
2692/// Get the result of the first future to return `Some`, if either do.
2693async 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        // If the first future resolves with `Some`, immediately return the result.
2699        Either::Left((Some(a), _)) => Some(a),
2700        Either::Right((Some(b), _)) => Some(b),
2701
2702        // If the first future resolves with `None`, wait for the result of the second future.
2703        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        // Inclusive bounds, partial last chunk.
2736        assert_eq!(
2737            range_chunks(0..=4, 2).collect::<Vec<_>>(),
2738            [0..2, 2..4, 4..5]
2739        );
2740
2741        // Inclusive bounds, complete last chunk.
2742        assert_eq!(
2743            range_chunks(0..=5, 2).collect::<Vec<_>>(),
2744            [0..2, 2..4, 4..6]
2745        );
2746
2747        // Exclusive bounds, partial last chunk.
2748        assert_eq!(
2749            range_chunks(0..5, 2).collect::<Vec<_>>(),
2750            [0..2, 2..4, 4..5]
2751        );
2752
2753        // Exclusive bounds, complete last chunk.
2754        assert_eq!(
2755            range_chunks(0..6, 2).collect::<Vec<_>>(),
2756            [0..2, 2..4, 4..6]
2757        );
2758
2759        // Unbounded.
2760        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        // Aligned first chunk, partial last chunk.
2771        assert_eq!(
2772            range_chunks_aligned(2..5, 2).collect::<Vec<_>>(),
2773            [2..4, 4..5]
2774        );
2775
2776        // Misaligned first chunk, complete last chunk.
2777        assert_eq!(
2778            range_chunks_aligned(1..4, 2).collect::<Vec<_>>(),
2779            [1..2, 2..4]
2780        );
2781
2782        // Incomplete chunk.
2783        assert_eq!(range_chunks_aligned(1..3, 10).collect::<Vec<_>>(), [1..3]);
2784
2785        // Unbounded.
2786        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        // Inclusive bounds, partial last chunk.
2795        assert_eq!(
2796            range_chunks_rev(Bound::Included(0), 4, 2).collect::<Vec<_>>(),
2797            [3..5, 1..3, 0..1]
2798        );
2799
2800        // Inclusive bounds, complete last chunk.
2801        assert_eq!(
2802            range_chunks_rev(Bound::Included(0), 5, 2).collect::<Vec<_>>(),
2803            [4..6, 2..4, 0..2]
2804        );
2805
2806        // Exclusive bounds, partial last chunk.
2807        assert_eq!(
2808            range_chunks_rev(Bound::Excluded(0), 5, 2).collect::<Vec<_>>(),
2809            [4..6, 2..4, 1..2]
2810        );
2811
2812        // Exclusive bounds, complete last chunk.
2813        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        // Generate some mock leaves to insert.
2833        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        // Set up.
2848        {
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        // Verify missing.
2870        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        // Verify ranges.
2877        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    /// Repeatedly poll `f` until it returns `Some`, up to `max_wait`. Panics on timeout.
3007    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    /// Aggregator recovers after blocks land in storage without a notification, and produces the
3026    /// exact expected cumulative transaction count.
3027    ///
3028    /// This exercises the stall scenario: the aggregator's `get_range` stream subscribes to
3029    /// notifications before checking storage. If blocks are written to the underlying storage
3030    /// after the subscription+check cycle (bypassing `store_and_notify`), the notification is
3031    /// missed and the stream parks. The `AGGREGATOR_RETRY_INTERVAL` timeout fires, the aggregator
3032    /// breaks and rebuilds the stream from the committed aggregate height, finding the now-present
3033    /// blocks as `Ready`.
3034    ///
3035    /// Each block at height `i` carries `i + 1` transactions, so the expected total is
3036    /// `n * (n + 1) / 2`. A double-count or skip would fail the final assertion.
3037    ///
3038    /// This test is slow because it must wait for `AGGREGATOR_RETRY_INTERVAL` (30 s) to elapse.
3039    #[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        // Build N blocks: block at height i has i+1 transactions.
3053        // Each block gets a distinct payload (and thus a distinct payload_commitment),
3054        // so the payload table rows are not collapsed by the (hash, ns_table) dedup.
3055        // Expected cumulative total = 1 + 2 + ... + n = n*(n+1)/2.
3056        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            // Height i carries i+1 distinct transactions.
3071            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        // Insert leaves and blocks directly into the underlying storage, bypassing
3105        // `store_and_notify`. The aggregator task has already started and is waiting on a
3106        // notification for height 0 that will never arrive via this path.
3107        {
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        // The aggregator rebuilds after AGGREGATOR_RETRY_INTERVAL elapses. Allow 4× for the
3120        // rebuild and processing.
3121        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        // Exact sum verifies no double-count and no skip.
3134        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}