Skip to main content

hotshot_query_service/data_source/storage/
fs.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#![cfg(feature = "file-system-data-source")]
14
15use std::{
16    collections::{
17        BTreeMap,
18        hash_map::{Entry, HashMap},
19    },
20    hash::Hash,
21    iter,
22    ops::{Bound, Deref, RangeBounds},
23    path::Path,
24};
25
26use async_lock::{RwLock, RwLockReadGuard, RwLockWriteGuard};
27use async_trait::async_trait;
28use atomic_store::{AtomicStore, AtomicStoreLoader, PersistenceError};
29use committable::Committable;
30use futures::future::Future;
31use hotshot_types::{
32    data::{VidCommitment, VidShare},
33    simple_certificate::CertificatePair,
34    traits::{block_contents::BlockHeader, node_implementation::NodeType},
35};
36use serde::{Serialize, de::DeserializeOwned};
37use snafu::OptionExt;
38
39use super::{
40    Aggregate, AggregatesStorage, AvailabilityStorage, NodeStorage, PayloadMetadata,
41    SerializableRetry, UpdateAggregatesStorage, UpdateAvailabilityStorage, VidCommonMetadata,
42    ledger_log::{Iter, LedgerLog},
43    pruning::{PruneStorage, PrunedHeightStorage, PrunerConfig},
44};
45use crate::{
46    Header, MissingSnafu, NotFoundSnafu, Payload, QueryError, QueryResult,
47    availability::{
48        Certificate2, NamespaceId,
49        data_source::{BlockId, LeafId},
50        query_data::{
51            BlockHash, BlockQueryData, LeafHash, LeafQueryData, PayloadQueryData, QueryableHeader,
52            QueryablePayload, TransactionHash, VidCommonQueryData,
53        },
54    },
55    data_source::{VersionedDataSource, update},
56    metrics::PrometheusMetrics,
57    node::{SyncStatusQueryData, TimeWindowQueryData, WindowStart},
58    status::HasMetrics,
59    types::HeightIndexed,
60};
61
62const CACHED_LEAVES_COUNT: usize = 100;
63const CACHED_BLOCKS_COUNT: usize = 100;
64const CACHED_VID_COMMON_COUNT: usize = 100;
65const CACHED_CERT2_COUNT: usize = 100;
66
67#[derive(custom_debug::Debug)]
68pub struct FileSystemStorageInner<Types>
69where
70    Types: NodeType,
71    Header<Types>: QueryableHeader<Types>,
72    Payload<Types>: QueryablePayload<Types>,
73{
74    index_by_leaf_hash: HashMap<LeafHash<Types>, u64>,
75    index_by_block_hash: HashMap<BlockHash<Types>, u64>,
76    index_by_payload_hash: HashMap<VidCommitment, u64>,
77    index_by_txn_hash: HashMap<TransactionHash<Types>, u64>,
78    index_by_time: BTreeMap<u64, Vec<u64>>,
79    num_transactions: usize,
80    payload_size: usize,
81    #[debug(skip)]
82    top_storage: Option<AtomicStore>,
83    leaf_storage: LedgerLog<LeafQueryData<Types>>,
84    block_storage: LedgerLog<BlockQueryData<Types>>,
85    vid_storage: LedgerLog<(VidCommonQueryData<Types>, Option<VidShare>)>,
86    latest_qc_chain: Option<[CertificatePair<Types>; 2]>,
87    cert2_storage: LedgerLog<Certificate2<Types>>,
88}
89
90impl<Types> FileSystemStorageInner<Types>
91where
92    Types: NodeType,
93    Header<Types>: QueryableHeader<Types>,
94    Payload<Types>: QueryablePayload<Types>,
95{
96    fn get_block_index(&self, id: BlockId<Types>) -> QueryResult<usize> {
97        match id {
98            BlockId::Number(n) => Ok(n),
99            BlockId::Hash(h) => {
100                Ok(*self.index_by_block_hash.get(&h).context(NotFoundSnafu)? as usize)
101            },
102            BlockId::PayloadHash(h) => {
103                Ok(*self.index_by_payload_hash.get(&h).context(NotFoundSnafu)? as usize)
104            },
105        }
106    }
107
108    fn get_block(&self, id: BlockId<Types>) -> QueryResult<BlockQueryData<Types>> {
109        self.block_storage
110            .iter()
111            .nth(self.get_block_index(id)?)
112            .context(NotFoundSnafu)?
113            .context(MissingSnafu)
114    }
115
116    fn get_header(&self, id: BlockId<Types>) -> QueryResult<Header<Types>> {
117        self.get_block(id).map(|block| block.header)
118    }
119
120    fn get_block_range<R>(&self, range: R) -> QueryResult<Vec<QueryResult<BlockQueryData<Types>>>>
121    where
122        R: RangeBounds<usize> + Send,
123    {
124        Ok(range_iter(self.block_storage.iter(), range).collect())
125    }
126}
127
128/// Storage for the APIs provided in this crate, backed by a remote PostgreSQL database.
129#[derive(Debug)]
130pub struct FileSystemStorage<Types: NodeType>
131where
132    Header<Types>: QueryableHeader<Types>,
133    Payload<Types>: QueryablePayload<Types>,
134{
135    inner: RwLock<FileSystemStorageInner<Types>>,
136    metrics: PrometheusMetrics,
137}
138
139impl<Types: NodeType> PrunerConfig for FileSystemStorage<Types>
140where
141    Header<Types>: QueryableHeader<Types>,
142    Payload<Types>: QueryablePayload<Types>,
143{
144}
145impl<Types: NodeType> PruneStorage for FileSystemStorage<Types>
146where
147    Header<Types>: QueryableHeader<Types>,
148    Payload<Types>: QueryablePayload<Types>,
149{
150    type Pruner<'a> = ();
151}
152
153impl<Types: NodeType> FileSystemStorage<Types>
154where
155    Payload<Types>: QueryablePayload<Types>,
156    Header<Types>: QueryableHeader<Types>,
157{
158    /// Create a new [FileSystemStorage] with storage at `path`.
159    ///
160    /// If there is already data at `path`, it will be archived.
161    ///
162    /// The [FileSystemStorage] will manage its own persistence synchronization.
163    pub async fn create(path: &Path) -> Result<Self, PersistenceError> {
164        let mut loader = AtomicStoreLoader::create(path, "hotshot_data_source")?;
165        loader.retain_archives(1);
166        let data_source = Self::create_with_store(&mut loader).await?;
167        data_source.inner.write().await.top_storage = Some(AtomicStore::open(loader)?);
168        Ok(data_source)
169    }
170
171    /// Open an existing [FileSystemStorage] from storage at `path`.
172    ///
173    /// If there is no data at `path`, a new store will be created.
174    ///
175    /// The [FileSystemStorage] will manage its own persistence synchronization.
176    pub async fn open(path: &Path) -> Result<Self, PersistenceError> {
177        let mut loader = AtomicStoreLoader::load(path, "hotshot_data_source")?;
178        loader.retain_archives(1);
179        let data_source = Self::open_with_store(&mut loader).await?;
180        data_source.inner.write().await.top_storage = Some(AtomicStore::open(loader)?);
181        Ok(data_source)
182    }
183
184    /// Create a new [FileSystemStorage] using a persistent storage loader.
185    ///
186    /// If there is existing data corresponding to the [FileSystemStorage] data structures, it will
187    /// be archived.
188    ///
189    /// The [FileSystemStorage] will register its persistent data structures with `loader`. The
190    /// caller is responsible for creating an [AtomicStore] from `loader` and managing
191    /// synchronization of the store.
192    pub async fn create_with_store(
193        loader: &mut AtomicStoreLoader,
194    ) -> Result<Self, PersistenceError> {
195        Ok(Self {
196            inner: RwLock::new(FileSystemStorageInner {
197                index_by_leaf_hash: Default::default(),
198                index_by_block_hash: Default::default(),
199                index_by_payload_hash: Default::default(),
200                index_by_txn_hash: Default::default(),
201                index_by_time: Default::default(),
202                num_transactions: 0,
203                payload_size: 0,
204                top_storage: None,
205                leaf_storage: LedgerLog::create(loader, "leaves", CACHED_LEAVES_COUNT)?,
206                block_storage: LedgerLog::create(loader, "blocks", CACHED_BLOCKS_COUNT)?,
207                vid_storage: LedgerLog::create(loader, "vid_common", CACHED_VID_COMMON_COUNT)?,
208                cert2_storage: LedgerLog::create(loader, "cert2", CACHED_CERT2_COUNT)?,
209                latest_qc_chain: None,
210            }),
211            metrics: Default::default(),
212        })
213    }
214
215    /// Open an existing [FileSystemStorage] using a persistent storage loader.
216    ///
217    /// If there is no existing data corresponding to the [FileSystemStorage] data structures, a new
218    /// store will be created.
219    ///
220    /// The [FileSystemStorage] will register its persistent data structures with `loader`. The
221    /// caller is responsible for creating an [AtomicStore] from `loader` and managing
222    /// synchronization of the store.
223    pub async fn open_with_store(loader: &mut AtomicStoreLoader) -> Result<Self, PersistenceError> {
224        let leaf_storage =
225            LedgerLog::<LeafQueryData<Types>>::open(loader, "leaves", CACHED_LEAVES_COUNT)?;
226        let block_storage =
227            LedgerLog::<BlockQueryData<Types>>::open(loader, "blocks", CACHED_BLOCKS_COUNT)?;
228        let vid_storage = LedgerLog::<(VidCommonQueryData<Types>, Option<VidShare>)>::open(
229            loader,
230            "vid_common",
231            CACHED_VID_COMMON_COUNT,
232        )?;
233        let cert2_storage =
234            LedgerLog::<Certificate2<Types>>::open(loader, "cert2", CACHED_CERT2_COUNT)?;
235
236        let mut index_by_block_hash = HashMap::new();
237        let mut index_by_payload_hash = HashMap::new();
238        let mut index_by_time = BTreeMap::<u64, Vec<u64>>::new();
239        let index_by_leaf_hash = leaf_storage
240            .iter()
241            .flatten()
242            .map(|leaf| {
243                update_index_by_hash(&mut index_by_block_hash, leaf.block_hash(), leaf.height());
244                update_index_by_hash(
245                    &mut index_by_payload_hash,
246                    leaf.payload_hash(),
247                    leaf.height(),
248                );
249                index_by_time
250                    .entry(leaf.header().timestamp())
251                    .or_default()
252                    .push(leaf.height());
253                (leaf.hash(), leaf.height())
254            })
255            .collect();
256
257        let mut index_by_txn_hash = HashMap::new();
258        let mut num_transactions = 0;
259        let mut payload_size = 0;
260        for block in block_storage.iter().flatten() {
261            num_transactions += block.len();
262            payload_size += block.size() as usize;
263
264            let height = block.height();
265            for (_, txn) in block.enumerate() {
266                update_index_by_hash(&mut index_by_txn_hash, txn.commit(), height);
267            }
268        }
269
270        Ok(Self {
271            inner: RwLock::new(FileSystemStorageInner {
272                index_by_leaf_hash,
273                index_by_block_hash,
274                index_by_payload_hash,
275                index_by_txn_hash,
276                index_by_time,
277                num_transactions,
278                payload_size,
279                leaf_storage,
280                block_storage,
281                vid_storage,
282                cert2_storage,
283                top_storage: None,
284                latest_qc_chain: None,
285            }),
286            metrics: Default::default(),
287        })
288    }
289
290    /// Advance the version of the persistent store without committing changes to persistent state.
291    pub async fn skip_version(&self) -> Result<(), PersistenceError> {
292        let mut inner = self.inner.write().await;
293        inner.leaf_storage.skip_version()?;
294        inner.block_storage.skip_version()?;
295        inner.vid_storage.skip_version()?;
296        inner.cert2_storage.skip_version()?;
297        if let Some(store) = &mut inner.top_storage {
298            store.commit_version()?;
299        }
300        Ok(())
301    }
302
303    /// Get the stored VID share for a given block, if one exists.
304    pub async fn get_vid_share(&self, block_id: BlockId<Types>) -> QueryResult<VidShare> {
305        let mut tx = self.read().await.map_err(|err| QueryError::Error {
306            message: err.to_string(),
307        })?;
308        let share = tx.vid_share(block_id).await?;
309        Ok(share)
310    }
311
312    /// Get the stored VID common data for a given block, if one exists.
313    pub async fn get_vid_common(
314        &self,
315        block_id: BlockId<Types>,
316    ) -> QueryResult<VidCommonQueryData<Types>> {
317        let mut tx = self.read().await.map_err(|err| QueryError::Error {
318            message: err.to_string(),
319        })?;
320        let share = tx.get_vid_common(block_id).await?;
321        Ok(share)
322    }
323
324    /// Get the stored VID common metadata for a given block, if one exists.
325    pub async fn get_vid_common_metadata(
326        &self,
327        block_id: BlockId<Types>,
328    ) -> QueryResult<VidCommonMetadata<Types>> {
329        let mut tx = self.read().await.map_err(|err| QueryError::Error {
330            message: err.to_string(),
331        })?;
332        let share = tx.get_vid_common_metadata(block_id).await?;
333        Ok(share)
334    }
335}
336
337pub trait Revert {
338    fn revert(&mut self);
339}
340
341impl<Types> Revert for RwLockWriteGuard<'_, FileSystemStorageInner<Types>>
342where
343    Types: NodeType,
344    Header<Types>: QueryableHeader<Types>,
345    Payload<Types>: QueryablePayload<Types>,
346{
347    fn revert(&mut self) {
348        self.leaf_storage.revert_version().unwrap();
349        self.block_storage.revert_version().unwrap();
350        self.vid_storage.revert_version().unwrap();
351        self.cert2_storage.revert_version().unwrap();
352    }
353}
354
355impl<Types> Revert for RwLockReadGuard<'_, FileSystemStorageInner<Types>>
356where
357    Types: NodeType,
358    Header<Types>: QueryableHeader<Types>,
359    Payload<Types>: QueryablePayload<Types>,
360{
361    fn revert(&mut self) {
362        // Nothing to revert for a read-only transaction.
363    }
364}
365
366#[derive(Debug)]
367pub struct Transaction<T: Revert> {
368    inner: T,
369}
370
371impl<T: Revert> Drop for Transaction<T> {
372    fn drop(&mut self) {
373        self.inner.revert();
374    }
375}
376impl<Types> update::Transaction for Transaction<RwLockWriteGuard<'_, FileSystemStorageInner<Types>>>
377where
378    Types: NodeType,
379    Header<Types>: QueryableHeader<Types>,
380    Payload<Types>: QueryablePayload<Types>,
381{
382    async fn commit(mut self) -> anyhow::Result<()> {
383        self.inner.leaf_storage.commit_version().await?;
384        self.inner.block_storage.commit_version().await?;
385        self.inner.vid_storage.commit_version().await?;
386        self.inner.cert2_storage.commit_version().await?;
387        if let Some(store) = &mut self.inner.top_storage {
388            store.commit_version()?;
389        }
390        Ok(())
391    }
392
393    fn revert(self) -> impl Future + Send {
394        // The revert is handled when `self` is dropped.
395        async move {}
396    }
397}
398
399impl<Types> update::Transaction for Transaction<RwLockReadGuard<'_, FileSystemStorageInner<Types>>>
400where
401    Types: NodeType,
402    Header<Types>: QueryableHeader<Types>,
403    Payload<Types>: QueryablePayload<Types>,
404{
405    async fn commit(self) -> anyhow::Result<()> {
406        // Nothing to commit for a read-only transaction.
407        Ok(())
408    }
409
410    fn revert(self) -> impl Future + Send {
411        // The revert is handled when `self` is dropped.
412        async move {}
413    }
414}
415
416impl<Types: NodeType> VersionedDataSource for FileSystemStorage<Types>
417where
418    Header<Types>: QueryableHeader<Types>,
419    Payload<Types>: QueryablePayload<Types>,
420{
421    type Transaction<'a>
422        = Transaction<RwLockWriteGuard<'a, FileSystemStorageInner<Types>>>
423    where
424        Self: 'a;
425    type ReadOnly<'a>
426        = Transaction<RwLockReadGuard<'a, FileSystemStorageInner<Types>>>
427    where
428        Self: 'a;
429
430    async fn write(&self) -> anyhow::Result<Self::Transaction<'_>> {
431        Ok(Transaction {
432            inner: self.inner.write().await,
433        })
434    }
435
436    async fn read(&self) -> anyhow::Result<Self::ReadOnly<'_>> {
437        Ok(Transaction {
438            inner: self.inner.read().await,
439        })
440    }
441}
442
443#[async_trait]
444impl<Types: NodeType> SerializableRetry for FileSystemStorage<Types>
445where
446    Header<Types>: QueryableHeader<Types>,
447    Payload<Types>: QueryablePayload<Types>,
448{
449    /// The file system backend has no serializable isolation and so cannot produce serialization
450    /// conflicts; this simply runs `f` once.
451    async fn serializable_retry<T, E, F, Fut>(&self, _op: &'static str, f: F) -> Result<T, E>
452    where
453        T: Send,
454        E: std::fmt::Display + Send,
455        F: Fn() -> Fut + Send + Sync,
456        Fut: Future<Output = Result<T, E>> + Send,
457    {
458        f().await
459    }
460}
461
462fn range_iter<T>(
463    mut iter: Iter<'_, T>,
464    range: impl RangeBounds<usize>,
465) -> impl '_ + Iterator<Item = QueryResult<T>>
466where
467    T: Clone + Serialize + DeserializeOwned,
468{
469    let start = range.start_bound().cloned();
470    let end = range.end_bound().cloned();
471
472    // Advance the underlying iterator to the start of the range.
473    let mut pos = match start {
474        Bound::Included(n) => {
475            if n > 0 {
476                iter.nth(n - 1);
477            }
478            n
479        },
480        Bound::Excluded(n) => {
481            iter.nth(n);
482            n + 1
483        },
484        Bound::Unbounded => 0,
485    };
486
487    iter::from_fn(move || {
488        // Check if we have reached the end of the range.
489        let reached_end = match end {
490            Bound::Included(n) => pos > n,
491            Bound::Excluded(n) => pos >= n,
492            Bound::Unbounded => false,
493        };
494        if reached_end {
495            return None;
496        }
497        let opt = iter.next()?;
498        pos += 1;
499        Some(opt.context(MissingSnafu))
500    })
501}
502
503#[async_trait]
504impl<Types, T> AvailabilityStorage<Types> for Transaction<T>
505where
506    Types: NodeType,
507    Payload<Types>: QueryablePayload<Types>,
508    Header<Types>: QueryableHeader<Types>,
509    T: Revert + Deref<Target = FileSystemStorageInner<Types>> + Send + Sync,
510{
511    async fn get_leaf(&mut self, id: LeafId<Types>) -> QueryResult<LeafQueryData<Types>> {
512        let n = match id {
513            LeafId::Number(n) => n,
514            LeafId::Hash(h) => *self
515                .inner
516                .index_by_leaf_hash
517                .get(&h)
518                .context(NotFoundSnafu)? as usize,
519        };
520        self.inner
521            .leaf_storage
522            .iter()
523            .nth(n)
524            .context(NotFoundSnafu)?
525            .context(MissingSnafu)
526    }
527
528    async fn get_block(&mut self, id: BlockId<Types>) -> QueryResult<BlockQueryData<Types>> {
529        self.inner.get_block(id)
530    }
531
532    async fn get_header(&mut self, id: BlockId<Types>) -> QueryResult<Header<Types>> {
533        self.inner.get_header(id)
534    }
535
536    async fn get_payload(&mut self, id: BlockId<Types>) -> QueryResult<PayloadQueryData<Types>> {
537        self.get_block(id).await.map(PayloadQueryData::from)
538    }
539
540    async fn get_payload_metadata(
541        &mut self,
542        id: BlockId<Types>,
543    ) -> QueryResult<PayloadMetadata<Types>> {
544        self.get_block(id).await.map(PayloadMetadata::from)
545    }
546
547    async fn get_vid_common(
548        &mut self,
549        id: BlockId<Types>,
550    ) -> QueryResult<VidCommonQueryData<Types>> {
551        Ok(self
552            .inner
553            .vid_storage
554            .iter()
555            .nth(self.inner.get_block_index(id)?)
556            .context(NotFoundSnafu)?
557            .context(MissingSnafu)?
558            .0)
559    }
560
561    async fn get_vid_common_metadata(
562        &mut self,
563        id: BlockId<Types>,
564    ) -> QueryResult<VidCommonMetadata<Types>> {
565        self.get_vid_common(id).await.map(VidCommonMetadata::from)
566    }
567
568    async fn get_leaf_range<R>(
569        &mut self,
570        range: R,
571    ) -> QueryResult<Vec<QueryResult<LeafQueryData<Types>>>>
572    where
573        R: RangeBounds<usize> + Send,
574    {
575        Ok(range_iter(self.inner.leaf_storage.iter(), range).collect())
576    }
577
578    async fn get_block_range<R>(
579        &mut self,
580        range: R,
581    ) -> QueryResult<Vec<QueryResult<BlockQueryData<Types>>>>
582    where
583        R: RangeBounds<usize> + Send,
584    {
585        self.inner.get_block_range(range)
586    }
587
588    async fn get_payload_range<R>(
589        &mut self,
590        range: R,
591    ) -> QueryResult<Vec<QueryResult<PayloadQueryData<Types>>>>
592    where
593        R: RangeBounds<usize> + Send,
594    {
595        Ok(range_iter(self.inner.block_storage.iter(), range)
596            .map(|res| res.map(PayloadQueryData::from))
597            .collect())
598    }
599
600    async fn get_payload_metadata_range<R>(
601        &mut self,
602        range: R,
603    ) -> QueryResult<Vec<QueryResult<PayloadMetadata<Types>>>>
604    where
605        R: RangeBounds<usize> + Send + 'static,
606    {
607        Ok(range_iter(self.inner.block_storage.iter(), range)
608            .map(|res| res.map(PayloadMetadata::from))
609            .collect())
610    }
611
612    async fn get_vid_common_range<R>(
613        &mut self,
614        range: R,
615    ) -> QueryResult<Vec<QueryResult<VidCommonQueryData<Types>>>>
616    where
617        R: RangeBounds<usize> + Send,
618    {
619        Ok(range_iter(self.inner.vid_storage.iter(), range)
620            .map(|res| res.map(|(common, _)| common))
621            .collect())
622    }
623
624    async fn get_vid_common_metadata_range<R>(
625        &mut self,
626        range: R,
627    ) -> QueryResult<Vec<QueryResult<VidCommonMetadata<Types>>>>
628    where
629        R: RangeBounds<usize> + Send,
630    {
631        Ok(range_iter(self.inner.vid_storage.iter(), range)
632            .map(|res| res.map(|(common, _)| common.into()))
633            .collect())
634    }
635
636    async fn get_block_with_transaction(
637        &mut self,
638        hash: TransactionHash<Types>,
639    ) -> QueryResult<BlockQueryData<Types>> {
640        let height = self
641            .inner
642            .index_by_txn_hash
643            .get(&hash)
644            .context(NotFoundSnafu)?;
645        self.inner.get_block((*height as usize).into())
646    }
647}
648
649impl<Types: NodeType> UpdateAvailabilityStorage<Types>
650    for Transaction<RwLockWriteGuard<'_, FileSystemStorageInner<Types>>>
651where
652    Payload<Types>: QueryablePayload<Types>,
653    Header<Types>: QueryableHeader<Types>,
654{
655    async fn insert_qc_chain(
656        &mut self,
657        height: u64,
658        qc_chain: Option<[CertificatePair<Types>; 2]>,
659    ) -> anyhow::Result<()> {
660        if height + 1 >= (self.inner.leaf_storage.iter().len() as u64) {
661            // If this QC chain is for the latest leaf we know about, store it so that we can prove
662            // to clients that the corresponding leaf is finalized. (If it is not the latest leaf,
663            // this is unnecessary, since we can prove it is an ancestor of some later, finalized
664            // leaf.)
665            if let Some(qc_chain) = qc_chain {
666                self.inner.latest_qc_chain = Some(qc_chain);
667            } else {
668                // Since we have a new latest leaf, we have to update latest QC chain even if we
669                // don't actually have a QC chain to store.
670                self.inner.latest_qc_chain = None;
671            }
672        }
673
674        Ok(())
675    }
676
677    async fn insert_cert2(
678        &mut self,
679        height: u64,
680        cert2: Certificate2<Types>,
681    ) -> anyhow::Result<()> {
682        self.inner.cert2_storage.insert(height as usize, cert2)?;
683        Ok(())
684    }
685
686    async fn insert_leaf_range<'a>(
687        &mut self,
688        leaves: impl Send + IntoIterator<Item = &'a LeafQueryData<Types>>,
689    ) -> anyhow::Result<()> {
690        for leaf in leaves {
691            if !self
692                .inner
693                .leaf_storage
694                .insert(leaf.height() as usize, leaf.clone())?
695            {
696                // The leaf was already present.
697                continue;
698            }
699            self.inner
700                .index_by_leaf_hash
701                .insert(leaf.hash(), leaf.height());
702            update_index_by_hash(
703                &mut self.inner.index_by_block_hash,
704                leaf.block_hash(),
705                leaf.height(),
706            );
707            update_index_by_hash(
708                &mut self.inner.index_by_payload_hash,
709                leaf.payload_hash(),
710                leaf.height(),
711            );
712            self.inner
713                .index_by_time
714                .entry(leaf.header().timestamp())
715                .or_default()
716                .push(leaf.height());
717        }
718
719        Ok(())
720    }
721
722    async fn insert_block_range<'a>(
723        &mut self,
724        blocks: impl Send + IntoIterator<IntoIter: Send, Item = &'a BlockQueryData<Types>>,
725    ) -> anyhow::Result<()> {
726        for block in blocks {
727            if !self
728                .inner
729                .block_storage
730                .insert(block.height() as usize, block.clone())?
731            {
732                // The block was already present.
733                continue;
734            }
735            self.inner.num_transactions += block.len();
736            self.inner.payload_size += block.size() as usize;
737            for (_, txn) in block.enumerate() {
738                update_index_by_hash(
739                    &mut self.inner.index_by_txn_hash,
740                    txn.commit(),
741                    block.height(),
742                );
743            }
744        }
745        Ok(())
746    }
747
748    async fn insert_vid_range<'a>(
749        &mut self,
750        vid: impl Send
751        + IntoIterator<
752            IntoIter: Send,
753            Item = (&'a VidCommonQueryData<Types>, Option<&'a VidShare>),
754        >,
755    ) -> anyhow::Result<()> {
756        for (common, share) in vid {
757            self.inner
758                .vid_storage
759                .insert(common.height() as usize, (common.clone(), share.cloned()))?;
760        }
761        Ok(())
762    }
763}
764
765/// Update an index mapping hashes of objects to their positions in the ledger.
766///
767/// This function will insert the mapping from `hash` to `pos` into `index`, _unless_ there is
768/// already an entry for `hash` at an earlier position in the ledger.
769fn update_index_by_hash<H: Eq + Hash, P: Ord>(index: &mut HashMap<H, P>, hash: H, pos: P) {
770    match index.entry(hash) {
771        Entry::Occupied(mut e) => {
772            if &pos < e.get() {
773                // Overwrite the existing entry if the new object was sequenced first.
774                e.insert(pos);
775            }
776        },
777        Entry::Vacant(e) => {
778            e.insert(pos);
779        },
780    }
781}
782
783#[async_trait]
784impl<Types, T> NodeStorage<Types> for Transaction<T>
785where
786    Types: NodeType,
787    Payload<Types>: QueryablePayload<Types>,
788    Header<Types>: QueryableHeader<Types>,
789    T: Revert + Deref<Target = FileSystemStorageInner<Types>> + Send + Sync,
790{
791    async fn block_height(&mut self) -> QueryResult<usize> {
792        Ok(self.inner.leaf_storage.iter().len())
793    }
794
795    async fn count_transactions_in_range(
796        &mut self,
797        range: impl RangeBounds<usize> + Send,
798        namespace: Option<NamespaceId<Types>>,
799    ) -> QueryResult<usize> {
800        if !matches!(range.start_bound(), Bound::Unbounded | Bound::Included(0))
801            || !matches!(range.end_bound(), Bound::Unbounded)
802        {
803            return Err(QueryError::Error {
804                message: "partial aggregates are not supported with file system backend".into(),
805            });
806        }
807
808        if namespace.is_some() {
809            return Err(QueryError::Error {
810                message: "file system does not support per-namespace stats".into(),
811            });
812        }
813
814        Ok(self.inner.num_transactions)
815    }
816
817    async fn payload_size_in_range(
818        &mut self,
819        range: impl RangeBounds<usize> + Send,
820        namespace: Option<NamespaceId<Types>>,
821    ) -> QueryResult<usize> {
822        if !matches!(range.start_bound(), Bound::Unbounded | Bound::Included(0))
823            || !matches!(range.end_bound(), Bound::Unbounded)
824        {
825            return Err(QueryError::Error {
826                message: "partial aggregates are not supported with file system backend".into(),
827            });
828        }
829
830        if namespace.is_some() {
831            return Err(QueryError::Error {
832                message: "file system does not support per-namespace stats".into(),
833            });
834        }
835
836        Ok(self.inner.payload_size)
837    }
838
839    async fn vid_share<ID>(&mut self, id: ID) -> QueryResult<VidShare>
840    where
841        ID: Into<BlockId<Types>> + Send + Sync,
842    {
843        self.inner
844            .vid_storage
845            .iter()
846            .nth(self.inner.get_block_index(id.into())?)
847            .context(NotFoundSnafu)?
848            .context(MissingSnafu)?
849            .1
850            .context(MissingSnafu)
851    }
852
853    async fn sync_status_for_range(
854        &mut self,
855        start: usize,
856        end: usize,
857    ) -> QueryResult<SyncStatusQueryData> {
858        Ok(SyncStatusQueryData {
859            leaves: self.inner.leaf_storage.sync_status(start, end),
860            blocks: self.inner.block_storage.sync_status(start, end),
861            vid_common: self.inner.vid_storage.sync_status(start, end),
862            pruned_height: None,
863        })
864    }
865
866    async fn get_header_window(
867        &mut self,
868        start: impl Into<WindowStart<Types>> + Send + Sync,
869        end: u64,
870        limit: usize,
871    ) -> QueryResult<TimeWindowQueryData<Header<Types>>> {
872        let first_block = match start.into() {
873            WindowStart::Height(h) => h,
874            WindowStart::Hash(h) => self.inner.get_header(h.into())?.block_number(),
875            WindowStart::Time(t) => {
876                // Find the minimum timestamp which is at least `t`, and all the blocks with
877                // that timestamp.
878                let blocks = self
879                    .inner
880                    .index_by_time
881                    .range(t..)
882                    .next()
883                    .context(NotFoundSnafu)?
884                    .1;
885                // Multiple blocks can have the same timestamp (when truncated to seconds);
886                // we want the first one. It is an invariant that any timestamp which has an
887                // entry in `index_by_time` has a non-empty list associated with it, so this
888                // indexing is safe.
889                blocks[0]
890            },
891        } as usize;
892
893        let mut res = TimeWindowQueryData::default();
894
895        // Include the block just before the start of the window, if there is one.
896        if first_block > 0 {
897            res.prev = Some(self.inner.get_header((first_block - 1).into())?);
898        }
899
900        // Add blocks to the window, starting from `first_block`, until we reach the end of
901        // the requested time window.
902        for block in self.inner.get_block_range(first_block..)? {
903            let header = block?.header().clone();
904            if header.timestamp() >= end {
905                res.next = Some(header);
906                break;
907            }
908            res.window.push(header);
909            if res.window.len() >= limit {
910                break;
911            }
912        }
913
914        Ok(res)
915    }
916
917    async fn latest_qc_chain(&mut self) -> QueryResult<Option<[CertificatePair<Types>; 2]>> {
918        Ok(self.inner.latest_qc_chain.clone())
919    }
920
921    async fn load_cert2(&mut self, height: u64) -> QueryResult<Option<Certificate2<Types>>> {
922        Ok(self
923            .inner
924            .cert2_storage
925            .iter()
926            .nth(height as usize)
927            .flatten())
928    }
929
930    async fn load_earliest_cert2(
931        &mut self,
932        height: u64,
933    ) -> QueryResult<Option<Certificate2<Types>>> {
934        Ok(self
935            .inner
936            .cert2_storage
937            .iter()
938            .skip(height as usize)
939            .flatten()
940            .next())
941    }
942}
943
944impl<Types, T: Revert + Send> AggregatesStorage<Types> for Transaction<T>
945where
946    Types: NodeType,
947    Header<Types>: QueryableHeader<Types>,
948{
949    async fn aggregates_height(&mut self) -> anyhow::Result<usize> {
950        Ok(0)
951    }
952
953    async fn load_prev_aggregate(&mut self) -> anyhow::Result<Option<Aggregate<Types>>> {
954        Ok(None)
955    }
956}
957
958impl<Types, T: Revert + Send> UpdateAggregatesStorage<Types> for Transaction<T>
959where
960    Types: NodeType,
961    Header<Types>: QueryableHeader<Types>,
962{
963    async fn update_aggregates(
964        &mut self,
965        _prev: Aggregate<Types>,
966        _blocks: &[PayloadMetadata<Types>],
967    ) -> anyhow::Result<Aggregate<Types>> {
968        Ok(Aggregate::default())
969    }
970}
971
972impl<T: Revert> PrunedHeightStorage for Transaction<T> {}
973
974impl<Types> HasMetrics for FileSystemStorage<Types>
975where
976    Types: NodeType,
977    Header<Types>: QueryableHeader<Types>,
978    Payload<Types>: QueryablePayload<Types>,
979{
980    fn metrics(&self) -> &PrometheusMetrics {
981        &self.metrics
982    }
983}