Skip to main content

solana_runtime/
status_cache.rs

1#[cfg(feature = "shuttle-test")]
2use shuttle::sync::{Arc, Mutex};
3#[cfg(not(feature = "shuttle-test"))]
4use std::sync::{Arc, Mutex};
5use {
6    log::*,
7    smallvec::SmallVec,
8    solana_accounts_db::ancestors::Ancestors,
9    solana_clock::{MAX_RECENT_BLOCKHASHES, Slot},
10    solana_hash::Hash,
11    std::{
12        collections::{HashMap, HashSet, hash_map::Entry},
13        num::{NonZero, NonZeroUsize},
14    },
15};
16
17// The maximum number of entries to store in the cache. This is the same as the number of recent
18// blockhashes because we automatically reject txs that use older blockhashes so we don't need to
19// track those explicitly.
20const MAX_ROOT_ENTRIES: usize = MAX_RECENT_BLOCKHASHES;
21
22// Only store 20 bytes of the tx keys processed to save some memory.
23const CACHED_KEY_SIZE: usize = 20;
24
25// Store forks in a single chunk of memory to avoid another hash lookup.
26pub type ForkStatus<T> = SmallVec<[(Slot, T); 1]>;
27
28// The type of the key used in the cache.
29pub(crate) type KeySlice = [u8; CACHED_KEY_SIZE];
30
31type KeyMap<T> = HashMap<KeySlice, ForkStatus<T>>;
32
33// Map of Hash and status
34pub type Status<T> =
35    Arc<Mutex<HashMap<Hash, (usize, Vec<(KeySlice, T)>), solana_hash::HashHasherBuilder>>>;
36
37// A Map of hash + the highest fork it's been observed on along with
38// the key offset and a Map of the key slice + Fork status for that key
39type KeyStatusMap<T> = HashMap<Hash, (Slot, usize, KeyMap<T>), solana_hash::HashHasherBuilder>;
40
41// The type used for StatusCache::slot_deltas. See the field definition for more details.
42type SlotDeltaMap<T> = HashMap<Slot, Status<T>>;
43
44// The statuses added during a slot, can be used to build on top of a status cache or to
45// construct a new one. Usually derived from a status cache's `SlotDeltaMap`
46pub type SlotDelta<T> = (Slot, bool, Status<T>);
47
48#[derive(Clone, Debug)]
49pub struct StatusCache<T: Clone> {
50    // cache[blockhash][tx_key] => [(fork1_slot, tx_result), (fork2_slot, tx_result), ...] used to
51    // check if a tx_key was seen on a fork and for rpc to retrieve the tx_result
52    cache: KeyStatusMap<T>,
53    roots: HashSet<Slot>,
54    max_root_entries: NonZeroUsize,
55    // slot_deltas[slot][blockhash] => [(tx_key, tx_result), ...] used to serialize for snapshots
56    // and to rebuild cache[blockhash][tx_key] from a snapshot
57    slot_deltas: SlotDeltaMap<T>,
58}
59
60impl<T: Clone> Default for StatusCache<T> {
61    fn default() -> Self {
62        Self {
63            cache: HashMap::default(),
64            // 0 is always a root
65            roots: HashSet::from([0]),
66            max_root_entries: NonZero::new(MAX_ROOT_ENTRIES).unwrap(),
67            slot_deltas: HashMap::default(),
68        }
69    }
70}
71
72impl<T: Clone> StatusCache<T> {
73    /// Clear all entries for a slot.
74    ///
75    /// This is used when a slot is purged from the cache, see
76    /// ReplayStage::purge_unconfirmed_duplicate_slot().
77    ///
78    /// When this is called, it's guaranteed that there are no threads inserting new entries for
79    /// this slot. root_slot_deltas() also never accesses slots that are being cleared because roots
80    /// are never purged.
81    pub fn clear_slot_entries(&mut self, slot: Slot) {
82        let slot_deltas = self.slot_deltas.remove(&slot);
83        if let Some(slot_deltas) = slot_deltas {
84            let slot_deltas = slot_deltas.lock().unwrap();
85            let mut warned = false;
86            for (blockhash, (_, key_list)) in slot_deltas.iter() {
87                // Any blockhash that exists in self.slot_deltas must also exist
88                // in self.cache, because in self.purge_roots(), when an entry
89                // (b, (max_slot, _, _)) is removed from self.cache, this implies
90                // all entries in self.slot_deltas < max_slot are also removed
91                if let Entry::Occupied(mut o_blockhash_entries) = self.cache.entry(*blockhash) {
92                    let (_, _, all_hash_maps) = o_blockhash_entries.get_mut();
93
94                    for (key_slice, _) in key_list {
95                        if let Entry::Occupied(mut o_key_list) = all_hash_maps.entry(*key_slice) {
96                            let key_list = o_key_list.get_mut();
97                            key_list.retain(|(updated_slot, _)| *updated_slot != slot);
98                            if key_list.is_empty() {
99                                o_key_list.remove_entry();
100                            }
101                        } else if !warned {
102                            // On invalid blocks, we can have:
103                            //
104                            // slot_deltas[slot_1][blockhash] => [
105                            //     (signature, tx1_result), // dup
106                            //     (signature, tx2_result), // dup
107                            // ];
108                            // cache[blockhash][signature] => [
109                            //     (slot_1, tx1_result), // dup
110                            //     (slot_1, tx2_result), // dup
111                            // ];
112                            //
113                            // this can happen because tx execution and signature verification run
114                            // in parallel, so tx1 and tx2 can finish executing and get inserted
115                            // into the cache before their signatures are verified.
116                            //
117                            // This is an invalid condition that we eventually detect and mark the
118                            // slot as dead. If clear_slot_entries() is called on such a slot,
119                            // iterating on the first element of slot_deltas[slot_1][blockhash] will
120                            // (correctly) remove the whole cache[blockhash][signature] entry, and
121                            // then on the 2nd element we get here.
122                            warn!(
123                                "signature found more than once in the same slot, this means \
124                                 we're clearing a dead slot: {slot}"
125                            );
126                            warned = true;
127                        }
128                    }
129
130                    if all_hash_maps.is_empty() {
131                        o_blockhash_entries.remove_entry();
132                    }
133                } else {
134                    panic!("Blockhash must exist if it exists in self.slot_deltas, slot: {slot}")
135                }
136            }
137        }
138    }
139
140    /// Check if the key is in any of the forks in the ancestors set and
141    /// with a certain blockhash.
142    pub fn get_status<K: AsRef<[u8]>>(
143        &self,
144        key: K,
145        transaction_blockhash: &Hash,
146        ancestors: &Ancestors,
147    ) -> Option<(Slot, T)> {
148        let map = self.cache.get(transaction_blockhash)?;
149        let (_, index, keymap) = map;
150        let max_key_index = key.as_ref().len().saturating_sub(CACHED_KEY_SIZE + 1);
151        let index = (*index).min(max_key_index);
152        let key_slice: &[u8; CACHED_KEY_SIZE] =
153            arrayref::array_ref![key.as_ref(), index, CACHED_KEY_SIZE];
154        if let Some(stored_forks) = keymap.get(key_slice) {
155            let res = stored_forks
156                .iter()
157                .find(|(f, _)| ancestors.contains_key(f) || self.roots.contains(f))
158                .cloned();
159            if res.is_some() {
160                return res;
161            }
162        }
163        None
164    }
165
166    /// Search for a key with any blockhash.
167    ///
168    /// Prefer get_status for performance reasons, it doesn't need to search all blockhashes.
169    pub fn get_status_any_blockhash<K: AsRef<[u8]>>(
170        &self,
171        key: K,
172        ancestors: &Ancestors,
173    ) -> Option<(Slot, T)> {
174        self.cache.keys().find_map(|blockhash| {
175            trace!("get_status_any_blockhash: trying {blockhash}");
176            self.get_status(&key, blockhash, ancestors)
177        })
178    }
179
180    /// Add a known root fork.
181    ///
182    /// Roots are always valid ancestors. After `max_root_entries`, roots are removed, and any old
183    /// keys are cleared.
184    pub fn add_root(&mut self, fork: Slot) {
185        self.roots.insert(fork);
186        self.purge_roots();
187    }
188
189    pub fn add_roots<I: IntoIterator<Item = Slot>>(&mut self, forks: I) {
190        self.roots.extend(forks);
191        self.purge_roots();
192    }
193
194    pub fn roots(&self) -> &HashSet<Slot> {
195        &self.roots
196    }
197
198    pub fn max_root_entries(&self) -> usize {
199        self.max_root_entries.into()
200    }
201
202    pub fn set_max_root_entries(&mut self, max_root_entries: NonZeroUsize) {
203        self.max_root_entries = max_root_entries;
204        self.purge_roots();
205    }
206
207    /// Insert a new key using the given blockhash at the given slot.
208    pub fn insert<K: AsRef<[u8]>>(
209        &mut self,
210        transaction_blockhash: &Hash,
211        key: K,
212        slot: Slot,
213        res: T,
214    ) {
215        let max_key_index = key.as_ref().len().saturating_sub(CACHED_KEY_SIZE + 1);
216
217        // Get the cache entry for this blockhash.
218        let (max_slot, key_index, hash_map) = self
219            .cache
220            .entry(*transaction_blockhash)
221            .or_insert_with(|| (slot, 0, HashMap::new()));
222
223        // Update the max slot observed to contain txs using this blockhash.
224        *max_slot = std::cmp::max(slot, *max_slot);
225
226        // Grab the key slice.
227        let key_index = (*key_index).min(max_key_index);
228        let mut key_slice = [0u8; CACHED_KEY_SIZE];
229        key_slice.clone_from_slice(&key.as_ref()[key_index..key_index + CACHED_KEY_SIZE]);
230
231        // Insert the slot and tx result into the cache entry associated with
232        // this blockhash and keyslice.
233        let forks = hash_map.entry(key_slice).or_default();
234        forks.push((slot, res.clone()));
235
236        self.add_to_slot_delta(transaction_blockhash, slot, key_index, key_slice, res);
237    }
238
239    pub fn purge_roots(&mut self) {
240        let max_root_entries = self.max_root_entries();
241        if self.roots.len() > max_root_entries {
242            let num_roots_to_purge = self.roots.len() - max_root_entries;
243            let mut roots = self
244                .roots
245                .iter()
246                .copied()
247                .collect::<SmallVec<[Slot; 0x200]>>();
248            let (_, cutoff, _) = roots.select_nth_unstable(num_roots_to_purge - 1);
249            let cutoff = *cutoff;
250
251            self.roots.retain(|root| *root > cutoff);
252            self.cache.retain(|_, (fork, _, _)| *fork > cutoff);
253            self.slot_deltas.retain(|slot, _| *slot > cutoff);
254        }
255    }
256
257    #[cfg(feature = "dev-context-only-utils")]
258    pub fn clear(&mut self) {
259        for v in self.cache.values_mut() {
260            v.2 = HashMap::new();
261        }
262
263        self.slot_deltas
264            .iter_mut()
265            .for_each(|(_, status)| status.lock().unwrap().clear());
266    }
267
268    /// Get the statuses for all the root slots.
269    ///
270    /// This is never called concurrently with add_root(), and for a slot to be a root there must be
271    /// no new entries for that slot, so there are no races.
272    ///
273    /// See ReplayStage::handle_new_root() => BankForks::set_root() =>
274    /// BankForks::do_set_root_return_metrics() => root_slot_deltas()
275    pub fn root_slot_deltas(&self) -> Vec<SlotDelta<T>> {
276        self.roots()
277            .iter()
278            .map(|root| {
279                (
280                    *root,
281                    true, // <-- is_root
282                    self.slot_deltas.get(root).cloned().unwrap_or_default(),
283                )
284            })
285            .collect()
286    }
287
288    /// Populate the cache with the slot deltas from a snapshot.
289    ///
290    /// Really badly named method. See load_bank_forks() => ... =>
291    /// rebuild_bank_from_snapshot() => [load slot deltas from snapshot] => append()
292    pub fn append(&mut self, slot_deltas: &[SlotDelta<T>]) {
293        for (slot, is_root, statuses) in slot_deltas {
294            statuses
295                .lock()
296                .unwrap()
297                .iter()
298                .for_each(|(tx_hash, (key_index, statuses))| {
299                    for (key_slice, res) in statuses.iter() {
300                        self.insert_with_slice(tx_hash, *slot, *key_index, *key_slice, res.clone())
301                    }
302                });
303            if *is_root {
304                self.add_root(*slot);
305            }
306        }
307    }
308
309    fn insert_with_slice(
310        &mut self,
311        transaction_blockhash: &Hash,
312        slot: Slot,
313        key_index: usize,
314        key_slice: [u8; CACHED_KEY_SIZE],
315        res: T,
316    ) {
317        let hash_map =
318            self.cache
319                .entry(*transaction_blockhash)
320                .or_insert((slot, key_index, HashMap::new()));
321        hash_map.0 = std::cmp::max(slot, hash_map.0);
322
323        let forks = hash_map.2.entry(key_slice).or_default();
324        forks.push((slot, res.clone()));
325
326        self.add_to_slot_delta(transaction_blockhash, slot, key_index, key_slice, res);
327    }
328
329    // Add this key slice to the list of key slices for this slot and blockhash combo.
330    fn add_to_slot_delta(
331        &mut self,
332        transaction_blockhash: &Hash,
333        slot: Slot,
334        key_index: usize,
335        key_slice: [u8; CACHED_KEY_SIZE],
336        res: T,
337    ) {
338        let mut fork_entry = self.slot_deltas.entry(slot).or_default().lock().unwrap();
339        let (_key_index, hash_entry) = fork_entry
340            .entry(*transaction_blockhash)
341            .or_insert((key_index, vec![]));
342        hash_entry.push((key_slice, res))
343    }
344}
345
346#[cfg(test)]
347mod tests {
348    use {super::*, solana_sha256_hasher::hash, solana_signature::Signature};
349
350    type BankStatusCache = StatusCache<()>;
351
352    impl<T: Clone> StatusCache<T> {
353        fn from_slot_deltas(slot_deltas: &[SlotDelta<T>]) -> Self {
354            let mut cache = Self::default();
355            cache.append(slot_deltas);
356            cache
357        }
358    }
359
360    impl<T: Clone + PartialEq> PartialEq for StatusCache<T> {
361        fn eq(&self, other: &Self) -> bool {
362            self.roots == other.roots
363                && self
364                    .cache
365                    .iter()
366                    .all(|(hash, (slot, key_index, hash_map))| {
367                        if let Some((other_slot, other_key_index, other_hash_map)) =
368                            other.cache.get(hash)
369                            && slot == other_slot
370                            && key_index == other_key_index
371                        {
372                            return hash_map.iter().all(|(slice, fork_map)| {
373                                if let Some(other_fork_map) = other_hash_map.get(slice) {
374                                    // all this work just to compare the highest forks in the fork map
375                                    // per entry
376                                    return fork_map.last() == other_fork_map.last();
377                                }
378                                false
379                            });
380                        }
381                        false
382                    })
383        }
384    }
385
386    #[test]
387    fn test_empty_has_no_sigs() {
388        let sig = Signature::default();
389        let blockhash = hash(Hash::default().as_ref());
390        let status_cache = BankStatusCache::default();
391        assert_eq!(
392            status_cache.get_status(sig, &blockhash, &Ancestors::default()),
393            None
394        );
395        assert_eq!(
396            status_cache.get_status_any_blockhash(sig, &Ancestors::default()),
397            None
398        );
399    }
400
401    #[test]
402    fn test_find_sig_with_ancestor_fork() {
403        let sig = Signature::default();
404        let mut status_cache = BankStatusCache::default();
405        let blockhash = hash(Hash::default().as_ref());
406        let ancestors = Ancestors::from(vec![0]);
407        status_cache.insert(&blockhash, sig, 0, ());
408        assert_eq!(
409            status_cache.get_status(sig, &blockhash, &ancestors),
410            Some((0, ()))
411        );
412        assert_eq!(
413            status_cache.get_status_any_blockhash(sig, &ancestors),
414            Some((0, ()))
415        );
416    }
417
418    #[test]
419    fn test_find_sig_without_ancestor_fork() {
420        let sig = Signature::default();
421        let mut status_cache = BankStatusCache::default();
422        let blockhash = hash(Hash::default().as_ref());
423        let ancestors = Ancestors::default();
424        status_cache.insert(&blockhash, sig, 1, ());
425        assert_eq!(status_cache.get_status(sig, &blockhash, &ancestors), None);
426        assert_eq!(status_cache.get_status_any_blockhash(sig, &ancestors), None);
427    }
428
429    #[test]
430    fn test_find_sig_with_root_ancestor_fork() {
431        let sig = Signature::default();
432        let mut status_cache = BankStatusCache::default();
433        let blockhash = hash(Hash::default().as_ref());
434        let ancestors = Ancestors::default();
435        status_cache.insert(&blockhash, sig, 0, ());
436        status_cache.add_root(0);
437        assert_eq!(
438            status_cache.get_status(sig, &blockhash, &ancestors),
439            Some((0, ()))
440        );
441    }
442
443    #[test]
444    fn test_insert_picks_latest_blockhash_fork() {
445        let sig = Signature::default();
446        let mut status_cache = BankStatusCache::default();
447        let blockhash = hash(Hash::default().as_ref());
448        let ancestors = Ancestors::from(vec![0]);
449        status_cache.insert(&blockhash, sig, 0, ());
450        status_cache.insert(&blockhash, sig, 1, ());
451        for i in 0..=status_cache.max_root_entries() {
452            status_cache.add_root(i as u64);
453        }
454        assert!(
455            status_cache
456                .get_status(sig, &blockhash, &ancestors)
457                .is_some()
458        );
459    }
460
461    #[test]
462    fn test_root_expires() {
463        let sig = Signature::default();
464        let mut status_cache = BankStatusCache::default();
465        let blockhash = hash(Hash::default().as_ref());
466        let ancestors = Ancestors::default();
467        status_cache.insert(&blockhash, sig, 0, ());
468        for i in 0..=status_cache.max_root_entries() {
469            status_cache.add_root(i as u64);
470        }
471        assert_eq!(status_cache.get_status(sig, &blockhash, &ancestors), None);
472    }
473
474    #[test]
475    fn test_max_root_entries() {
476        const INITIAL_MAX_ROOT_ENTRIES: usize = 4;
477        const GROWN_MAX_ROOT_ENTRIES: usize = 6;
478        const SHRUNK_MAX_ROOT_ENTRIES: usize = 3;
479
480        let mut status_cache = BankStatusCache::default();
481        let ancestors = Ancestors::default();
482        let oldest_sig = Signature::from([1_u8; 64]);
483        let oldest_blockhash = Hash::new_unique();
484        let newest_sig = Signature::from([2_u8; 64]);
485        let newest_blockhash = Hash::new_unique();
486
487        status_cache.set_max_root_entries(NonZeroUsize::new(INITIAL_MAX_ROOT_ENTRIES).unwrap());
488
489        status_cache.insert(&oldest_blockhash, oldest_sig, 0, ());
490        status_cache.insert(&newest_blockhash, newest_sig, 3, ());
491        for root in 1..INITIAL_MAX_ROOT_ENTRIES as Slot {
492            status_cache.add_root(root);
493        }
494
495        assert_eq!(status_cache.roots(), &HashSet::from([0_u64, 1, 2, 3]));
496        assert_eq!(
497            status_cache.get_status(oldest_sig, &oldest_blockhash, &ancestors),
498            Some((0, ()))
499        );
500        assert_eq!(
501            status_cache.get_status(newest_sig, &newest_blockhash, &ancestors),
502            Some((3, ()))
503        );
504
505        status_cache.set_max_root_entries(NonZeroUsize::new(GROWN_MAX_ROOT_ENTRIES).unwrap());
506        for root in INITIAL_MAX_ROOT_ENTRIES as Slot..GROWN_MAX_ROOT_ENTRIES as Slot {
507            status_cache.add_root(root);
508        }
509
510        assert_eq!(status_cache.roots(), &HashSet::from([0_u64, 1, 2, 3, 4, 5]));
511        assert_eq!(
512            status_cache.get_status(oldest_sig, &oldest_blockhash, &ancestors),
513            Some((0, ()))
514        );
515        assert_eq!(
516            status_cache.get_status(newest_sig, &newest_blockhash, &ancestors),
517            Some((3, ()))
518        );
519
520        status_cache.set_max_root_entries(NonZeroUsize::new(SHRUNK_MAX_ROOT_ENTRIES).unwrap());
521
522        assert_eq!(status_cache.roots(), &HashSet::from([3_u64, 4, 5]));
523        assert_eq!(
524            status_cache.get_status(oldest_sig, &oldest_blockhash, &ancestors),
525            None
526        );
527        assert_eq!(
528            status_cache.get_status(newest_sig, &newest_blockhash, &ancestors),
529            Some((3, ()))
530        );
531        assert!(!status_cache.cache.contains_key(&oldest_blockhash));
532        assert!(status_cache.cache.contains_key(&newest_blockhash));
533        assert!(!status_cache.slot_deltas.contains_key(&0));
534        assert!(status_cache.slot_deltas.contains_key(&3));
535    }
536
537    #[test]
538    fn test_clear_signatures_sigs_are_gone() {
539        let sig = Signature::default();
540        let mut status_cache = BankStatusCache::default();
541        let blockhash = hash(Hash::default().as_ref());
542        let ancestors = Ancestors::default();
543        status_cache.insert(&blockhash, sig, 0, ());
544        status_cache.add_root(0);
545        status_cache.clear();
546        assert_eq!(status_cache.get_status(sig, &blockhash, &ancestors), None);
547    }
548
549    #[test]
550    fn test_clear_signatures_insert_works() {
551        let sig = Signature::default();
552        let mut status_cache = BankStatusCache::default();
553        let blockhash = hash(Hash::default().as_ref());
554        let ancestors = Ancestors::default();
555        status_cache.add_root(0);
556        status_cache.clear();
557        status_cache.insert(&blockhash, sig, 0, ());
558        assert!(
559            status_cache
560                .get_status(sig, &blockhash, &ancestors)
561                .is_some()
562        );
563    }
564
565    #[test]
566    fn test_signatures_slice() {
567        let sig = Signature::default();
568        let mut status_cache = BankStatusCache::default();
569        let blockhash = hash(Hash::default().as_ref());
570        status_cache.clear();
571        status_cache.insert(&blockhash, sig, 0, ());
572        let (_, index, sig_map) = status_cache.cache.get(&blockhash).unwrap();
573        let sig_slice: &[u8; CACHED_KEY_SIZE] =
574            arrayref::array_ref![sig.as_ref(), *index, CACHED_KEY_SIZE];
575        assert!(sig_map.get(sig_slice).is_some());
576    }
577
578    #[test]
579    fn test_slot_deltas() {
580        let sig = Signature::default();
581        let mut status_cache = BankStatusCache::default();
582        let blockhash = hash(Hash::default().as_ref());
583        status_cache.clear();
584        status_cache.insert(&blockhash, sig, 0, ());
585        assert!(status_cache.roots().contains(&0));
586        let slot_deltas = status_cache.root_slot_deltas();
587        let cache = StatusCache::from_slot_deltas(&slot_deltas);
588        assert_eq!(cache, status_cache);
589        let slot_deltas = cache.root_slot_deltas();
590        let cache = StatusCache::from_slot_deltas(&slot_deltas);
591        assert_eq!(cache, status_cache);
592    }
593
594    #[test]
595    fn test_roots_deltas() {
596        let sig = Signature::default();
597        let mut status_cache = BankStatusCache::default();
598        let blockhash = hash(Hash::default().as_ref());
599        let blockhash2 = hash(blockhash.as_ref());
600        status_cache.insert(&blockhash, sig, 0, ());
601        status_cache.insert(&blockhash, sig, 1, ());
602        status_cache.insert(&blockhash2, sig, 1, ());
603        for i in 0..=status_cache.max_root_entries() {
604            status_cache.add_root(i as u64);
605        }
606        assert_eq!(status_cache.slot_deltas.len(), 1);
607        assert!(status_cache.slot_deltas.contains_key(&1));
608        let slot_deltas = status_cache.root_slot_deltas();
609        let cache = StatusCache::from_slot_deltas(&slot_deltas);
610        assert_eq!(cache, status_cache);
611    }
612
613    #[test]
614    fn test_clear_slot_signatures() {
615        let sig = Signature::default();
616        let mut status_cache = BankStatusCache::default();
617        let blockhash = hash(Hash::default().as_ref());
618        let blockhash2 = hash(blockhash.as_ref());
619        status_cache.insert(&blockhash, sig, 0, ());
620        status_cache.insert(&blockhash, sig, 1, ());
621        status_cache.insert(&blockhash2, sig, 1, ());
622
623        let mut ancestors0 = Ancestors::default();
624        ancestors0.insert(0);
625        let mut ancestors1 = Ancestors::default();
626        ancestors1.insert(1);
627
628        // Clear slot 0 related data
629        assert!(
630            status_cache
631                .get_status(sig, &blockhash, &ancestors0)
632                .is_some()
633        );
634        status_cache.clear_slot_entries(0);
635        assert!(
636            status_cache
637                .get_status(sig, &blockhash, &ancestors0)
638                .is_none()
639        );
640        assert!(
641            status_cache
642                .get_status(sig, &blockhash, &ancestors1)
643                .is_some()
644        );
645        assert!(
646            status_cache
647                .get_status(sig, &blockhash2, &ancestors1)
648                .is_some()
649        );
650
651        // Check that the slot delta for slot 0 is gone, but slot 1 still
652        // exists
653        assert!(!status_cache.slot_deltas.contains_key(&0));
654        assert!(status_cache.slot_deltas.contains_key(&1));
655
656        // Clear slot 1 related data
657        status_cache.clear_slot_entries(1);
658        assert!(status_cache.slot_deltas.is_empty());
659        assert!(
660            status_cache
661                .get_status(sig, &blockhash, &ancestors1)
662                .is_none()
663        );
664        assert!(
665            status_cache
666                .get_status(sig, &blockhash2, &ancestors1)
667                .is_none()
668        );
669        assert!(status_cache.cache.is_empty());
670    }
671
672    #[test]
673    fn test_clear_invalid_slot_signatures() {
674        let mut status_cache = BankStatusCache::default();
675        let blockhash = hash(Hash::default().as_ref());
676        let sig = Signature::default();
677        status_cache.insert(&blockhash, sig, 0, ());
678        // Insert the same signature for the same blockhash and slot twice to
679        // model dead slots with duplicate signatures
680        status_cache.insert(&blockhash, sig, 0, ());
681        // ensure that clear_slot_entries() doesn't panic
682        status_cache.clear_slot_entries(0);
683    }
684
685    // Status cache uses a random key offset for each blockhash. Ensure that shorter
686    // keys can still be used if the offset if greater than the key length.
687    #[test]
688    fn test_different_sized_keys() {
689        let mut status_cache = BankStatusCache::default();
690        let ancestors = Ancestors::from(vec![0]);
691        let blockhash = Hash::default();
692        for _ in 0..100 {
693            let blockhash = hash(blockhash.as_ref());
694            let sig_key = Signature::default();
695            let hash_key = Hash::new_unique();
696            status_cache.insert(&blockhash, sig_key, 0, ());
697            status_cache.insert(&blockhash, hash_key, 0, ());
698            assert!(
699                status_cache
700                    .get_status(sig_key, &blockhash, &ancestors)
701                    .is_some()
702            );
703            assert!(
704                status_cache
705                    .get_status(hash_key, &blockhash, &ancestors)
706                    .is_some()
707            );
708        }
709    }
710}
711
712#[cfg(all(test, feature = "shuttle-test"))]
713mod shuttle_tests {
714    use {super::*, shuttle::sync::RwLock};
715
716    type BankStatusCache = RwLock<StatusCache<()>>;
717
718    const CLEAR_DFS_ITERATIONS: Option<usize> = None;
719    const CLEAR_RANDOM_ITERATIONS: usize = 20000;
720    const PURGE_DFS_ITERATIONS: Option<usize> = None;
721    const PURGE_RANDOM_ITERATIONS: usize = 8000;
722    const INSERT_DFS_ITERATIONS: Option<usize> = Some(20000);
723    const INSERT_RANDOM_ITERATIONS: usize = 20000;
724
725    fn do_test_shuttle_clear_slots_blockhash_overlap() {
726        let status_cache = Arc::new(BankStatusCache::default());
727
728        let blockhash1 = Hash::new_from_array([1; 32]);
729
730        let key1 = Hash::new_from_array([3; 32]);
731        let key2 = Hash::new_from_array([4; 32]);
732
733        status_cache
734            .write()
735            .unwrap()
736            .insert(&blockhash1, key1, 1, ());
737        let th_clear = shuttle::thread::spawn({
738            let status_cache = status_cache.clone();
739            move || {
740                status_cache.write().unwrap().clear_slot_entries(1);
741            }
742        });
743
744        let th_insert = shuttle::thread::spawn({
745            let status_cache = status_cache.clone();
746            move || {
747                // insert an entry for slot 1 so clear_slot_entries will remove it
748                status_cache
749                    .write()
750                    .unwrap()
751                    .insert(&blockhash1, key2, 2, ());
752            }
753        });
754
755        th_clear.join().unwrap();
756        th_insert.join().unwrap();
757
758        let mut ancestors2 = Ancestors::default();
759        ancestors2.insert(2);
760
761        assert!(
762            status_cache
763                .read()
764                .unwrap()
765                .get_status(key2, &blockhash1, &ancestors2)
766                .is_some()
767        );
768    }
769    #[test]
770    fn test_shuttle_clear_slots_blockhash_overlap_random() {
771        shuttle::check_random(
772            do_test_shuttle_clear_slots_blockhash_overlap,
773            CLEAR_RANDOM_ITERATIONS,
774        );
775    }
776
777    #[test]
778    fn test_shuttle_clear_slots_blockhash_overlap_dfs() {
779        shuttle::check_dfs(
780            do_test_shuttle_clear_slots_blockhash_overlap,
781            CLEAR_DFS_ITERATIONS,
782        );
783    }
784
785    // unlike clear_slot_entries(), purge_slots() can't overlap with regular blockhashes since
786    // they'd have expired by the time roots are old enough to be purged. However, nonces don't
787    // expire, so they can overlap.
788    fn do_test_shuttle_purge_nonce_overlap() {
789        let status_cache = Arc::new(BankStatusCache::default());
790        // fill the cache so that the next add_root() will purge the oldest root
791        let max_root_entries = status_cache.read().unwrap().max_root_entries();
792        for i in 0..max_root_entries {
793            status_cache.write().unwrap().add_root(i as u64);
794        }
795
796        let blockhash1 = Hash::new_from_array([1; 32]);
797
798        let key1 = Hash::new_from_array([3; 32]);
799        let key2 = Hash::new_from_array([4; 32]);
800
801        // this slot/key is going to get purged when the th_purge thread calls add_root()
802        status_cache
803            .write()
804            .unwrap()
805            .insert(&blockhash1, key1, 0, ());
806
807        let th_purge = shuttle::thread::spawn({
808            let status_cache = status_cache.clone();
809            move || {
810                status_cache
811                    .write()
812                    .unwrap()
813                    .add_root(max_root_entries as Slot + 1);
814            }
815        });
816
817        let th_insert = shuttle::thread::spawn({
818            let status_cache = status_cache.clone();
819            move || {
820                // insert an entry for a blockhash that gets concurrently purged
821                status_cache.write().unwrap().insert(
822                    &blockhash1,
823                    key2,
824                    max_root_entries as Slot + 2,
825                    (),
826                );
827            }
828        });
829        th_purge.join().unwrap();
830        th_insert.join().unwrap();
831
832        let mut ancestors2 = Ancestors::default();
833        ancestors2.insert(max_root_entries as Slot + 2);
834
835        assert!(
836            status_cache
837                .read()
838                .unwrap()
839                .get_status(key1, &blockhash1, &ancestors2)
840                .is_none()
841        );
842        assert!(
843            status_cache
844                .read()
845                .unwrap()
846                .get_status(key2, &blockhash1, &ancestors2)
847                .is_some()
848        );
849    }
850
851    #[test]
852    fn test_shuttle_purge_nonce_overlap_random() {
853        shuttle::check_random(do_test_shuttle_purge_nonce_overlap, PURGE_RANDOM_ITERATIONS);
854    }
855
856    #[test]
857    fn test_shuttle_purge_nonce_overlap_dfs() {
858        shuttle::check_dfs(do_test_shuttle_purge_nonce_overlap, PURGE_DFS_ITERATIONS);
859    }
860
861    fn do_test_shuttle_concurrent_inserts() {
862        let status_cache = Arc::new(BankStatusCache::default());
863        let blockhash1 = Hash::new_from_array([42; 32]);
864        let blockhash2 = Hash::new_from_array([43; 32]);
865        const N_INSERTS: u8 = 50;
866
867        let mut handles = Vec::with_capacity(N_INSERTS as usize);
868        for i in 0..N_INSERTS {
869            let status_cache = status_cache.clone();
870            let slot = (i % 3) + 1;
871            let bh = if i % 2 == 0 { blockhash1 } else { blockhash2 };
872            handles.push(shuttle::thread::spawn(move || {
873                let key = Hash::new_from_array([i; 32]);
874                status_cache
875                    .write()
876                    .unwrap()
877                    .insert(&bh, key, slot as Slot, ());
878            }));
879        }
880
881        for handle in handles {
882            handle.join().unwrap();
883        }
884
885        let mut ancestors = Ancestors::default();
886        ancestors.insert(1);
887        ancestors.insert(2);
888        ancestors.insert(3);
889
890        // verify all 100 inserts are visible
891        for i in 0..N_INSERTS {
892            let key = Hash::new_from_array([i; 32]);
893            let bh = if i % 2 == 0 { blockhash1 } else { blockhash2 };
894            assert!(
895                status_cache
896                    .read()
897                    .unwrap()
898                    .get_status(key, &bh, &ancestors)
899                    .is_some(),
900                "missing key {}",
901                i
902            );
903        }
904    }
905
906    #[test]
907    fn test_shuttle_concurrent_inserts_dfs() {
908        shuttle::check_dfs(do_test_shuttle_concurrent_inserts, INSERT_DFS_ITERATIONS);
909    }
910
911    #[test]
912    fn test_shuttle_concurrent_inserts_random() {
913        shuttle::check_random(do_test_shuttle_concurrent_inserts, INSERT_RANDOM_ITERATIONS);
914    }
915}