Skip to main content

event_scanner/block_range_scanner/
ring_buffer.rs

1use std::collections::VecDeque;
2
3/// Configuration for how many past block hashes to retain for reorg detection.
4///
5/// This type is re-exported as `PastBlocksStorageCapacity` from the crate root.
6#[derive(Copy, Clone, Debug)]
7pub enum RingBufferCapacity {
8    /// Keep at most `n` items.
9    ///
10    /// A value of `0` disables storing past block hashes and effectively disables reorg
11    /// detection.
12    Limited(usize),
13    /// Keep an unbounded number of items.
14    ///
15    /// WARNING: This can lead to unbounded memory growth over long-running processes.
16    /// Avoid using this in production deployments without an external bound.
17    Infinite,
18}
19
20macro_rules! impl_from_unsigned {
21    ($target:ty; $($source:ty),+ $(,)?) => {
22        $(
23            impl From<$source> for $target {
24                fn from(value: $source) -> Self {
25                    RingBufferCapacity::Limited(value as usize)
26                }
27            }
28        )+
29    };
30}
31
32impl_from_unsigned!(RingBufferCapacity; u8, u16, u32, usize);
33
34#[derive(Clone, Debug)]
35pub(crate) struct RingBuffer<T> {
36    inner: VecDeque<T>,
37    capacity: RingBufferCapacity,
38}
39
40impl<T> RingBuffer<T> {
41    /// Creates an empty [`RingBuffer`] with a specific capacity.
42    pub fn new(capacity: RingBufferCapacity) -> Self {
43        if let RingBufferCapacity::Limited(limit) = capacity {
44            Self { inner: VecDeque::with_capacity(limit), capacity }
45        } else {
46            Self { inner: VecDeque::new(), capacity }
47        }
48    }
49
50    /// Adds a new element to the buffer.
51    ///
52    /// If limited capacity and the buffer is full, the oldest element is removed to make space.
53    pub fn push(&mut self, item: T) {
54        match self.capacity {
55            RingBufferCapacity::Infinite => {
56                self.inner.push_back(item); // Add the new element
57            }
58            RingBufferCapacity::Limited(0) => {
59                // Do nothing, reorg handling disabled
60            }
61            RingBufferCapacity::Limited(limit) => {
62                if self.inner.len() == limit {
63                    self.inner.pop_front(); // Remove the oldest element
64                }
65                self.inner.push_back(item); // Add the new element
66            }
67        }
68    }
69
70    /// Removes and returns the newest element from the buffer.
71    pub fn pop_back(&mut self) -> Option<T> {
72        self.inner.pop_back()
73    }
74
75    /// Returns a reference to the newest element in the buffer.
76    pub fn back(&self) -> Option<&T> {
77        self.inner.back()
78    }
79
80    /// Clears all elements currently stored in the buffer.
81    pub fn clear(&mut self) {
82        self.inner.clear();
83    }
84}
85
86#[cfg(test)]
87mod tests {
88    use super::*;
89
90    #[test]
91    fn zero_capacity_should_ignore_elements() {
92        let mut buf = RingBuffer::<u32>::new(RingBufferCapacity::Limited(0));
93        buf.push(1);
94        assert!(buf.inner.is_empty());
95    }
96}