signet_sim/
cache.rs

1use crate::SimItem;
2use core::fmt;
3use std::{
4    collections::BTreeMap,
5    sync::{Arc, RwLock, RwLockWriteGuard},
6};
7
8/// A cache for the simulator.
9///
10/// This cache is used to store the items that are being simulated.
11#[derive(Clone)]
12pub struct SimCache {
13    inner: Arc<RwLock<BTreeMap<u128, SimItem>>>,
14    capacity: usize,
15}
16
17impl fmt::Debug for SimCache {
18    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result {
19        f.debug_struct("SimCache").finish()
20    }
21}
22
23impl Default for SimCache {
24    fn default() -> Self {
25        Self::new()
26    }
27}
28
29impl SimCache {
30    /// Create a new `SimCache` instance.
31    pub fn new() -> Self {
32        Self { inner: Arc::new(RwLock::new(BTreeMap::new())), capacity: 100 }
33    }
34
35    /// Create a new `SimCache` instance with a given capacity.
36    pub fn with_capacity(capacity: usize) -> Self {
37        Self { inner: Arc::new(RwLock::new(BTreeMap::new())), capacity }
38    }
39
40    /// Get an iterator over the best items in the cache.
41    pub fn read_best(&self, n: usize) -> Vec<(u128, SimItem)> {
42        self.inner.read().unwrap().iter().rev().take(n).map(|(k, v)| (*k, v.clone())).collect()
43    }
44
45    /// Get the number of items in the cache.
46    pub fn len(&self) -> usize {
47        self.inner.read().unwrap().len()
48    }
49
50    /// True if the cache is empty.
51    pub fn is_empty(&self) -> bool {
52        self.inner.read().unwrap().is_empty()
53    }
54
55    /// Get an item by key.
56    pub fn get(&self, key: u128) -> Option<SimItem> {
57        self.inner.read().unwrap().get(&key).cloned()
58    }
59
60    /// Remove an item by key.
61    pub fn remove(&self, key: u128) -> Option<SimItem> {
62        self.inner.write().unwrap().remove(&key)
63    }
64
65    fn add_inner(
66        guard: &mut RwLockWriteGuard<'_, BTreeMap<u128, SimItem>>,
67        mut score: u128,
68        item: SimItem,
69        capacity: usize,
70    ) {
71        // If it has the same score, we decrement (prioritizing earlier items)
72        while guard.contains_key(&score) && score != 0 {
73            score = score.saturating_sub(1);
74        }
75
76        if guard.len() >= capacity {
77            // If we are at capacity, we need to remove the lowest score
78            guard.pop_first();
79        }
80
81        guard.entry(score).or_insert(item);
82    }
83
84    /// Add an item to the cache.
85    ///
86    /// The basefee is used to calculate an estimated fee for the item.
87    pub fn add_item(&self, item: impl Into<SimItem>, basefee: u64) {
88        let item = item.into();
89
90        // Calculate the total fee for the item.
91        let score = item.calculate_total_fee(basefee);
92
93        let mut inner = self.inner.write().unwrap();
94
95        Self::add_inner(&mut inner, score, item, self.capacity);
96    }
97
98    /// Add an iterator of items to the cache. This locks the cache only once
99    pub fn add_items<I, Item>(&self, item: I, basefee: u64)
100    where
101        I: IntoIterator<Item = Item>,
102        Item: Into<SimItem>,
103    {
104        let iter = item.into_iter().map(|item| {
105            let item = item.into();
106            let score = item.calculate_total_fee(basefee);
107            (score, item)
108        });
109
110        let mut inner = self.inner.write().unwrap();
111
112        for (score, item) in iter {
113            Self::add_inner(&mut inner, score, item, self.capacity);
114        }
115    }
116
117    /// Clean the cache by removing bundles that are not valid in the current
118    /// block.
119    pub fn clean(&self, block_number: u64, block_timestamp: u64) {
120        let mut inner = self.inner.write().unwrap();
121
122        // Trim to capacity by dropping lower fees.
123        while inner.len() > self.capacity {
124            inner.pop_first();
125        }
126
127        inner.retain(|_, value| {
128            let SimItem::Bundle(bundle) = value else {
129                return true;
130            };
131            if bundle.bundle.block_number != block_number {
132                return false;
133            }
134            if let Some(timestamp) = bundle.min_timestamp() {
135                if timestamp > block_timestamp {
136                    return false;
137                }
138            }
139            if let Some(timestamp) = bundle.max_timestamp() {
140                if timestamp < block_timestamp {
141                    return false;
142                }
143            }
144            true
145        })
146    }
147
148    /// Clear the cache.
149    pub fn clear(&self) {
150        let mut inner = self.inner.write().unwrap();
151        inner.clear();
152    }
153}
154
155#[cfg(test)]
156mod test {
157    use super::*;
158    use crate::SimItem;
159
160    #[test]
161    fn test_cache() {
162        let items = vec![
163            SimItem::invalid_item_with_score(100, 1),
164            SimItem::invalid_item_with_score(100, 2),
165            SimItem::invalid_item_with_score(100, 3),
166        ];
167
168        let cache = SimCache::with_capacity(2);
169        cache.add_items(items, 0);
170
171        assert_eq!(cache.len(), 2);
172        assert_eq!(cache.get(300), Some(SimItem::invalid_item_with_score(100, 3)));
173        assert_eq!(cache.get(200), Some(SimItem::invalid_item_with_score(100, 2)));
174        assert_eq!(cache.get(100), None);
175    }
176
177    #[test]
178    fn overlap_at_zero() {
179        let items = vec![
180            SimItem::invalid_item_with_score(1, 1),
181            SimItem::invalid_item_with_score(1, 1),
182            SimItem::invalid_item_with_score(1, 1),
183        ];
184
185        let cache = SimCache::with_capacity(2);
186        cache.add_items(items, 0);
187
188        dbg!(&*cache.inner.read().unwrap());
189
190        assert_eq!(cache.len(), 2);
191        assert_eq!(cache.get(0), Some(SimItem::invalid_item_with_score(1, 1)));
192        assert_eq!(cache.get(1), Some(SimItem::invalid_item_with_score(1, 1)));
193        assert_eq!(cache.get(2), None);
194    }
195}