1use 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 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 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 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}