use crate::bloom::{Bloom, BloomHashIndex}; use crate::last_id_queue::MAX_ENTRY_IDS; use hashbrown::HashMap; use solana_sdk::hash::Hash; use solana_sdk::signature::Signature; use solana_sdk::timing::NUM_TICKS_PER_SECOND; use std::collections::VecDeque; use std::ops::{Deref, DerefMut}; /// This cache is designed to last 1 second const MAX_CACHE_ENTRIES: usize = MAX_ENTRY_IDS / NUM_TICKS_PER_SECOND; type FailureMap = HashMap; #[derive(Clone)] pub struct StatusCache { /// all signatures seen at this checkpoint signatures: Bloom, /// failures failures: FailureMap, /// Merges are empty unless this is the root checkpoint which cannot be unrolled merges: VecDeque>, } impl Default for StatusCache { fn default() -> Self { Self::new(&Hash::default()) } } impl StatusCache { pub fn new(last_id: &Hash) -> Self { let keys = (0..27).map(|i| last_id.hash_at_index(i)).collect(); Self { signatures: Bloom::new(38_340_234, keys), failures: HashMap::new(), merges: VecDeque::new(), } } fn has_signature_merged(&self, sig: &Signature) -> bool { for c in &self.merges { if c.has_signature(sig) { return true; } } false } /// test if a signature is known pub fn has_signature(&self, sig: &Signature) -> bool { self.signatures.contains(&sig) || self.has_signature_merged(sig) } /// add a signature pub fn add(&mut self, sig: &Signature) { self.signatures.add(&sig) } /// Save an error status for a signature pub fn save_failure_status(&mut self, sig: &Signature, err: T) { assert!(self.has_signature(sig), "sig not found"); self.failures.insert(*sig, err); } /// Forget all signatures. Useful for benchmarking. pub fn clear(&mut self) { self.failures.clear(); self.signatures.clear(); self.merges = VecDeque::new(); } fn get_signature_status_merged(&self, sig: &Signature) -> Option> { for c in &self.merges { if c.has_signature(sig) { return c.get_signature_status(sig); } } None } pub fn get_signature_status(&self, sig: &Signature) -> Option> { if let Some(res) = self.failures.get(sig) { return Some(Err(res.clone())); } else if self.signatures.contains(sig) { return Some(Ok(())); } self.get_signature_status_merged(sig) } /// like accounts, status cache starts with an new data structure for every checkpoint /// so only merge is implemented /// but the merges maintains a history pub fn merge_into_root(&mut self, other: Self) { // merges should be empty for every other checkpoint accept the root // which cannot be rolled back assert!(other.merges.is_empty()); self.merges.push_front(other); if self.merges.len() > MAX_CACHE_ENTRIES { self.merges.pop_back(); } } /// Crate a new cache, pushing the old cache into the merged queue pub fn new_cache(&mut self, last_id: &Hash) { let mut old = Self::new(last_id); std::mem::swap(&mut old.signatures, &mut self.signatures); std::mem::swap(&mut old.failures, &mut self.failures); assert!(old.merges.is_empty()); self.merges.push_front(old); if self.merges.len() > MAX_CACHE_ENTRIES { self.merges.pop_back(); } } pub fn get_signature_status_all( checkpoints: &[U], signature: &Signature, ) -> Option> where U: Deref, { for c in checkpoints { if let Some(status) = c.get_signature_status(signature) { return Some(status); } } None } pub fn has_signature_all(checkpoints: &[U], signature: &Signature) -> bool where U: Deref, { for c in checkpoints { if c.has_signature(signature) { return true; } } false } pub fn clear_all(checkpoints: &mut [U]) -> bool where U: DerefMut, { for c in checkpoints.iter_mut() { c.clear(); } false } } #[cfg(test)] mod tests { use super::*; use crate::bank::BankError; use solana_sdk::hash::hash; type BankStatusCache = StatusCache; #[test] fn test_has_signature() { let sig = Signature::default(); let last_id = hash(Hash::default().as_ref()); let mut status_cache = BankStatusCache::new(&last_id); assert_eq!(status_cache.has_signature(&sig), false); assert_eq!(status_cache.get_signature_status(&sig), None,); status_cache.add(&sig); assert_eq!(status_cache.has_signature(&sig), true); assert_eq!(status_cache.get_signature_status(&sig), Some(Ok(())),); } #[test] fn test_has_signature_checkpoint() { let sig = Signature::default(); let last_id = hash(Hash::default().as_ref()); let mut first = BankStatusCache::new(&last_id); first.add(&sig); assert_eq!(first.get_signature_status(&sig), Some(Ok(()))); let last_id = hash(last_id.as_ref()); let second = StatusCache::new(&last_id); let checkpoints = [&second, &first]; assert_eq!( BankStatusCache::get_signature_status_all(&checkpoints, &sig), Some(Ok(())), ); assert!(StatusCache::has_signature_all(&checkpoints, &sig)); } #[test] fn test_new_cache() { let sig = Signature::default(); let last_id = hash(Hash::default().as_ref()); let mut first = BankStatusCache::new(&last_id); first.add(&sig); assert_eq!(first.get_signature_status(&sig), Some(Ok(()))); let last_id = hash(last_id.as_ref()); first.new_cache(&last_id); assert_eq!(first.get_signature_status(&sig), Some(Ok(())),); assert!(first.has_signature(&sig)); first.clear(); assert_eq!(first.get_signature_status(&sig), None); assert!(!first.has_signature(&sig)); } #[test] fn test_new_cache_full() { let sig = Signature::default(); let last_id = hash(Hash::default().as_ref()); let mut first = BankStatusCache::new(&last_id); first.add(&sig); assert_eq!(first.get_signature_status(&sig), Some(Ok(()))); for _ in 0..(MAX_CACHE_ENTRIES + 1) { let last_id = hash(last_id.as_ref()); first.new_cache(&last_id); } assert_eq!(first.get_signature_status(&sig), None); assert!(!first.has_signature(&sig)); } #[test] fn test_has_signature_merged1() { let sig = Signature::default(); let last_id = hash(Hash::default().as_ref()); let mut first = BankStatusCache::new(&last_id); first.add(&sig); assert_eq!(first.get_signature_status(&sig), Some(Ok(()))); let last_id = hash(last_id.as_ref()); let second = BankStatusCache::new(&last_id); first.merge_into_root(second); assert_eq!(first.get_signature_status(&sig), Some(Ok(())),); assert!(first.has_signature(&sig)); } #[test] fn test_has_signature_merged2() { let sig = Signature::default(); let last_id = hash(Hash::default().as_ref()); let mut first = BankStatusCache::new(&last_id); first.add(&sig); assert_eq!(first.get_signature_status(&sig), Some(Ok(()))); let last_id = hash(last_id.as_ref()); let mut second = BankStatusCache::new(&last_id); second.merge_into_root(first); assert_eq!(second.get_signature_status(&sig), Some(Ok(())),); assert!(second.has_signature(&sig)); } #[test] fn test_failure_status() { let sig = Signature::default(); let last_id = hash(Hash::default().as_ref()); let mut first = StatusCache::new(&last_id); first.add(&sig); first.save_failure_status(&sig, BankError::DuplicateSignature); assert_eq!(first.has_signature(&sig), true); assert_eq!( first.get_signature_status(&sig), Some(Err(BankError::DuplicateSignature)), ); } #[test] fn test_clear_signatures() { let sig = Signature::default(); let last_id = hash(Hash::default().as_ref()); let mut first = StatusCache::new(&last_id); first.add(&sig); assert_eq!(first.has_signature(&sig), true); first.save_failure_status(&sig, BankError::DuplicateSignature); assert_eq!( first.get_signature_status(&sig), Some(Err(BankError::DuplicateSignature)), ); first.clear(); assert_eq!(first.has_signature(&sig), false); assert_eq!(first.get_signature_status(&sig), None,); } #[test] fn test_clear_signatures_all() { let sig = Signature::default(); let last_id = hash(Hash::default().as_ref()); let mut first = StatusCache::new(&last_id); first.add(&sig); assert_eq!(first.has_signature(&sig), true); let mut second = StatusCache::new(&last_id); let mut checkpoints = [&mut second, &mut first]; BankStatusCache::clear_all(&mut checkpoints); assert_eq!( BankStatusCache::has_signature_all(&checkpoints, &sig), false ); } }