pub(crate) mod in_mem_accounts_index;
use {
crate::{
accounts_index_storage::{AccountsIndexStorage, Startup},
accounts_partition::RentPayingAccountsByPartition,
ancestors::Ancestors,
bucket_map_holder::{Age, AtomicAge, BucketMapHolder},
contains::Contains,
pubkey_bins::PubkeyBinCalculator24,
rolling_bit_field::RollingBitField,
secondary_index::*,
},
in_mem_accounts_index::{InMemAccountsIndex, InsertNewEntryResults, StartupStats},
log::*,
rand::{thread_rng, Rng},
rayon::{
iter::{IntoParallelIterator, ParallelIterator},
ThreadPool,
},
solana_measure::measure::Measure,
solana_nohash_hasher::IntSet,
solana_sdk::{
account::ReadableAccount,
clock::{BankId, Slot},
pubkey::Pubkey,
},
std::{
collections::{btree_map::BTreeMap, HashSet},
fmt::Debug,
num::NonZeroUsize,
ops::{
Bound,
Bound::{Excluded, Included, Unbounded},
Range, RangeBounds,
},
path::PathBuf,
sync::{
atomic::{AtomicBool, AtomicU64, AtomicUsize, Ordering},
Arc, Mutex, OnceLock, RwLock,
},
},
thiserror::Error,
};
pub const ITER_BATCH_SIZE: usize = 1000;
pub const BINS_DEFAULT: usize = 8192;
pub const BINS_FOR_TESTING: usize = 2; pub const BINS_FOR_BENCHMARKS: usize = 8192;
pub const FLUSH_THREADS_TESTING: NonZeroUsize = unsafe { NonZeroUsize::new_unchecked(1) };
pub const ACCOUNTS_INDEX_CONFIG_FOR_TESTING: AccountsIndexConfig = AccountsIndexConfig {
bins: Some(BINS_FOR_TESTING),
num_flush_threads: Some(FLUSH_THREADS_TESTING),
drives: None,
index_limit_mb: IndexLimitMb::Unlimited,
ages_to_stay_in_cache: None,
scan_results_limit_bytes: None,
started_from_validator: false,
};
pub const ACCOUNTS_INDEX_CONFIG_FOR_BENCHMARKS: AccountsIndexConfig = AccountsIndexConfig {
bins: Some(BINS_FOR_BENCHMARKS),
num_flush_threads: Some(FLUSH_THREADS_TESTING),
drives: None,
index_limit_mb: IndexLimitMb::Unlimited,
ages_to_stay_in_cache: None,
scan_results_limit_bytes: None,
started_from_validator: false,
};
pub type ScanResult<T> = Result<T, ScanError>;
pub type SlotList<T> = Vec<(Slot, T)>;
pub type SlotSlice<'s, T> = &'s [(Slot, T)];
pub type RefCount = u64;
pub type AccountMap<T, U> = Arc<InMemAccountsIndex<T, U>>;
#[derive(Default, Debug, PartialEq, Eq)]
pub(crate) struct GenerateIndexResult<T: IndexValue> {
pub count: usize,
pub duplicates: Option<Vec<(Pubkey, (Slot, T))>>,
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub enum ScanFilter {
#[default]
All,
OnlyAbnormal,
OnlyAbnormalWithVerify,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum UpsertReclaim {
PreviousSlotEntryWasCached,
PopulateReclaims,
IgnoreReclaims,
}
#[derive(Debug)]
pub struct ScanConfig {
pub abort: Option<Arc<AtomicBool>>,
pub collect_all_unsorted: bool,
}
impl Default for ScanConfig {
fn default() -> Self {
Self {
abort: None,
collect_all_unsorted: true,
}
}
}
impl ScanConfig {
pub fn new(collect_all_unsorted: bool) -> Self {
Self {
collect_all_unsorted,
..Default::default()
}
}
pub fn abort(&self) {
if let Some(abort) = self.abort.as_ref() {
abort.store(true, Ordering::Relaxed)
}
}
pub fn recreate_with_abort(&self) -> Self {
ScanConfig {
abort: Some(self.abort.clone().unwrap_or_default()),
collect_all_unsorted: self.collect_all_unsorted,
}
}
pub fn is_aborted(&self) -> bool {
if let Some(abort) = self.abort.as_ref() {
abort.load(Ordering::Relaxed)
} else {
false
}
}
}
pub(crate) type AccountMapEntry<T> = Arc<AccountMapEntryInner<T>>;
pub trait IsCached {
fn is_cached(&self) -> bool;
}
pub trait IndexValue: 'static + IsCached + ZeroLamport + DiskIndexValue {}
pub trait DiskIndexValue:
'static + Clone + Debug + PartialEq + Copy + Default + Sync + Send
{
}
#[derive(Error, Debug, PartialEq, Eq)]
pub enum ScanError {
#[error("Node detected it replayed bad version of slot {slot:?} with id {bank_id:?}, thus the scan on said slot was aborted")]
SlotRemoved { slot: Slot, bank_id: BankId },
#[error("scan aborted: {0}")]
Aborted(String),
}
enum ScanTypes<R: RangeBounds<Pubkey>> {
Unindexed(Option<R>),
Indexed(IndexKey),
}
#[derive(Debug, Clone, Copy)]
pub enum IndexKey {
ProgramId(Pubkey),
SplTokenMint(Pubkey),
SplTokenOwner(Pubkey),
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum AccountIndex {
ProgramId,
SplTokenMint,
SplTokenOwner,
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct AccountSecondaryIndexesIncludeExclude {
pub exclude: bool,
pub keys: HashSet<Pubkey>,
}
#[derive(Debug, Copy, Clone, Default)]
pub enum IndexLimitMb {
#[default]
Unlimited,
InMemOnly,
}
#[derive(Debug, Default, Clone)]
pub struct AccountsIndexConfig {
pub bins: Option<usize>,
pub num_flush_threads: Option<NonZeroUsize>,
pub drives: Option<Vec<PathBuf>>,
pub index_limit_mb: IndexLimitMb,
pub ages_to_stay_in_cache: Option<Age>,
pub scan_results_limit_bytes: Option<usize>,
pub started_from_validator: bool,
}
pub fn default_num_flush_threads() -> NonZeroUsize {
NonZeroUsize::new(std::cmp::max(2, num_cpus::get() / 4)).expect("non-zero system threads")
}
#[derive(Debug, Default, Clone)]
pub struct AccountSecondaryIndexes {
pub keys: Option<AccountSecondaryIndexesIncludeExclude>,
pub indexes: HashSet<AccountIndex>,
}
impl AccountSecondaryIndexes {
pub fn is_empty(&self) -> bool {
self.indexes.is_empty()
}
pub fn contains(&self, index: &AccountIndex) -> bool {
self.indexes.contains(index)
}
pub fn include_key(&self, key: &Pubkey) -> bool {
match &self.keys {
Some(options) => options.exclude ^ options.keys.contains(key),
None => true, }
}
}
#[derive(Debug, Default)]
pub struct AccountMapEntryMeta {
pub dirty: AtomicBool,
pub age: AtomicAge,
}
impl AccountMapEntryMeta {
pub fn new_dirty<T: IndexValue, U: DiskIndexValue + From<T> + Into<T>>(
storage: &Arc<BucketMapHolder<T, U>>,
is_cached: bool,
) -> Self {
AccountMapEntryMeta {
dirty: AtomicBool::new(true),
age: AtomicAge::new(storage.future_age_to_flush(is_cached)),
}
}
pub fn new_clean<T: IndexValue, U: DiskIndexValue + From<T> + Into<T>>(
storage: &Arc<BucketMapHolder<T, U>>,
) -> Self {
AccountMapEntryMeta {
dirty: AtomicBool::new(false),
age: AtomicAge::new(storage.future_age_to_flush(false)),
}
}
}
#[derive(Debug, Default)]
pub struct AccountMapEntryInner<T> {
ref_count: AtomicU64,
pub slot_list: RwLock<SlotList<T>>,
pub meta: AccountMapEntryMeta,
}
impl<T: IndexValue> AccountMapEntryInner<T> {
pub fn new(slot_list: SlotList<T>, ref_count: RefCount, meta: AccountMapEntryMeta) -> Self {
Self {
slot_list: RwLock::new(slot_list),
ref_count: AtomicU64::new(ref_count),
meta,
}
}
pub fn ref_count(&self) -> RefCount {
self.ref_count.load(Ordering::Acquire)
}
pub fn addref(&self) {
self.ref_count.fetch_add(1, Ordering::Release);
self.set_dirty(true);
}
pub fn unref(&self) -> RefCount {
let previous = self.ref_count.fetch_sub(1, Ordering::Release);
self.set_dirty(true);
if previous == 0 {
inc_new_counter_info!("accounts_index-deref_from_0", 1);
}
previous
}
pub fn dirty(&self) -> bool {
self.meta.dirty.load(Ordering::Acquire)
}
pub fn set_dirty(&self, value: bool) {
self.meta.dirty.store(value, Ordering::Release)
}
pub fn clear_dirty(&self) -> bool {
self.meta
.dirty
.compare_exchange(true, false, Ordering::AcqRel, Ordering::Relaxed)
.is_ok()
}
pub fn age(&self) -> Age {
self.meta.age.load(Ordering::Acquire)
}
pub fn set_age(&self, value: Age) {
self.meta.age.store(value, Ordering::Release)
}
pub fn try_exchange_age(&self, next_age: Age, expected_age: Age) {
let _ = self.meta.age.compare_exchange(
expected_age,
next_age,
Ordering::AcqRel,
Ordering::Relaxed,
);
}
}
pub enum PreAllocatedAccountMapEntry<T: IndexValue> {
Entry(AccountMapEntry<T>),
Raw((Slot, T)),
}
impl<T: IndexValue> ZeroLamport for PreAllocatedAccountMapEntry<T> {
fn is_zero_lamport(&self) -> bool {
match self {
PreAllocatedAccountMapEntry::Entry(entry) => {
entry.slot_list.read().unwrap()[0].1.is_zero_lamport()
}
PreAllocatedAccountMapEntry::Raw(raw) => raw.1.is_zero_lamport(),
}
}
}
impl<T: IndexValue> From<PreAllocatedAccountMapEntry<T>> for (Slot, T) {
fn from(source: PreAllocatedAccountMapEntry<T>) -> (Slot, T) {
match source {
PreAllocatedAccountMapEntry::Entry(entry) => entry.slot_list.read().unwrap()[0],
PreAllocatedAccountMapEntry::Raw(raw) => raw,
}
}
}
impl<T: IndexValue> PreAllocatedAccountMapEntry<T> {
pub fn new<U: DiskIndexValue + From<T> + Into<T>>(
slot: Slot,
account_info: T,
storage: &Arc<BucketMapHolder<T, U>>,
store_raw: bool,
) -> PreAllocatedAccountMapEntry<T> {
if store_raw {
Self::Raw((slot, account_info))
} else {
Self::Entry(Self::allocate(slot, account_info, storage))
}
}
fn allocate<U: DiskIndexValue + From<T> + Into<T>>(
slot: Slot,
account_info: T,
storage: &Arc<BucketMapHolder<T, U>>,
) -> AccountMapEntry<T> {
let is_cached = account_info.is_cached();
let ref_count = u64::from(!is_cached);
let meta = AccountMapEntryMeta::new_dirty(storage, is_cached);
Arc::new(AccountMapEntryInner::new(
vec![(slot, account_info)],
ref_count,
meta,
))
}
pub fn into_account_map_entry<U: DiskIndexValue + From<T> + Into<T>>(
self,
storage: &Arc<BucketMapHolder<T, U>>,
) -> AccountMapEntry<T> {
match self {
Self::Entry(entry) => entry,
Self::Raw((slot, account_info)) => Self::allocate(slot, account_info, storage),
}
}
}
#[derive(Debug)]
pub struct RootsTracker {
pub alive_roots: RollingBitField,
uncleaned_roots: IntSet<Slot>,
}
impl Default for RootsTracker {
fn default() -> Self {
RootsTracker::new(4194304)
}
}
impl RootsTracker {
pub fn new(max_width: u64) -> Self {
Self {
alive_roots: RollingBitField::new(max_width),
uncleaned_roots: IntSet::default(),
}
}
pub fn min_alive_root(&self) -> Option<Slot> {
self.alive_roots.min()
}
}
#[derive(Debug, Default)]
pub struct AccountsIndexRootsStats {
pub roots_len: Option<usize>,
pub uncleaned_roots_len: Option<usize>,
pub roots_range: Option<u64>,
pub rooted_cleaned_count: usize,
pub unrooted_cleaned_count: usize,
pub clean_unref_from_storage_us: u64,
pub clean_dead_slot_us: u64,
}
pub struct AccountsIndexIterator<'a, T: IndexValue, U: DiskIndexValue + From<T> + Into<T>> {
account_maps: &'a LockMapTypeSlice<T, U>,
bin_calculator: &'a PubkeyBinCalculator24,
start_bound: Bound<Pubkey>,
end_bound: Bound<Pubkey>,
is_finished: bool,
collect_all_unsorted: bool,
}
impl<'a, T: IndexValue, U: DiskIndexValue + From<T> + Into<T>> AccountsIndexIterator<'a, T, U> {
fn range<R>(
map: &AccountMaps<T, U>,
range: R,
collect_all_unsorted: bool,
) -> Vec<(Pubkey, AccountMapEntry<T>)>
where
R: RangeBounds<Pubkey> + std::fmt::Debug,
{
let mut result = map.items(&range);
if !collect_all_unsorted {
result.sort_unstable_by(|a, b| a.0.cmp(&b.0));
}
result
}
fn clone_bound(bound: Bound<&Pubkey>) -> Bound<Pubkey> {
match bound {
Unbounded => Unbounded,
Included(k) => Included(*k),
Excluded(k) => Excluded(*k),
}
}
fn bin_from_bound(&self, bound: &Bound<Pubkey>, unbounded_bin: usize) -> usize {
match bound {
Bound::Included(bound) | Bound::Excluded(bound) => {
self.bin_calculator.bin_from_pubkey(bound)
}
Bound::Unbounded => unbounded_bin,
}
}
fn start_bin(&self) -> usize {
self.bin_from_bound(&self.start_bound, 0)
}
fn end_bin_inclusive(&self) -> usize {
self.bin_from_bound(&self.end_bound, usize::MAX)
}
fn bin_start_and_range(&self) -> (usize, usize) {
let start_bin = self.start_bin();
let end_bin_inclusive = self.end_bin_inclusive();
let bin_range = if start_bin > end_bin_inclusive {
0 } else if end_bin_inclusive == usize::MAX {
usize::MAX
} else {
end_bin_inclusive.saturating_add(1) - start_bin
};
(start_bin, bin_range)
}
pub fn new<R>(
index: &'a AccountsIndex<T, U>,
range: Option<&R>,
collect_all_unsorted: bool,
) -> Self
where
R: RangeBounds<Pubkey>,
{
Self {
start_bound: range
.as_ref()
.map(|r| Self::clone_bound(r.start_bound()))
.unwrap_or(Unbounded),
end_bound: range
.as_ref()
.map(|r| Self::clone_bound(r.end_bound()))
.unwrap_or(Unbounded),
account_maps: &index.account_maps,
is_finished: false,
bin_calculator: &index.bin_calculator,
collect_all_unsorted,
}
}
pub fn hold_range_in_memory<R>(&self, range: &R, start_holding: bool, thread_pool: &ThreadPool)
where
R: RangeBounds<Pubkey> + Debug + Sync,
{
let (start_bin, bin_range) = self.bin_start_and_range();
thread_pool.install(|| {
(0..bin_range).into_par_iter().for_each(|idx| {
let map = &self.account_maps[idx + start_bin];
map.hold_range_in_memory(range, start_holding);
});
});
}
}
impl<'a, T: IndexValue, U: DiskIndexValue + From<T> + Into<T>> Iterator
for AccountsIndexIterator<'a, T, U>
{
type Item = Vec<(Pubkey, AccountMapEntry<T>)>;
fn next(&mut self) -> Option<Self::Item> {
if self.is_finished {
return None;
}
let (start_bin, bin_range) = self.bin_start_and_range();
let mut chunk = Vec::with_capacity(ITER_BATCH_SIZE);
'outer: for i in self.account_maps.iter().skip(start_bin).take(bin_range) {
for (pubkey, account_map_entry) in Self::range(
&i,
(self.start_bound, self.end_bound),
self.collect_all_unsorted,
) {
if chunk.len() >= ITER_BATCH_SIZE && !self.collect_all_unsorted {
break 'outer;
}
let item = (pubkey, account_map_entry);
chunk.push(item);
}
}
if chunk.is_empty() {
self.is_finished = true;
return None;
} else if self.collect_all_unsorted {
self.is_finished = true;
}
self.start_bound = Excluded(chunk.last().unwrap().0);
Some(chunk)
}
}
pub trait ZeroLamport {
fn is_zero_lamport(&self) -> bool;
}
type MapType<T, U> = AccountMap<T, U>;
type LockMapType<T, U> = Vec<MapType<T, U>>;
type LockMapTypeSlice<T, U> = [MapType<T, U>];
type AccountMaps<'a, T, U> = &'a MapType<T, U>;
#[derive(Debug, Default)]
pub struct ScanSlotTracker {
is_removed: bool,
}
impl ScanSlotTracker {
pub fn is_removed(&self) -> bool {
self.is_removed
}
pub fn mark_removed(&mut self) {
self.is_removed = true;
}
}
#[derive(Copy, Clone)]
pub enum AccountsIndexScanResult {
OnlyKeepInMemoryIfDirty,
KeepInMemory,
Unref,
UnrefAssert0,
UnrefLog0,
}
#[derive(Debug)]
pub struct AccountsIndex<T: IndexValue, U: DiskIndexValue + From<T> + Into<T>> {
pub account_maps: LockMapType<T, U>,
pub bin_calculator: PubkeyBinCalculator24,
program_id_index: SecondaryIndex<RwLockSecondaryIndexEntry>,
spl_token_mint_index: SecondaryIndex<RwLockSecondaryIndexEntry>,
spl_token_owner_index: SecondaryIndex<RwLockSecondaryIndexEntry>,
pub roots_tracker: RwLock<RootsTracker>,
ongoing_scan_roots: RwLock<BTreeMap<Slot, u64>>,
pub removed_bank_ids: Mutex<HashSet<BankId>>,
storage: AccountsIndexStorage<T, U>,
pub scan_results_limit_bytes: Option<usize>,
pub purge_older_root_entries_one_slot_list: AtomicUsize,
pub roots_added: AtomicUsize,
pub roots_removed: AtomicUsize,
pub active_scans: AtomicUsize,
pub max_distance_to_min_scan_slot: AtomicU64,
pub unref_zero_count: AtomicU64,
pub rent_paying_accounts_by_partition: OnceLock<RentPayingAccountsByPartition>,
}
impl<T: IndexValue, U: DiskIndexValue + From<T> + Into<T>> AccountsIndex<T, U> {
pub fn default_for_tests() -> Self {
Self::new(Some(ACCOUNTS_INDEX_CONFIG_FOR_TESTING), Arc::default())
}
pub fn new(config: Option<AccountsIndexConfig>, exit: Arc<AtomicBool>) -> Self {
let scan_results_limit_bytes = config
.as_ref()
.and_then(|config| config.scan_results_limit_bytes);
let (account_maps, bin_calculator, storage) = Self::allocate_accounts_index(config, exit);
Self {
purge_older_root_entries_one_slot_list: AtomicUsize::default(),
account_maps,
bin_calculator,
program_id_index: SecondaryIndex::<RwLockSecondaryIndexEntry>::new(
"program_id_index_stats",
),
spl_token_mint_index: SecondaryIndex::<RwLockSecondaryIndexEntry>::new(
"spl_token_mint_index_stats",
),
spl_token_owner_index: SecondaryIndex::<RwLockSecondaryIndexEntry>::new(
"spl_token_owner_index_stats",
),
roots_tracker: RwLock::<RootsTracker>::default(),
ongoing_scan_roots: RwLock::<BTreeMap<Slot, u64>>::default(),
removed_bank_ids: Mutex::<HashSet<BankId>>::default(),
storage,
scan_results_limit_bytes,
roots_added: AtomicUsize::default(),
roots_removed: AtomicUsize::default(),
active_scans: AtomicUsize::default(),
max_distance_to_min_scan_slot: AtomicU64::default(),
unref_zero_count: AtomicU64::default(),
rent_paying_accounts_by_partition: OnceLock::default(),
}
}
fn allocate_accounts_index(
config: Option<AccountsIndexConfig>,
exit: Arc<AtomicBool>,
) -> (
LockMapType<T, U>,
PubkeyBinCalculator24,
AccountsIndexStorage<T, U>,
) {
let bins = config
.as_ref()
.and_then(|config| config.bins)
.unwrap_or(BINS_DEFAULT);
let bin_calculator = PubkeyBinCalculator24::new(bins);
let storage = AccountsIndexStorage::new(bins, &config, exit);
let account_maps = (0..bins)
.map(|bin| Arc::clone(&storage.in_mem[bin]))
.collect::<Vec<_>>();
(account_maps, bin_calculator, storage)
}
fn iter<R>(&self, range: Option<&R>, collect_all_unsorted: bool) -> AccountsIndexIterator<T, U>
where
R: RangeBounds<Pubkey>,
{
AccountsIndexIterator::new(self, range, collect_all_unsorted)
}
pub fn is_disk_index_enabled(&self) -> bool {
self.storage.storage.is_disk_index_enabled()
}
fn min_ongoing_scan_root_from_btree(ongoing_scan_roots: &BTreeMap<Slot, u64>) -> Option<Slot> {
ongoing_scan_roots.keys().next().cloned()
}
fn do_checked_scan_accounts<F, R>(
&self,
metric_name: &'static str,
ancestors: &Ancestors,
scan_bank_id: BankId,
func: F,
scan_type: ScanTypes<R>,
config: &ScanConfig,
) -> Result<(), ScanError>
where
F: FnMut(&Pubkey, (&T, Slot)),
R: RangeBounds<Pubkey> + std::fmt::Debug,
{
{
let locked_removed_bank_ids = self.removed_bank_ids.lock().unwrap();
if locked_removed_bank_ids.contains(&scan_bank_id) {
return Err(ScanError::SlotRemoved {
slot: ancestors.max_slot(),
bank_id: scan_bank_id,
});
}
}
self.active_scans.fetch_add(1, Ordering::Relaxed);
let max_root = {
let mut w_ongoing_scan_roots = self
.ongoing_scan_roots
.write()
.unwrap();
let max_root_inclusive = self.max_root_inclusive();
if let Some(min_ongoing_scan_root) =
Self::min_ongoing_scan_root_from_btree(&w_ongoing_scan_roots)
{
if min_ongoing_scan_root < max_root_inclusive {
let current = max_root_inclusive - min_ongoing_scan_root;
self.max_distance_to_min_scan_slot
.fetch_max(current, Ordering::Relaxed);
}
}
*w_ongoing_scan_roots.entry(max_root_inclusive).or_default() += 1;
max_root_inclusive
};
let empty = Ancestors::default();
let ancestors = if ancestors.contains_key(&max_root) {
ancestors
} else {
&empty
};
match scan_type {
ScanTypes::Unindexed(range) => {
self.do_scan_accounts(metric_name, ancestors, func, range, Some(max_root), config);
}
ScanTypes::Indexed(IndexKey::ProgramId(program_id)) => {
self.do_scan_secondary_index(
ancestors,
func,
&self.program_id_index,
&program_id,
Some(max_root),
config,
);
}
ScanTypes::Indexed(IndexKey::SplTokenMint(mint_key)) => {
self.do_scan_secondary_index(
ancestors,
func,
&self.spl_token_mint_index,
&mint_key,
Some(max_root),
config,
);
}
ScanTypes::Indexed(IndexKey::SplTokenOwner(owner_key)) => {
self.do_scan_secondary_index(
ancestors,
func,
&self.spl_token_owner_index,
&owner_key,
Some(max_root),
config,
);
}
}
{
self.active_scans.fetch_sub(1, Ordering::Relaxed);
let mut ongoing_scan_roots = self.ongoing_scan_roots.write().unwrap();
let count = ongoing_scan_roots.get_mut(&max_root).unwrap();
*count -= 1;
if *count == 0 {
ongoing_scan_roots.remove(&max_root);
}
}
let was_scan_corrupted = self
.removed_bank_ids
.lock()
.unwrap()
.contains(&scan_bank_id);
if was_scan_corrupted {
Err(ScanError::SlotRemoved {
slot: ancestors.max_slot(),
bank_id: scan_bank_id,
})
} else {
Ok(())
}
}
fn do_unchecked_scan_accounts<F, R>(
&self,
metric_name: &'static str,
ancestors: &Ancestors,
func: F,
range: Option<R>,
config: &ScanConfig,
) where
F: FnMut(&Pubkey, (&T, Slot)),
R: RangeBounds<Pubkey> + std::fmt::Debug,
{
self.do_scan_accounts(metric_name, ancestors, func, range, None, config);
}
fn do_scan_accounts<F, R>(
&self,
metric_name: &'static str,
ancestors: &Ancestors,
mut func: F,
range: Option<R>,
max_root: Option<Slot>,
config: &ScanConfig,
) where
F: FnMut(&Pubkey, (&T, Slot)),
R: RangeBounds<Pubkey> + std::fmt::Debug,
{
let mut total_elapsed_timer = Measure::start("total");
let mut num_keys_iterated = 0;
let mut latest_slot_elapsed = 0;
let mut load_account_elapsed = 0;
let mut read_lock_elapsed = 0;
let mut iterator_elapsed = 0;
let mut iterator_timer = Measure::start("iterator_elapsed");
for pubkey_list in self.iter(range.as_ref(), config.collect_all_unsorted) {
iterator_timer.stop();
iterator_elapsed += iterator_timer.as_us();
for (pubkey, list) in pubkey_list {
num_keys_iterated += 1;
let mut read_lock_timer = Measure::start("read_lock");
let list_r = &list.slot_list.read().unwrap();
read_lock_timer.stop();
read_lock_elapsed += read_lock_timer.as_us();
let mut latest_slot_timer = Measure::start("latest_slot");
if let Some(index) = self.latest_slot(Some(ancestors), list_r, max_root) {
latest_slot_timer.stop();
latest_slot_elapsed += latest_slot_timer.as_us();
let mut load_account_timer = Measure::start("load_account");
func(&pubkey, (&list_r[index].1, list_r[index].0));
load_account_timer.stop();
load_account_elapsed += load_account_timer.as_us();
}
if config.is_aborted() {
return;
}
}
iterator_timer = Measure::start("iterator_elapsed");
}
total_elapsed_timer.stop();
if !metric_name.is_empty() {
datapoint_info!(
metric_name,
("total_elapsed", total_elapsed_timer.as_us(), i64),
("latest_slot_elapsed", latest_slot_elapsed, i64),
("read_lock_elapsed", read_lock_elapsed, i64),
("load_account_elapsed", load_account_elapsed, i64),
("iterator_elapsed", iterator_elapsed, i64),
("num_keys_iterated", num_keys_iterated, i64),
)
}
}
fn do_scan_secondary_index<
F,
SecondaryIndexEntryType: SecondaryIndexEntry + Default + Sync + Send,
>(
&self,
ancestors: &Ancestors,
mut func: F,
index: &SecondaryIndex<SecondaryIndexEntryType>,
index_key: &Pubkey,
max_root: Option<Slot>,
config: &ScanConfig,
) where
F: FnMut(&Pubkey, (&T, Slot)),
{
for pubkey in index.get(index_key) {
if config.is_aborted() {
break;
}
if let Some(entry) = self.get_cloned(&pubkey) {
self.get_account_info_with_and_then(
&entry,
Some(ancestors),
max_root,
|(slot, account_info)| func(&pubkey, (&account_info, slot)),
);
};
}
}
pub fn get_and_then<R>(
&self,
pubkey: &Pubkey,
callback: impl FnOnce(Option<&AccountMapEntryInner<T>>) -> (bool, R),
) -> R {
self.get_bin(pubkey).get_internal_inner(pubkey, callback)
}
pub(crate) fn get_with_and_then<R>(
&self,
pubkey: &Pubkey,
ancestors: Option<&Ancestors>,
max_root: Option<Slot>,
should_add_to_in_mem_cache: bool,
callback: impl FnOnce((Slot, T)) -> R,
) -> Option<R> {
self.get_and_then(pubkey, |entry| {
let callback_result = entry.and_then(|entry| {
self.get_account_info_with_and_then(entry, ancestors, max_root, callback)
});
(should_add_to_in_mem_cache, callback_result)
})
}
pub(crate) fn get_account_info_with_and_then<R>(
&self,
entry: &AccountMapEntryInner<T>,
ancestors: Option<&Ancestors>,
max_root: Option<Slot>,
callback: impl FnOnce((Slot, T)) -> R,
) -> Option<R> {
let slot_list = entry.slot_list.read().unwrap();
self.latest_slot(ancestors, &slot_list, max_root)
.map(|found_index| callback(slot_list[found_index]))
}
pub fn get_cloned(&self, pubkey: &Pubkey) -> Option<AccountMapEntry<T>> {
self.get_bin(pubkey)
.get_internal_cloned(pubkey, |entry| entry)
}
pub fn contains(&self, pubkey: &Pubkey) -> bool {
self.get_and_then(pubkey, |entry| (false, entry.is_some()))
}
#[cfg(test)]
pub(crate) fn contains_with(
&self,
pubkey: &Pubkey,
ancestors: Option<&Ancestors>,
max_root: Option<Slot>,
) -> bool {
self.get_with_and_then(pubkey, ancestors, max_root, false, |_| ())
.is_some()
}
fn slot_list_mut<RT>(
&self,
pubkey: &Pubkey,
user_fn: impl FnOnce(&mut SlotList<T>) -> RT,
) -> Option<RT> {
let read_lock = self.get_bin(pubkey);
read_lock.slot_list_mut(pubkey, user_fn)
}
#[must_use]
pub fn handle_dead_keys(
&self,
dead_keys: &[&Pubkey],
account_indexes: &AccountSecondaryIndexes,
) -> HashSet<Pubkey> {
let mut pubkeys_removed_from_accounts_index = HashSet::default();
if !dead_keys.is_empty() {
for key in dead_keys.iter() {
let w_index = self.get_bin(key);
if w_index.remove_if_slot_list_empty(**key) {
pubkeys_removed_from_accounts_index.insert(**key);
self.purge_secondary_indexes_by_inner_key(key, account_indexes);
}
}
}
pubkeys_removed_from_accounts_index
}
pub(crate) fn scan_accounts<F>(
&self,
ancestors: &Ancestors,
scan_bank_id: BankId,
func: F,
config: &ScanConfig,
) -> Result<(), ScanError>
where
F: FnMut(&Pubkey, (&T, Slot)),
{
self.do_checked_scan_accounts(
"",
ancestors,
scan_bank_id,
func,
ScanTypes::Unindexed(None::<Range<Pubkey>>),
config,
)
}
pub(crate) fn unchecked_scan_accounts<F>(
&self,
metric_name: &'static str,
ancestors: &Ancestors,
func: F,
config: &ScanConfig,
) where
F: FnMut(&Pubkey, (&T, Slot)),
{
self.do_unchecked_scan_accounts(
metric_name,
ancestors,
func,
None::<Range<Pubkey>>,
config,
);
}
pub(crate) fn range_scan_accounts<F, R>(
&self,
metric_name: &'static str,
ancestors: &Ancestors,
range: R,
config: &ScanConfig,
func: F,
) where
F: FnMut(&Pubkey, (&T, Slot)),
R: RangeBounds<Pubkey> + std::fmt::Debug,
{
self.do_unchecked_scan_accounts(metric_name, ancestors, func, Some(range), config);
}
pub(crate) fn index_scan_accounts<F>(
&self,
ancestors: &Ancestors,
scan_bank_id: BankId,
index_key: IndexKey,
func: F,
config: &ScanConfig,
) -> Result<(), ScanError>
where
F: FnMut(&Pubkey, (&T, Slot)),
{
self.do_checked_scan_accounts(
"",
ancestors,
scan_bank_id,
func,
ScanTypes::<Range<Pubkey>>::Indexed(index_key),
config,
)
}
pub fn get_rooted_entries(
&self,
slice: SlotSlice<T>,
max_inclusive: Option<Slot>,
) -> SlotList<T> {
let max_inclusive = max_inclusive.unwrap_or(Slot::MAX);
let lock = &self.roots_tracker.read().unwrap().alive_roots;
slice
.iter()
.filter(|(slot, _)| *slot <= max_inclusive && lock.contains(slot))
.cloned()
.collect()
}
pub(crate) fn purge_exact<'a, C>(
&'a self,
pubkey: &Pubkey,
slots_to_purge: &'a C,
reclaims: &mut SlotList<T>,
) -> bool
where
C: Contains<'a, Slot>,
{
self.slot_list_mut(pubkey, |slot_list| {
slot_list.retain(|(slot, item)| {
let should_purge = slots_to_purge.contains(slot);
if should_purge {
reclaims.push((*slot, *item));
false
} else {
true
}
});
slot_list.is_empty()
})
.unwrap_or(true)
}
pub fn min_ongoing_scan_root(&self) -> Option<Slot> {
Self::min_ongoing_scan_root_from_btree(&self.ongoing_scan_roots.read().unwrap())
}
pub(crate) fn latest_slot(
&self,
ancestors: Option<&Ancestors>,
slice: SlotSlice<T>,
max_root_inclusive: Option<Slot>,
) -> Option<usize> {
let mut current_max = 0;
let mut rv = None;
if let Some(ancestors) = ancestors {
if !ancestors.is_empty() {
for (i, (slot, _t)) in slice.iter().rev().enumerate() {
if (rv.is_none() || *slot > current_max) && ancestors.contains_key(slot) {
rv = Some(i);
current_max = *slot;
}
}
}
}
let max_root_inclusive = max_root_inclusive.unwrap_or(Slot::MAX);
let mut tracker = None;
for (i, (slot, _t)) in slice.iter().rev().enumerate() {
if (rv.is_none() || *slot > current_max) && *slot <= max_root_inclusive {
let lock = match tracker {
Some(inner) => inner,
None => self.roots_tracker.read().unwrap(),
};
if lock.alive_roots.contains(slot) {
rv = Some(i);
current_max = *slot;
}
tracker = Some(lock);
}
}
rv.map(|index| slice.len() - 1 - index)
}
pub fn hold_range_in_memory<R>(&self, range: &R, start_holding: bool, thread_pool: &ThreadPool)
where
R: RangeBounds<Pubkey> + Debug + Sync,
{
let iter = self.iter(Some(range), true);
iter.hold_range_in_memory(range, start_holding, thread_pool);
}
pub(crate) fn get_startup_stats(&self) -> &StartupStats {
&self.storage.storage.startup_stats
}
pub fn set_startup(&self, value: Startup) {
self.storage.set_startup(value);
}
pub fn get_startup_remaining_items_to_flush_estimate(&self) -> usize {
self.storage.get_startup_remaining_items_to_flush_estimate()
}
pub(crate) fn scan<'a, F, I>(
&self,
pubkeys: I,
mut callback: F,
avoid_callback_result: Option<AccountsIndexScanResult>,
provide_entry_in_callback: bool,
filter: ScanFilter,
) where
F: FnMut(
&'a Pubkey,
Option<(&SlotList<T>, RefCount)>,
Option<&AccountMapEntry<T>>,
) -> AccountsIndexScanResult,
I: Iterator<Item = &'a Pubkey>,
{
let mut lock = None;
let mut last_bin = self.bins(); pubkeys.into_iter().for_each(|pubkey| {
let bin = self.bin_calculator.bin_from_pubkey(pubkey);
if bin != last_bin {
lock = Some(&self.account_maps[bin]);
last_bin = bin;
}
let mut internal_callback = |entry: Option<&AccountMapEntry<T>>| {
let mut cache = false;
match entry {
Some(locked_entry) => {
let result = if let Some(result) = avoid_callback_result.as_ref() {
*result
} else {
let slot_list = &locked_entry.slot_list.read().unwrap();
callback(
pubkey,
Some((slot_list, locked_entry.ref_count())),
provide_entry_in_callback.then_some(locked_entry),
)
};
cache = match result {
AccountsIndexScanResult::Unref => {
if locked_entry.unref() == 0 {
info!("scan: refcount of item already at 0: {pubkey}");
self.unref_zero_count.fetch_add(1, Ordering::Relaxed);
}
true
}
AccountsIndexScanResult::UnrefAssert0 => {
assert_eq!(
locked_entry.unref(),
1,
"ref count expected to be zero, but is {}! {pubkey}, {:?}",
locked_entry.ref_count(),
locked_entry.slot_list.read().unwrap(),
);
true
}
AccountsIndexScanResult::UnrefLog0 => {
let old_ref = locked_entry.unref();
if old_ref != 1 {
info!("Unexpected unref {pubkey} with {old_ref} {:?}, expect old_ref to be 1", locked_entry.slot_list.read().unwrap());
datapoint_warn!(
"accounts_db-unexpected-unref-zero",
("old_ref", old_ref, i64),
("pubkey", pubkey.to_string(), String),
);
}
true
}
AccountsIndexScanResult::KeepInMemory => true,
AccountsIndexScanResult::OnlyKeepInMemoryIfDirty => false,
};
}
None => {
avoid_callback_result.unwrap_or_else(|| callback(pubkey, None, None));
}
}
(cache, ())
};
match filter {
ScanFilter::All => {
lock.as_ref()
.unwrap()
.get_internal(pubkey, internal_callback);
}
ScanFilter::OnlyAbnormal | ScanFilter::OnlyAbnormalWithVerify => {
let found = lock
.as_ref()
.unwrap()
.get_only_in_mem(pubkey, false, |entry| {
internal_callback(entry);
entry.is_some()
});
if !found && matches!(filter, ScanFilter::OnlyAbnormalWithVerify) {
lock.as_ref().unwrap().get_internal(pubkey, |entry| {
assert!(entry.is_some(), "{pubkey}, entry: {entry:?}");
let entry = entry.unwrap();
assert_eq!(entry.ref_count(), 1, "{pubkey}");
assert_eq!(entry.slot_list.read().unwrap().len(), 1, "{pubkey}");
(false, ())
});
}
}
}
});
}
fn get_newest_root_in_slot_list(
alive_roots: &RollingBitField,
slice: SlotSlice<T>,
max_allowed_root_inclusive: Option<Slot>,
) -> Slot {
let mut max_root = 0;
for (slot, _) in slice.iter() {
if let Some(max_allowed_root_inclusive) = max_allowed_root_inclusive {
if *slot > max_allowed_root_inclusive {
continue;
}
}
if *slot > max_root && alive_roots.contains(slot) {
max_root = *slot;
}
}
max_root
}
fn update_spl_token_secondary_indexes<G: solana_inline_spl::token::GenericTokenAccount>(
&self,
token_id: &Pubkey,
pubkey: &Pubkey,
account_owner: &Pubkey,
account_data: &[u8],
account_indexes: &AccountSecondaryIndexes,
) {
if *account_owner == *token_id {
if account_indexes.contains(&AccountIndex::SplTokenOwner) {
if let Some(owner_key) = G::unpack_account_owner(account_data) {
if account_indexes.include_key(owner_key) {
self.spl_token_owner_index.insert(owner_key, pubkey);
}
}
}
if account_indexes.contains(&AccountIndex::SplTokenMint) {
if let Some(mint_key) = G::unpack_account_mint(account_data) {
if account_indexes.include_key(mint_key) {
self.spl_token_mint_index.insert(mint_key, pubkey);
}
}
}
}
}
pub fn get_index_key_size(&self, index: &AccountIndex, index_key: &Pubkey) -> Option<usize> {
match index {
AccountIndex::ProgramId => self.program_id_index.index.get(index_key).map(|x| x.len()),
AccountIndex::SplTokenOwner => self
.spl_token_owner_index
.index
.get(index_key)
.map(|x| x.len()),
AccountIndex::SplTokenMint => self
.spl_token_mint_index
.index
.get(index_key)
.map(|x| x.len()),
}
}
pub(crate) fn log_secondary_indexes(&self) {
if !self.program_id_index.index.is_empty() {
info!("secondary index: {:?}", AccountIndex::ProgramId);
self.program_id_index.log_contents();
}
if !self.spl_token_mint_index.index.is_empty() {
info!("secondary index: {:?}", AccountIndex::SplTokenMint);
self.spl_token_mint_index.log_contents();
}
if !self.spl_token_owner_index.index.is_empty() {
info!("secondary index: {:?}", AccountIndex::SplTokenOwner);
self.spl_token_owner_index.log_contents();
}
}
pub(crate) fn update_secondary_indexes(
&self,
pubkey: &Pubkey,
account: &impl ReadableAccount,
account_indexes: &AccountSecondaryIndexes,
) {
if account_indexes.is_empty() {
return;
}
let account_owner = account.owner();
let account_data = account.data();
if account_indexes.contains(&AccountIndex::ProgramId)
&& account_indexes.include_key(account_owner)
{
self.program_id_index.insert(account_owner, pubkey);
}
self.update_spl_token_secondary_indexes::<solana_inline_spl::token::Account>(
&solana_inline_spl::token::id(),
pubkey,
account_owner,
account_data,
account_indexes,
);
self.update_spl_token_secondary_indexes::<solana_inline_spl::token_2022::Account>(
&solana_inline_spl::token_2022::id(),
pubkey,
account_owner,
account_data,
account_indexes,
);
}
pub(crate) fn get_bin(&self, pubkey: &Pubkey) -> AccountMaps<T, U> {
&self.account_maps[self.bin_calculator.bin_from_pubkey(pubkey)]
}
pub fn bins(&self) -> usize {
self.account_maps.len()
}
fn remove_older_duplicate_pubkeys(
items: &mut Vec<(Pubkey, (Slot, T))>,
) -> Option<Vec<(Pubkey, (Slot, T))>> {
if items.len() < 2 {
return None;
}
items.sort_by(|a, b| a.0.cmp(&b.0));
let mut duplicates = None::<Vec<(Pubkey, (Slot, T))>>;
let n = items.len();
let mut last_key = items[n - 1].0;
let mut write = n - 1;
let mut curr = write;
while curr > 0 {
let curr_item = items[curr - 1];
if curr_item.0 == last_key {
let mut duplicates_insert = duplicates.unwrap_or_default();
duplicates_insert.push(curr_item);
duplicates = Some(duplicates_insert);
curr -= 1;
} else {
if curr < write {
items[write - 1] = curr_item;
}
curr -= 1;
write -= 1;
last_key = curr_item.0;
}
}
items.drain(..(write - curr));
duplicates
}
pub(crate) fn insert_new_if_missing_into_primary_index(
&self,
slot: Slot,
approx_items_len: usize,
items: impl Iterator<Item = (Pubkey, T)>,
) -> (Vec<Pubkey>, u64, GenerateIndexResult<T>) {
let bins = self.bins();
let expected_items_per_bin = approx_items_len * 2 / bins;
let use_disk = self.storage.storage.disk.is_some();
let mut binned = (0..bins)
.map(|_| Vec::with_capacity(expected_items_per_bin))
.collect::<Vec<_>>();
let mut count = 0;
let mut dirty_pubkeys = items
.filter_map(|(pubkey, account_info)| {
let pubkey_bin = self.bin_calculator.bin_from_pubkey(&pubkey);
let is_zero_lamport = account_info.is_zero_lamport();
let result = if is_zero_lamport { Some(pubkey) } else { None };
binned[pubkey_bin].push((pubkey, (slot, account_info)));
result
})
.collect::<Vec<_>>();
let insertion_time = AtomicU64::new(0);
let random_offset = thread_rng().gen_range(0..bins);
let mut duplicates = Vec::default();
(0..bins).for_each(|pubkey_bin| {
let pubkey_bin = (pubkey_bin + random_offset) % bins;
let mut items = std::mem::take(&mut binned[pubkey_bin]);
if items.is_empty() {
return;
}
let these_duplicates = Self::remove_older_duplicate_pubkeys(&mut items);
if let Some(mut these_duplicates) = these_duplicates {
duplicates.append(&mut these_duplicates);
}
let r_account_maps = &self.account_maps[pubkey_bin];
let mut insert_time = Measure::start("insert_into_primary_index");
count += items.len();
if use_disk {
r_account_maps.startup_insert_only(items.into_iter());
} else {
items
.into_iter()
.for_each(|(pubkey, (slot, account_info))| {
let new_entry = PreAllocatedAccountMapEntry::new(
slot,
account_info,
&self.storage.storage,
use_disk,
);
match r_account_maps
.insert_new_entry_if_missing_with_lock(pubkey, new_entry)
{
InsertNewEntryResults::DidNotExist => {}
InsertNewEntryResults::ExistedNewEntryZeroLamports => {}
InsertNewEntryResults::ExistedNewEntryNonZeroLamports => {
dirty_pubkeys.push(pubkey);
}
}
});
}
insert_time.stop();
insertion_time.fetch_add(insert_time.as_us(), Ordering::Relaxed);
});
(
dirty_pubkeys,
insertion_time.load(Ordering::Relaxed),
GenerateIndexResult {
count,
duplicates: (!duplicates.is_empty()).then_some(duplicates),
},
)
}
pub(crate) fn populate_and_retrieve_duplicate_keys_from_startup(
&self,
f: impl Fn(Vec<(Slot, Pubkey)>) + Sync + Send,
) {
(0..self.bins())
.into_par_iter()
.map(|pubkey_bin| {
let r_account_maps = &self.account_maps[pubkey_bin];
r_account_maps.populate_and_retrieve_duplicate_keys_from_startup()
})
.for_each(f);
}
pub fn upsert(
&self,
new_slot: Slot,
old_slot: Slot,
pubkey: &Pubkey,
account: &impl ReadableAccount,
account_indexes: &AccountSecondaryIndexes,
account_info: T,
reclaims: &mut SlotList<T>,
reclaim: UpsertReclaim,
) {
let store_raw = true;
let new_item = PreAllocatedAccountMapEntry::new(
new_slot,
account_info,
&self.storage.storage,
store_raw,
);
let map = self.get_bin(pubkey);
map.upsert(pubkey, new_item, Some(old_slot), reclaims, reclaim);
self.update_secondary_indexes(pubkey, account, account_indexes);
}
pub fn ref_count_from_storage(&self, pubkey: &Pubkey) -> RefCount {
let map = self.get_bin(pubkey);
map.get_internal_inner(pubkey, |entry| {
(
false,
entry.map(|entry| entry.ref_count()).unwrap_or_default(),
)
})
}
fn purge_secondary_indexes_by_inner_key(
&self,
inner_key: &Pubkey,
account_indexes: &AccountSecondaryIndexes,
) {
if account_indexes.contains(&AccountIndex::ProgramId) {
self.program_id_index.remove_by_inner_key(inner_key);
}
if account_indexes.contains(&AccountIndex::SplTokenOwner) {
self.spl_token_owner_index.remove_by_inner_key(inner_key);
}
if account_indexes.contains(&AccountIndex::SplTokenMint) {
self.spl_token_mint_index.remove_by_inner_key(inner_key);
}
}
fn purge_older_root_entries(
&self,
slot_list: &mut SlotList<T>,
reclaims: &mut SlotList<T>,
max_clean_root_inclusive: Option<Slot>,
) {
if slot_list.len() <= 1 {
self.purge_older_root_entries_one_slot_list
.fetch_add(1, Ordering::Relaxed);
}
let newest_root_in_slot_list;
let max_clean_root_inclusive = {
let roots_tracker = &self.roots_tracker.read().unwrap();
newest_root_in_slot_list = Self::get_newest_root_in_slot_list(
&roots_tracker.alive_roots,
slot_list,
max_clean_root_inclusive,
);
max_clean_root_inclusive.unwrap_or_else(|| roots_tracker.alive_roots.max_inclusive())
};
slot_list.retain(|(slot, value)| {
let should_purge = Self::can_purge_older_entries(
max_clean_root_inclusive,
newest_root_in_slot_list,
*slot,
) && !value.is_cached();
if should_purge {
reclaims.push((*slot, *value));
}
!should_purge
});
}
#[must_use]
pub fn clean_rooted_entries(
&self,
pubkey: &Pubkey,
reclaims: &mut SlotList<T>,
max_clean_root_inclusive: Option<Slot>,
) -> bool {
let mut is_slot_list_empty = false;
let missing_in_accounts_index = self
.slot_list_mut(pubkey, |slot_list| {
self.purge_older_root_entries(slot_list, reclaims, max_clean_root_inclusive);
is_slot_list_empty = slot_list.is_empty();
})
.is_none();
let mut removed = false;
if is_slot_list_empty {
let w_maps = self.get_bin(pubkey);
removed = w_maps.remove_if_slot_list_empty(*pubkey);
}
removed || missing_in_accounts_index
}
fn can_purge_older_entries(
max_clean_root_exclusive: Slot,
newest_root_in_slot_list: Slot,
slot: Slot,
) -> bool {
slot < max_clean_root_exclusive && slot != newest_root_in_slot_list
}
pub fn get_rooted_from_list<'a>(&self, slots: impl Iterator<Item = &'a Slot>) -> Vec<Slot> {
let roots_tracker = self.roots_tracker.read().unwrap();
slots
.filter_map(|s| {
if roots_tracker.alive_roots.contains(s) {
Some(*s)
} else {
None
}
})
.collect()
}
pub fn is_alive_root(&self, slot: Slot) -> bool {
self.roots_tracker
.read()
.unwrap()
.alive_roots
.contains(&slot)
}
pub fn add_root(&self, slot: Slot) {
self.roots_added.fetch_add(1, Ordering::Relaxed);
let mut w_roots_tracker = self.roots_tracker.write().unwrap();
assert!(
slot >= w_roots_tracker.alive_roots.max_inclusive(),
"Roots must be added in order: {} < {}",
slot,
w_roots_tracker.alive_roots.max_inclusive()
);
w_roots_tracker.alive_roots.insert(slot);
}
pub fn add_uncleaned_roots<I>(&self, roots: I)
where
I: IntoIterator<Item = Slot>,
{
let mut w_roots_tracker = self.roots_tracker.write().unwrap();
w_roots_tracker.uncleaned_roots.extend(roots);
}
pub fn max_root_inclusive(&self) -> Slot {
self.roots_tracker
.read()
.unwrap()
.alive_roots
.max_inclusive()
}
pub fn clean_dead_slot(&self, slot: Slot) -> bool {
let mut w_roots_tracker = self.roots_tracker.write().unwrap();
let removed_from_unclean_roots = w_roots_tracker.uncleaned_roots.remove(&slot);
if !w_roots_tracker.alive_roots.remove(&slot) {
if removed_from_unclean_roots {
error!("clean_dead_slot-removed_from_unclean_roots: {}", slot);
inc_new_counter_error!("clean_dead_slot-removed_from_unclean_roots", 1, 1);
}
false
} else {
drop(w_roots_tracker);
self.roots_removed.fetch_add(1, Ordering::Relaxed);
true
}
}
pub(crate) fn update_roots_stats(&self, stats: &mut AccountsIndexRootsStats) {
let roots_tracker = self.roots_tracker.read().unwrap();
stats.roots_len = Some(roots_tracker.alive_roots.len());
stats.uncleaned_roots_len = Some(roots_tracker.uncleaned_roots.len());
stats.roots_range = Some(roots_tracker.alive_roots.range_width());
}
pub fn min_alive_root(&self) -> Option<Slot> {
self.roots_tracker.read().unwrap().min_alive_root()
}
pub(crate) fn reset_uncleaned_roots(&self, max_clean_root: Option<Slot>) {
let mut w_roots_tracker = self.roots_tracker.write().unwrap();
w_roots_tracker.uncleaned_roots.retain(|root| {
let is_cleaned = max_clean_root
.map(|max_clean_root| *root <= max_clean_root)
.unwrap_or(true);
!is_cleaned
});
}
pub fn num_alive_roots(&self) -> usize {
self.roots_tracker.read().unwrap().alive_roots.len()
}
pub fn all_alive_roots(&self) -> Vec<Slot> {
let tracker = self.roots_tracker.read().unwrap();
tracker.alive_roots.get_all()
}
pub fn clone_uncleaned_roots(&self) -> IntSet<Slot> {
self.roots_tracker.read().unwrap().uncleaned_roots.clone()
}
pub fn uncleaned_roots_len(&self) -> usize {
self.roots_tracker.read().unwrap().uncleaned_roots.len()
}
#[cfg(feature = "dev-context-only-utils")]
pub fn purge_roots(&self, pubkey: &Pubkey) -> (SlotList<T>, bool) {
self.slot_list_mut(pubkey, |slot_list| {
let reclaims = self.get_rooted_entries(slot_list, None);
slot_list.retain(|(slot, _)| !self.is_alive_root(*slot));
(reclaims, slot_list.is_empty())
})
.unwrap()
}
}
#[cfg(test)]
pub mod tests {
use {
super::*,
solana_inline_spl::token::SPL_TOKEN_ACCOUNT_OWNER_OFFSET,
solana_sdk::{
account::{AccountSharedData, WritableAccount},
pubkey::PUBKEY_BYTES,
},
std::ops::RangeInclusive,
};
const SPL_TOKENS: &[Pubkey] = &[
solana_inline_spl::token::id(),
solana_inline_spl::token_2022::id(),
];
pub enum SecondaryIndexTypes<'a> {
RwLock(&'a SecondaryIndex<RwLockSecondaryIndexEntry>),
DashMap(&'a SecondaryIndex<DashMapSecondaryIndexEntry>),
}
pub fn spl_token_mint_index_enabled() -> AccountSecondaryIndexes {
let mut account_indexes = HashSet::new();
account_indexes.insert(AccountIndex::SplTokenMint);
AccountSecondaryIndexes {
indexes: account_indexes,
keys: None,
}
}
pub fn spl_token_owner_index_enabled() -> AccountSecondaryIndexes {
let mut account_indexes = HashSet::new();
account_indexes.insert(AccountIndex::SplTokenOwner);
AccountSecondaryIndexes {
indexes: account_indexes,
keys: None,
}
}
fn create_spl_token_mint_secondary_index_state() -> (usize, usize, AccountSecondaryIndexes) {
{
let index = AccountsIndex::<bool, bool>::default_for_tests();
let _type_check = SecondaryIndexTypes::RwLock(&index.spl_token_mint_index);
}
(0, PUBKEY_BYTES, spl_token_mint_index_enabled())
}
fn create_spl_token_owner_secondary_index_state() -> (usize, usize, AccountSecondaryIndexes) {
{
let index = AccountsIndex::<bool, bool>::default_for_tests();
let _type_check = SecondaryIndexTypes::RwLock(&index.spl_token_owner_index);
}
(
SPL_TOKEN_ACCOUNT_OWNER_OFFSET,
SPL_TOKEN_ACCOUNT_OWNER_OFFSET + PUBKEY_BYTES,
spl_token_owner_index_enabled(),
)
}
impl<T: IndexValue> Clone for PreAllocatedAccountMapEntry<T> {
fn clone(&self) -> Self {
match self {
PreAllocatedAccountMapEntry::Entry(entry) => {
let (slot, account_info) = entry.slot_list.read().unwrap()[0];
let meta = AccountMapEntryMeta {
dirty: AtomicBool::new(entry.dirty()),
age: AtomicAge::new(entry.age()),
};
PreAllocatedAccountMapEntry::Entry(Arc::new(AccountMapEntryInner::new(
vec![(slot, account_info)],
entry.ref_count(),
meta,
)))
}
PreAllocatedAccountMapEntry::Raw(raw) => PreAllocatedAccountMapEntry::Raw(*raw),
}
}
}
const COLLECT_ALL_UNSORTED_FALSE: bool = false;
#[test]
fn test_get_empty() {
let key = solana_sdk::pubkey::new_rand();
let index = AccountsIndex::<bool, bool>::default_for_tests();
let ancestors = Ancestors::default();
let key = &key;
assert!(!index.contains_with(key, Some(&ancestors), None));
assert!(!index.contains_with(key, None, None));
let mut num = 0;
index.unchecked_scan_accounts(
"",
&ancestors,
|_pubkey, _index| num += 1,
&ScanConfig::default(),
);
assert_eq!(num, 0);
}
#[test]
fn test_remove_older_duplicate_pubkeys() {
let pk1 = Pubkey::new_from_array([0; 32]);
let pk2 = Pubkey::new_from_array([1; 32]);
let slot0 = 0;
let info2 = 55;
let mut items = vec![];
let removed = AccountsIndex::<u64, u64>::remove_older_duplicate_pubkeys(&mut items);
assert!(items.is_empty());
assert!(removed.is_none());
let mut items = vec![(pk1, (slot0, 1u64)), (pk2, (slot0, 2))];
let expected = items.clone();
let removed = AccountsIndex::<u64, u64>::remove_older_duplicate_pubkeys(&mut items);
assert_eq!(items, expected);
assert!(removed.is_none());
for dup in 0..3 {
for other in 0..dup + 2 {
let first_info = 10u64;
let mut items = vec![(pk1, (slot0, first_info))];
let mut expected_dups = items.clone();
for i in 0..dup {
let this_dup = (pk1, (slot0, i + 10u64 + 1));
if i < dup.saturating_sub(1) {
expected_dups.push(this_dup);
}
items.push(this_dup);
}
let mut expected = vec![*items.last().unwrap()];
let other_item = (pk2, (slot0, info2));
if other == dup + 1 {
} else if other == dup {
expected.push(other_item);
items.push(other_item);
} else {
expected.push(other_item);
items.insert(other as usize, other_item);
}
let result = AccountsIndex::<u64, u64>::remove_older_duplicate_pubkeys(&mut items);
assert_eq!(items, expected);
if dup != 0 {
expected_dups.reverse();
assert_eq!(result.unwrap(), expected_dups);
} else {
assert!(result.is_none());
}
}
}
}
#[test]
fn test_secondary_index_include_exclude() {
let pk1 = Pubkey::new_unique();
let pk2 = Pubkey::new_unique();
let mut index = AccountSecondaryIndexes::default();
assert!(!index.contains(&AccountIndex::ProgramId));
index.indexes.insert(AccountIndex::ProgramId);
assert!(index.contains(&AccountIndex::ProgramId));
assert!(index.include_key(&pk1));
assert!(index.include_key(&pk2));
let exclude = false;
index.keys = Some(AccountSecondaryIndexesIncludeExclude {
keys: [pk1].iter().cloned().collect::<HashSet<_>>(),
exclude,
});
assert!(index.include_key(&pk1));
assert!(!index.include_key(&pk2));
let exclude = true;
index.keys = Some(AccountSecondaryIndexesIncludeExclude {
keys: [pk1].iter().cloned().collect::<HashSet<_>>(),
exclude,
});
assert!(!index.include_key(&pk1));
assert!(index.include_key(&pk2));
let exclude = true;
index.keys = Some(AccountSecondaryIndexesIncludeExclude {
keys: [pk1, pk2].iter().cloned().collect::<HashSet<_>>(),
exclude,
});
assert!(!index.include_key(&pk1));
assert!(!index.include_key(&pk2));
let exclude = false;
index.keys = Some(AccountSecondaryIndexesIncludeExclude {
keys: [pk1, pk2].iter().cloned().collect::<HashSet<_>>(),
exclude,
});
assert!(index.include_key(&pk1));
assert!(index.include_key(&pk2));
}
const UPSERT_POPULATE_RECLAIMS: UpsertReclaim = UpsertReclaim::PopulateReclaims;
#[test]
fn test_insert_no_ancestors() {
let key = solana_sdk::pubkey::new_rand();
let index = AccountsIndex::<bool, bool>::default_for_tests();
let mut gc = Vec::new();
index.upsert(
0,
0,
&key,
&AccountSharedData::default(),
&AccountSecondaryIndexes::default(),
true,
&mut gc,
UPSERT_POPULATE_RECLAIMS,
);
assert!(gc.is_empty());
let ancestors = Ancestors::default();
assert!(!index.contains_with(&key, Some(&ancestors), None));
assert!(!index.contains_with(&key, None, None));
let mut num = 0;
index.unchecked_scan_accounts(
"",
&ancestors,
|_pubkey, _index| num += 1,
&ScanConfig::default(),
);
assert_eq!(num, 0);
}
type AccountInfoTest = f64;
impl IndexValue for AccountInfoTest {}
impl DiskIndexValue for AccountInfoTest {}
impl IsCached for AccountInfoTest {
fn is_cached(&self) -> bool {
true
}
}
impl ZeroLamport for AccountInfoTest {
fn is_zero_lamport(&self) -> bool {
true
}
}
#[test]
fn test_insert_duplicates() {
let key = solana_sdk::pubkey::new_rand();
let pubkey = &key;
let slot = 0;
let mut ancestors = Ancestors::default();
ancestors.insert(slot, 0);
let account_info = true;
let index = AccountsIndex::<bool, bool>::default_for_tests();
let account_info2: bool = !account_info;
let items = vec![(*pubkey, account_info), (*pubkey, account_info2)];
index.set_startup(Startup::Startup);
let (_, _, result) =
index.insert_new_if_missing_into_primary_index(slot, items.len(), items.into_iter());
assert_eq!(result.count, 1);
index.set_startup(Startup::Normal);
let index_entry = index.get_cloned(pubkey).unwrap();
let slot_list = index_entry.slot_list.read().unwrap();
assert_eq!(slot_list.len(), 1);
assert_eq!(slot_list[0], (slot, account_info2));
}
#[test]
fn test_insert_new_with_lock_no_ancestors() {
let key = solana_sdk::pubkey::new_rand();
let pubkey = &key;
let slot = 0;
let index = AccountsIndex::<bool, bool>::default_for_tests();
let account_info = true;
let items = vec![(*pubkey, account_info)];
index.set_startup(Startup::Startup);
let expected_len = items.len();
let (_, _, result) =
index.insert_new_if_missing_into_primary_index(slot, items.len(), items.into_iter());
assert_eq!(result.count, expected_len);
index.set_startup(Startup::Normal);
let mut ancestors = Ancestors::default();
assert!(!index.contains_with(pubkey, Some(&ancestors), None));
assert!(!index.contains_with(pubkey, None, None));
let mut num = 0;
index.unchecked_scan_accounts(
"",
&ancestors,
|_pubkey, _index| num += 1,
&ScanConfig::default(),
);
assert_eq!(num, 0);
ancestors.insert(slot, 0);
assert!(index.contains_with(pubkey, Some(&ancestors), None));
assert_eq!(index.ref_count_from_storage(pubkey), 1);
index.unchecked_scan_accounts(
"",
&ancestors,
|_pubkey, _index| num += 1,
&ScanConfig::default(),
);
assert_eq!(num, 1);
let index = AccountsIndex::<bool, bool>::default_for_tests();
let account_info = false;
let items = vec![(*pubkey, account_info)];
index.set_startup(Startup::Startup);
let expected_len = items.len();
let (_, _, result) =
index.insert_new_if_missing_into_primary_index(slot, items.len(), items.into_iter());
assert_eq!(result.count, expected_len);
index.set_startup(Startup::Normal);
let mut ancestors = Ancestors::default();
assert!(!index.contains_with(pubkey, Some(&ancestors), None));
assert!(!index.contains_with(pubkey, None, None));
let mut num = 0;
index.unchecked_scan_accounts(
"",
&ancestors,
|_pubkey, _index| num += 1,
&ScanConfig::default(),
);
assert_eq!(num, 0);
ancestors.insert(slot, 0);
assert!(index.contains_with(pubkey, Some(&ancestors), None));
assert_eq!(index.ref_count_from_storage(pubkey), 1);
index.unchecked_scan_accounts(
"",
&ancestors,
|_pubkey, _index| num += 1,
&ScanConfig::default(),
);
assert_eq!(num, 1);
}
fn get_pre_allocated<T: IndexValue>(
slot: Slot,
account_info: T,
storage: &Arc<BucketMapHolder<T, T>>,
store_raw: bool,
to_raw_first: bool,
) -> PreAllocatedAccountMapEntry<T> {
let entry = PreAllocatedAccountMapEntry::new(slot, account_info, storage, store_raw);
if to_raw_first {
let (slot2, account_info2) = entry.into();
PreAllocatedAccountMapEntry::new(slot2, account_info2, storage, store_raw)
} else {
entry
}
}
#[test]
fn test_new_entry() {
for store_raw in [false, true] {
for to_raw_first in [false, true] {
let slot = 0;
let account_info = AccountInfoTest::default();
let index = AccountsIndex::default_for_tests();
let new_entry = get_pre_allocated(
slot,
account_info,
&index.storage.storage,
store_raw,
to_raw_first,
)
.into_account_map_entry(&index.storage.storage);
assert_eq!(new_entry.ref_count(), 0);
assert_eq!(new_entry.slot_list.read().unwrap().capacity(), 1);
assert_eq!(
new_entry.slot_list.read().unwrap().to_vec(),
vec![(slot, account_info)]
);
let account_info = true;
let index = AccountsIndex::default_for_tests();
let new_entry = get_pre_allocated(
slot,
account_info,
&index.storage.storage,
store_raw,
to_raw_first,
)
.into_account_map_entry(&index.storage.storage);
assert_eq!(new_entry.ref_count(), 1);
assert_eq!(new_entry.slot_list.read().unwrap().capacity(), 1);
assert_eq!(
new_entry.slot_list.read().unwrap().to_vec(),
vec![(slot, account_info)]
);
}
}
}
#[test]
fn test_batch_insert() {
let slot0 = 0;
let key0 = solana_sdk::pubkey::new_rand();
let key1 = solana_sdk::pubkey::new_rand();
let index = AccountsIndex::<bool, bool>::default_for_tests();
let account_infos = [true, false];
index.set_startup(Startup::Startup);
let items = vec![(key0, account_infos[0]), (key1, account_infos[1])];
let expected_len = items.len();
let (_, _, result) =
index.insert_new_if_missing_into_primary_index(slot0, items.len(), items.into_iter());
assert_eq!(result.count, expected_len);
index.set_startup(Startup::Normal);
for (i, key) in [key0, key1].iter().enumerate() {
let entry = index.get_cloned(key).unwrap();
assert_eq!(entry.ref_count.load(Ordering::Relaxed), 1);
assert_eq!(
entry.slot_list.read().unwrap().as_slice(),
&[(slot0, account_infos[i])],
);
}
}
fn test_new_entry_code_paths_helper<T: IndexValue>(
account_infos: [T; 2],
is_cached: bool,
upsert: bool,
use_disk: bool,
) {
if is_cached && !upsert {
return;
}
let slot0 = 0;
let slot1 = 1;
let key = solana_sdk::pubkey::new_rand();
let mut config = ACCOUNTS_INDEX_CONFIG_FOR_TESTING;
config.index_limit_mb = if use_disk {
IndexLimitMb::Unlimited
} else {
IndexLimitMb::InMemOnly };
let index = AccountsIndex::<T, T>::new(Some(config), Arc::default());
let mut gc = Vec::new();
if upsert {
index.upsert(
slot0,
slot0,
&key,
&AccountSharedData::default(),
&AccountSecondaryIndexes::default(),
account_infos[0],
&mut gc,
UPSERT_POPULATE_RECLAIMS,
);
} else {
let items = vec![(key, account_infos[0])];
index.set_startup(Startup::Startup);
let expected_len = items.len();
let (_, _, result) = index.insert_new_if_missing_into_primary_index(
slot0,
items.len(),
items.into_iter(),
);
assert_eq!(result.count, expected_len);
index.set_startup(Startup::Normal);
}
assert!(gc.is_empty());
{
let entry = index.get_cloned(&key).unwrap();
let slot_list = entry.slot_list.read().unwrap();
assert_eq!(entry.ref_count(), u64::from(!is_cached));
assert_eq!(slot_list.as_slice(), &[(slot0, account_infos[0])]);
let new_entry: AccountMapEntry<_> = PreAllocatedAccountMapEntry::new(
slot0,
account_infos[0],
&index.storage.storage,
false,
)
.into_account_map_entry(&index.storage.storage);
assert_eq!(
slot_list.as_slice(),
new_entry.slot_list.read().unwrap().as_slice(),
);
}
if upsert {
index.upsert(
slot1,
slot1,
&key,
&AccountSharedData::default(),
&AccountSecondaryIndexes::default(),
account_infos[1],
&mut gc,
UPSERT_POPULATE_RECLAIMS,
);
} else {
for _ in 0..5 {
index.set_startup(Startup::Startup);
index.set_startup(Startup::Normal);
}
let items = vec![(key, account_infos[1])];
index.set_startup(Startup::Startup);
let expected_len = items.len();
let (_, _, result) = index.insert_new_if_missing_into_primary_index(
slot1,
items.len(),
items.into_iter(),
);
assert_eq!(result.count, expected_len);
index.set_startup(Startup::Normal);
}
assert!(gc.is_empty());
index.populate_and_retrieve_duplicate_keys_from_startup(|_slot_keys| {});
let entry = index.get_cloned(&key).unwrap();
let slot_list = entry.slot_list.read().unwrap();
assert_eq!(entry.ref_count(), if is_cached { 0 } else { 2 });
assert_eq!(
slot_list.as_slice(),
&[(slot0, account_infos[0]), (slot1, account_infos[1])],
);
let new_entry = PreAllocatedAccountMapEntry::new(
slot1,
account_infos[1],
&index.storage.storage,
false,
);
assert_eq!(slot_list[1], new_entry.into());
}
#[test]
fn test_new_entry_and_update_code_paths() {
for use_disk in [false, true] {
for is_upsert in &[false, true] {
test_new_entry_code_paths_helper([1.0, 2.0], true, *is_upsert, use_disk);
test_new_entry_code_paths_helper([true, false], false, *is_upsert, use_disk);
}
}
}
#[test]
fn test_insert_with_lock_no_ancestors() {
let key = solana_sdk::pubkey::new_rand();
let index = AccountsIndex::<bool, bool>::default_for_tests();
let slot = 0;
let account_info = true;
let new_entry =
PreAllocatedAccountMapEntry::new(slot, account_info, &index.storage.storage, false);
assert_eq!(0, account_maps_stats_len(&index));
assert_eq!((slot, account_info), new_entry.clone().into());
assert_eq!(0, account_maps_stats_len(&index));
let r_account_maps = index.get_bin(&key);
r_account_maps.upsert(
&key,
new_entry,
None,
&mut SlotList::default(),
UPSERT_POPULATE_RECLAIMS,
);
assert_eq!(1, account_maps_stats_len(&index));
let mut ancestors = Ancestors::default();
assert!(!index.contains_with(&key, Some(&ancestors), None));
assert!(!index.contains_with(&key, None, None));
let mut num = 0;
index.unchecked_scan_accounts(
"",
&ancestors,
|_pubkey, _index| num += 1,
&ScanConfig::default(),
);
assert_eq!(num, 0);
ancestors.insert(slot, 0);
assert!(index.contains_with(&key, Some(&ancestors), None));
index.unchecked_scan_accounts(
"",
&ancestors,
|_pubkey, _index| num += 1,
&ScanConfig::default(),
);
assert_eq!(num, 1);
}
#[test]
fn test_insert_wrong_ancestors() {
let key = solana_sdk::pubkey::new_rand();
let index = AccountsIndex::<bool, bool>::default_for_tests();
let mut gc = Vec::new();
index.upsert(
0,
0,
&key,
&AccountSharedData::default(),
&AccountSecondaryIndexes::default(),
true,
&mut gc,
UPSERT_POPULATE_RECLAIMS,
);
assert!(gc.is_empty());
let ancestors = vec![(1, 1)].into_iter().collect();
assert!(!index.contains_with(&key, Some(&ancestors), None));
let mut num = 0;
index.unchecked_scan_accounts(
"",
&ancestors,
|_pubkey, _index| num += 1,
&ScanConfig::default(),
);
assert_eq!(num, 0);
}
#[test]
fn test_insert_ignore_reclaims() {
{
let key = solana_sdk::pubkey::new_rand();
let index = AccountsIndex::<u64, u64>::default_for_tests();
let mut reclaims = Vec::new();
let slot = 0;
let value = 1;
assert!(!value.is_cached());
index.upsert(
slot,
slot,
&key,
&AccountSharedData::default(),
&AccountSecondaryIndexes::default(),
value,
&mut reclaims,
UpsertReclaim::PopulateReclaims,
);
assert!(reclaims.is_empty());
index.upsert(
slot,
slot,
&key,
&AccountSharedData::default(),
&AccountSecondaryIndexes::default(),
value,
&mut reclaims,
UpsertReclaim::PopulateReclaims,
);
assert!(!reclaims.is_empty());
reclaims.clear();
index.upsert(
slot,
slot,
&key,
&AccountSharedData::default(),
&AccountSecondaryIndexes::default(),
value,
&mut reclaims,
UpsertReclaim::IgnoreReclaims,
);
assert!(reclaims.is_empty());
}
{
let key = solana_sdk::pubkey::new_rand();
let index = AccountsIndex::<AccountInfoTest, AccountInfoTest>::default_for_tests();
let mut reclaims = Vec::new();
let slot = 0;
let value = 1.0;
assert!(value.is_cached());
index.upsert(
slot,
slot,
&key,
&AccountSharedData::default(),
&AccountSecondaryIndexes::default(),
value,
&mut reclaims,
UpsertReclaim::PopulateReclaims,
);
assert!(reclaims.is_empty());
index.upsert(
slot,
slot,
&key,
&AccountSharedData::default(),
&AccountSecondaryIndexes::default(),
value,
&mut reclaims,
UpsertReclaim::PopulateReclaims,
);
assert!(!reclaims.is_empty());
reclaims.clear();
index.upsert(
slot,
slot,
&key,
&AccountSharedData::default(),
&AccountSecondaryIndexes::default(),
value,
&mut reclaims,
UpsertReclaim::IgnoreReclaims,
);
assert!(reclaims.is_empty());
}
}
#[test]
fn test_insert_with_ancestors() {
let key = solana_sdk::pubkey::new_rand();
let index = AccountsIndex::<bool, bool>::default_for_tests();
let mut gc = Vec::new();
index.upsert(
0,
0,
&key,
&AccountSharedData::default(),
&AccountSecondaryIndexes::default(),
true,
&mut gc,
UPSERT_POPULATE_RECLAIMS,
);
assert!(gc.is_empty());
let ancestors = vec![(0, 0)].into_iter().collect();
index
.get_with_and_then(
&key,
Some(&ancestors),
None,
false,
|(slot, account_info)| {
assert_eq!(slot, 0);
assert!(account_info);
},
)
.unwrap();
let mut num = 0;
let mut found_key = false;
index.unchecked_scan_accounts(
"",
&ancestors,
|pubkey, _index| {
if pubkey == &key {
found_key = true
};
num += 1
},
&ScanConfig::default(),
);
assert_eq!(num, 1);
assert!(found_key);
}
fn setup_accounts_index_keys(num_pubkeys: usize) -> (AccountsIndex<bool, bool>, Vec<Pubkey>) {
let index = AccountsIndex::<bool, bool>::default_for_tests();
let root_slot = 0;
let mut pubkeys: Vec<Pubkey> = std::iter::repeat_with(|| {
let new_pubkey = solana_sdk::pubkey::new_rand();
index.upsert(
root_slot,
root_slot,
&new_pubkey,
&AccountSharedData::default(),
&AccountSecondaryIndexes::default(),
true,
&mut vec![],
UPSERT_POPULATE_RECLAIMS,
);
new_pubkey
})
.take(num_pubkeys.saturating_sub(1))
.collect();
if num_pubkeys != 0 {
pubkeys.push(Pubkey::default());
index.upsert(
root_slot,
root_slot,
&Pubkey::default(),
&AccountSharedData::default(),
&AccountSecondaryIndexes::default(),
true,
&mut vec![],
UPSERT_POPULATE_RECLAIMS,
);
}
index.add_root(root_slot);
(index, pubkeys)
}
fn run_test_range(
index: &AccountsIndex<bool, bool>,
pubkeys: &[Pubkey],
start_bound: Bound<usize>,
end_bound: Bound<usize>,
) {
let (pubkey_start, index_start) = match start_bound {
Unbounded => (Unbounded, 0),
Included(i) => (Included(pubkeys[i]), i),
Excluded(i) => (Excluded(pubkeys[i]), i + 1),
};
let (pubkey_end, index_end) = match end_bound {
Unbounded => (Unbounded, pubkeys.len()),
Included(i) => (Included(pubkeys[i]), i + 1),
Excluded(i) => (Excluded(pubkeys[i]), i),
};
let pubkey_range = (pubkey_start, pubkey_end);
let ancestors = Ancestors::default();
let mut scanned_keys = HashSet::new();
index.range_scan_accounts(
"",
&ancestors,
pubkey_range,
&ScanConfig::default(),
|pubkey, _index| {
scanned_keys.insert(*pubkey);
},
);
let mut expected_len = 0;
for key in &pubkeys[index_start..index_end] {
expected_len += 1;
assert!(scanned_keys.contains(key));
}
assert_eq!(scanned_keys.len(), expected_len);
}
fn run_test_range_indexes(
index: &AccountsIndex<bool, bool>,
pubkeys: &[Pubkey],
start: Option<usize>,
end: Option<usize>,
) {
let start_options = start
.map(|i| vec![Included(i), Excluded(i)])
.unwrap_or_else(|| vec![Unbounded]);
let end_options = end
.map(|i| vec![Included(i), Excluded(i)])
.unwrap_or_else(|| vec![Unbounded]);
for start in &start_options {
for end in &end_options {
run_test_range(index, pubkeys, *start, *end);
}
}
}
#[test]
fn test_range_scan_accounts() {
let (index, mut pubkeys) = setup_accounts_index_keys(3 * ITER_BATCH_SIZE);
pubkeys.sort();
run_test_range_indexes(&index, &pubkeys, None, None);
run_test_range_indexes(&index, &pubkeys, Some(ITER_BATCH_SIZE), None);
run_test_range_indexes(&index, &pubkeys, None, Some(2 * ITER_BATCH_SIZE));
run_test_range_indexes(
&index,
&pubkeys,
Some(ITER_BATCH_SIZE),
Some(2 * ITER_BATCH_SIZE),
);
run_test_range_indexes(
&index,
&pubkeys,
Some(ITER_BATCH_SIZE),
Some(2 * ITER_BATCH_SIZE - 1),
);
run_test_range_indexes(
&index,
&pubkeys,
Some(ITER_BATCH_SIZE - 1_usize),
Some(2 * ITER_BATCH_SIZE + 1),
);
}
fn run_test_scan_accounts(num_pubkeys: usize) {
let (index, _) = setup_accounts_index_keys(num_pubkeys);
let ancestors = Ancestors::default();
let mut scanned_keys = HashSet::new();
index.unchecked_scan_accounts(
"",
&ancestors,
|pubkey, _index| {
scanned_keys.insert(*pubkey);
},
&ScanConfig::default(),
);
assert_eq!(scanned_keys.len(), num_pubkeys);
}
#[test]
fn test_scan_accounts() {
run_test_scan_accounts(0);
run_test_scan_accounts(1);
run_test_scan_accounts(ITER_BATCH_SIZE * 10);
run_test_scan_accounts(ITER_BATCH_SIZE * 10 - 1);
run_test_scan_accounts(ITER_BATCH_SIZE * 10 + 1);
}
#[test]
fn test_accounts_iter_finished() {
let (index, _) = setup_accounts_index_keys(0);
let mut iter = index.iter(None::<&Range<Pubkey>>, COLLECT_ALL_UNSORTED_FALSE);
assert!(iter.next().is_none());
let mut gc = vec![];
index.upsert(
0,
0,
&solana_sdk::pubkey::new_rand(),
&AccountSharedData::default(),
&AccountSecondaryIndexes::default(),
true,
&mut gc,
UPSERT_POPULATE_RECLAIMS,
);
assert!(iter.next().is_none());
}
#[test]
fn test_is_alive_root() {
let index = AccountsIndex::<bool, bool>::default_for_tests();
assert!(!index.is_alive_root(0));
index.add_root(0);
assert!(index.is_alive_root(0));
}
#[test]
fn test_insert_with_root() {
let key = solana_sdk::pubkey::new_rand();
let index = AccountsIndex::<bool, bool>::default_for_tests();
let mut gc = Vec::new();
index.upsert(
0,
0,
&key,
&AccountSharedData::default(),
&AccountSecondaryIndexes::default(),
true,
&mut gc,
UPSERT_POPULATE_RECLAIMS,
);
assert!(gc.is_empty());
index.add_root(0);
index
.get_with_and_then(&key, None, None, false, |(slot, account_info)| {
assert_eq!(slot, 0);
assert!(account_info);
})
.unwrap();
}
#[test]
fn test_clean_first() {
let index = AccountsIndex::<bool, bool>::default_for_tests();
index.add_root(0);
index.add_root(1);
index.clean_dead_slot(0);
assert!(index.is_alive_root(1));
assert!(!index.is_alive_root(0));
}
#[test]
fn test_clean_last() {
let index = AccountsIndex::<bool, bool>::default_for_tests();
index.add_root(0);
index.add_root(1);
index.clean_dead_slot(1);
assert!(!index.is_alive_root(1));
assert!(index.is_alive_root(0));
}
#[test]
fn test_clean_and_unclean_slot() {
let index = AccountsIndex::<bool, bool>::default_for_tests();
assert_eq!(0, index.roots_tracker.read().unwrap().uncleaned_roots.len());
index.add_root(0);
index.add_root(1);
index.add_uncleaned_roots([0, 1]);
assert_eq!(2, index.roots_tracker.read().unwrap().uncleaned_roots.len());
index.reset_uncleaned_roots(None);
assert_eq!(2, index.roots_tracker.read().unwrap().alive_roots.len());
assert_eq!(0, index.roots_tracker.read().unwrap().uncleaned_roots.len());
index.add_root(2);
index.add_root(3);
index.add_uncleaned_roots([2, 3]);
assert_eq!(4, index.roots_tracker.read().unwrap().alive_roots.len());
assert_eq!(2, index.roots_tracker.read().unwrap().uncleaned_roots.len());
index.clean_dead_slot(1);
assert_eq!(3, index.roots_tracker.read().unwrap().alive_roots.len());
assert_eq!(2, index.roots_tracker.read().unwrap().uncleaned_roots.len());
index.clean_dead_slot(2);
assert_eq!(2, index.roots_tracker.read().unwrap().alive_roots.len());
assert_eq!(1, index.roots_tracker.read().unwrap().uncleaned_roots.len());
}
#[test]
fn test_update_last_wins() {
let key = solana_sdk::pubkey::new_rand();
let index = AccountsIndex::<bool, bool>::default_for_tests();
let ancestors = vec![(0, 0)].into_iter().collect();
let mut gc = Vec::new();
index.upsert(
0,
0,
&key,
&AccountSharedData::default(),
&AccountSecondaryIndexes::default(),
true,
&mut gc,
UPSERT_POPULATE_RECLAIMS,
);
assert!(gc.is_empty());
index
.get_with_and_then(
&key,
Some(&ancestors),
None,
false,
|(slot, account_info)| {
assert_eq!(slot, 0);
assert!(account_info);
},
)
.unwrap();
let mut gc = Vec::new();
index.upsert(
0,
0,
&key,
&AccountSharedData::default(),
&AccountSecondaryIndexes::default(),
false,
&mut gc,
UPSERT_POPULATE_RECLAIMS,
);
assert_eq!(gc, vec![(0, true)]);
index
.get_with_and_then(
&key,
Some(&ancestors),
None,
false,
|(slot, account_info)| {
assert_eq!(slot, 0);
assert!(!account_info);
},
)
.unwrap();
}
#[test]
fn test_update_new_slot() {
solana_logger::setup();
let key = solana_sdk::pubkey::new_rand();
let index = AccountsIndex::<bool, bool>::default_for_tests();
let ancestors = vec![(0, 0)].into_iter().collect();
let mut gc = Vec::new();
index.upsert(
0,
0,
&key,
&AccountSharedData::default(),
&AccountSecondaryIndexes::default(),
true,
&mut gc,
UPSERT_POPULATE_RECLAIMS,
);
assert!(gc.is_empty());
index.upsert(
1,
1,
&key,
&AccountSharedData::default(),
&AccountSecondaryIndexes::default(),
false,
&mut gc,
UPSERT_POPULATE_RECLAIMS,
);
assert!(gc.is_empty());
index
.get_with_and_then(
&key,
Some(&ancestors),
None,
false,
|(slot, account_info)| {
assert_eq!(slot, 0);
assert!(account_info);
},
)
.unwrap();
let ancestors = vec![(1, 0)].into_iter().collect();
index
.get_with_and_then(
&key,
Some(&ancestors),
None,
false,
|(slot, account_info)| {
assert_eq!(slot, 1);
assert!(!account_info);
},
)
.unwrap();
}
#[test]
fn test_update_gc_purged_slot() {
let key = solana_sdk::pubkey::new_rand();
let index = AccountsIndex::<bool, bool>::default_for_tests();
let mut gc = Vec::new();
index.upsert(
0,
0,
&key,
&AccountSharedData::default(),
&AccountSecondaryIndexes::default(),
true,
&mut gc,
UPSERT_POPULATE_RECLAIMS,
);
assert!(gc.is_empty());
index.upsert(
1,
1,
&key,
&AccountSharedData::default(),
&AccountSecondaryIndexes::default(),
false,
&mut gc,
UPSERT_POPULATE_RECLAIMS,
);
index.upsert(
2,
2,
&key,
&AccountSharedData::default(),
&AccountSecondaryIndexes::default(),
true,
&mut gc,
UPSERT_POPULATE_RECLAIMS,
);
index.upsert(
3,
3,
&key,
&AccountSharedData::default(),
&AccountSecondaryIndexes::default(),
true,
&mut gc,
UPSERT_POPULATE_RECLAIMS,
);
index.add_root(0);
index.add_root(1);
index.add_root(3);
index.upsert(
4,
4,
&key,
&AccountSharedData::default(),
&AccountSecondaryIndexes::default(),
true,
&mut gc,
UPSERT_POPULATE_RECLAIMS,
);
assert_eq!(gc, vec![]);
index
.get_with_and_then(&key, None, None, false, |(slot, account_info)| {
assert_eq!(slot, 3);
assert!(account_info);
})
.unwrap();
let mut num = 0;
let mut found_key = false;
index.unchecked_scan_accounts(
"",
&Ancestors::default(),
|pubkey, index| {
if pubkey == &key {
found_key = true;
assert_eq!(index, (&true, 3));
};
num += 1
},
&ScanConfig::default(),
);
assert_eq!(num, 1);
assert!(found_key);
}
fn account_maps_stats_len<T: IndexValue>(index: &AccountsIndex<T, T>) -> usize {
index.storage.storage.stats.total_count()
}
#[test]
fn test_purge() {
let key = solana_sdk::pubkey::new_rand();
let index = AccountsIndex::<u64, u64>::default_for_tests();
let mut gc = Vec::new();
assert_eq!(0, account_maps_stats_len(&index));
index.upsert(
1,
1,
&key,
&AccountSharedData::default(),
&AccountSecondaryIndexes::default(),
12,
&mut gc,
UPSERT_POPULATE_RECLAIMS,
);
assert_eq!(1, account_maps_stats_len(&index));
index.upsert(
1,
1,
&key,
&AccountSharedData::default(),
&AccountSecondaryIndexes::default(),
10,
&mut gc,
UPSERT_POPULATE_RECLAIMS,
);
assert_eq!(1, account_maps_stats_len(&index));
let purges = index.purge_roots(&key);
assert_eq!(purges, (vec![], false));
index.add_root(1);
let purges = index.purge_roots(&key);
assert_eq!(purges, (vec![(1, 10)], true));
assert_eq!(1, account_maps_stats_len(&index));
index.upsert(
1,
1,
&key,
&AccountSharedData::default(),
&AccountSecondaryIndexes::default(),
9,
&mut gc,
UPSERT_POPULATE_RECLAIMS,
);
assert_eq!(1, account_maps_stats_len(&index));
}
#[test]
fn test_latest_slot() {
let slot_slice = vec![(0, true), (5, true), (3, true), (7, true)];
let index = AccountsIndex::<bool, bool>::default_for_tests();
assert!(index.latest_slot(None, &slot_slice, None).is_none());
index.add_root(5);
assert_eq!(index.latest_slot(None, &slot_slice, None).unwrap(), 1);
assert_eq!(index.latest_slot(None, &slot_slice, Some(5)).unwrap(), 1);
assert!(index.latest_slot(None, &slot_slice, Some(4)).is_none());
let ancestors = vec![(3, 1), (7, 1)].into_iter().collect();
assert_eq!(
index
.latest_slot(Some(&ancestors), &slot_slice, Some(4))
.unwrap(),
3
);
assert_eq!(
index
.latest_slot(Some(&ancestors), &slot_slice, Some(7))
.unwrap(),
3
);
assert_eq!(
index
.latest_slot(Some(&ancestors), &slot_slice, None)
.unwrap(),
3
);
}
fn make_empty_token_account_data() -> Vec<u8> {
vec![0; solana_inline_spl::token::Account::get_packed_len()]
}
fn run_test_purge_exact_secondary_index<
SecondaryIndexEntryType: SecondaryIndexEntry + Default + Sync + Send,
>(
index: &AccountsIndex<bool, bool>,
secondary_index: &SecondaryIndex<SecondaryIndexEntryType>,
key_start: usize,
key_end: usize,
secondary_indexes: &AccountSecondaryIndexes,
) {
let slots = vec![1, 2, 5, 9];
let index_key = Pubkey::new_unique();
let account_key = Pubkey::new_unique();
let mut account_data = make_empty_token_account_data();
account_data[key_start..key_end].clone_from_slice(&(index_key.to_bytes()));
for slot in &slots {
index.upsert(
*slot,
*slot,
&account_key,
&AccountSharedData::create(
0,
account_data.to_vec(),
solana_inline_spl::token::id(),
false,
0,
),
secondary_indexes,
true,
&mut vec![],
UPSERT_POPULATE_RECLAIMS,
);
}
assert_eq!(secondary_index.index.get(&index_key).unwrap().len(), 1);
assert_eq!(
secondary_index
.reverse_index
.get(&account_key)
.unwrap()
.value()
.read()
.unwrap()
.len(),
1
);
index.purge_exact(
&account_key,
&slots.into_iter().collect::<HashSet<Slot>>(),
&mut vec![],
);
let _ = index.handle_dead_keys(&[&account_key], secondary_indexes);
assert!(secondary_index.index.is_empty());
assert!(secondary_index.reverse_index.is_empty());
}
#[test]
fn test_purge_exact_spl_token_mint_secondary_index() {
let (key_start, key_end, secondary_indexes) = create_spl_token_mint_secondary_index_state();
let index = AccountsIndex::<bool, bool>::default_for_tests();
run_test_purge_exact_secondary_index(
&index,
&index.spl_token_mint_index,
key_start,
key_end,
&secondary_indexes,
);
}
#[test]
fn test_purge_exact_spl_token_owner_secondary_index() {
let (key_start, key_end, secondary_indexes) =
create_spl_token_owner_secondary_index_state();
let index = AccountsIndex::<bool, bool>::default_for_tests();
run_test_purge_exact_secondary_index(
&index,
&index.spl_token_owner_index,
key_start,
key_end,
&secondary_indexes,
);
}
#[test]
fn test_purge_older_root_entries() {
let index = AccountsIndex::<bool, bool>::default_for_tests();
let mut slot_list = vec![(1, true), (2, true), (5, true), (9, true)];
let mut reclaims = vec![];
index.purge_older_root_entries(&mut slot_list, &mut reclaims, None);
assert!(reclaims.is_empty());
assert_eq!(slot_list, vec![(1, true), (2, true), (5, true), (9, true)]);
slot_list = vec![(1, true), (2, true), (5, true), (9, true)];
index.add_root(1);
index.add_root(5);
reclaims = vec![];
index.purge_older_root_entries(&mut slot_list, &mut reclaims, None);
assert_eq!(reclaims, vec![(1, true), (2, true)]);
assert_eq!(slot_list, vec![(5, true), (9, true)]);
slot_list = vec![(1, true), (2, true), (5, true), (9, true)];
index.add_root(6);
reclaims = vec![];
index.purge_older_root_entries(&mut slot_list, &mut reclaims, None);
assert_eq!(reclaims, vec![(1, true), (2, true)]);
assert_eq!(slot_list, vec![(5, true), (9, true)]);
slot_list = vec![(1, true), (2, true), (5, true), (9, true)];
reclaims = vec![];
index.purge_older_root_entries(&mut slot_list, &mut reclaims, Some(6));
assert_eq!(reclaims, vec![(1, true), (2, true)]);
assert_eq!(slot_list, vec![(5, true), (9, true)]);
slot_list = vec![(1, true), (2, true), (5, true), (9, true)];
reclaims = vec![];
index.purge_older_root_entries(&mut slot_list, &mut reclaims, Some(5));
assert_eq!(reclaims, vec![(1, true), (2, true)]);
assert_eq!(slot_list, vec![(5, true), (9, true)]);
slot_list = vec![(1, true), (2, true), (5, true), (9, true)];
reclaims = vec![];
index.purge_older_root_entries(&mut slot_list, &mut reclaims, Some(2));
assert!(reclaims.is_empty());
assert_eq!(slot_list, vec![(1, true), (2, true), (5, true), (9, true)]);
slot_list = vec![(1, true), (2, true), (5, true), (9, true)];
reclaims = vec![];
index.purge_older_root_entries(&mut slot_list, &mut reclaims, Some(1));
assert!(reclaims.is_empty());
assert_eq!(slot_list, vec![(1, true), (2, true), (5, true), (9, true)]);
slot_list = vec![(1, true), (2, true), (5, true), (9, true)];
reclaims = vec![];
index.purge_older_root_entries(&mut slot_list, &mut reclaims, Some(7));
assert_eq!(reclaims, vec![(1, true), (2, true)]);
assert_eq!(slot_list, vec![(5, true), (9, true)]);
}
fn check_secondary_index_mapping_correct<SecondaryIndexEntryType>(
secondary_index: &SecondaryIndex<SecondaryIndexEntryType>,
secondary_index_keys: &[Pubkey],
account_key: &Pubkey,
) where
SecondaryIndexEntryType: SecondaryIndexEntry + Default + Sync + Send,
{
for secondary_index_key in secondary_index_keys {
assert_eq!(secondary_index.index.len(), secondary_index_keys.len());
let account_key_map = secondary_index.get(secondary_index_key);
assert_eq!(account_key_map.len(), 1);
assert_eq!(account_key_map, vec![*account_key]);
}
let secondary_index_key_map = secondary_index.reverse_index.get(account_key).unwrap();
assert_eq!(
&*secondary_index_key_map.value().read().unwrap(),
secondary_index_keys
);
}
fn run_test_spl_token_secondary_indexes<
SecondaryIndexEntryType: SecondaryIndexEntry + Default + Sync + Send,
>(
token_id: &Pubkey,
index: &AccountsIndex<bool, bool>,
secondary_index: &SecondaryIndex<SecondaryIndexEntryType>,
key_start: usize,
key_end: usize,
secondary_indexes: &AccountSecondaryIndexes,
) {
let mut secondary_indexes = secondary_indexes.clone();
let account_key = Pubkey::new_unique();
let index_key = Pubkey::new_unique();
let mut account_data = make_empty_token_account_data();
account_data[key_start..key_end].clone_from_slice(&(index_key.to_bytes()));
index.upsert(
0,
0,
&account_key,
&AccountSharedData::create(0, account_data.to_vec(), Pubkey::default(), false, 0),
&secondary_indexes,
true,
&mut vec![],
UPSERT_POPULATE_RECLAIMS,
);
assert!(secondary_index.index.is_empty());
assert!(secondary_index.reverse_index.is_empty());
index.upsert(
0,
0,
&account_key,
&AccountSharedData::create(0, account_data[1..].to_vec(), *token_id, false, 0),
&secondary_indexes,
true,
&mut vec![],
UPSERT_POPULATE_RECLAIMS,
);
assert!(secondary_index.index.is_empty());
assert!(secondary_index.reverse_index.is_empty());
secondary_indexes.keys = None;
for _ in 0..2 {
index.update_secondary_indexes(
&account_key,
&AccountSharedData::create(0, account_data.to_vec(), *token_id, false, 0),
&secondary_indexes,
);
check_secondary_index_mapping_correct(secondary_index, &[index_key], &account_key);
}
assert!(!secondary_index.index.is_empty());
assert!(!secondary_index.reverse_index.is_empty());
secondary_indexes.keys = Some(AccountSecondaryIndexesIncludeExclude {
keys: [index_key].iter().cloned().collect::<HashSet<_>>(),
exclude: false,
});
secondary_index.index.clear();
secondary_index.reverse_index.clear();
index.update_secondary_indexes(
&account_key,
&AccountSharedData::create(0, account_data.to_vec(), *token_id, false, 0),
&secondary_indexes,
);
assert!(!secondary_index.index.is_empty());
assert!(!secondary_index.reverse_index.is_empty());
check_secondary_index_mapping_correct(secondary_index, &[index_key], &account_key);
secondary_indexes.keys = Some(AccountSecondaryIndexesIncludeExclude {
keys: [].iter().cloned().collect::<HashSet<_>>(),
exclude: true,
});
secondary_index.index.clear();
secondary_index.reverse_index.clear();
index.update_secondary_indexes(
&account_key,
&AccountSharedData::create(0, account_data.to_vec(), *token_id, false, 0),
&secondary_indexes,
);
assert!(!secondary_index.index.is_empty());
assert!(!secondary_index.reverse_index.is_empty());
check_secondary_index_mapping_correct(secondary_index, &[index_key], &account_key);
secondary_indexes.keys = None;
index.slot_list_mut(&account_key, |slot_list| slot_list.clear());
let _ = index.handle_dead_keys(&[&account_key], &secondary_indexes);
assert!(secondary_index.index.is_empty());
assert!(secondary_index.reverse_index.is_empty());
}
#[test]
fn test_spl_token_mint_secondary_index() {
let (key_start, key_end, secondary_indexes) = create_spl_token_mint_secondary_index_state();
let index = AccountsIndex::<bool, bool>::default_for_tests();
for token_id in SPL_TOKENS {
run_test_spl_token_secondary_indexes(
token_id,
&index,
&index.spl_token_mint_index,
key_start,
key_end,
&secondary_indexes,
);
}
}
#[test]
fn test_spl_token_owner_secondary_index() {
let (key_start, key_end, secondary_indexes) =
create_spl_token_owner_secondary_index_state();
let index = AccountsIndex::<bool, bool>::default_for_tests();
for token_id in SPL_TOKENS {
run_test_spl_token_secondary_indexes(
token_id,
&index,
&index.spl_token_owner_index,
key_start,
key_end,
&secondary_indexes,
);
}
}
fn run_test_secondary_indexes_same_slot_and_forks<
SecondaryIndexEntryType: SecondaryIndexEntry + Default + Sync + Send,
>(
token_id: &Pubkey,
index: &AccountsIndex<bool, bool>,
secondary_index: &SecondaryIndex<SecondaryIndexEntryType>,
index_key_start: usize,
index_key_end: usize,
secondary_indexes: &AccountSecondaryIndexes,
) {
let account_key = Pubkey::new_unique();
let secondary_key1 = Pubkey::new_unique();
let secondary_key2 = Pubkey::new_unique();
let slot = 1;
let mut account_data1 = make_empty_token_account_data();
account_data1[index_key_start..index_key_end]
.clone_from_slice(&(secondary_key1.to_bytes()));
let mut account_data2 = make_empty_token_account_data();
account_data2[index_key_start..index_key_end]
.clone_from_slice(&(secondary_key2.to_bytes()));
index.upsert(
slot,
slot,
&account_key,
&AccountSharedData::create(0, account_data1.to_vec(), *token_id, false, 0),
secondary_indexes,
true,
&mut vec![],
UPSERT_POPULATE_RECLAIMS,
);
index.upsert(
slot,
slot,
&account_key,
&AccountSharedData::create(0, account_data2.to_vec(), *token_id, false, 0),
secondary_indexes,
true,
&mut vec![],
UPSERT_POPULATE_RECLAIMS,
);
check_secondary_index_mapping_correct(
secondary_index,
&[secondary_key1, secondary_key2],
&account_key,
);
let later_slot = slot + 1;
index.upsert(
later_slot,
later_slot,
&account_key,
&AccountSharedData::create(0, account_data1.to_vec(), *token_id, false, 0),
secondary_indexes,
true,
&mut vec![],
UPSERT_POPULATE_RECLAIMS,
);
assert_eq!(secondary_index.get(&secondary_key1), vec![account_key]);
index.add_root(later_slot);
index.slot_list_mut(&account_key, |slot_list| {
index.purge_older_root_entries(slot_list, &mut vec![], None)
});
check_secondary_index_mapping_correct(
secondary_index,
&[secondary_key1, secondary_key2],
&account_key,
);
let mut reclaims = vec![];
index.purge_exact(&account_key, &later_slot, &mut reclaims);
let _ = index.handle_dead_keys(&[&account_key], secondary_indexes);
assert!(secondary_index.index.is_empty());
assert!(secondary_index.reverse_index.is_empty());
}
#[test]
fn test_spl_token_mint_secondary_index_same_slot_and_forks() {
let (key_start, key_end, account_index) = create_spl_token_mint_secondary_index_state();
let index = AccountsIndex::<bool, bool>::default_for_tests();
for token_id in SPL_TOKENS {
run_test_secondary_indexes_same_slot_and_forks(
token_id,
&index,
&index.spl_token_mint_index,
key_start,
key_end,
&account_index,
);
}
}
#[test]
fn test_rwlock_secondary_index_same_slot_and_forks() {
let (key_start, key_end, account_index) = create_spl_token_owner_secondary_index_state();
let index = AccountsIndex::<bool, bool>::default_for_tests();
for token_id in SPL_TOKENS {
run_test_secondary_indexes_same_slot_and_forks(
token_id,
&index,
&index.spl_token_owner_index,
key_start,
key_end,
&account_index,
);
}
}
impl IndexValue for bool {}
impl IndexValue for u64 {}
impl DiskIndexValue for bool {}
impl DiskIndexValue for u64 {}
impl IsCached for bool {
fn is_cached(&self) -> bool {
false
}
}
impl IsCached for u64 {
fn is_cached(&self) -> bool {
false
}
}
impl ZeroLamport for bool {
fn is_zero_lamport(&self) -> bool {
false
}
}
impl ZeroLamport for u64 {
fn is_zero_lamport(&self) -> bool {
false
}
}
#[test]
fn test_bin_start_and_range() {
let index = AccountsIndex::<bool, bool>::default_for_tests();
let iter = AccountsIndexIterator::new(
&index,
None::<&RangeInclusive<Pubkey>>,
COLLECT_ALL_UNSORTED_FALSE,
);
assert_eq!((0, usize::MAX), iter.bin_start_and_range());
let key_0 = Pubkey::from([0; 32]);
let key_ff = Pubkey::from([0xff; 32]);
let iter = AccountsIndexIterator::new(
&index,
Some(&RangeInclusive::new(key_0, key_ff)),
COLLECT_ALL_UNSORTED_FALSE,
);
let bins = index.bins();
assert_eq!((0, bins), iter.bin_start_and_range());
let iter = AccountsIndexIterator::new(
&index,
Some(&RangeInclusive::new(key_ff, key_0)),
COLLECT_ALL_UNSORTED_FALSE,
);
assert_eq!((bins - 1, 0), iter.bin_start_and_range());
let iter = AccountsIndexIterator::new(
&index,
Some(&(Included(key_0), Unbounded)),
COLLECT_ALL_UNSORTED_FALSE,
);
assert_eq!((0, usize::MAX), iter.bin_start_and_range());
let iter = AccountsIndexIterator::new(
&index,
Some(&(Included(key_ff), Unbounded)),
COLLECT_ALL_UNSORTED_FALSE,
);
assert_eq!((bins - 1, usize::MAX), iter.bin_start_and_range());
assert_eq!((0..2).skip(1).take(usize::MAX).collect::<Vec<_>>(), vec![1]);
}
#[test]
fn test_get_newest_root_in_slot_list() {
let index = AccountsIndex::<bool, bool>::default_for_tests();
let return_0 = 0;
let slot1 = 1;
let slot2 = 2;
let slot99 = 99;
{
let roots_tracker = &index.roots_tracker.read().unwrap();
let slot_list = Vec::<(Slot, bool)>::default();
assert_eq!(
return_0,
AccountsIndex::<bool, bool>::get_newest_root_in_slot_list(
&roots_tracker.alive_roots,
&slot_list,
Some(slot1),
)
);
assert_eq!(
return_0,
AccountsIndex::<bool, bool>::get_newest_root_in_slot_list(
&roots_tracker.alive_roots,
&slot_list,
Some(slot2),
)
);
assert_eq!(
return_0,
AccountsIndex::<bool, bool>::get_newest_root_in_slot_list(
&roots_tracker.alive_roots,
&slot_list,
Some(slot99),
)
);
}
index.add_root(slot2);
{
let roots_tracker = &index.roots_tracker.read().unwrap();
let slot_list = vec![(slot2, true)];
assert_eq!(
slot2,
AccountsIndex::<bool, bool>::get_newest_root_in_slot_list(
&roots_tracker.alive_roots,
&slot_list,
Some(slot2),
)
);
assert_eq!(
return_0,
AccountsIndex::<bool, bool>::get_newest_root_in_slot_list(
&roots_tracker.alive_roots,
&slot_list,
Some(slot1),
)
);
assert_eq!(
slot2,
AccountsIndex::<bool, bool>::get_newest_root_in_slot_list(
&roots_tracker.alive_roots,
&slot_list,
Some(slot99),
)
);
}
}
impl<T: IndexValue> AccountsIndex<T, T> {
fn upsert_simple_test(&self, key: &Pubkey, slot: Slot, value: T) {
let mut gc = Vec::new();
self.upsert(
slot,
slot,
key,
&AccountSharedData::default(),
&AccountSecondaryIndexes::default(),
value,
&mut gc,
UPSERT_POPULATE_RECLAIMS,
);
assert!(gc.is_empty());
}
pub fn clear_uncleaned_roots(&self, max_clean_root: Option<Slot>) -> HashSet<Slot> {
let mut cleaned_roots = HashSet::new();
let mut w_roots_tracker = self.roots_tracker.write().unwrap();
w_roots_tracker.uncleaned_roots.retain(|root| {
let is_cleaned = max_clean_root
.map(|max_clean_root| *root <= max_clean_root)
.unwrap_or(true);
if is_cleaned {
cleaned_roots.insert(*root);
}
!is_cleaned
});
cleaned_roots
}
pub(crate) fn is_uncleaned_root(&self, slot: Slot) -> bool {
self.roots_tracker
.read()
.unwrap()
.uncleaned_roots
.contains(&slot)
}
pub fn clear_roots(&self) {
self.roots_tracker.write().unwrap().alive_roots.clear()
}
}
#[test]
fn test_unref() {
let value = true;
let key = solana_sdk::pubkey::new_rand();
let index = AccountsIndex::<bool, bool>::default_for_tests();
let slot1 = 1;
index.upsert_simple_test(&key, slot1, value);
let map = index.get_bin(&key);
for expected in [false, true] {
assert!(map.get_internal_inner(&key, |entry| {
assert_eq!(u64::from(!expected), entry.unwrap().ref_count());
assert_eq!(u64::from(!expected), entry.unwrap().unref());
assert_eq!(
if expected {
(0 as RefCount).wrapping_sub(1)
} else {
0
},
entry.unwrap().ref_count()
);
(false, true)
}));
}
}
#[test]
fn test_clean_rooted_entries_return() {
solana_logger::setup();
let value = true;
let key = solana_sdk::pubkey::new_rand();
let key_unknown = solana_sdk::pubkey::new_rand();
let index = AccountsIndex::<bool, bool>::default_for_tests();
let slot1 = 1;
let mut gc = Vec::new();
assert!(index.clean_rooted_entries(&key_unknown, &mut gc, None));
index.upsert_simple_test(&key, slot1, value);
let slot2 = 2;
assert!(!index.clean_rooted_entries(&key, &mut gc, None));
assert!(!index.clean_rooted_entries(&key, &mut gc, Some(slot1)));
{
let mut gc = Vec::new();
assert!(index.clean_rooted_entries(&key, &mut gc, Some(slot2)));
assert_eq!(gc, vec![(slot1, value)]);
}
index.upsert_simple_test(&key, slot1, value);
index.add_root(slot1);
assert!(!index.clean_rooted_entries(&key, &mut gc, Some(slot2)));
index.upsert_simple_test(&key, slot2, value);
{
let account_map_entry = index.get_cloned(&key).unwrap();
let slot_list = account_map_entry.slot_list.read().unwrap();
assert_eq!(2, slot_list.len());
assert_eq!(&[(slot1, value), (slot2, value)], slot_list.as_slice());
}
assert!(!index.clean_rooted_entries(&key, &mut gc, Some(slot2)));
assert_eq!(
2,
index
.get_cloned(&key)
.unwrap()
.slot_list
.read()
.unwrap()
.len()
);
assert!(gc.is_empty());
{
{
let roots_tracker = &index.roots_tracker.read().unwrap();
let slot_list = vec![(slot2, value)];
assert_eq!(
0,
AccountsIndex::<bool, bool>::get_newest_root_in_slot_list(
&roots_tracker.alive_roots,
&slot_list,
None,
)
);
}
index.add_root(slot2);
{
let roots_tracker = &index.roots_tracker.read().unwrap();
let slot_list = vec![(slot2, value)];
assert_eq!(
slot2,
AccountsIndex::<bool, bool>::get_newest_root_in_slot_list(
&roots_tracker.alive_roots,
&slot_list,
None,
)
);
assert_eq!(
0,
AccountsIndex::<bool, bool>::get_newest_root_in_slot_list(
&roots_tracker.alive_roots,
&slot_list,
Some(0),
)
);
}
}
assert!(gc.is_empty());
assert!(!index.clean_rooted_entries(&key, &mut gc, Some(slot2)));
assert_eq!(gc, vec![(slot1, value)]);
gc.clear();
index.clean_dead_slot(slot2);
let slot3 = 3;
assert!(index.clean_rooted_entries(&key, &mut gc, Some(slot3)));
assert_eq!(gc, vec![(slot2, value)]);
}
#[test]
fn test_handle_dead_keys_return() {
let key = solana_sdk::pubkey::new_rand();
let index = AccountsIndex::<bool, bool>::default_for_tests();
assert_eq!(
index.handle_dead_keys(&[&key], &AccountSecondaryIndexes::default()),
vec![key].into_iter().collect::<HashSet<_>>()
);
}
#[test]
fn test_start_end_bin() {
let index = AccountsIndex::<bool, bool>::default_for_tests();
assert_eq!(index.bins(), BINS_FOR_TESTING);
let iter = AccountsIndexIterator::new(
&index,
None::<&RangeInclusive<Pubkey>>,
COLLECT_ALL_UNSORTED_FALSE,
);
assert_eq!(iter.start_bin(), 0); assert_eq!(iter.end_bin_inclusive(), usize::MAX); let key = Pubkey::from([0; 32]);
let iter = AccountsIndexIterator::new(
&index,
Some(&RangeInclusive::new(key, key)),
COLLECT_ALL_UNSORTED_FALSE,
);
assert_eq!(iter.start_bin(), 0); assert_eq!(iter.end_bin_inclusive(), 0); let iter = AccountsIndexIterator::new(
&index,
Some(&(Included(key), Excluded(key))),
COLLECT_ALL_UNSORTED_FALSE,
);
assert_eq!(iter.start_bin(), 0); assert_eq!(iter.end_bin_inclusive(), 0); let iter = AccountsIndexIterator::new(
&index,
Some(&(Excluded(key), Excluded(key))),
COLLECT_ALL_UNSORTED_FALSE,
);
assert_eq!(iter.start_bin(), 0); assert_eq!(iter.end_bin_inclusive(), 0); let key = Pubkey::from([0xff; 32]);
let iter = AccountsIndexIterator::new(
&index,
Some(&RangeInclusive::new(key, key)),
COLLECT_ALL_UNSORTED_FALSE,
);
let bins = index.bins();
assert_eq!(iter.start_bin(), bins - 1); assert_eq!(iter.end_bin_inclusive(), bins - 1);
let iter = AccountsIndexIterator::new(
&index,
Some(&(Included(key), Excluded(key))),
COLLECT_ALL_UNSORTED_FALSE,
);
assert_eq!(iter.start_bin(), bins - 1); assert_eq!(iter.end_bin_inclusive(), bins - 1);
let iter = AccountsIndexIterator::new(
&index,
Some(&(Excluded(key), Excluded(key))),
COLLECT_ALL_UNSORTED_FALSE,
);
assert_eq!(iter.start_bin(), bins - 1); assert_eq!(iter.end_bin_inclusive(), bins - 1);
}
#[test]
#[should_panic(expected = "bins.is_power_of_two()")]
#[allow(clippy::field_reassign_with_default)]
fn test_illegal_bins() {
let mut config = AccountsIndexConfig::default();
config.bins = Some(3);
AccountsIndex::<bool, bool>::new(Some(config), Arc::default());
}
#[test]
fn test_scan_config() {
for collect_all_unsorted in [false, true] {
let config = ScanConfig::new(collect_all_unsorted);
assert_eq!(config.collect_all_unsorted, collect_all_unsorted);
assert!(config.abort.is_none()); assert!(!config.is_aborted());
config.abort(); assert!(!config.is_aborted());
}
let config = ScanConfig::new(false);
assert!(!config.collect_all_unsorted);
assert!(config.abort.is_none());
let config = ScanConfig::default();
assert!(config.collect_all_unsorted);
assert!(config.abort.is_none());
let config = config.recreate_with_abort();
assert!(config.abort.is_some());
assert!(!config.is_aborted());
config.abort();
assert!(config.is_aborted());
let config = config.recreate_with_abort();
assert!(config.is_aborted());
}
}