Replace recursive status cache with a single global fast status cache (#3541)
Fast Status Cache
This commit is contained in:
parent
753d0dcabe
commit
10239c3b3c
|
@ -18,7 +18,7 @@ use solana_sdk::native_loader;
|
|||
use solana_sdk::pubkey::Pubkey;
|
||||
use solana_sdk::signature::{Keypair, Signature};
|
||||
use solana_sdk::system_transaction::SystemTransaction;
|
||||
use solana_sdk::timing::{duration_as_us, MAX_RECENT_BLOCKHASHES, NUM_TICKS_PER_SECOND};
|
||||
use solana_sdk::timing::{duration_as_us, MAX_RECENT_BLOCKHASHES};
|
||||
use solana_sdk::transaction::{Transaction, TransactionError};
|
||||
use solana_vote_api::vote_instruction::Vote;
|
||||
use solana_vote_api::vote_state::{Lockout, VoteState};
|
||||
|
@ -105,7 +105,7 @@ impl EpochSchedule {
|
|||
|
||||
pub type Result<T> = result::Result<T, TransactionError>;
|
||||
|
||||
type BankStatusCache = StatusCache<TransactionError>;
|
||||
type BankStatusCache = StatusCache<Result<()>>;
|
||||
|
||||
/// Manager for the state of all accounts and programs after processing its entries.
|
||||
#[derive(Default)]
|
||||
|
@ -117,7 +117,7 @@ pub struct Bank {
|
|||
accounts_id: u64,
|
||||
|
||||
/// A cache of signature statuses
|
||||
status_cache: RwLock<BankStatusCache>,
|
||||
status_cache: Arc<RwLock<BankStatusCache>>,
|
||||
|
||||
/// FIFO queue of `recent_blockhash` items
|
||||
blockhash_queue: RwLock<BlockhashQueue>,
|
||||
|
@ -173,14 +173,12 @@ impl Bank {
|
|||
let mut bank = Self::default();
|
||||
bank.accounts = Arc::new(Accounts::new(bank.slot, paths));
|
||||
bank.process_genesis_block(genesis_block);
|
||||
|
||||
// genesis needs stakes for all epochs up to the epoch implied by
|
||||
// slot = 0 and genesis configuration
|
||||
let vote_accounts: HashMap<_, _> = bank.vote_accounts().collect();
|
||||
for i in 0..=bank.get_stakers_epoch(bank.slot) {
|
||||
bank.epoch_vote_accounts.insert(i, vote_accounts.clone());
|
||||
}
|
||||
|
||||
bank
|
||||
}
|
||||
|
||||
|
@ -191,6 +189,8 @@ impl Bank {
|
|||
|
||||
let mut bank = Self::default();
|
||||
bank.blockhash_queue = RwLock::new(parent.blockhash_queue.read().unwrap().clone());
|
||||
bank.status_cache = parent.status_cache.clone();
|
||||
|
||||
bank.tick_height
|
||||
.store(parent.tick_height.load(Ordering::SeqCst), Ordering::SeqCst);
|
||||
bank.ticks_per_slot = parent.ticks_per_slot;
|
||||
|
@ -246,7 +246,6 @@ impl Bank {
|
|||
// freeze is a one-way trip, idempotent
|
||||
*hash = self.hash_internal_state();
|
||||
}
|
||||
// self.status_cache.write().unwrap().freeze();
|
||||
}
|
||||
|
||||
/// squash the parent's state up into this Bank,
|
||||
|
@ -259,15 +258,9 @@ impl Bank {
|
|||
|
||||
self.accounts.squash(self.accounts_id);
|
||||
|
||||
let parent_caches: Vec<_> = parents
|
||||
parents
|
||||
.iter()
|
||||
.map(|p| {
|
||||
let mut parent = p.status_cache.write().unwrap();
|
||||
parent.freeze();
|
||||
parent
|
||||
})
|
||||
.collect();
|
||||
self.status_cache.write().unwrap().squash(&parent_caches);
|
||||
.for_each(|p| self.status_cache.write().unwrap().add_root(p.slot()));
|
||||
}
|
||||
|
||||
/// Return the more recent checkpoint of this bank instance.
|
||||
|
@ -355,7 +348,7 @@ impl Bank {
|
|||
|
||||
/// Forget all signatures. Useful for benchmarking.
|
||||
pub fn clear_signatures(&self) {
|
||||
self.status_cache.write().unwrap().clear();
|
||||
self.status_cache.write().unwrap().clear_signatures();
|
||||
}
|
||||
|
||||
fn update_transaction_statuses(&self, txs: &[Transaction], res: &[Result<()>]) {
|
||||
|
@ -364,7 +357,12 @@ impl Bank {
|
|||
match &res[i] {
|
||||
Ok(_) => {
|
||||
if !tx.signatures.is_empty() {
|
||||
status_cache.add(&tx.signatures[0]);
|
||||
status_cache.insert(
|
||||
&tx.recent_blockhash,
|
||||
&tx.signatures[0],
|
||||
self.slot(),
|
||||
Ok(()),
|
||||
);
|
||||
}
|
||||
}
|
||||
Err(TransactionError::BlockhashNotFound) => (),
|
||||
|
@ -372,8 +370,12 @@ impl Bank {
|
|||
Err(TransactionError::AccountNotFound) => (),
|
||||
Err(e) => {
|
||||
if !tx.signatures.is_empty() {
|
||||
status_cache.add(&tx.signatures[0]);
|
||||
status_cache.save_failure_status(&tx.signatures[0], e.clone());
|
||||
status_cache.insert(
|
||||
&tx.recent_blockhash,
|
||||
&tx.signatures[0],
|
||||
self.slot(),
|
||||
Err(e.clone()),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -430,12 +432,7 @@ impl Bank {
|
|||
|
||||
// Register a new block hash if at the last tick in the slot
|
||||
if current_tick_height % self.ticks_per_slot == self.ticks_per_slot - 1 {
|
||||
let mut blockhash_queue = self.blockhash_queue.write().unwrap();
|
||||
blockhash_queue.register_hash(hash);
|
||||
}
|
||||
|
||||
if current_tick_height % NUM_TICKS_PER_SECOND == 0 {
|
||||
self.status_cache.write().unwrap().new_cache(hash);
|
||||
self.blockhash_queue.write().unwrap().register_hash(hash);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -515,16 +512,24 @@ impl Bank {
|
|||
lock_results: Vec<Result<()>>,
|
||||
error_counters: &mut ErrorCounters,
|
||||
) -> Vec<Result<()>> {
|
||||
let parents = self.parents();
|
||||
let mut caches = vec![self.status_cache.read().unwrap()];
|
||||
caches.extend(parents.iter().map(|b| b.status_cache.read().unwrap()));
|
||||
let mut ancestors = HashMap::new();
|
||||
ancestors.insert(self.slot(), 0);
|
||||
self.parents().iter().enumerate().for_each(|(i, p)| {
|
||||
ancestors.insert(p.slot(), i + 1);
|
||||
});
|
||||
|
||||
let rcache = self.status_cache.read().unwrap();
|
||||
txs.iter()
|
||||
.zip(lock_results.into_iter())
|
||||
.map(|(tx, lock_res)| {
|
||||
if tx.signatures.is_empty() {
|
||||
return lock_res;
|
||||
}
|
||||
if lock_res.is_ok() && StatusCache::has_signature_all(&caches, &tx.signatures[0]) {
|
||||
if lock_res.is_ok()
|
||||
&& rcache
|
||||
.get_signature_status(&tx.signatures[0], &tx.recent_blockhash, &ancestors)
|
||||
.is_some()
|
||||
{
|
||||
error_counters.duplicate_signature += 1;
|
||||
Err(TransactionError::DuplicateSignature)
|
||||
} else {
|
||||
|
@ -793,10 +798,13 @@ impl Bank {
|
|||
&self,
|
||||
signature: &Signature,
|
||||
) -> Option<(usize, Result<()>)> {
|
||||
let parents = self.parents();
|
||||
let mut caches = vec![self.status_cache.read().unwrap()];
|
||||
caches.extend(parents.iter().map(|b| b.status_cache.read().unwrap()));
|
||||
StatusCache::get_signature_status_all(&caches, signature)
|
||||
let mut ancestors = HashMap::new();
|
||||
ancestors.insert(self.slot(), 0);
|
||||
self.parents().iter().enumerate().for_each(|(i, p)| {
|
||||
ancestors.insert(p.slot(), i + 1);
|
||||
});
|
||||
let rcache = self.status_cache.read().unwrap();
|
||||
rcache.get_signature_status_slow(signature, &ancestors)
|
||||
}
|
||||
|
||||
pub fn get_signature_status(&self, signature: &Signature) -> Option<Result<()>> {
|
||||
|
@ -805,10 +813,7 @@ impl Bank {
|
|||
}
|
||||
|
||||
pub fn has_signature(&self, signature: &Signature) -> bool {
|
||||
let parents = self.parents();
|
||||
let mut caches = vec![self.status_cache.read().unwrap()];
|
||||
caches.extend(parents.iter().map(|b| b.status_cache.read().unwrap()));
|
||||
StatusCache::has_signature_all(&caches, signature)
|
||||
self.get_signature_confirmation_status(signature).is_some()
|
||||
}
|
||||
|
||||
/// Hash the `accounts` HashMap. This represents a validator's interpretation
|
||||
|
@ -1432,6 +1437,7 @@ mod tests {
|
|||
/// Verifies that last ids and accounts are correctly referenced from parent
|
||||
#[test]
|
||||
fn test_bank_squash() {
|
||||
solana_logger::setup();
|
||||
let (genesis_block, mint_keypair) = GenesisBlock::new(2);
|
||||
let key1 = Keypair::new();
|
||||
let key2 = Keypair::new();
|
||||
|
@ -1439,10 +1445,23 @@ mod tests {
|
|||
|
||||
let tx_move_mint_to_1 =
|
||||
SystemTransaction::new_move(&mint_keypair, &key1.pubkey(), 1, genesis_block.hash(), 0);
|
||||
trace!("parent process tx ");
|
||||
assert_eq!(parent.process_transaction(&tx_move_mint_to_1), Ok(()));
|
||||
trace!("done parent process tx ");
|
||||
assert_eq!(parent.transaction_count(), 1);
|
||||
assert_eq!(
|
||||
parent.get_signature_status(&tx_move_mint_to_1.signatures[0]),
|
||||
Some(Ok(()))
|
||||
);
|
||||
|
||||
trace!("new form parent");
|
||||
let bank = new_from_parent(&parent);
|
||||
trace!("done new form parent");
|
||||
assert_eq!(
|
||||
bank.get_signature_status(&tx_move_mint_to_1.signatures[0]),
|
||||
Some(Ok(()))
|
||||
);
|
||||
|
||||
assert_eq!(bank.transaction_count(), parent.transaction_count());
|
||||
let tx_move_1_to_2 =
|
||||
SystemTransaction::new_move(&key1, &key2.pubkey(), 1, genesis_block.hash(), 0);
|
||||
|
@ -1459,6 +1478,7 @@ mod tests {
|
|||
assert_eq!(bank.get_balance(&key1.pubkey()), 0);
|
||||
assert_eq!(bank.get_account(&key1.pubkey()), None);
|
||||
assert_eq!(bank.get_balance(&key2.pubkey()), 1);
|
||||
trace!("start");
|
||||
assert_eq!(
|
||||
bank.get_signature_status(&tx_move_mint_to_1.signatures[0]),
|
||||
Some(Ok(()))
|
||||
|
@ -1469,6 +1489,7 @@ mod tests {
|
|||
);
|
||||
|
||||
// works iteration 0, no-ops on iteration 1 and 2
|
||||
trace!("SQUASH");
|
||||
bank.squash();
|
||||
|
||||
assert_eq!(parent.transaction_count(), 1);
|
||||
|
|
|
@ -69,6 +69,10 @@ impl BlockhashQueue {
|
|||
self.last_hash = Some(*hash);
|
||||
}
|
||||
|
||||
fn check_age(hash_height: u64, max_age: usize, age: &HashAge) -> bool {
|
||||
hash_height - age.hash_height <= max_age as u64
|
||||
}
|
||||
|
||||
pub fn register_hash(&mut self, hash: &Hash) {
|
||||
self.hash_height += 1;
|
||||
let hash_height = self.hash_height;
|
||||
|
@ -78,7 +82,7 @@ impl BlockhashQueue {
|
|||
let max_age = self.max_age;
|
||||
if self.ages.len() >= max_age {
|
||||
self.ages
|
||||
.retain(|_, age| hash_height - age.hash_height <= max_age as u64);
|
||||
.retain(|_, age| Self::check_age(hash_height, max_age, age));
|
||||
}
|
||||
self.ages.insert(
|
||||
*hash,
|
||||
|
@ -118,10 +122,10 @@ mod tests {
|
|||
}
|
||||
#[test]
|
||||
fn test_reject_old_last_hash() {
|
||||
let last_hash = Hash::default();
|
||||
let mut hash_queue = BlockhashQueue::new(100);
|
||||
for i in 0..100 {
|
||||
let last_hash = hash(&serialize(&i).unwrap()); // Unique hash
|
||||
let last_hash = hash(&serialize(&0).unwrap());
|
||||
for i in 0..102 {
|
||||
let last_hash = hash(&serialize(&i).unwrap());
|
||||
hash_queue.register_hash(&last_hash);
|
||||
}
|
||||
// Assert we're no longer able to use the oldest hash.
|
||||
|
|
|
@ -1,411 +1,245 @@
|
|||
use crate::bloom::{Bloom, BloomHashIndex};
|
||||
use hashbrown::HashMap;
|
||||
use hashbrown::{HashMap, HashSet};
|
||||
use log::*;
|
||||
use solana_sdk::hash::Hash;
|
||||
use solana_sdk::signature::Signature;
|
||||
use std::collections::VecDeque;
|
||||
use std::ops::Deref;
|
||||
#[cfg(test)]
|
||||
use std::ops::DerefMut;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Each cache entry is designed to span ~1 second of signatures
|
||||
const MAX_CACHE_ENTRIES: usize = solana_sdk::timing::MAX_HASH_AGE_IN_SECONDS;
|
||||
|
||||
type FailureMap<T> = HashMap<Signature, T>;
|
||||
// Store forks in a single chunk of memory to avoid another lookup.
|
||||
pub type ForkId = u64;
|
||||
pub type ForkStatus<T> = Vec<(ForkId, T)>;
|
||||
type SignatureMap<T> = HashMap<Signature, ForkStatus<T>>;
|
||||
type StatusMap<T> = HashMap<Hash, (ForkId, SignatureMap<T>)>;
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Status<T> {
|
||||
pub struct StatusCache<T: Clone> {
|
||||
/// all signatures seen during a hash period
|
||||
signatures: Bloom<Signature>,
|
||||
|
||||
/// failures
|
||||
failures: FailureMap<T>,
|
||||
}
|
||||
|
||||
impl<T: Clone> Status<T> {
|
||||
// new needs to be called once per second to be useful for the Bank
|
||||
// 1M TPS * 1s (length of block in sigs) == 1M items in filter
|
||||
// 1.0E-8 false positive rate
|
||||
// https://hur.st/bloomfilter/?n=1000000&p=1.0E-8&m=&k=
|
||||
fn new(blockhash: &Hash) -> Self {
|
||||
let keys = (0..27).map(|i| blockhash.hash_at_index(i)).collect();
|
||||
Status {
|
||||
signatures: Bloom::new(38_340_234, keys),
|
||||
failures: HashMap::default(),
|
||||
}
|
||||
}
|
||||
fn has_signature(&self, sig: &Signature) -> bool {
|
||||
self.signatures.contains(&sig)
|
||||
}
|
||||
|
||||
fn add(&mut self, sig: &Signature) {
|
||||
self.signatures.add(&sig);
|
||||
}
|
||||
|
||||
fn clear(&mut self) {
|
||||
self.failures.clear();
|
||||
self.signatures.clear();
|
||||
}
|
||||
pub fn get_signature_status(&self, sig: &Signature) -> Option<Result<(), T>> {
|
||||
if let Some(res) = self.failures.get(sig) {
|
||||
return Some(Err(res.clone()));
|
||||
} else if self.signatures.contains(sig) {
|
||||
return Some(Ok(()));
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct StatusCache<T> {
|
||||
/// currently active status
|
||||
active: Option<Status<T>>,
|
||||
|
||||
/// merges cover previous periods, and are read-only
|
||||
merges: VecDeque<Arc<Status<T>>>,
|
||||
cache: StatusMap<T>,
|
||||
roots: HashSet<ForkId>,
|
||||
}
|
||||
|
||||
impl<T: Clone> Default for StatusCache<T> {
|
||||
fn default() -> Self {
|
||||
Self::new(&Hash::default())
|
||||
Self {
|
||||
cache: HashMap::default(),
|
||||
roots: HashSet::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone> StatusCache<T> {
|
||||
pub fn new(blockhash: &Hash) -> Self {
|
||||
Self {
|
||||
active: Some(Status::new(blockhash)),
|
||||
merges: VecDeque::default(),
|
||||
}
|
||||
}
|
||||
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.active
|
||||
.as_ref()
|
||||
.map_or(false, |active| active.has_signature(&sig))
|
||||
|| self.has_signature_merged(sig)
|
||||
}
|
||||
|
||||
/// add a signature
|
||||
pub fn add(&mut self, sig: &Signature) {
|
||||
if let Some(active) = self.active.as_mut() {
|
||||
active.add(&sig);
|
||||
}
|
||||
}
|
||||
|
||||
/// Save an error status for a signature
|
||||
pub fn save_failure_status(&mut self, sig: &Signature, err: T) {
|
||||
assert!(
|
||||
self.active
|
||||
.as_ref()
|
||||
.map_or(false, |active| active.has_signature(sig)),
|
||||
"sig not found"
|
||||
);
|
||||
|
||||
self.active
|
||||
.as_mut()
|
||||
.map(|active| active.failures.insert(*sig, err));
|
||||
}
|
||||
/// Forget all signatures. Useful for benchmarking.
|
||||
pub fn clear(&mut self) {
|
||||
if let Some(active) = self.active.as_mut() {
|
||||
active.clear();
|
||||
}
|
||||
self.merges = VecDeque::new();
|
||||
}
|
||||
|
||||
fn get_signature_status_merged(&self, sig: &Signature) -> Option<Result<(), T>> {
|
||||
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<Result<(), T>> {
|
||||
self.active
|
||||
.as_ref()
|
||||
.and_then(|active| active.get_signature_status(sig))
|
||||
.or_else(|| self.get_signature_status_merged(sig))
|
||||
}
|
||||
|
||||
fn squash_parent_is_full(&mut self, parent: &Self) -> bool {
|
||||
// flatten and squash the parent and its merges into self.merges,
|
||||
// returns true if self is full
|
||||
if parent.active.is_some() {
|
||||
warn!("=========== FIXME: squash() on an active parent! ================");
|
||||
}
|
||||
// TODO: put this assert back in
|
||||
//assert!(parent.active.is_none());
|
||||
|
||||
if self.merges.len() < MAX_CACHE_ENTRIES {
|
||||
for merge in parent
|
||||
.merges
|
||||
/// Check if the signature from a transaction is in any of the forks in the ancestors set.
|
||||
pub fn get_signature_status(
|
||||
&self,
|
||||
sig: &Signature,
|
||||
transaction_blockhash: &Hash,
|
||||
ancestors: &HashMap<ForkId, usize>,
|
||||
) -> Option<(ForkId, T)> {
|
||||
let (_, sigmap) = self.cache.get(transaction_blockhash)?;
|
||||
let stored_forks = sigmap.get(sig)?;
|
||||
stored_forks
|
||||
.iter()
|
||||
.take(MAX_CACHE_ENTRIES - self.merges.len())
|
||||
{
|
||||
self.merges.push_back(merge.clone());
|
||||
}
|
||||
}
|
||||
self.merges.len() == MAX_CACHE_ENTRIES
|
||||
.filter(|(f, _)| ancestors.get(f).is_some() || self.roots.get(f).is_some())
|
||||
.nth(0)
|
||||
.cloned()
|
||||
}
|
||||
|
||||
/// copy the parents and parents' merges up to this instance, up to
|
||||
/// MAX_CACHE_ENTRIES deep
|
||||
pub fn squash<U>(&mut self, parents: &[U])
|
||||
where
|
||||
U: Deref<Target = Self>,
|
||||
{
|
||||
for parent in parents {
|
||||
if self.squash_parent_is_full(parent) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Crate a new cache, pushing the old cache into the merged queue
|
||||
pub fn new_cache(&mut self, blockhash: &Hash) {
|
||||
assert!(self.active.is_some());
|
||||
let merge = self.active.replace(Status::new(blockhash));
|
||||
|
||||
self.merges.push_front(Arc::new(merge.unwrap()));
|
||||
if self.merges.len() > MAX_CACHE_ENTRIES {
|
||||
self.merges.pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn freeze(&mut self) {
|
||||
if let Some(active) = self.active.take() {
|
||||
self.merges.push_front(Arc::new(active));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_signature_status_all<U>(
|
||||
checkpoints: &[U],
|
||||
signature: &Signature,
|
||||
) -> Option<(usize, Result<(), T>)>
|
||||
where
|
||||
U: Deref<Target = Self>,
|
||||
{
|
||||
for (i, c) in checkpoints.iter().enumerate() {
|
||||
if let Some(status) = c.get_signature_status(signature) {
|
||||
return Some((i, status));
|
||||
/// TODO: wallets should send the Transactions recent blockhash as well
|
||||
pub fn get_signature_status_slow(
|
||||
&self,
|
||||
sig: &Signature,
|
||||
ancestors: &HashMap<ForkId, usize>,
|
||||
) -> Option<(usize, T)> {
|
||||
trace!("get_signature_status_slow");
|
||||
for blockhash in self.cache.keys() {
|
||||
trace!("get_signature_status_slow: trying {}", blockhash);
|
||||
if let Some((forkid, res)) = self.get_signature_status(sig, blockhash, ancestors) {
|
||||
trace!("get_signature_status_slow: got {}", forkid);
|
||||
return ancestors
|
||||
.get(&forkid)
|
||||
.map(|id| (*id, res.clone()))
|
||||
.or_else(|| Some((ancestors.len(), res)));
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
pub fn has_signature_all<U>(checkpoints: &[U], signature: &Signature) -> bool
|
||||
where
|
||||
U: Deref<Target = Self>,
|
||||
{
|
||||
for c in checkpoints {
|
||||
if c.has_signature(signature) {
|
||||
return true;
|
||||
|
||||
/// Add a known root fork. Roots are always valid ancestors.
|
||||
/// After MAX_CACHE_ENTRIES, roots are removed, and any old signatures are cleared.
|
||||
pub fn add_root(&mut self, fork: ForkId) {
|
||||
self.roots.insert(fork);
|
||||
if self.roots.len() > MAX_CACHE_ENTRIES {
|
||||
if let Some(min) = self.roots.iter().min().cloned() {
|
||||
self.roots.remove(&min);
|
||||
self.cache.retain(|_, (fork, _)| *fork > min);
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
#[cfg(test)]
|
||||
pub fn clear_all<U>(checkpoints: &mut [U]) -> bool
|
||||
where
|
||||
U: DerefMut<Target = Self>,
|
||||
{
|
||||
for c in checkpoints.iter_mut() {
|
||||
c.clear();
|
||||
|
||||
/// Insert a new signature for a specific fork.
|
||||
pub fn insert(&mut self, transaction_blockhash: &Hash, sig: &Signature, fork: ForkId, res: T) {
|
||||
let sig_map = self
|
||||
.cache
|
||||
.entry(*transaction_blockhash)
|
||||
.or_insert((fork, HashMap::new()));
|
||||
sig_map.0 = std::cmp::max(fork, sig_map.0);
|
||||
let sig_forks = sig_map.1.entry(*sig).or_insert(vec![]);
|
||||
sig_forks.push((fork, res));
|
||||
}
|
||||
|
||||
/// Clear for testing
|
||||
pub fn clear_signatures(&mut self) {
|
||||
for v in self.cache.values_mut() {
|
||||
v.1 = HashMap::new();
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use solana_sdk::hash::hash;
|
||||
use solana_sdk::transaction::TransactionError;
|
||||
|
||||
type BankStatusCache = StatusCache<TransactionError>;
|
||||
type BankStatusCache = StatusCache<()>;
|
||||
|
||||
#[test]
|
||||
fn test_has_signature() {
|
||||
fn test_empty_has_no_sigs() {
|
||||
let sig = Signature::default();
|
||||
let blockhash = hash(Hash::default().as_ref());
|
||||
let mut status_cache = BankStatusCache::new(&blockhash);
|
||||
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 blockhash = hash(Hash::default().as_ref());
|
||||
let mut first = BankStatusCache::new(&blockhash);
|
||||
first.add(&sig);
|
||||
assert_eq!(first.get_signature_status(&sig), Some(Ok(())));
|
||||
let blockhash = hash(blockhash.as_ref());
|
||||
let second = StatusCache::new(&blockhash);
|
||||
let checkpoints = [&second, &first];
|
||||
let status_cache = BankStatusCache::default();
|
||||
assert_eq!(
|
||||
BankStatusCache::get_signature_status_all(&checkpoints, &sig),
|
||||
Some((1, Ok(()))),
|
||||
status_cache.get_signature_status(&sig, &blockhash, &HashMap::new()),
|
||||
None
|
||||
);
|
||||
assert!(StatusCache::has_signature_all(&checkpoints, &sig));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_new_cache() {
|
||||
let sig = Signature::default();
|
||||
let blockhash = hash(Hash::default().as_ref());
|
||||
let mut first = BankStatusCache::new(&blockhash);
|
||||
first.add(&sig);
|
||||
assert_eq!(first.get_signature_status(&sig), Some(Ok(())));
|
||||
let blockhash = hash(blockhash.as_ref());
|
||||
first.new_cache(&blockhash);
|
||||
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 blockhash = hash(Hash::default().as_ref());
|
||||
let mut first = BankStatusCache::new(&blockhash);
|
||||
first.add(&sig);
|
||||
assert_eq!(first.get_signature_status(&sig), Some(Ok(())));
|
||||
for _ in 0..(MAX_CACHE_ENTRIES + 1) {
|
||||
let blockhash = hash(blockhash.as_ref());
|
||||
first.new_cache(&blockhash);
|
||||
}
|
||||
assert_eq!(first.get_signature_status(&sig), None);
|
||||
assert!(!first.has_signature(&sig));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_status_cache_squash_has_signature() {
|
||||
let sig = Signature::default();
|
||||
let blockhash = hash(Hash::default().as_ref());
|
||||
let mut first = BankStatusCache::new(&blockhash);
|
||||
first.add(&sig);
|
||||
assert_eq!(first.get_signature_status(&sig), Some(Ok(())));
|
||||
|
||||
// give first a merge
|
||||
let blockhash = hash(blockhash.as_ref());
|
||||
first.new_cache(&blockhash);
|
||||
|
||||
let blockhash = hash(blockhash.as_ref());
|
||||
let mut second = BankStatusCache::new(&blockhash);
|
||||
first.freeze();
|
||||
second.squash(&[&first]);
|
||||
|
||||
assert_eq!(second.get_signature_status(&sig), Some(Ok(())));
|
||||
assert!(second.has_signature(&sig));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_status_cache_squash_overflow() {
|
||||
let mut blockhash = hash(Hash::default().as_ref());
|
||||
let mut cache = BankStatusCache::new(&blockhash);
|
||||
|
||||
let parents: Vec<_> = (0..MAX_CACHE_ENTRIES)
|
||||
.map(|_| {
|
||||
blockhash = hash(blockhash.as_ref());
|
||||
|
||||
let mut p = BankStatusCache::new(&blockhash);
|
||||
p.freeze();
|
||||
p
|
||||
})
|
||||
.collect();
|
||||
|
||||
let mut parents_refs: Vec<_> = parents.iter().collect();
|
||||
|
||||
blockhash = hash(Hash::default().as_ref());
|
||||
let mut root = BankStatusCache::new(&blockhash);
|
||||
|
||||
let sig = Signature::default();
|
||||
root.add(&sig);
|
||||
|
||||
parents_refs.push(&root);
|
||||
|
||||
assert_eq!(root.get_signature_status(&sig), Some(Ok(())));
|
||||
assert!(root.has_signature(&sig));
|
||||
|
||||
// will overflow
|
||||
cache.squash(&parents_refs);
|
||||
|
||||
assert_eq!(cache.get_signature_status(&sig), None);
|
||||
assert!(!cache.has_signature(&sig));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_failure_status() {
|
||||
let sig = Signature::default();
|
||||
let blockhash = hash(Hash::default().as_ref());
|
||||
let mut first = StatusCache::new(&blockhash);
|
||||
first.add(&sig);
|
||||
first.save_failure_status(&sig, TransactionError::DuplicateSignature);
|
||||
assert_eq!(first.has_signature(&sig), true);
|
||||
assert_eq!(
|
||||
first.get_signature_status(&sig),
|
||||
Some(Err(TransactionError::DuplicateSignature)),
|
||||
status_cache.get_signature_status_slow(&sig, &HashMap::new()),
|
||||
None
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_clear_signatures() {
|
||||
fn test_find_sig_with_ancestor_fork() {
|
||||
let sig = Signature::default();
|
||||
let mut status_cache = BankStatusCache::default();
|
||||
let blockhash = hash(Hash::default().as_ref());
|
||||
let mut first = StatusCache::new(&blockhash);
|
||||
first.add(&sig);
|
||||
assert_eq!(first.has_signature(&sig), true);
|
||||
first.save_failure_status(&sig, TransactionError::DuplicateSignature);
|
||||
let ancestors = vec![(0, 1)].into_iter().collect();
|
||||
status_cache.insert(&blockhash, &sig, 0, ());
|
||||
assert_eq!(
|
||||
first.get_signature_status(&sig),
|
||||
Some(Err(TransactionError::DuplicateSignature)),
|
||||
status_cache.get_signature_status(&sig, &blockhash, &ancestors),
|
||||
Some((0, ()))
|
||||
);
|
||||
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 blockhash = hash(Hash::default().as_ref());
|
||||
let mut first = StatusCache::new(&blockhash);
|
||||
first.add(&sig);
|
||||
assert_eq!(first.has_signature(&sig), true);
|
||||
let mut second = StatusCache::new(&blockhash);
|
||||
let mut checkpoints = [&mut second, &mut first];
|
||||
BankStatusCache::clear_all(&mut checkpoints);
|
||||
assert_eq!(
|
||||
BankStatusCache::has_signature_all(&checkpoints, &sig),
|
||||
false
|
||||
status_cache.get_signature_status_slow(&sig, &ancestors),
|
||||
Some((1, ()))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_status_cache_freeze() {
|
||||
fn test_find_sig_without_ancestor_fork() {
|
||||
let sig = Signature::default();
|
||||
let mut status_cache = BankStatusCache::default();
|
||||
let blockhash = hash(Hash::default().as_ref());
|
||||
let mut cache: StatusCache<()> = StatusCache::new(&blockhash);
|
||||
|
||||
cache.freeze();
|
||||
cache.freeze();
|
||||
|
||||
cache.add(&sig);
|
||||
assert_eq!(cache.has_signature(&sig), false);
|
||||
let ancestors = HashMap::new();
|
||||
status_cache.insert(&blockhash, &sig, 0, ());
|
||||
assert_eq!(
|
||||
status_cache.get_signature_status(&sig, &blockhash, &ancestors),
|
||||
None
|
||||
);
|
||||
assert_eq!(
|
||||
status_cache.get_signature_status_slow(&sig, &ancestors),
|
||||
None
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_find_sig_with_root_ancestor_fork() {
|
||||
let sig = Signature::default();
|
||||
let mut status_cache = BankStatusCache::default();
|
||||
let blockhash = hash(Hash::default().as_ref());
|
||||
let ancestors = HashMap::new();
|
||||
status_cache.insert(&blockhash, &sig, 0, ());
|
||||
status_cache.add_root(0);
|
||||
assert_eq!(
|
||||
status_cache.get_signature_status(&sig, &blockhash, &ancestors),
|
||||
Some((0, ()))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_find_sig_with_root_ancestor_fork_max_len() {
|
||||
let sig = Signature::default();
|
||||
let mut status_cache = BankStatusCache::default();
|
||||
let blockhash = hash(Hash::default().as_ref());
|
||||
let ancestors = vec![(2, 2)].into_iter().collect();
|
||||
status_cache.insert(&blockhash, &sig, 0, ());
|
||||
status_cache.add_root(0);
|
||||
assert_eq!(
|
||||
status_cache.get_signature_status_slow(&sig, &ancestors),
|
||||
Some((ancestors.len(), ()))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_insert_picks_latest_blockhash_fork() {
|
||||
let sig = Signature::default();
|
||||
let mut status_cache = BankStatusCache::default();
|
||||
let blockhash = hash(Hash::default().as_ref());
|
||||
let ancestors = vec![(0, 0)].into_iter().collect();
|
||||
status_cache.insert(&blockhash, &sig, 0, ());
|
||||
status_cache.insert(&blockhash, &sig, 1, ());
|
||||
for i in 0..(MAX_CACHE_ENTRIES + 1) {
|
||||
status_cache.add_root(i as u64);
|
||||
}
|
||||
assert!(status_cache
|
||||
.get_signature_status(&sig, &blockhash, &ancestors)
|
||||
.is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_root_expires() {
|
||||
let sig = Signature::default();
|
||||
let mut status_cache = BankStatusCache::default();
|
||||
let blockhash = hash(Hash::default().as_ref());
|
||||
let ancestors = HashMap::new();
|
||||
status_cache.insert(&blockhash, &sig, 0, ());
|
||||
for i in 0..(MAX_CACHE_ENTRIES + 1) {
|
||||
status_cache.add_root(i as u64);
|
||||
}
|
||||
assert_eq!(
|
||||
status_cache.get_signature_status(&sig, &blockhash, &ancestors),
|
||||
None
|
||||
);
|
||||
assert_eq!(
|
||||
status_cache.get_signature_status_slow(&sig, &ancestors),
|
||||
None
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_clear_signatures_sigs_are_gone() {
|
||||
let sig = Signature::default();
|
||||
let mut status_cache = BankStatusCache::default();
|
||||
let blockhash = hash(Hash::default().as_ref());
|
||||
let ancestors = HashMap::new();
|
||||
status_cache.insert(&blockhash, &sig, 0, ());
|
||||
status_cache.add_root(0);
|
||||
status_cache.clear_signatures();
|
||||
assert_eq!(
|
||||
status_cache.get_signature_status(&sig, &blockhash, &ancestors),
|
||||
None
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_clear_signatures_insert_works() {
|
||||
let sig = Signature::default();
|
||||
let mut status_cache = BankStatusCache::default();
|
||||
let blockhash = hash(Hash::default().as_ref());
|
||||
let ancestors = HashMap::new();
|
||||
status_cache.add_root(0);
|
||||
status_cache.clear_signatures();
|
||||
status_cache.insert(&blockhash, &sig, 0, ());
|
||||
assert!(status_cache
|
||||
.get_signature_status(&sig, &blockhash, &ancestors)
|
||||
.is_some());
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue