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