solana_runtime/
status_cache.rs

1use {
2    log::*,
3    rand::{thread_rng, Rng},
4    serde::Serialize,
5    solana_accounts_db::ancestors::Ancestors,
6    solana_sdk::{
7        clock::{Slot, MAX_RECENT_BLOCKHASHES},
8        hash::Hash,
9    },
10    std::{
11        collections::{hash_map::Entry, HashMap, HashSet},
12        sync::{Arc, Mutex},
13    },
14};
15
16pub const MAX_CACHE_ENTRIES: usize = MAX_RECENT_BLOCKHASHES;
17const CACHED_KEY_SIZE: usize = 20;
18
19// Store forks in a single chunk of memory to avoid another lookup.
20pub type ForkStatus<T> = Vec<(Slot, T)>;
21type KeySlice = [u8; CACHED_KEY_SIZE];
22type KeyMap<T> = HashMap<KeySlice, ForkStatus<T>>;
23// Map of Hash and status
24pub type Status<T> = Arc<Mutex<HashMap<Hash, (usize, Vec<(KeySlice, T)>)>>>;
25// A Map of hash + the highest fork it's been observed on along with
26// the key offset and a Map of the key slice + Fork status for that key
27type KeyStatusMap<T> = HashMap<Hash, (Slot, usize, KeyMap<T>)>;
28
29// A map of keys recorded in each fork; used to serialize for snapshots easily.
30// Doesn't store a `SlotDelta` in it because the bool `root` is usually set much later
31type SlotDeltaMap<T> = HashMap<Slot, Status<T>>;
32
33// The statuses added during a slot, can be used to build on top of a status cache or to
34// construct a new one. Usually derived from a status cache's `SlotDeltaMap`
35pub type SlotDelta<T> = (Slot, bool, Status<T>);
36
37#[cfg_attr(feature = "frozen-abi", derive(AbiExample))]
38#[derive(Clone, Debug)]
39pub struct StatusCache<T: Serialize + Clone> {
40    cache: KeyStatusMap<T>,
41    roots: HashSet<Slot>,
42    /// all keys seen during a fork/slot
43    slot_deltas: SlotDeltaMap<T>,
44}
45
46impl<T: Serialize + Clone> Default for StatusCache<T> {
47    fn default() -> Self {
48        Self {
49            cache: HashMap::default(),
50            // 0 is always a root
51            roots: HashSet::from([0]),
52            slot_deltas: HashMap::default(),
53        }
54    }
55}
56
57impl<T: Serialize + Clone + PartialEq> PartialEq for StatusCache<T> {
58    fn eq(&self, other: &Self) -> bool {
59        self.roots == other.roots
60            && self
61                .cache
62                .iter()
63                .all(|(hash, (slot, key_index, hash_map))| {
64                    if let Some((other_slot, other_key_index, other_hash_map)) =
65                        other.cache.get(hash)
66                    {
67                        if slot == other_slot && key_index == other_key_index {
68                            return hash_map.iter().all(|(slice, fork_map)| {
69                                if let Some(other_fork_map) = other_hash_map.get(slice) {
70                                    // all this work just to compare the highest forks in the fork map
71                                    // per entry
72                                    return fork_map.last() == other_fork_map.last();
73                                }
74                                false
75                            });
76                        }
77                    }
78                    false
79                })
80    }
81}
82
83impl<T: Serialize + Clone> StatusCache<T> {
84    pub fn clear_slot_entries(&mut self, slot: Slot) {
85        let slot_deltas = self.slot_deltas.remove(&slot);
86        if let Some(slot_deltas) = slot_deltas {
87            let slot_deltas = slot_deltas.lock().unwrap();
88            for (blockhash, (_, key_list)) in slot_deltas.iter() {
89                // Any blockhash that exists in self.slot_deltas must also exist
90                // in self.cache, because in self.purge_roots(), when an entry
91                // (b, (max_slot, _, _)) is removed from self.cache, this implies
92                // all entries in self.slot_deltas < max_slot are also removed
93                if let Entry::Occupied(mut o_blockhash_entries) = self.cache.entry(*blockhash) {
94                    let (_, _, all_hash_maps) = o_blockhash_entries.get_mut();
95
96                    for (key_slice, _) in key_list {
97                        if let Entry::Occupied(mut o_key_list) = all_hash_maps.entry(*key_slice) {
98                            let key_list = o_key_list.get_mut();
99                            key_list.retain(|(updated_slot, _)| *updated_slot != slot);
100                            if key_list.is_empty() {
101                                o_key_list.remove_entry();
102                            }
103                        } else {
104                            panic!(
105                                "Map for key must exist if key exists in self.slot_deltas, slot: {slot}"
106                            )
107                        }
108                    }
109
110                    if all_hash_maps.is_empty() {
111                        o_blockhash_entries.remove_entry();
112                    }
113                } else {
114                    panic!("Blockhash must exist if it exists in self.slot_deltas, slot: {slot}")
115                }
116            }
117        }
118    }
119
120    /// Check if the key is in any of the forks in the ancestors set and
121    /// with a certain blockhash.
122    pub fn get_status<K: AsRef<[u8]>>(
123        &self,
124        key: K,
125        transaction_blockhash: &Hash,
126        ancestors: &Ancestors,
127    ) -> Option<(Slot, T)> {
128        let map = self.cache.get(transaction_blockhash)?;
129        let (_, index, keymap) = map;
130        let max_key_index = key.as_ref().len().saturating_sub(CACHED_KEY_SIZE + 1);
131        let index = (*index).min(max_key_index);
132        let key_slice: &[u8; CACHED_KEY_SIZE] =
133            arrayref::array_ref![key.as_ref(), index, CACHED_KEY_SIZE];
134        if let Some(stored_forks) = keymap.get(key_slice) {
135            let res = stored_forks
136                .iter()
137                .find(|(f, _)| ancestors.contains_key(f) || self.roots.contains(f))
138                .cloned();
139            if res.is_some() {
140                return res;
141            }
142        }
143        None
144    }
145
146    /// Search for a key with any blockhash
147    /// Prefer get_status for performance reasons, it doesn't need
148    /// to search all blockhashes.
149    pub fn get_status_any_blockhash<K: AsRef<[u8]>>(
150        &self,
151        key: K,
152        ancestors: &Ancestors,
153    ) -> Option<(Slot, T)> {
154        let keys: Vec<_> = self.cache.keys().copied().collect();
155
156        for blockhash in keys.iter() {
157            trace!("get_status_any_blockhash: trying {}", blockhash);
158            let status = self.get_status(&key, blockhash, ancestors);
159            if status.is_some() {
160                return status;
161            }
162        }
163        None
164    }
165
166    /// Add a known root fork.  Roots are always valid ancestors.
167    /// After MAX_CACHE_ENTRIES, roots are removed, and any old keys are cleared.
168    pub fn add_root(&mut self, fork: Slot) {
169        self.roots.insert(fork);
170        self.purge_roots();
171    }
172
173    pub fn roots(&self) -> &HashSet<Slot> {
174        &self.roots
175    }
176
177    /// Insert a new key for a specific slot.
178    pub fn insert<K: AsRef<[u8]>>(
179        &mut self,
180        transaction_blockhash: &Hash,
181        key: K,
182        slot: Slot,
183        res: T,
184    ) {
185        let max_key_index = key.as_ref().len().saturating_sub(CACHED_KEY_SIZE + 1);
186        let hash_map = self.cache.entry(*transaction_blockhash).or_insert_with(|| {
187            let key_index = thread_rng().gen_range(0..max_key_index + 1);
188            (slot, key_index, HashMap::new())
189        });
190
191        hash_map.0 = std::cmp::max(slot, hash_map.0);
192        let key_index = hash_map.1.min(max_key_index);
193        let mut key_slice = [0u8; CACHED_KEY_SIZE];
194        key_slice.clone_from_slice(&key.as_ref()[key_index..key_index + CACHED_KEY_SIZE]);
195        self.insert_with_slice(transaction_blockhash, slot, key_index, key_slice, res);
196    }
197
198    pub fn purge_roots(&mut self) {
199        if self.roots.len() > MAX_CACHE_ENTRIES {
200            if let Some(min) = self.roots.iter().min().cloned() {
201                self.roots.remove(&min);
202                self.cache.retain(|_, (fork, _, _)| *fork > min);
203                self.slot_deltas.retain(|slot, _| *slot > min);
204            }
205        }
206    }
207
208    /// Clear for testing
209    pub fn clear(&mut self) {
210        for v in self.cache.values_mut() {
211            v.2 = HashMap::new();
212        }
213
214        self.slot_deltas
215            .iter_mut()
216            .for_each(|(_, status)| status.lock().unwrap().clear());
217    }
218
219    /// Get the statuses for all the root slots
220    pub fn root_slot_deltas(&self) -> Vec<SlotDelta<T>> {
221        self.roots()
222            .iter()
223            .map(|root| {
224                (
225                    *root,
226                    true, // <-- is_root
227                    self.slot_deltas.get(root).cloned().unwrap_or_default(),
228                )
229            })
230            .collect()
231    }
232
233    // replay deltas into a status_cache allows "appending" data
234    pub fn append(&mut self, slot_deltas: &[SlotDelta<T>]) {
235        for (slot, is_root, statuses) in slot_deltas {
236            statuses
237                .lock()
238                .unwrap()
239                .iter()
240                .for_each(|(tx_hash, (key_index, statuses))| {
241                    for (key_slice, res) in statuses.iter() {
242                        self.insert_with_slice(tx_hash, *slot, *key_index, *key_slice, res.clone())
243                    }
244                });
245            if *is_root {
246                self.add_root(*slot);
247            }
248        }
249    }
250
251    pub fn from_slot_deltas(slot_deltas: &[SlotDelta<T>]) -> Self {
252        // play all deltas back into the status cache
253        let mut me = Self::default();
254        me.append(slot_deltas);
255        me
256    }
257
258    fn insert_with_slice(
259        &mut self,
260        transaction_blockhash: &Hash,
261        slot: Slot,
262        key_index: usize,
263        key_slice: [u8; CACHED_KEY_SIZE],
264        res: T,
265    ) {
266        let hash_map =
267            self.cache
268                .entry(*transaction_blockhash)
269                .or_insert((slot, key_index, HashMap::new()));
270        hash_map.0 = std::cmp::max(slot, hash_map.0);
271
272        let forks = hash_map.2.entry(key_slice).or_default();
273        forks.push((slot, res.clone()));
274        let slot_deltas = self.slot_deltas.entry(slot).or_default();
275        let mut fork_entry = slot_deltas.lock().unwrap();
276        let (_, hash_entry) = fork_entry
277            .entry(*transaction_blockhash)
278            .or_insert((key_index, vec![]));
279        hash_entry.push((key_slice, res))
280    }
281}
282
283#[cfg(test)]
284mod tests {
285    use {
286        super::*,
287        solana_sdk::{hash::hash, signature::Signature},
288    };
289
290    type BankStatusCache = StatusCache<()>;
291
292    #[test]
293    fn test_empty_has_no_sigs() {
294        let sig = Signature::default();
295        let blockhash = hash(Hash::default().as_ref());
296        let status_cache = BankStatusCache::default();
297        assert_eq!(
298            status_cache.get_status(sig, &blockhash, &Ancestors::default()),
299            None
300        );
301        assert_eq!(
302            status_cache.get_status_any_blockhash(sig, &Ancestors::default()),
303            None
304        );
305    }
306
307    #[test]
308    fn test_find_sig_with_ancestor_fork() {
309        let sig = Signature::default();
310        let mut status_cache = BankStatusCache::default();
311        let blockhash = hash(Hash::default().as_ref());
312        let ancestors = vec![(0, 1)].into_iter().collect();
313        status_cache.insert(&blockhash, sig, 0, ());
314        assert_eq!(
315            status_cache.get_status(sig, &blockhash, &ancestors),
316            Some((0, ()))
317        );
318        assert_eq!(
319            status_cache.get_status_any_blockhash(sig, &ancestors),
320            Some((0, ()))
321        );
322    }
323
324    #[test]
325    fn test_find_sig_without_ancestor_fork() {
326        let sig = Signature::default();
327        let mut status_cache = BankStatusCache::default();
328        let blockhash = hash(Hash::default().as_ref());
329        let ancestors = Ancestors::default();
330        status_cache.insert(&blockhash, sig, 1, ());
331        assert_eq!(status_cache.get_status(sig, &blockhash, &ancestors), None);
332        assert_eq!(status_cache.get_status_any_blockhash(sig, &ancestors), None);
333    }
334
335    #[test]
336    fn test_find_sig_with_root_ancestor_fork() {
337        let sig = Signature::default();
338        let mut status_cache = BankStatusCache::default();
339        let blockhash = hash(Hash::default().as_ref());
340        let ancestors = Ancestors::default();
341        status_cache.insert(&blockhash, sig, 0, ());
342        status_cache.add_root(0);
343        assert_eq!(
344            status_cache.get_status(sig, &blockhash, &ancestors),
345            Some((0, ()))
346        );
347    }
348
349    #[test]
350    fn test_insert_picks_latest_blockhash_fork() {
351        let sig = Signature::default();
352        let mut status_cache = BankStatusCache::default();
353        let blockhash = hash(Hash::default().as_ref());
354        let ancestors = vec![(0, 0)].into_iter().collect();
355        status_cache.insert(&blockhash, sig, 0, ());
356        status_cache.insert(&blockhash, sig, 1, ());
357        for i in 0..(MAX_CACHE_ENTRIES + 1) {
358            status_cache.add_root(i as u64);
359        }
360        assert!(status_cache
361            .get_status(sig, &blockhash, &ancestors)
362            .is_some());
363    }
364
365    #[test]
366    fn test_root_expires() {
367        let sig = Signature::default();
368        let mut status_cache = BankStatusCache::default();
369        let blockhash = hash(Hash::default().as_ref());
370        let ancestors = Ancestors::default();
371        status_cache.insert(&blockhash, sig, 0, ());
372        for i in 0..(MAX_CACHE_ENTRIES + 1) {
373            status_cache.add_root(i as u64);
374        }
375        assert_eq!(status_cache.get_status(sig, &blockhash, &ancestors), None);
376    }
377
378    #[test]
379    fn test_clear_signatures_sigs_are_gone() {
380        let sig = Signature::default();
381        let mut status_cache = BankStatusCache::default();
382        let blockhash = hash(Hash::default().as_ref());
383        let ancestors = Ancestors::default();
384        status_cache.insert(&blockhash, sig, 0, ());
385        status_cache.add_root(0);
386        status_cache.clear();
387        assert_eq!(status_cache.get_status(sig, &blockhash, &ancestors), None);
388    }
389
390    #[test]
391    fn test_clear_signatures_insert_works() {
392        let sig = Signature::default();
393        let mut status_cache = BankStatusCache::default();
394        let blockhash = hash(Hash::default().as_ref());
395        let ancestors = Ancestors::default();
396        status_cache.add_root(0);
397        status_cache.clear();
398        status_cache.insert(&blockhash, sig, 0, ());
399        assert!(status_cache
400            .get_status(sig, &blockhash, &ancestors)
401            .is_some());
402    }
403
404    #[test]
405    fn test_signatures_slice() {
406        let sig = Signature::default();
407        let mut status_cache = BankStatusCache::default();
408        let blockhash = hash(Hash::default().as_ref());
409        status_cache.clear();
410        status_cache.insert(&blockhash, sig, 0, ());
411        let (_, index, sig_map) = status_cache.cache.get(&blockhash).unwrap();
412        let sig_slice: &[u8; CACHED_KEY_SIZE] =
413            arrayref::array_ref![sig.as_ref(), *index, CACHED_KEY_SIZE];
414        assert!(sig_map.get(sig_slice).is_some());
415    }
416
417    #[test]
418    fn test_slot_deltas() {
419        let sig = Signature::default();
420        let mut status_cache = BankStatusCache::default();
421        let blockhash = hash(Hash::default().as_ref());
422        status_cache.clear();
423        status_cache.insert(&blockhash, sig, 0, ());
424        assert!(status_cache.roots().contains(&0));
425        let slot_deltas = status_cache.root_slot_deltas();
426        let cache = StatusCache::from_slot_deltas(&slot_deltas);
427        assert_eq!(cache, status_cache);
428        let slot_deltas = cache.root_slot_deltas();
429        let cache = StatusCache::from_slot_deltas(&slot_deltas);
430        assert_eq!(cache, status_cache);
431    }
432
433    #[test]
434    fn test_roots_deltas() {
435        let sig = Signature::default();
436        let mut status_cache = BankStatusCache::default();
437        let blockhash = hash(Hash::default().as_ref());
438        let blockhash2 = hash(blockhash.as_ref());
439        status_cache.insert(&blockhash, sig, 0, ());
440        status_cache.insert(&blockhash, sig, 1, ());
441        status_cache.insert(&blockhash2, sig, 1, ());
442        for i in 0..(MAX_CACHE_ENTRIES + 1) {
443            status_cache.add_root(i as u64);
444        }
445        assert_eq!(status_cache.slot_deltas.len(), 1);
446        assert!(status_cache.slot_deltas.contains_key(&1));
447        let slot_deltas = status_cache.root_slot_deltas();
448        let cache = StatusCache::from_slot_deltas(&slot_deltas);
449        assert_eq!(cache, status_cache);
450    }
451
452    #[test]
453    #[allow(clippy::assertions_on_constants)]
454    fn test_age_sanity() {
455        assert!(MAX_CACHE_ENTRIES <= MAX_RECENT_BLOCKHASHES);
456    }
457
458    #[test]
459    fn test_clear_slot_signatures() {
460        let sig = Signature::default();
461        let mut status_cache = BankStatusCache::default();
462        let blockhash = hash(Hash::default().as_ref());
463        let blockhash2 = hash(blockhash.as_ref());
464        status_cache.insert(&blockhash, sig, 0, ());
465        status_cache.insert(&blockhash, sig, 1, ());
466        status_cache.insert(&blockhash2, sig, 1, ());
467
468        let mut ancestors0 = Ancestors::default();
469        ancestors0.insert(0, 0);
470        let mut ancestors1 = Ancestors::default();
471        ancestors1.insert(1, 0);
472
473        // Clear slot 0 related data
474        assert!(status_cache
475            .get_status(sig, &blockhash, &ancestors0)
476            .is_some());
477        status_cache.clear_slot_entries(0);
478        assert!(status_cache
479            .get_status(sig, &blockhash, &ancestors0)
480            .is_none());
481        assert!(status_cache
482            .get_status(sig, &blockhash, &ancestors1)
483            .is_some());
484        assert!(status_cache
485            .get_status(sig, &blockhash2, &ancestors1)
486            .is_some());
487
488        // Check that the slot delta for slot 0 is gone, but slot 1 still
489        // exists
490        assert!(!status_cache.slot_deltas.contains_key(&0));
491        assert!(status_cache.slot_deltas.contains_key(&1));
492
493        // Clear slot 1 related data
494        status_cache.clear_slot_entries(1);
495        assert!(status_cache.slot_deltas.is_empty());
496        assert!(status_cache
497            .get_status(sig, &blockhash, &ancestors1)
498            .is_none());
499        assert!(status_cache
500            .get_status(sig, &blockhash2, &ancestors1)
501            .is_none());
502        assert!(status_cache.cache.is_empty());
503    }
504
505    // Status cache uses a random key offset for each blockhash. Ensure that shorter
506    // keys can still be used if the offset if greater than the key length.
507    #[test]
508    fn test_different_sized_keys() {
509        let mut status_cache = BankStatusCache::default();
510        let ancestors = vec![(0, 0)].into_iter().collect();
511        let blockhash = Hash::default();
512        for _ in 0..100 {
513            let blockhash = hash(blockhash.as_ref());
514            let sig_key = Signature::default();
515            let hash_key = Hash::new_unique();
516            status_cache.insert(&blockhash, sig_key, 0, ());
517            status_cache.insert(&blockhash, hash_key, 0, ());
518            assert!(status_cache
519                .get_status(sig_key, &blockhash, &ancestors)
520                .is_some());
521            assert!(status_cache
522                .get_status(hash_key, &blockhash, &ancestors)
523                .is_some());
524        }
525    }
526}