use std::{
collections::{HashMap, VecDeque},
time::{Duration, Instant},
};
use zebra_chain::transaction;
pub struct EvictionList {
unique_entries: HashMap<transaction::Hash, Instant>,
ordered_entries: VecDeque<transaction::Hash>,
max_size: usize,
eviction_memory_time: Duration,
}
impl EvictionList {
pub fn new(max_size: usize, eviction_memory_time: Duration) -> Self {
Self {
unique_entries: Default::default(),
ordered_entries: Default::default(),
max_size,
eviction_memory_time,
}
}
pub fn insert(&mut self, key: transaction::Hash) {
self.prune_old();
if self.len() >= self.max_size {
self.pop_front();
}
let value = Instant::now();
let old_value = self.unique_entries.insert(key, value);
assert_eq!(
old_value, None,
"an already-evicted transaction should not be evicted again"
);
self.ordered_entries.push_back(key)
}
pub fn contains_key(&self, txid: &transaction::Hash) -> bool {
if let Some(evicted_at) = self.unique_entries.get(txid) {
if !self.has_expired(evicted_at) {
return true;
}
}
false
}
pub fn len(&mut self) -> usize {
self.prune_old();
self.unique_entries.len()
}
#[allow(dead_code)]
pub fn clear(&mut self) {
self.unique_entries.clear();
self.ordered_entries.clear();
}
pub fn prune_old(&mut self) {
while let Some(txid) = self.front() {
let evicted_at = self
.unique_entries
.get(txid)
.unwrap_or_else(|| panic!("all entries should exist in both ordered_entries and unique_entries, missing {txid:?} in unique_entries"));
if self.has_expired(evicted_at) {
self.pop_front();
} else {
break;
}
}
}
fn front(&self) -> Option<&transaction::Hash> {
self.ordered_entries.front()
}
fn pop_front(&mut self) -> Option<transaction::Hash> {
if let Some(key) = self.ordered_entries.pop_front() {
let removed = self.unique_entries.remove(&key);
assert!(
removed.is_some(),
"all entries should exist in both ordered_entries and unique_entries, missing {key:?} in unique_entries"
);
Some(key)
} else {
None
}
}
fn has_expired(&self, evicted_at: &Instant) -> bool {
let now = Instant::now();
(now - *evicted_at) > self.eviction_memory_time
}
}