Skip to main content

forest/db/car/
many.rs

1// Copyright 2019-2026 ChainSafe Systems
2// SPDX-License-Identifier: Apache-2.0, MIT
3
4//! The [`ManyCar`] block store is the union of `N` read-only CAR-backed block
5//! stores and a single writable block store. Get requests are forwarded to each
6//! store (including the writable store) and the first hit is returned. Write
7//! requests are only forwarded to the writable store.
8//!
9//! A single z-frame cache is shared between all read-only stores.
10
11use super::{AnyCar, ZstdFrameCache};
12use crate::blocks::TipsetKey;
13use crate::db::parity_db::GarbageCollectableDb;
14use crate::db::{
15    BlockstoreWriteOpsSubscribable, EthMappingsStore, MemoryDB, PersistentStore, SettingsStore,
16    SettingsStoreExt,
17};
18use crate::libp2p_bitswap::BitswapStoreReadWrite;
19use crate::prelude::*;
20use crate::rpc::eth::types::EthHash;
21use crate::shim::clock::ChainEpoch;
22use crate::utils::io::EitherMmapOrRandomAccessFile;
23use crate::utils::multihash::prelude::*;
24use crate::{blocks::Tipset, libp2p_bitswap::BitswapStoreRead};
25use parking_lot::RwLock;
26use std::{
27    cmp::Ord,
28    collections::BinaryHeap,
29    path::{Path, PathBuf},
30};
31
32struct WithHeaviestEpoch {
33    pub car: AnyCar<Box<dyn super::RandomAccessFileReader>>,
34    epoch: ChainEpoch,
35}
36
37impl WithHeaviestEpoch {
38    pub fn new(car: AnyCar<Box<dyn super::RandomAccessFileReader>>) -> anyhow::Result<Self> {
39        let epoch = car
40            .heaviest_tipset()
41            .context("store doesn't have a heaviest tipset")?
42            .epoch();
43        Ok(Self { car, epoch })
44    }
45}
46
47impl Ord for WithHeaviestEpoch {
48    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
49        self.epoch.cmp(&other.epoch)
50    }
51}
52
53impl Eq for WithHeaviestEpoch {}
54
55impl PartialOrd for WithHeaviestEpoch {
56    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
57        Some(self.cmp(other))
58    }
59}
60
61impl PartialEq for WithHeaviestEpoch {
62    fn eq(&self, other: &Self) -> bool {
63        self.epoch == other.epoch
64    }
65}
66
67pub struct ManyCar<WriterT = MemoryDB> {
68    shared_cache: RwLock<ZstdFrameCache>,
69    read_only: RwLock<BinaryHeap<WithHeaviestEpoch>>,
70    writer: WriterT,
71}
72
73impl<WriterT> ManyCar<WriterT> {
74    pub fn new(writer: WriterT) -> Self {
75        ManyCar {
76            shared_cache: RwLock::new(ZstdFrameCache::default()),
77            read_only: RwLock::new(BinaryHeap::default()),
78            writer,
79        }
80    }
81
82    pub fn writer(&self) -> &WriterT {
83        &self.writer
84    }
85
86    pub fn register_metrics(self: Arc<Self>)
87    where
88        WriterT: Send + Sync + 'static,
89    {
90        crate::metrics::register_collector(Box::new(ManyCarMetricsCollector(self)));
91    }
92}
93
94impl<WriterT: Default> Default for ManyCar<WriterT> {
95    fn default() -> Self {
96        Self::new(Default::default())
97    }
98}
99
100impl<WriterT> ManyCar<WriterT> {
101    pub fn with_read_only<ReaderT: super::RandomAccessFileReader>(
102        self,
103        any_car: AnyCar<ReaderT>,
104    ) -> anyhow::Result<Self> {
105        self.read_only(any_car)?;
106        Ok(self)
107    }
108
109    pub fn read_only<ReaderT: super::RandomAccessFileReader>(
110        &self,
111        any_car: AnyCar<ReaderT>,
112    ) -> anyhow::Result<()> {
113        let mut read_only = self.read_only.write();
114        Self::read_only_inner(
115            &mut read_only,
116            self.shared_cache.read().shallow_clone(),
117            any_car,
118        )
119    }
120
121    fn read_only_inner<ReaderT: super::RandomAccessFileReader>(
122        read_only: &mut BinaryHeap<WithHeaviestEpoch>,
123        shared_cache: ZstdFrameCache,
124        any_car: AnyCar<ReaderT>,
125    ) -> anyhow::Result<()> {
126        let key = read_only.len() as u64;
127        read_only.push(WithHeaviestEpoch::new(
128            any_car.with_cache(shared_cache, key).into_dyn(),
129        )?);
130        Ok(())
131    }
132
133    pub fn with_read_only_files(
134        self,
135        files: impl Iterator<Item = PathBuf>,
136    ) -> anyhow::Result<Self> {
137        self.read_only_files(files)?;
138        Ok(self)
139    }
140
141    pub fn read_only_files(&self, files: impl Iterator<Item = PathBuf>) -> anyhow::Result<()> {
142        for file in files {
143            self.read_only_file(file)?;
144        }
145        Ok(())
146    }
147
148    pub fn read_only_file(&self, file: impl AsRef<Path>) -> anyhow::Result<()> {
149        (|| {
150            self.read_only(AnyCar::new(EitherMmapOrRandomAccessFile::open(
151                file.as_ref(),
152            )?)?)
153        })()
154        .with_context(|| format!("failed to load CAR at {}", file.as_ref().display()))
155    }
156
157    /// Reload `CAR` files after garbage collection.
158    pub fn clear_and_append_read_only_files(
159        &self,
160        files: impl Iterator<Item = PathBuf>,
161    ) -> anyhow::Result<()> {
162        let mut read_only = BinaryHeap::default();
163        let shared_cache = ZstdFrameCache::default();
164        for f in files {
165            let car = AnyCar::new(EitherMmapOrRandomAccessFile::open(f)?)?;
166            Self::read_only_inner(&mut read_only, shared_cache.shallow_clone(), car)?;
167        }
168        *self.read_only.write() = read_only;
169        *self.shared_cache.write() = shared_cache;
170        Ok(())
171    }
172
173    pub fn heaviest_tipset_key(&self) -> anyhow::Result<Option<TipsetKey>> {
174        Ok(self
175            .read_only
176            .read()
177            .peek()
178            .map(|w| AnyCar::heaviest_tipset_key(&w.car)))
179    }
180
181    pub fn heaviest_tipset(&self) -> anyhow::Result<Tipset> {
182        self.read_only
183            .read()
184            .peek()
185            .map(|w| AnyCar::heaviest_tipset(&w.car))
186            .context("ManyCar store doesn't have a heaviest tipset")?
187    }
188
189    /// Number of read-only `CAR`s
190    pub fn len(&self) -> usize {
191        self.read_only.read().len()
192    }
193}
194
195pub trait ReloadableManyCar {
196    fn clear_and_reload_cars(&self, files: impl Iterator<Item = PathBuf>) -> anyhow::Result<()>;
197
198    fn heaviest_car_tipset(&self) -> anyhow::Result<Tipset>;
199}
200
201impl<T> ReloadableManyCar for ManyCar<T> {
202    fn clear_and_reload_cars(&self, files: impl Iterator<Item = PathBuf>) -> anyhow::Result<()> {
203        self.clear_and_append_read_only_files(files)
204    }
205
206    fn heaviest_car_tipset(&self) -> anyhow::Result<Tipset> {
207        self.heaviest_tipset()
208    }
209}
210
211impl<ReaderT: super::RandomAccessFileReader> TryFrom<AnyCar<ReaderT>> for ManyCar<MemoryDB> {
212    type Error = anyhow::Error;
213    fn try_from(any_car: AnyCar<ReaderT>) -> anyhow::Result<Self> {
214        ManyCar::default().with_read_only(any_car)
215    }
216}
217
218impl TryFrom<Vec<PathBuf>> for ManyCar<MemoryDB> {
219    type Error = anyhow::Error;
220    fn try_from(files: Vec<PathBuf>) -> anyhow::Result<Self> {
221        ManyCar::default().with_read_only_files(files.into_iter())
222    }
223}
224
225impl<WriterT: Blockstore> Blockstore for ManyCar<WriterT> {
226    fn get(&self, k: &Cid) -> anyhow::Result<Option<Vec<u8>>> {
227        // Theoretically it should be easily parallelizable with `rayon`.
228        // In practice, there is a massive performance loss when providing
229        // more than a single reader.
230        if let Ok(Some(value)) = self.writer.get(k) {
231            return Ok(Some(value));
232        }
233        for reader in self.read_only.read().iter() {
234            if let Some(val) = reader.car.get(k)? {
235                return Ok(Some(val));
236            }
237        }
238        Ok(None)
239    }
240
241    fn put_keyed(&self, k: &Cid, block: &[u8]) -> anyhow::Result<()> {
242        self.writer.put_keyed(k, block)
243    }
244}
245
246impl<WriterT: PersistentStore> PersistentStore for ManyCar<WriterT> {
247    fn put_keyed_persistent(&self, k: &Cid, block: &[u8]) -> anyhow::Result<()> {
248        self.writer.put_keyed_persistent(k, block)
249    }
250}
251
252impl<WriterT: BitswapStoreRead + Blockstore> BitswapStoreRead for ManyCar<WriterT> {
253    fn contains(&self, cid: &Cid) -> anyhow::Result<bool> {
254        Blockstore::has(self, cid)
255    }
256
257    fn get(&self, cid: &Cid) -> anyhow::Result<Option<Vec<u8>>> {
258        Blockstore::get(self, cid)
259    }
260}
261
262impl<WriterT: BitswapStoreReadWrite + Blockstore> BitswapStoreReadWrite for ManyCar<WriterT> {
263    type Hashes = MultihashCode;
264
265    fn insert(&self, block: &crate::libp2p_bitswap::Block64<Self::Hashes>) -> anyhow::Result<()> {
266        self.put_keyed(block.cid(), block.data())
267    }
268}
269
270impl<WriterT: SettingsStore> SettingsStore for ManyCar<WriterT> {
271    fn read_bin(&self, key: &str) -> anyhow::Result<Option<Vec<u8>>> {
272        SettingsStore::read_bin(self.writer(), key)
273    }
274
275    fn write_bin(&self, key: &str, value: &[u8]) -> anyhow::Result<()> {
276        SettingsStore::write_bin(self.writer(), key, value)
277    }
278
279    fn exists(&self, key: &str) -> anyhow::Result<bool> {
280        SettingsStore::exists(self.writer(), key)
281    }
282
283    fn setting_keys(&self) -> anyhow::Result<Vec<String>> {
284        SettingsStore::setting_keys(self.writer())
285    }
286}
287
288impl<WriterT: EthMappingsStore> EthMappingsStore for ManyCar<WriterT> {
289    fn read_bin(&self, key: &EthHash) -> anyhow::Result<Option<Vec<u8>>> {
290        EthMappingsStore::read_bin(self.writer(), key)
291    }
292
293    fn write_bin(&self, key: &EthHash, value: &[u8]) -> anyhow::Result<()> {
294        EthMappingsStore::write_bin(self.writer(), key, value)
295    }
296
297    fn exists(&self, key: &EthHash) -> anyhow::Result<bool> {
298        EthMappingsStore::exists(self.writer(), key)
299    }
300
301    fn get_message_cids(&self) -> anyhow::Result<Vec<(Cid, u64)>> {
302        EthMappingsStore::get_message_cids(self.writer())
303    }
304
305    fn delete(&self, keys: Vec<EthHash>) -> anyhow::Result<()> {
306        EthMappingsStore::delete(self.writer(), keys)
307    }
308
309    fn tipset_key_by_epoch(&self, epoch: ChainEpoch) -> anyhow::Result<Option<TipsetKey>> {
310        EthMappingsStore::tipset_key_by_epoch(self.writer(), epoch)
311    }
312
313    fn delete_tipset_key_at_epoch(&self, epoch: ChainEpoch) -> anyhow::Result<()> {
314        EthMappingsStore::delete_tipset_key_at_epoch(self.writer(), epoch)
315    }
316
317    fn set_tipset_key_at_epoch_raw(
318        &self,
319        epoch: ChainEpoch,
320        tsk: &TipsetKey,
321    ) -> anyhow::Result<()> {
322        EthMappingsStore::set_tipset_key_at_epoch_raw(self.writer(), epoch, tsk)
323    }
324}
325
326impl<T: Blockstore + SettingsStore> super::super::HeaviestTipsetKeyProvider for ManyCar<T> {
327    fn heaviest_tipset_key(&self) -> anyhow::Result<Option<TipsetKey>> {
328        match SettingsStoreExt::read_obj::<TipsetKey>(self, crate::db::setting_keys::HEAD_KEY)? {
329            Some(tsk) => Ok(Some(tsk)),
330            None => self.heaviest_tipset_key(),
331        }
332    }
333
334    fn set_heaviest_tipset_key(&self, tsk: &TipsetKey) -> anyhow::Result<()> {
335        SettingsStoreExt::write_obj(self, crate::db::setting_keys::HEAD_KEY, tsk)
336    }
337}
338
339impl<WriterT: BlockstoreWriteOpsSubscribable> BlockstoreWriteOpsSubscribable for ManyCar<WriterT> {
340    fn subscribe_write_ops(
341        &self,
342    ) -> anyhow::Result<tokio::sync::broadcast::Receiver<Vec<(Cid, bytes::Bytes)>>> {
343        self.writer().subscribe_write_ops()
344    }
345
346    fn unsubscribe_write_ops(&self) {
347        self.writer().unsubscribe_write_ops()
348    }
349}
350
351impl<T: GarbageCollectableDb> GarbageCollectableDb for ManyCar<T> {
352    fn reset_gc_columns(&self) -> anyhow::Result<()> {
353        self.writer().reset_gc_columns()
354    }
355}
356
357#[derive(derive_more::Debug, derive_more::Deref)]
358struct ManyCarMetricsCollector<T>(#[debug(skip)] Arc<ManyCar<T>>);
359
360mod metrics_collection {
361    use super::*;
362    use prometheus_client::{
363        collector::Collector,
364        encoding::{DescriptorEncoder, EncodeMetric},
365        metrics::gauge::Gauge,
366        registry::Unit,
367    };
368
369    impl<T> Collector for ManyCarMetricsCollector<T>
370    where
371        T: Send + Sync + 'static,
372    {
373        fn encode(&self, mut encoder: DescriptorEncoder) -> Result<(), std::fmt::Error> {
374            {
375                let size_in_bytes = {
376                    let g: Gauge = Default::default();
377                    g.set(self.shared_cache.read().cache.weight() as i64);
378                    g
379                };
380                let size_metric_encoder = encoder.encode_descriptor(
381                    "many_car_cache_size",
382                    "Size of the many car zstd frame cache in bytes",
383                    Some(&Unit::Bytes),
384                    size_in_bytes.metric_type(),
385                )?;
386                size_in_bytes.encode(size_metric_encoder)?;
387            }
388            {
389                let len = {
390                    let g: Gauge = Default::default();
391                    g.set(self.shared_cache.read().len() as i64);
392                    g
393                };
394                let size_metric_encoder = encoder.encode_descriptor(
395                    "many_car_cache_len",
396                    "Length of the many car zstd frame cache",
397                    None,
398                    len.metric_type(),
399                )?;
400                len.encode(size_metric_encoder)?;
401            }
402            {
403                let cap = {
404                    let g: Gauge = Default::default();
405                    g.set(self.shared_cache.read().capacity() as i64);
406                    g
407                };
408                let size_metric_encoder = encoder.encode_descriptor(
409                    "many_car_cache_cap",
410                    "Capacity of the many car zstd frame cache in bytes",
411                    Some(&Unit::Bytes),
412                    cap.metric_type(),
413                )?;
414                cap.encode(size_metric_encoder)?;
415            }
416
417            Ok(())
418        }
419    }
420}
421
422#[cfg(test)]
423mod tests {
424    use super::super::AnyCar;
425    use super::*;
426    use crate::networks::{calibnet, mainnet};
427
428    #[test]
429    fn many_car_empty() {
430        let many = ManyCar::new(MemoryDB::default());
431        assert!(many.heaviest_tipset().is_err());
432    }
433
434    #[test]
435    fn many_car_idempotent() {
436        let many = ManyCar::new(MemoryDB::default())
437            .with_read_only(AnyCar::try_from(mainnet::DEFAULT_GENESIS).unwrap())
438            .unwrap()
439            .with_read_only(AnyCar::try_from(mainnet::DEFAULT_GENESIS).unwrap())
440            .unwrap();
441        assert_eq!(
442            many.heaviest_tipset().unwrap(),
443            AnyCar::try_from(mainnet::DEFAULT_GENESIS)
444                .unwrap()
445                .heaviest_tipset()
446                .unwrap()
447        );
448    }
449
450    #[test]
451    fn many_car_calibnet_heaviest() {
452        let many = ManyCar::try_from(AnyCar::try_from(calibnet::DEFAULT_GENESIS).unwrap()).unwrap();
453        let heaviest = many.heaviest_tipset().unwrap();
454        assert_eq!(
455            heaviest.min_ticket_block(),
456            heaviest.genesis_blocking(&many).unwrap().min_ticket_block()
457        );
458    }
459}