Skip to main content

forest/chain_sync/
bad_block_cache.rs

1// Copyright 2019-2026 ChainSafe Systems
2// SPDX-License-Identifier: Apache-2.0, MIT
3
4use std::num::NonZeroUsize;
5
6use cid::Cid;
7use nonzero_ext::nonzero;
8
9use crate::utils::{ShallowClone, cache::SizeTrackingLruCache, get_size};
10
11/// Default capacity for CID caches (32768 entries).
12/// That's about 4 MiB.
13const DEFAULT_CID_CACHE_CAPACITY: NonZeroUsize = nonzero!(1usize << 15);
14
15/// Thread-safe cache for tracking bad blocks.
16/// This cache is checked before validating a block, to ensure no duplicate
17/// work.
18#[derive(Debug)]
19pub struct BadBlockCache {
20    cache: SizeTrackingLruCache<get_size::CidWrapper, ()>,
21}
22
23impl Default for BadBlockCache {
24    fn default() -> Self {
25        Self::new(DEFAULT_CID_CACHE_CAPACITY)
26    }
27}
28
29impl BadBlockCache {
30    pub fn new(cap: NonZeroUsize) -> Self {
31        Self {
32            cache: SizeTrackingLruCache::new_with_metrics("bad_block".into(), cap),
33        }
34    }
35
36    pub fn push(&self, c: Cid) {
37        self.cache.push(c.into(), ());
38        tracing::warn!("Marked bad block: {c}");
39    }
40
41    /// Returns `Some` if the block CID is in bad block cache.
42    /// This function does not update the head position of the `Cid` key.
43    pub fn peek(&self, c: &Cid) -> Option<()> {
44        self.cache.peek_cloned(&(*c).into())
45    }
46
47    pub fn clear(&self) {
48        self.cache.clear()
49    }
50}
51
52/// Thread-safe LRU cache for tracking recently seen gossip block CIDs.
53/// Used to de-duplicate gossip blocks before expensive message fetching.
54#[derive(Debug)]
55pub struct SeenBlockCache {
56    cache: SizeTrackingLruCache<get_size::CidWrapper, ()>,
57}
58
59impl ShallowClone for SeenBlockCache {
60    fn shallow_clone(&self) -> Self {
61        Self {
62            cache: self.cache.shallow_clone(),
63        }
64    }
65}
66
67impl Default for SeenBlockCache {
68    fn default() -> Self {
69        Self::new(DEFAULT_CID_CACHE_CAPACITY)
70    }
71}
72
73impl SeenBlockCache {
74    pub fn new(cap: NonZeroUsize) -> Self {
75        Self {
76            cache: SizeTrackingLruCache::new_with_metrics("seen_gossip_block".into(), cap),
77        }
78    }
79
80    /// Returns `true` if the CID was already present (duplicate).
81    /// Always inserts/refreshes the entry.
82    pub fn test_and_insert(&self, c: &Cid) -> bool {
83        self.cache.push((*c).into(), ()).is_some()
84    }
85}