diff --git a/runtime/benches/accounts_index.rs b/runtime/benches/accounts_index.rs index 0a5215726cb8dc..9b13a8d70a1d6c 100644 --- a/runtime/benches/accounts_index.rs +++ b/runtime/benches/accounts_index.rs @@ -26,6 +26,7 @@ fn bench_accounts_index(bencher: &mut Bencher) { for f in 0..NUM_FORKS { for pubkey in pubkeys.iter().take(NUM_PUBKEYS) { index.upsert( + f, f, pubkey, &Pubkey::default(), @@ -44,6 +45,7 @@ fn bench_accounts_index(bencher: &mut Bencher) { for _p in 0..NUM_PUBKEYS { let pubkey = thread_rng().gen_range(0, NUM_PUBKEYS); index.upsert( + fork, fork, &pubkeys[pubkey], &Pubkey::default(), diff --git a/runtime/src/accounts.rs b/runtime/src/accounts.rs index 6dc827d688a2d9..ae63a2aa1fca9b 100644 --- a/runtime/src/accounts.rs +++ b/runtime/src/accounts.rs @@ -953,11 +953,13 @@ impl Accounts { /// WARNING: This noncached version is only to be used for tests/benchmarking /// as bypassing the cache in general is not supported pub fn store_slow_uncached(&self, slot: Slot, pubkey: &Pubkey, account: &AccountSharedData) { - self.accounts_db.store_uncached(slot, &[(pubkey, account)]); + self.accounts_db + .store_uncached(slot, &[(pubkey, account, slot)]); } pub fn store_slow_cached(&self, slot: Slot, pubkey: &Pubkey, account: &AccountSharedData) { - self.accounts_db.store_cached(slot, &[(pubkey, account)]); + self.accounts_db + .store_cached(slot, &[(pubkey, account, slot)]); } fn lock_account( @@ -1122,6 +1124,7 @@ impl Accounts { blockhash, lamports_per_signature, leave_nonce_on_success, + slot, ); self.accounts_db.store_cached(slot, &accounts_to_store); } @@ -1148,7 +1151,8 @@ impl Accounts { blockhash: &Hash, lamports_per_signature: u64, leave_nonce_on_success: bool, - ) -> Vec<(&'a Pubkey, &'a AccountSharedData)> { + slot: Slot, + ) -> Vec<(&'a Pubkey, &'a AccountSharedData, Slot)> { let mut accounts = Vec::with_capacity(load_results.len()); for (i, ((tx_load_result, nonce), tx)) in load_results.iter_mut().zip(txs).enumerate() { if tx_load_result.is_err() { @@ -1217,7 +1221,7 @@ impl Accounts { } // Add to the accounts to store - accounts.push((&*address, &*account)); + accounts.push((&*address, &*account, slot)); } } } @@ -2937,14 +2941,15 @@ mod tests { &Hash::default(), 0, true, // leave_nonce_on_success + 0, ); assert_eq!(collected_accounts.len(), 2); assert!(collected_accounts .iter() - .any(|(pubkey, _account)| *pubkey == &keypair0.pubkey())); + .any(|(pubkey, _account, _slot)| *pubkey == &keypair0.pubkey())); assert!(collected_accounts .iter() - .any(|(pubkey, _account)| *pubkey == &keypair1.pubkey())); + .any(|(pubkey, _account, _slot)| *pubkey == &keypair1.pubkey())); // Ensure readonly_lock reflects lock assert_eq!( @@ -3366,21 +3371,22 @@ mod tests { &next_blockhash, 0, true, // leave_nonce_on_success + 0, ); assert_eq!(collected_accounts.len(), 2); assert_eq!( collected_accounts .iter() - .find(|(pubkey, _account)| *pubkey == &from_address) - .map(|(_pubkey, account)| *account) + .find(|(pubkey, _account, _slot)| *pubkey == &from_address) + .map(|(_pubkey, account, _slot)| *account) .cloned() .unwrap(), from_account_pre, ); let collected_nonce_account = collected_accounts .iter() - .find(|(pubkey, _account)| *pubkey == &nonce_address) - .map(|(_pubkey, account)| *account) + .find(|(pubkey, _account, _slot)| *pubkey == &nonce_address) + .map(|(_pubkey, account, _slot)| *account) .cloned() .unwrap(); assert_eq!( @@ -3475,12 +3481,13 @@ mod tests { &next_blockhash, 0, true, // leave_nonce_on_success + 0, ); assert_eq!(collected_accounts.len(), 1); let collected_nonce_account = collected_accounts .iter() - .find(|(pubkey, _account)| *pubkey == &nonce_address) - .map(|(_pubkey, account)| *account) + .find(|(pubkey, _account, _slot)| *pubkey == &nonce_address) + .map(|(_pubkey, account, _slot)| *account) .cloned() .unwrap(); assert_eq!( diff --git a/runtime/src/accounts_db.rs b/runtime/src/accounts_db.rs index f361d9a8cc107f..287a5909615a59 100644 --- a/runtime/src/accounts_db.rs +++ b/runtime/src/accounts_db.rs @@ -1487,7 +1487,7 @@ impl solana_frozen_abi::abi_example::AbiExample for AccountsDb { let some_data_len = 5; let some_slot: Slot = 0; let account = AccountSharedData::new(1, some_data_len, &key); - accounts_db.store_uncached(some_slot, &[(&key, &account)]); + accounts_db.store_uncached(some_slot, &[(&key, &account, some_slot)]); accounts_db.add_root(0); accounts_db @@ -2767,7 +2767,7 @@ impl AccountsDb { let mut write_versions = Vec::with_capacity(alive_accounts.len()); for (pubkey, alive_account) in alive_accounts { - accounts.push((pubkey, &alive_account.account)); + accounts.push((pubkey, &alive_account.account, slot)); hashes.push(alive_account.account.hash); write_versions.push(alive_account.account.meta.write_version); } @@ -4550,7 +4550,7 @@ impl AccountsDb { } pub fn flush_accounts_cache_slot(&self, slot: Slot) { - self.flush_slot_cache(slot, None::<&mut fn(&_, &_) -> bool>); + self.flush_slot_cache(slot, None::<&mut fn(&_, &_, _) -> bool>); } /// true if write cache is too big @@ -4618,7 +4618,7 @@ impl AccountsDb { if old_slot > max_flushed_root { if self.should_aggressively_flush_cache() { if let Some(stats) = - self.flush_slot_cache(old_slot, None::<&mut fn(&_, &_) -> bool>) + self.flush_slot_cache(old_slot, None::<&mut fn(&_, &_, _) -> bool>) { flush_stats.num_flushed += stats.num_flushed; flush_stats.num_purged += stats.num_purged; @@ -4673,7 +4673,7 @@ impl AccountsDb { let rand_slot = frozen_slots.choose(&mut thread_rng()); if let Some(rand_slot) = rand_slot { let random_flush_stats = - self.flush_slot_cache(*rand_slot, None::<&mut fn(&_, &_) -> bool>); + self.flush_slot_cache(*rand_slot, None::<&mut fn(&_, &_, _) -> bool>); info!( "Flushed random slot: num_remaining: {} {:?}", num_slots_remaining, random_flush_stats, @@ -4699,7 +4699,7 @@ impl AccountsDb { // If `should_clean` is None, then`should_flush_f` is also None, which will cause // `flush_slot_cache` to flush all accounts to storage without cleaning any accounts. let mut should_flush_f = should_clean.map(|(account_bytes_saved, num_accounts_saved)| { - move |&pubkey: &Pubkey, account: &AccountSharedData| { + move |&pubkey: &Pubkey, account: &AccountSharedData, _slot: Slot| { use std::collections::hash_map::Entry::{Occupied, Vacant}; let should_flush = match written_accounts.entry(pubkey) { Vacant(vacant_entry) => { @@ -4764,7 +4764,7 @@ impl AccountsDb { &self, slot: Slot, slot_cache: &SlotCache, - mut should_flush_f: Option<&mut impl FnMut(&Pubkey, &AccountSharedData) -> bool>, + mut should_flush_f: Option<&mut impl FnMut(&Pubkey, &AccountSharedData, Slot) -> bool>, ) -> FlushStats { let mut num_purged = 0; let mut total_size = 0; @@ -4772,20 +4772,20 @@ impl AccountsDb { let iter_items: Vec<_> = slot_cache.iter().collect(); let mut purged_slot_pubkeys: HashSet<(Slot, Pubkey)> = HashSet::new(); let mut pubkey_to_slot_set: Vec<(Pubkey, Slot)> = vec![]; - let (accounts, hashes): (Vec<(&Pubkey, &AccountSharedData)>, Vec) = iter_items + let (accounts, hashes): (Vec<(&Pubkey, &AccountSharedData, Slot)>, Vec) = iter_items .iter() .filter_map(|iter_item| { let key = iter_item.key(); let account = &iter_item.value().account; let should_flush = should_flush_f .as_mut() - .map(|should_flush_f| should_flush_f(key, account)) + .map(|should_flush_f| should_flush_f(key, account, slot)) .unwrap_or(true); if should_flush { let hash = iter_item.value().hash(); total_size += (account.data().len() + STORE_META_OVERHEAD) as u64; num_flushed += 1; - Some(((key, account), hash)) + Some(((key, account, slot), hash)) } else { // If we don't flush, we have to remove the entry from the // index, since it's equivalent to purging @@ -4848,7 +4848,7 @@ impl AccountsDb { fn flush_slot_cache( &self, slot: Slot, - should_flush_f: Option<&mut impl FnMut(&Pubkey, &AccountSharedData) -> bool>, + should_flush_f: Option<&mut impl FnMut(&Pubkey, &AccountSharedData, Slot) -> bool>, ) -> Option { let is_being_purged = { let mut slots_under_contention = self @@ -4947,7 +4947,7 @@ impl AccountsDb { >( &self, slot: Slot, - accounts: &[(&Pubkey, &(impl ReadableAccount + ZeroLamport))], + accounts: &[(&Pubkey, &(impl ReadableAccount + ZeroLamport), Slot)], hashes: Option<&[impl Borrow]>, storage_finder: F, mut write_version_producer: P, @@ -4956,8 +4956,9 @@ impl AccountsDb { let mut calc_stored_meta_time = Measure::start("calc_stored_meta"); let accounts_and_meta_to_store: Vec<_> = accounts .iter() - .map(|(pubkey, account)| { - self.read_only_accounts_cache.remove(**pubkey, slot); + .map(|(pubkey, account, original_slot)| { + self.read_only_accounts_cache + .remove(**pubkey, *original_slot); // this is the source of Some(Account) or None. // Some(Account) = store 'Account' // None = store a default/empty account with 0 lamports @@ -5211,7 +5212,6 @@ impl AccountsDb { scan.stop(); let total_lamports = *total_lamports.lock().unwrap(); - let mut hash_time = Measure::start("hash"); let (accumulated_hash, hash_total) = AccountsHash::calculate_hash(hashes); hash_time.stop(); @@ -5944,9 +5944,9 @@ impl AccountsDb { // that there are no items we would have put in reclaims that are not cached fn update_index( &self, - slot: Slot, + new_slot: Slot, infos: Vec, - accounts: &[(&Pubkey, &T)], + accounts: &[(&Pubkey, &T, Slot)], previous_slot_entry_was_cached: bool, ) -> SlotList { // using a thread pool here results in deadlock panics from bank_hashes.write() @@ -5959,8 +5959,10 @@ impl AccountsDb { let mut reclaims = Vec::with_capacity(infos_chunk.len() / 2); for (info, pubkey_account) in infos_chunk.iter().zip(accounts_chunk.iter()) { let pubkey = pubkey_account.0; + let old_slot = pubkey_account.2; self.accounts_index.upsert( - slot, + new_slot, + old_slot, pubkey, pubkey_account.1.owner(), pubkey_account.1.data(), @@ -6061,8 +6063,8 @@ impl AccountsDb { .insert((*slot, store.append_vec_id()), store.clone()); dead_slots.insert(*slot); } else if self.caching_enabled - && Self::is_shrinking_productive(*slot, &[store.clone()]) && self.is_candidate_for_shrink(&store) + && Self::is_shrinking_productive(*slot, &[store.clone()]) { // Checking that this single storage entry is ready for shrinking, // should be a sufficient indication that the slot is ready to be shrunk @@ -6254,17 +6256,22 @@ impl AccountsDb { .fetch_add(measure.as_us(), Ordering::Relaxed); } - pub fn store_cached(&self, slot: Slot, accounts: &[(&Pubkey, &AccountSharedData)]) { + pub fn store_cached(&self, slot: Slot, accounts: &[(&Pubkey, &AccountSharedData, Slot)]) { self.store(slot, accounts, self.caching_enabled); } /// Store the account update. /// only called by tests - pub fn store_uncached(&self, slot: Slot, accounts: &[(&Pubkey, &AccountSharedData)]) { + pub fn store_uncached(&self, slot: Slot, accounts: &[(&Pubkey, &AccountSharedData, Slot)]) { self.store(slot, accounts, false); } - fn store(&self, slot: Slot, accounts: &[(&Pubkey, &AccountSharedData)], is_cached_store: bool) { + fn store( + &self, + slot: Slot, + accounts: &[(&Pubkey, &AccountSharedData, Slot)], + is_cached_store: bool, + ) { // If all transactions in a batch are errored, // it's possible to get a store with no accounts. if accounts.is_empty() { @@ -6273,7 +6280,7 @@ impl AccountsDb { let mut stats = BankHashStats::default(); let mut total_data = 0; - accounts.iter().for_each(|(_pubkey, account)| { + accounts.iter().for_each(|(_pubkey, account, _slot)| { total_data += account.data().len(); stats.update(*account); }); @@ -6410,7 +6417,7 @@ impl AccountsDb { fn store_accounts_unfrozen( &self, slot: Slot, - accounts: &[(&Pubkey, &AccountSharedData)], + accounts: &[(&Pubkey, &AccountSharedData, Slot)], hashes: Option<&[&Hash]>, is_cached_store: bool, ) { @@ -6436,7 +6443,7 @@ impl AccountsDb { fn store_accounts_frozen<'a, T: ReadableAccount + Sync + ZeroLamport>( &'a self, slot: Slot, - accounts: &[(&Pubkey, &T)], + accounts: &[(&Pubkey, &T, Slot)], hashes: Option<&[impl Borrow]>, storage_finder: Option>, write_version_producer: Option>>, @@ -6460,7 +6467,7 @@ impl AccountsDb { fn store_accounts_custom<'a, T: ReadableAccount + Sync + ZeroLamport>( &'a self, slot: Slot, - accounts: &[(&Pubkey, &T)], + accounts: &[(&Pubkey, &T, Slot)], hashes: Option<&[impl Borrow]>, storage_finder: Option>, write_version_producer: Option>>, @@ -6881,7 +6888,7 @@ impl AccountsDb { .collect::>(); let add = accounts .iter() - .map(|key| (key, &account)) + .map(|key| (key, &account, *slot)) .collect::>(); let hashes = (0..filler_entries).map(|_| hash).collect::>(); self.store_accounts_frozen(*slot, &add[..], Some(&hashes[..]), None, None); @@ -7603,7 +7610,7 @@ pub mod tests { let to_store = raw_accounts .iter() .zip(raw_expected.iter()) - .map(|(account, intermediate)| (&intermediate.pubkey, account)) + .map(|(account, intermediate)| (&intermediate.pubkey, account, slot)) .collect::>(); accounts.store_uncached(slot, &to_store[..]); @@ -8107,7 +8114,7 @@ pub mod tests { let key = Pubkey::default(); let account0 = AccountSharedData::new(1, 0, &key); - db.store_uncached(0, &[(&key, &account0)]); + db.store_uncached(0, &[(&key, &account0, 0)]); db.add_root(0); let ancestors = vec![(1, 1)].into_iter().collect(); assert_eq!( @@ -8123,10 +8130,10 @@ pub mod tests { let key = Pubkey::default(); let account0 = AccountSharedData::new(1, 0, &key); - db.store_uncached(0, &[(&key, &account0)]); + db.store_uncached(0, &[(&key, &account0, 0)]); let account1 = AccountSharedData::new(0, 0, &key); - db.store_uncached(1, &[(&key, &account1)]); + db.store_uncached(1, &[(&key, &account1, 1)]); let ancestors = vec![(1, 1)].into_iter().collect(); assert_eq!( @@ -8158,10 +8165,10 @@ pub mod tests { let key = Pubkey::default(); let account0 = AccountSharedData::new(1, 0, &key); - db.store_uncached(0, &[(&key, &account0)]); + db.store_uncached(0, &[(&key, &account0, 0)]); let account1 = AccountSharedData::new(0, 0, &key); - db.store_uncached(1, &[(&key, &account1)]); + db.store_uncached(1, &[(&key, &account1, 1)]); db.add_root(0); let ancestors = vec![(1, 1)].into_iter().collect(); @@ -8186,7 +8193,7 @@ pub mod tests { let account0 = AccountSharedData::new(1, 0, &key); // store value 1 in the "root", i.e. db zero - db.store_uncached(0, &[(&key, &account0)]); + db.store_uncached(0, &[(&key, &account0, 0)]); // now we have: // @@ -8199,7 +8206,7 @@ pub mod tests { // store value 0 in one child let account1 = AccountSharedData::new(0, 0, &key); - db.store_uncached(1, &[(&key, &account1)]); + db.store_uncached(1, &[(&key, &account1, 1)]); // masking accounts is done at the Accounts level, at accountsDB we see // original account (but could also accept "None", which is implemented @@ -8283,8 +8290,8 @@ pub mod tests { let pubkey = solana_sdk::pubkey::new_rand(); let account = AccountSharedData::new(1, DEFAULT_FILE_SIZE as usize / 3, &pubkey); - db.store_uncached(1, &[(&pubkey, &account)]); - db.store_uncached(1, &[(&pubkeys[0], &account)]); + db.store_uncached(1, &[(&pubkey, &account, 1)]); + db.store_uncached(1, &[(&pubkeys[0], &account, 1)]); { let slot_0_stores = &db.storage.get_slot_stores(0).unwrap(); let slot_1_stores = &db.storage.get_slot_stores(1).unwrap(); @@ -8316,7 +8323,7 @@ pub mod tests { // overwrite old rooted account version; only the r_slot_0_stores.count() should be // decremented - db.store_uncached(2, &[(&pubkeys[0], &account)]); + db.store_uncached(2, &[(&pubkeys[0], &account, 2)]); db.clean_accounts(None, false, None); { let slot_0_stores = &db.storage.get_slot_stores(0).unwrap(); @@ -8339,11 +8346,11 @@ pub mod tests { // 1 token in the "root", i.e. db zero let db0 = AccountsDb::new(Vec::new(), &ClusterType::Development); let account0 = AccountSharedData::new(1, 0, &key); - db0.store_uncached(0, &[(&key, &account0)]); + db0.store_uncached(0, &[(&key, &account0, 0)]); // 0 lamports in the child let account1 = AccountSharedData::new(0, 0, &key); - db0.store_uncached(1, &[(&key, &account1)]); + db0.store_uncached(1, &[(&key, &account1, 1)]); // masking accounts is done at the Accounts level, at accountsDB we see // original account @@ -8368,9 +8375,9 @@ pub mod tests { let account0 = AccountSharedData::new(1, 0, &key); let ancestors = vec![(unrooted_slot, 1)].into_iter().collect(); if is_cached { - db.store_cached(unrooted_slot, &[(&key, &account0)]); + db.store_cached(unrooted_slot, &[(&key, &account0, unrooted_slot)]); } else { - db.store_uncached(unrooted_slot, &[(&key, &account0)]); + db.store_uncached(unrooted_slot, &[(&key, &account0, unrooted_slot)]); } db.bank_hashes .write() @@ -8396,7 +8403,7 @@ pub mod tests { // Test we can store for the same slot again and get the right information let account0 = AccountSharedData::new(2, 0, &key); - db.store_uncached(unrooted_slot, &[(&key, &account0)]); + db.store_uncached(unrooted_slot, &[(&key, &account0, unrooted_slot)]); assert_load_account(&db, unrooted_slot, key, 2); } @@ -8418,7 +8425,7 @@ pub mod tests { let db = AccountsDb::new(Vec::new(), &ClusterType::Development); let key = solana_sdk::pubkey::new_rand(); let account0 = AccountSharedData::new(1, 0, &key); - db.store_uncached(unrooted_slot, &[(&key, &account0)]); + db.store_uncached(unrooted_slot, &[(&key, &account0, unrooted_slot)]); // Purge the slot db.remove_unrooted_slots(&[(unrooted_slot, unrooted_bank_id)]); @@ -8426,7 +8433,7 @@ pub mod tests { // Add a new root let key2 = solana_sdk::pubkey::new_rand(); let new_root = unrooted_slot + 1; - db.store_uncached(new_root, &[(&key2, &account0)]); + db.store_uncached(new_root, &[(&key2, &account0, unrooted_slot)]); db.add_root(new_root); // Simulate reconstruction from snapshot @@ -8459,7 +8466,7 @@ pub mod tests { assert!(accounts .load_without_fixed_root(&ancestors, &pubkey) .is_none()); - accounts.store_uncached(slot, &[(&pubkey, &account)]); + accounts.store_uncached(slot, &[(&pubkey, &account, slot)]); } for t in 0..num_vote { let pubkey = solana_sdk::pubkey::new_rand(); @@ -8470,7 +8477,7 @@ pub mod tests { assert!(accounts .load_without_fixed_root(&ancestors, &pubkey) .is_none()); - accounts.store_uncached(slot, &[(&pubkey, &account)]); + accounts.store_uncached(slot, &[(&pubkey, &account, slot)]); } } @@ -8482,7 +8489,7 @@ pub mod tests { accounts.load_without_fixed_root(&ancestors, &pubkeys[idx]) { account.checked_add_lamports(1).unwrap(); - accounts.store_uncached(slot, &[(&pubkeys[idx], &account)]); + accounts.store_uncached(slot, &[(&pubkeys[idx], &account, slot)]); if account.is_zero_lamport() { let ancestors = vec![(slot, 0)].into_iter().collect(); assert!(accounts @@ -8569,7 +8576,7 @@ pub mod tests { 0, AccountSharedData::default().owner(), ); - accounts.store_uncached(slot, &[(&pubkeys[idx], &account)]); + accounts.store_uncached(slot, &[(&pubkeys[idx], &account, slot)]); } } @@ -8615,7 +8622,7 @@ pub mod tests { for i in 0..9 { let key = solana_sdk::pubkey::new_rand(); let account = AccountSharedData::new(i + 1, size as usize / 4, &key); - accounts.store_uncached(0, &[(&key, &account)]); + accounts.store_uncached(0, &[(&key, &account, 0)]); keys.push(key); } let ancestors = vec![(0, 0)].into_iter().collect(); @@ -8650,7 +8657,7 @@ pub mod tests { let status = [AccountStorageStatus::Available, AccountStorageStatus::Full]; let pubkey1 = solana_sdk::pubkey::new_rand(); let account1 = AccountSharedData::new(1, DEFAULT_FILE_SIZE as usize / 2, &pubkey1); - accounts.store_uncached(0, &[(&pubkey1, &account1)]); + accounts.store_uncached(0, &[(&pubkey1, &account1, 0)]); { let stores = &accounts.storage.get_slot_stores(0).unwrap(); let r_stores = stores.read().unwrap(); @@ -8661,7 +8668,7 @@ pub mod tests { let pubkey2 = solana_sdk::pubkey::new_rand(); let account2 = AccountSharedData::new(1, DEFAULT_FILE_SIZE as usize / 2, &pubkey2); - accounts.store_uncached(0, &[(&pubkey2, &account2)]); + accounts.store_uncached(0, &[(&pubkey2, &account2, 0)]); { assert_eq!(accounts.storage.map.len(), 1); let stores = &accounts.storage.get_slot_stores(0).unwrap(); @@ -8690,7 +8697,7 @@ pub mod tests { // lots of stores, but 7 storages should be enough for everything for _ in 0..25 { - accounts.store_uncached(0, &[(&pubkey1, &account1)]); + accounts.store_uncached(0, &[(&pubkey1, &account1, 0)]); { assert_eq!(accounts.storage.map.len(), 1); let stores = &accounts.storage.get_slot_stores(0).unwrap(); @@ -8726,7 +8733,7 @@ pub mod tests { let pubkey = solana_sdk::pubkey::new_rand(); let account = AccountSharedData::new(1, 0, AccountSharedData::default().owner()); //store an account - accounts.store_uncached(0, &[(&pubkey, &account)]); + accounts.store_uncached(0, &[(&pubkey, &account, 0)]); let ancestors = vec![(0, 0)].into_iter().collect(); let id = { let (lock, idx) = accounts @@ -8749,7 +8756,7 @@ pub mod tests { .is_some()); //store causes clean - accounts.store_uncached(1, &[(&pubkey, &account)]); + accounts.store_uncached(1, &[(&pubkey, &account, 1)]); // generate delta state for slot 1, so clean operates on it. accounts.get_accounts_delta_hash(1); @@ -8804,8 +8811,8 @@ pub mod tests { AccountSharedData::new(0, 0, AccountSharedData::default().owner()); // Store two accounts - accounts.store_uncached(0, &[(&pubkey1, &account)]); - accounts.store_uncached(0, &[(&pubkey2, &account)]); + accounts.store_uncached(0, &[(&pubkey1, &account, 0)]); + accounts.store_uncached(0, &[(&pubkey2, &account, 0)]); // Make sure both accounts are in the same AppendVec in slot 0, which // will prevent pubkey1 from being cleaned up later even when it's a @@ -8826,10 +8833,10 @@ pub mod tests { assert_eq!(account_info1.store_id(), account_info2.store_id()); // Update account 1 in slot 1 - accounts.store_uncached(1, &[(&pubkey1, &account)]); + accounts.store_uncached(1, &[(&pubkey1, &account, 1)]); // Update account 1 as zero lamports account - accounts.store_uncached(2, &[(&pubkey1, &zero_lamport_account)]); + accounts.store_uncached(2, &[(&pubkey1, &zero_lamport_account, 2)]); // Pubkey 1 was the only account in slot 1, and it was updated in slot 2, so // slot 1 should be purged @@ -8860,10 +8867,10 @@ pub mod tests { AccountSharedData::new(0, 0, AccountSharedData::default().owner()); // Store 2 accounts in slot 0, then update account 1 in two more slots - accounts.store_uncached(0, &[(&pubkey1, &zero_lamport_account)]); - accounts.store_uncached(0, &[(&pubkey2, &zero_lamport_account)]); - accounts.store_uncached(1, &[(&pubkey1, &zero_lamport_account)]); - accounts.store_uncached(2, &[(&pubkey1, &zero_lamport_account)]); + accounts.store_uncached(0, &[(&pubkey1, &zero_lamport_account, 0)]); + accounts.store_uncached(0, &[(&pubkey2, &zero_lamport_account, 0)]); + accounts.store_uncached(1, &[(&pubkey1, &zero_lamport_account, 1)]); + accounts.store_uncached(2, &[(&pubkey1, &zero_lamport_account, 2)]); // Root all slots accounts.add_root(0); accounts.add_root(1); @@ -8905,8 +8912,8 @@ pub mod tests { AccountSharedData::new(0, 0, AccountSharedData::default().owner()); // Store a zero-lamport account - accounts.store_uncached(0, &[(&pubkey, &account)]); - accounts.store_uncached(1, &[(&pubkey, &zero_lamport_account)]); + accounts.store_uncached(0, &[(&pubkey, &account, 0)]); + accounts.store_uncached(1, &[(&pubkey, &zero_lamport_account, 1)]); // Simulate rooting the zero-lamport account, should be a // candidate for cleaning @@ -8945,8 +8952,8 @@ pub mod tests { let pubkey = solana_sdk::pubkey::new_rand(); let account = AccountSharedData::new(1, 0, AccountSharedData::default().owner()); //store an account - accounts.store_uncached(0, &[(&pubkey, &account)]); - accounts.store_uncached(1, &[(&pubkey, &account)]); + accounts.store_uncached(0, &[(&pubkey, &account, 0)]); + accounts.store_uncached(1, &[(&pubkey, &account, 1)]); // simulate slots are rooted after while accounts.get_accounts_delta_hash(0); @@ -8975,10 +8982,10 @@ pub mod tests { let normal_account = AccountSharedData::new(1, 0, AccountSharedData::default().owner()); let zero_account = AccountSharedData::new(0, 0, AccountSharedData::default().owner()); //store an account - accounts.store_uncached(0, &[(&pubkey1, &normal_account)]); - accounts.store_uncached(1, &[(&pubkey1, &zero_account)]); - accounts.store_uncached(0, &[(&pubkey2, &normal_account)]); - accounts.store_uncached(1, &[(&pubkey2, &normal_account)]); + accounts.store_uncached(0, &[(&pubkey1, &normal_account, 0)]); + accounts.store_uncached(1, &[(&pubkey1, &zero_account, 1)]); + accounts.store_uncached(0, &[(&pubkey2, &normal_account, 0)]); + accounts.store_uncached(1, &[(&pubkey2, &normal_account, 1)]); //simulate slots are rooted after while accounts.get_accounts_delta_hash(0); @@ -9026,11 +9033,11 @@ pub mod tests { zero_account.set_data(account_data_with_mint); //store an account - accounts.store_uncached(0, &[(&pubkey1, &normal_account)]); - accounts.store_uncached(0, &[(&pubkey1, &normal_account)]); - accounts.store_uncached(1, &[(&pubkey1, &zero_account)]); - accounts.store_uncached(0, &[(&pubkey2, &normal_account)]); - accounts.store_uncached(2, &[(&pubkey2, &normal_account)]); + accounts.store_uncached(0, &[(&pubkey1, &normal_account, 0)]); + accounts.store_uncached(0, &[(&pubkey1, &normal_account, 0)]); + accounts.store_uncached(1, &[(&pubkey1, &zero_account, 1)]); + accounts.store_uncached(0, &[(&pubkey2, &normal_account, 0)]); + accounts.store_uncached(2, &[(&pubkey2, &normal_account, 2)]); //simulate slots are rooted after while accounts.get_accounts_delta_hash(0); @@ -9152,8 +9159,8 @@ pub mod tests { // store an account, make it a zero lamport account // in slot 1 - accounts.store_uncached(0, &[(&pubkey, &account)]); - accounts.store_uncached(1, &[(&pubkey, &zero_account)]); + accounts.store_uncached(0, &[(&pubkey, &account, 0)]); + accounts.store_uncached(1, &[(&pubkey, &zero_account, 1)]); // simulate slots are rooted after while accounts.add_root(0); @@ -9192,7 +9199,7 @@ pub mod tests { let pubkey = solana_sdk::pubkey::new_rand(); let account = AccountSharedData::new(1, 0, AccountSharedData::default().owner()); //store an account - accounts.store_uncached(0, &[(&pubkey, &account)]); + accounts.store_uncached(0, &[(&pubkey, &account, 0)]); assert_eq!(accounts.accounts_index.uncleaned_roots_len(), 0); // simulate slots are rooted after while @@ -9249,7 +9256,7 @@ pub mod tests { // Overwrite account 30 from slot 0 with lamports=0 into slot 1. // Slot 1 should now have 10 + 1 = 11 accounts let account = AccountSharedData::new(0, 0, AccountSharedData::default().owner()); - accounts.store_uncached(latest_slot, &[(&pubkeys[30], &account)]); + accounts.store_uncached(latest_slot, &[(&pubkeys[30], &account, latest_slot)]); // Create 10 new accounts in slot 1, should now have 11 + 10 = 21 // accounts @@ -9269,7 +9276,7 @@ pub mod tests { // Overwrite account 31 from slot 0 with lamports=0 into slot 2. // Slot 2 should now have 20 + 1 = 21 accounts let account = AccountSharedData::new(0, 0, AccountSharedData::default().owner()); - accounts.store_uncached(latest_slot, &[(&pubkeys[31], &account)]); + accounts.store_uncached(latest_slot, &[(&pubkeys[31], &account, latest_slot)]); // Create 10 new accounts in slot 2. Slot 2 should now have // 21 + 10 = 31 accounts @@ -9378,10 +9385,10 @@ pub mod tests { // Step A let mut current_slot = 1; - accounts.store_uncached(current_slot, &[(&pubkey, &account)]); + accounts.store_uncached(current_slot, &[(&pubkey, &account, current_slot)]); // Store another live account to slot 1 which will prevent any purge // since the store count will not be zero - accounts.store_uncached(current_slot, &[(&pubkey2, &account2)]); + accounts.store_uncached(current_slot, &[(&pubkey2, &account2, current_slot)]); accounts.add_root(current_slot); let (slot1, account_info1) = accounts .accounts_index @@ -9400,7 +9407,10 @@ pub mod tests { // Step B current_slot += 1; let zero_lamport_slot = current_slot; - accounts.store_uncached(current_slot, &[(&pubkey, &zero_lamport_account)]); + accounts.store_uncached( + current_slot, + &[(&pubkey, &zero_lamport_account, current_slot)], + ); accounts.add_root(current_slot); assert_load_account(&accounts, current_slot, pubkey, zero_lamport); @@ -9454,12 +9464,15 @@ pub mod tests { let mut current_slot = 1; accounts.set_hash(current_slot, current_slot - 1); - accounts.store_uncached(current_slot, &[(&pubkey, &account)]); + accounts.store_uncached(current_slot, &[(&pubkey, &account, current_slot)]); accounts.add_root(current_slot); current_slot += 1; accounts.set_hash(current_slot, current_slot - 1); - accounts.store_uncached(current_slot, &[(&pubkey, &zero_lamport_account)]); + accounts.store_uncached( + current_slot, + &[(&pubkey, &zero_lamport_account, current_slot)], + ); accounts.add_root(current_slot); assert_load_account(&accounts, current_slot, pubkey, zero_lamport); @@ -9517,12 +9530,15 @@ pub mod tests { let accounts = AccountsDb::new_single_for_tests(); let mut current_slot = 1; - accounts.store_uncached(current_slot, &[(&pubkey, &account)]); + accounts.store_uncached(current_slot, &[(&pubkey, &account, current_slot)]); accounts.add_root(current_slot); current_slot += 1; - accounts.store_uncached(current_slot, &[(&pubkey, &zero_lamport_account)]); - accounts.store_uncached(current_slot, &[(&pubkey2, &account2)]); + accounts.store_uncached( + current_slot, + &[(&pubkey, &zero_lamport_account, current_slot)], + ); + accounts.store_uncached(current_slot, &[(&pubkey2, &account2, current_slot)]); // Store enough accounts such that an additional store for slot 2 is created. while accounts @@ -9534,7 +9550,10 @@ pub mod tests { .len() < 2 { - accounts.store_uncached(current_slot, &[(&filler_account_pubkey, &filler_account)]); + accounts.store_uncached( + current_slot, + &[(&filler_account_pubkey, &filler_account, current_slot)], + ); } accounts.add_root(current_slot); @@ -9577,21 +9596,30 @@ pub mod tests { let accounts = AccountsDb::new_single_for_tests(); let mut current_slot = 1; - accounts.store_uncached(current_slot, &[(&pubkey, &account)]); - accounts.store_uncached(current_slot, &[(&purged_pubkey1, &account2)]); + accounts.store_uncached(current_slot, &[(&pubkey, &account, current_slot)]); + accounts.store_uncached(current_slot, &[(&purged_pubkey1, &account2, current_slot)]); accounts.add_root(current_slot); current_slot += 1; - accounts.store_uncached(current_slot, &[(&purged_pubkey1, &zero_lamport_account)]); - accounts.store_uncached(current_slot, &[(&purged_pubkey2, &account3)]); + accounts.store_uncached( + current_slot, + &[(&purged_pubkey1, &zero_lamport_account, current_slot)], + ); + accounts.store_uncached(current_slot, &[(&purged_pubkey2, &account3, current_slot)]); accounts.add_root(current_slot); current_slot += 1; - accounts.store_uncached(current_slot, &[(&purged_pubkey2, &zero_lamport_account)]); + accounts.store_uncached( + current_slot, + &[(&purged_pubkey2, &zero_lamport_account, current_slot)], + ); accounts.add_root(current_slot); current_slot += 1; - accounts.store_uncached(current_slot, &[(&dummy_pubkey, &dummy_account)]); + accounts.store_uncached( + current_slot, + &[(&dummy_pubkey, &dummy_account, current_slot)], + ); accounts.add_root(current_slot); accounts.print_accounts_stats("pre_f"); @@ -9655,7 +9683,7 @@ pub mod tests { loop { let account_bal = thread_rng().gen_range(1, 99); account.set_lamports(account_bal); - db.store_uncached(slot, &[(&pubkey, &account)]); + db.store_uncached(slot, &[(&pubkey, &account, slot)]); let (account, slot) = db .load_without_fixed_root(&Ancestors::default(), &pubkey) @@ -9684,11 +9712,11 @@ pub mod tests { let key0 = solana_sdk::pubkey::new_rand(); let account0 = AccountSharedData::new(1, 0, &key); - db.store_uncached(0, &[(&key0, &account0)]); + db.store_uncached(0, &[(&key0, &account0, 0)]); let key1 = solana_sdk::pubkey::new_rand(); let account1 = AccountSharedData::new(2, 0, &key); - db.store_uncached(1, &[(&key1, &account1)]); + db.store_uncached(1, &[(&key1, &account1, 1)]); let ancestors = vec![(0, 0)].into_iter().collect(); let accounts: Vec = db.unchecked_scan_accounts( @@ -9722,11 +9750,11 @@ pub mod tests { let key0 = solana_sdk::pubkey::new_rand(); let account0 = AccountSharedData::new(1, 0, &key); - db.store_uncached(0, &[(&key0, &account0)]); + db.store_uncached(0, &[(&key0, &account0, 0)]); let key1 = solana_sdk::pubkey::new_rand(); let account1 = AccountSharedData::new(2, 0, &key); - db.store_uncached(1, &[(&key1, &account1)]); + db.store_uncached(1, &[(&key1, &account1, 1)]); db.print_accounts_stats("pre"); @@ -9735,7 +9763,7 @@ pub mod tests { db.purge_keys_exact(purge_keys.iter()); let account2 = AccountSharedData::new(3, 0, &key); - db.store_uncached(2, &[(&key1, &account2)]); + db.store_uncached(2, &[(&key1, &account2, 2)]); db.print_accounts_stats("post"); let ancestors = vec![(2, 0)].into_iter().collect(); @@ -9757,7 +9785,7 @@ pub mod tests { let data_len = DEFAULT_FILE_SIZE as usize + 7; let account = AccountSharedData::new(1, data_len, &key); - db.store_uncached(0, &[(&key, &account)]); + db.store_uncached(0, &[(&key, &account, 0)]); let ancestors = vec![(0, 0)].into_iter().collect(); let ret = db.load_without_fixed_root(&ancestors, &key).unwrap(); @@ -9871,11 +9899,11 @@ pub mod tests { let account = AccountSharedData::new(1, some_data_len, &key); let ancestors = vec![(some_slot, 0)].into_iter().collect(); - db.store_uncached(some_slot, &[(&key, &account)]); + db.store_uncached(some_slot, &[(&key, &account, some_slot)]); let mut account = db.load_without_fixed_root(&ancestors, &key).unwrap().0; account.checked_sub_lamports(1).unwrap(); account.set_executable(true); - db.store_uncached(some_slot, &[(&key, &account)]); + db.store_uncached(some_slot, &[(&key, &account, some_slot)]); db.add_root(some_slot); let bank_hashes = db.bank_hashes.read().unwrap(); @@ -9902,7 +9930,7 @@ pub mod tests { // put wrong hash value in store so we get a mismatch db.store_accounts_unfrozen( some_slot, - &[(&key, &account)], + &[(&key, &account, some_slot)], Some(&[&Hash::default()]), false, ); @@ -9929,7 +9957,7 @@ pub mod tests { let ancestors = vec![(some_slot, 0)].into_iter().collect(); - db.store_uncached(some_slot, &[(&key, &account)]); + db.store_uncached(some_slot, &[(&key, &account, some_slot)]); db.add_root(some_slot); let check_hash = true; assert_eq!( @@ -9956,7 +9984,7 @@ pub mod tests { let account = AccountSharedData::new(1, some_data_len, &key); let ancestors = vec![(some_slot, 0)].into_iter().collect(); - db.store_uncached(some_slot, &[(&key, &account)]); + db.store_uncached(some_slot, &[(&key, &account, some_slot)]); db.add_root(some_slot); db.update_accounts_hash_test(some_slot, &ancestors); assert_matches!( @@ -9998,7 +10026,7 @@ pub mod tests { let account = AccountSharedData::new(1, some_data_len, &key); let ancestors = vec![(some_slot, 0)].into_iter().collect(); - db.store_uncached(some_slot, &[(&key, &account)]); + db.store_uncached(some_slot, &[(&key, &account, some_slot)]); db.add_root(some_slot); db.update_accounts_hash_test(some_slot, &ancestors); assert_matches!( @@ -10012,6 +10040,7 @@ pub mod tests { &[( &native_account_pubkey, &solana_sdk::native_loader::create_loadable_account_for_test("foo"), + some_slot, )], ); db.update_accounts_hash_test(some_slot, &ancestors); @@ -10058,7 +10087,7 @@ pub mod tests { let account = AccountSharedData::new(1, some_data_len, &key); let ancestors = vec![(some_slot, 0)].into_iter().collect(); - let accounts = &[(&key, &account)]; + let accounts = &[(&key, &account, 0)]; // update AccountsDb's bank hash { let mut bank_hashes = db.bank_hashes.write().unwrap(); @@ -10086,7 +10115,7 @@ pub mod tests { let account = AccountSharedData::new(lamports, data_len, &solana_sdk::pubkey::new_rand()); // pre-populate with a smaller empty store db.create_and_insert_store(1, 8192, "test_storage_finder"); - db.store_uncached(1, &[(&key, &account)]); + db.store_uncached(1, &[(&key, &account, 1)]); } #[test] @@ -10106,7 +10135,7 @@ pub mod tests { let after_slot = base_slot + 1; db.add_root(base_slot); - db.store_uncached(base_slot, &[(&key, &account)]); + db.store_uncached(base_slot, &[(&key, &account, base_slot)]); assert!(db .get_snapshot_storages(before_slot, None, None) .0 @@ -10125,7 +10154,7 @@ pub mod tests { let base_slot = 0; let after_slot = base_slot + 1; - db.store_uncached(base_slot, &[(&key, &account)]); + db.store_uncached(base_slot, &[(&key, &account, base_slot)]); db.storage .get_slot_stores(base_slot) .unwrap() @@ -10138,7 +10167,7 @@ pub mod tests { .0 .is_empty()); - db.store_uncached(base_slot, &[(&key, &account)]); + db.store_uncached(base_slot, &[(&key, &account, base_slot)]); assert_eq!(1, db.get_snapshot_storages(after_slot, None, None).0.len()); } @@ -10151,7 +10180,7 @@ pub mod tests { let base_slot = 0; let after_slot = base_slot + 1; - db.store_uncached(base_slot, &[(&key, &account)]); + db.store_uncached(base_slot, &[(&key, &account, base_slot)]); assert!(db .get_snapshot_storages(after_slot, None, None) .0 @@ -10170,7 +10199,7 @@ pub mod tests { let base_slot = 0; let after_slot = base_slot + 1; - db.store_uncached(base_slot, &[(&key, &account)]); + db.store_uncached(base_slot, &[(&key, &account, base_slot)]); db.add_root(base_slot); assert_eq!(1, db.get_snapshot_storages(after_slot, None, None).0.len()); @@ -10197,7 +10226,7 @@ pub mod tests { let account = AccountSharedData::new(1, 0, &key); let slot = 10; - db.store_uncached(slot, &[(&key, &account)]); + db.store_uncached(slot, &[(&key, &account, slot)]); db.add_root(slot); assert_eq!( 0, @@ -10217,7 +10246,7 @@ pub mod tests { let accounts = AccountsDb::new(Vec::new(), &ClusterType::Development); let pubkey = solana_sdk::pubkey::new_rand(); let account = AccountSharedData::new(1, 0, AccountSharedData::default().owner()); - accounts.store_uncached(0, &[(&pubkey, &account)]); + accounts.store_uncached(0, &[(&pubkey, &account, 0)]); let storage_entry = accounts .storage .get_slot_stores(0) @@ -10257,29 +10286,38 @@ pub mod tests { // create intermediate updates to purged_pubkey1 so that // generate_index must add slots as root last at once current_slot += 1; - accounts.store_uncached(current_slot, &[(&pubkey, &account)]); - accounts.store_uncached(current_slot, &[(&purged_pubkey1, &account2)]); + accounts.store_uncached(current_slot, &[(&pubkey, &account, current_slot)]); + accounts.store_uncached(current_slot, &[(&purged_pubkey1, &account2, current_slot)]); accounts.add_root(current_slot); current_slot += 1; - accounts.store_uncached(current_slot, &[(&purged_pubkey1, &account2)]); + accounts.store_uncached(current_slot, &[(&purged_pubkey1, &account2, current_slot)]); accounts.add_root(current_slot); current_slot += 1; - accounts.store_uncached(current_slot, &[(&purged_pubkey1, &account2)]); + accounts.store_uncached(current_slot, &[(&purged_pubkey1, &account2, current_slot)]); accounts.add_root(current_slot); current_slot += 1; - accounts.store_uncached(current_slot, &[(&purged_pubkey1, &zero_lamport_account)]); - accounts.store_uncached(current_slot, &[(&purged_pubkey2, &account3)]); + accounts.store_uncached( + current_slot, + &[(&purged_pubkey1, &zero_lamport_account, current_slot)], + ); + accounts.store_uncached(current_slot, &[(&purged_pubkey2, &account3, current_slot)]); accounts.add_root(current_slot); current_slot += 1; - accounts.store_uncached(current_slot, &[(&purged_pubkey2, &zero_lamport_account)]); + accounts.store_uncached( + current_slot, + &[(&purged_pubkey2, &zero_lamport_account, current_slot)], + ); accounts.add_root(current_slot); current_slot += 1; - accounts.store_uncached(current_slot, &[(&dummy_pubkey, &dummy_account)]); + accounts.store_uncached( + current_slot, + &[(&dummy_pubkey, &dummy_account, current_slot)], + ); accounts.add_root(current_slot); accounts.print_count_and_status("before reconstruct"); @@ -10319,11 +10357,11 @@ pub mod tests { // A: Initialize AccountsDb with pubkey1 and pubkey2 current_slot += 1; if store1_first { - accounts.store_uncached(current_slot, &[(&pubkey1, &account)]); - accounts.store_uncached(current_slot, &[(&pubkey2, &account)]); + accounts.store_uncached(current_slot, &[(&pubkey1, &account, current_slot)]); + accounts.store_uncached(current_slot, &[(&pubkey2, &account, current_slot)]); } else { - accounts.store_uncached(current_slot, &[(&pubkey2, &account)]); - accounts.store_uncached(current_slot, &[(&pubkey1, &account)]); + accounts.store_uncached(current_slot, &[(&pubkey2, &account, current_slot)]); + accounts.store_uncached(current_slot, &[(&pubkey1, &account, current_slot)]); } accounts.get_accounts_delta_hash(current_slot); accounts.add_root(current_slot); @@ -10335,8 +10373,8 @@ pub mod tests { current_slot += 1; assert_eq!(0, accounts.alive_account_count_in_slot(current_slot)); assert_eq!(1, accounts.ref_count_for_pubkey(&pubkey1)); - accounts.store_uncached(current_slot, &[(&pubkey1, &account2)]); - accounts.store_uncached(current_slot, &[(&pubkey1, &account2)]); + accounts.store_uncached(current_slot, &[(&pubkey1, &account2, current_slot)]); + accounts.store_uncached(current_slot, &[(&pubkey1, &account2, current_slot)]); assert_eq!(1, accounts.alive_account_count_in_slot(current_slot)); // Stores to same pubkey, same slot only count once towards the // ref count @@ -10354,9 +10392,9 @@ pub mod tests { // C: more updates to trigger clean of previous updates current_slot += 1; assert_eq!(2, accounts.ref_count_for_pubkey(&pubkey1)); - accounts.store_uncached(current_slot, &[(&pubkey1, &account3)]); - accounts.store_uncached(current_slot, &[(&pubkey2, &account3)]); - accounts.store_uncached(current_slot, &[(&pubkey3, &account4)]); + accounts.store_uncached(current_slot, &[(&pubkey1, &account3, current_slot)]); + accounts.store_uncached(current_slot, &[(&pubkey2, &account3, current_slot)]); + accounts.store_uncached(current_slot, &[(&pubkey3, &account4, current_slot)]); assert_eq!(3, accounts.ref_count_for_pubkey(&pubkey1)); accounts.get_accounts_delta_hash(current_slot); accounts.add_root(current_slot); @@ -10368,9 +10406,18 @@ pub mod tests { // D: Make all keys 0-lamport, cleans all keys current_slot += 1; assert_eq!(3, accounts.ref_count_for_pubkey(&pubkey1)); - accounts.store_uncached(current_slot, &[(&pubkey1, &zero_lamport_account)]); - accounts.store_uncached(current_slot, &[(&pubkey2, &zero_lamport_account)]); - accounts.store_uncached(current_slot, &[(&pubkey3, &zero_lamport_account)]); + accounts.store_uncached( + current_slot, + &[(&pubkey1, &zero_lamport_account, current_slot)], + ); + accounts.store_uncached( + current_slot, + &[(&pubkey2, &zero_lamport_account, current_slot)], + ); + accounts.store_uncached( + current_slot, + &[(&pubkey3, &zero_lamport_account, current_slot)], + ); let snapshot_stores = accounts.get_snapshot_storages(current_slot, None, None).0; let total_accounts: usize = snapshot_stores @@ -10446,8 +10493,8 @@ pub mod tests { // A: Initialize AccountsDb with pubkey1 and pubkey2 current_slot += 1; - accounts.store_uncached(current_slot, &[(&pubkey1, &account)]); - accounts.store_uncached(current_slot, &[(&pubkey2, &account)]); + accounts.store_uncached(current_slot, &[(&pubkey1, &account, current_slot)]); + accounts.store_uncached(current_slot, &[(&pubkey2, &account, current_slot)]); accounts.get_accounts_delta_hash(current_slot); accounts.add_root(current_slot); @@ -10455,8 +10502,8 @@ pub mod tests { current_slot += 1; assert_eq!(0, accounts.alive_account_count_in_slot(current_slot)); assert_eq!(1, accounts.ref_count_for_pubkey(&pubkey1)); - accounts.store_uncached(current_slot, &[(&pubkey1, &account2)]); - accounts.store_uncached(current_slot, &[(&pubkey1, &account2)]); + accounts.store_uncached(current_slot, &[(&pubkey1, &account2, current_slot)]); + accounts.store_uncached(current_slot, &[(&pubkey1, &account2, current_slot)]); assert_eq!(1, accounts.alive_account_count_in_slot(current_slot)); // Stores to same pubkey, same slot only count once towards the // ref count @@ -10467,7 +10514,7 @@ pub mod tests { // C: Yet more update to trigger lazy clean of step A current_slot += 1; assert_eq!(2, accounts.ref_count_for_pubkey(&pubkey1)); - accounts.store_uncached(current_slot, &[(&pubkey1, &account3)]); + accounts.store_uncached(current_slot, &[(&pubkey1, &account3, current_slot)]); assert_eq!(3, accounts.ref_count_for_pubkey(&pubkey1)); accounts.get_accounts_delta_hash(current_slot); accounts.add_root(current_slot); @@ -10475,7 +10522,10 @@ pub mod tests { // D: Make pubkey1 0-lamport; also triggers clean of step B current_slot += 1; assert_eq!(3, accounts.ref_count_for_pubkey(&pubkey1)); - accounts.store_uncached(current_slot, &[(&pubkey1, &zero_lamport_account)]); + accounts.store_uncached( + current_slot, + &[(&pubkey1, &zero_lamport_account, current_slot)], + ); accounts.clean_accounts(None, false, None); assert_eq!( @@ -10489,7 +10539,10 @@ pub mod tests { // E: Avoid missing bank hash error current_slot += 1; - accounts.store_uncached(current_slot, &[(&dummy_pubkey, &dummy_account)]); + accounts.store_uncached( + current_slot, + &[(&dummy_pubkey, &dummy_account, current_slot)], + ); accounts.get_accounts_delta_hash(current_slot); accounts.add_root(current_slot); @@ -10513,7 +10566,7 @@ pub mod tests { // F: Finally, make Step A cleanable current_slot += 1; - accounts.store_uncached(current_slot, &[(&pubkey2, &account)]); + accounts.store_uncached(current_slot, &[(&pubkey2, &account, current_slot)]); accounts.get_accounts_delta_hash(current_slot); accounts.add_root(current_slot); @@ -10646,7 +10699,7 @@ pub mod tests { current_slot += 1; for pubkey in &pubkeys { - accounts.store_uncached(current_slot, &[(pubkey, &account)]); + accounts.store_uncached(current_slot, &[(pubkey, &account, current_slot)]); } let shrink_slot = current_slot; accounts.get_accounts_delta_hash(current_slot); @@ -10657,7 +10710,7 @@ pub mod tests { let updated_pubkeys = &pubkeys[0..pubkey_count - pubkey_count_after_shrink]; for pubkey in updated_pubkeys { - accounts.store_uncached(current_slot, &[(pubkey, &account)]); + accounts.store_uncached(current_slot, &[(pubkey, &account, current_slot)]); } accounts.get_accounts_delta_hash(current_slot); accounts.add_root(current_slot); @@ -10715,7 +10768,7 @@ pub mod tests { current_slot += 1; for pubkey in &pubkeys { - accounts.store_uncached(current_slot, &[(pubkey, &account)]); + accounts.store_uncached(current_slot, &[(pubkey, &account, current_slot)]); } let shrink_slot = current_slot; accounts.get_accounts_delta_hash(current_slot); @@ -10726,7 +10779,7 @@ pub mod tests { let updated_pubkeys = &pubkeys[0..pubkey_count - pubkey_count_after_shrink]; for pubkey in updated_pubkeys { - accounts.store_uncached(current_slot, &[(pubkey, &account)]); + accounts.store_uncached(current_slot, &[(pubkey, &account, current_slot)]); } accounts.get_accounts_delta_hash(current_slot); accounts.add_root(current_slot); @@ -11001,7 +11054,7 @@ pub mod tests { current_slot += 1; for pubkey in &pubkeys { - accounts.store_uncached(current_slot, &[(pubkey, &account)]); + accounts.store_uncached(current_slot, &[(pubkey, &account, current_slot)]); } let shrink_slot = current_slot; accounts.get_accounts_delta_hash(current_slot); @@ -11012,7 +11065,7 @@ pub mod tests { let updated_pubkeys = &pubkeys[0..pubkey_count - pubkey_count_after_shrink]; for pubkey in updated_pubkeys { - accounts.store_uncached(current_slot, &[(pubkey, &account)]); + accounts.store_uncached(current_slot, &[(pubkey, &account, current_slot)]); } accounts.get_accounts_delta_hash(current_slot); accounts.add_root(current_slot); @@ -11054,6 +11107,7 @@ pub mod tests { let info3 = AccountInfo::new(StorageLocation::AppendVec(3, 0), 0, 0); let mut reclaims = vec![]; accounts_index.upsert( + 0, 0, &key0, &Pubkey::default(), @@ -11064,6 +11118,7 @@ pub mod tests { UPSERT_PREVIOUS_SLOT_ENTRY_WAS_CACHED_FALSE, ); accounts_index.upsert( + 1, 1, &key0, &Pubkey::default(), @@ -11074,6 +11129,7 @@ pub mod tests { UPSERT_PREVIOUS_SLOT_ENTRY_WAS_CACHED_FALSE, ); accounts_index.upsert( + 1, 1, &key1, &Pubkey::default(), @@ -11084,6 +11140,7 @@ pub mod tests { UPSERT_PREVIOUS_SLOT_ENTRY_WAS_CACHED_FALSE, ); accounts_index.upsert( + 2, 2, &key1, &Pubkey::default(), @@ -11094,6 +11151,7 @@ pub mod tests { UPSERT_PREVIOUS_SLOT_ENTRY_WAS_CACHED_FALSE, ); accounts_index.upsert( + 2, 2, &key2, &Pubkey::default(), @@ -11104,6 +11162,7 @@ pub mod tests { UPSERT_PREVIOUS_SLOT_ENTRY_WAS_CACHED_FALSE, ); accounts_index.upsert( + 3, 3, &key2, &Pubkey::default(), @@ -11189,7 +11248,7 @@ pub mod tests { let accounts = AccountsDb::new_single_for_tests(); let account = AccountSharedData::default(); let pubkey = solana_sdk::pubkey::new_rand(); - accounts.store_uncached(0, &[(&pubkey, &account)]); + accounts.store_uncached(0, &[(&pubkey, &account, 0)]); let slot_stores = accounts.storage.get_slot_stores(0).unwrap(); let mut total_len = 0; for (_id, store) in slot_stores.read().unwrap().iter() { @@ -11212,13 +11271,13 @@ pub mod tests { let account = AccountSharedData::new(1, 16 * 4096, &Pubkey::default()); let pubkey1 = solana_sdk::pubkey::new_rand(); - accounts.store_cached(0, &[(&pubkey1, &account)]); + accounts.store_cached(0, &[(&pubkey1, &account, 0)]); let pubkey2 = solana_sdk::pubkey::new_rand(); - accounts.store_cached(0, &[(&pubkey2, &account)]); + accounts.store_cached(0, &[(&pubkey2, &account, 0)]); let zero_account = AccountSharedData::new(0, 1, &Pubkey::default()); - accounts.store_cached(1, &[(&pubkey1, &zero_account)]); + accounts.store_cached(1, &[(&pubkey1, &zero_account, 1)]); // Add root 0 and flush separately accounts.get_accounts_delta_hash(0); @@ -11259,7 +11318,7 @@ pub mod tests { for i in 0..num_accounts { let account = AccountSharedData::new((i + 1) as u64, size, &Pubkey::default()); let pubkey = solana_sdk::pubkey::new_rand(); - accounts.store_uncached(0, &[(&pubkey, &account)]); + accounts.store_uncached(0, &[(&pubkey, &account, 0)]); keys.push(pubkey); } accounts.add_root(0); @@ -11267,7 +11326,7 @@ pub mod tests { for (i, key) in keys[1..].iter().enumerate() { let account = AccountSharedData::new((1 + i + num_accounts) as u64, size, &Pubkey::default()); - accounts.store_uncached(1, &[(key, &account)]); + accounts.store_uncached(1, &[(key, &account, 1)]); } accounts.add_root(1); accounts.clean_accounts(None, false, None); @@ -11288,7 +11347,7 @@ pub mod tests { i + 20, &Pubkey::default(), ); - accounts.store_uncached(2, &[(key, &account)]); + accounts.store_uncached(2, &[(key, &account, 2)]); account_refs.push(account); } assert!(accounts.recycle_stores.read().unwrap().entry_count() < num_stores); @@ -11320,7 +11379,7 @@ pub mod tests { // write unique keys to successive slots keys.iter().enumerate().for_each(|(slot, key)| { let slot = slot as Slot; - db.store_uncached(slot, &[(key, &zero_lamport_account)]); + db.store_uncached(slot, &[(key, &zero_lamport_account, slot)]); db.get_accounts_delta_hash(slot); db.add_root(slot); }); @@ -11346,7 +11405,7 @@ pub mod tests { // write unique keys to successive slots keys.iter().enumerate().for_each(|(slot, key)| { let slot = slot as Slot; - db.store_uncached(slot, &[(key, &zero_lamport_account)]); + db.store_uncached(slot, &[(key, &zero_lamport_account, slot)]); db.get_accounts_delta_hash(slot); db.add_root(slot); // reset next_id to what it was previously to cause us to re-use the same id @@ -11366,8 +11425,8 @@ pub mod tests { AccountSharedData::new(0, 0, AccountSharedData::default().owner()); // Store zero lamport account into slots 0 and 1, root both slots - db.store_uncached(0, &[(&account_key, &zero_lamport_account)]); - db.store_uncached(1, &[(&account_key, &zero_lamport_account)]); + db.store_uncached(0, &[(&account_key, &zero_lamport_account, 0)]); + db.store_uncached(1, &[(&account_key, &zero_lamport_account, 1)]); db.get_accounts_delta_hash(0); db.add_root(0); db.get_accounts_delta_hash(1); @@ -11390,7 +11449,7 @@ pub mod tests { let key = Pubkey::default(); let account0 = AccountSharedData::new(1, 0, &key); let slot = 0; - db.store_cached(slot, &[(&key, &account0)]); + db.store_cached(slot, &[(&key, &account0, slot)]); // Load with no ancestors and no root will return nothing assert!(db @@ -11423,7 +11482,7 @@ pub mod tests { let key = Pubkey::default(); let account0 = AccountSharedData::new(1, 0, &key); let slot = 0; - db.store_cached(slot, &[(&key, &account0)]); + db.store_cached(slot, &[(&key, &account0, slot)]); db.mark_slot_frozen(slot); // No root was added yet, requires an ancestor to find @@ -11456,9 +11515,9 @@ pub mod tests { let unrooted_key = solana_sdk::pubkey::new_rand(); let key5 = solana_sdk::pubkey::new_rand(); let key6 = solana_sdk::pubkey::new_rand(); - db.store_cached(unrooted_slot, &[(&unrooted_key, &account0)]); - db.store_cached(root5, &[(&key5, &account0)]); - db.store_cached(root6, &[(&key6, &account0)]); + db.store_cached(unrooted_slot, &[(&unrooted_key, &account0, unrooted_slot)]); + db.store_cached(root5, &[(&key5, &account0, root5)]); + db.store_cached(root6, &[(&key6, &account0, root6)]); for slot in &[unrooted_slot, root5, root6] { db.mark_slot_frozen(*slot); } @@ -11500,6 +11559,7 @@ pub mod tests { #[test] fn test_flush_accounts_cache_if_needed() { + solana_logger::setup(); run_test_flush_accounts_cache_if_needed(0, 2 * max_cache_slots()); run_test_flush_accounts_cache_if_needed(2 * max_cache_slots(), 0); run_test_flush_accounts_cache_if_needed(max_cache_slots() - 1, 0); @@ -11521,7 +11581,7 @@ pub mod tests { let num_slots = 2 * max_cache_slots(); for i in 0..num_roots + num_unrooted { let key = Pubkey::new_unique(); - db.store_cached(i as Slot, &[(&key, &account0)]); + db.store_cached(i as Slot, &[(&key, &account0, i as Slot)]); keys.push(key); db.mark_slot_frozen(i as Slot); if i < num_roots { @@ -11539,18 +11599,25 @@ pub mod tests { // Otherwise, all the roots are flushed, and only at most max_cache_slots() // of the unrooted slots are kept in the cache let expected_size = std::cmp::min(num_unrooted, max_cache_slots()); + const FLUSH_WINDOW_SIZE: usize = 1; if expected_size > 0 { // +1: slot is 1-based. slot 1 has 1 byte of data for unrooted_slot in (total_slots - expected_size + 1)..total_slots { - assert!( - db.accounts_cache - .slot_cache(unrooted_slot as Slot) - .is_some(), - "unrooted_slot: {}, total_slots: {}, expected_size: {}", + if db + .accounts_cache + .slot_cache(unrooted_slot as Slot) + .is_none() + && unrooted_slot > total_slots - expected_size + 1 + FLUSH_WINDOW_SIZE + { + panic!( + "unrooted_slot: {}, total_slots: {}, expected_size: {}, num_unrooted: {}, max_cache_slots: {}", unrooted_slot, total_slots, - expected_size + expected_size, + num_unrooted, + max_cache_slots(), ); + } } } } @@ -11563,8 +11630,8 @@ pub mod tests { vec![(slot, 1)].into_iter().collect() }; assert_eq!( - db.load_without_fixed_root(&ancestors, &key), - Some((account0.clone(), slot)) + db.load_without_fixed_root(&ancestors, &key).unwrap().0, + account0 ); } } @@ -11590,8 +11657,8 @@ pub mod tests { let zero_lamport_account = AccountSharedData::new(0, 0, AccountSharedData::default().owner()); let slot1_account = AccountSharedData::new(1, 1, AccountSharedData::default().owner()); - db.store_cached(0, &[(&account_key, &zero_lamport_account)]); - db.store_cached(1, &[(&account_key, &slot1_account)]); + db.store_cached(0, &[(&account_key, &zero_lamport_account, 0)]); + db.store_cached(1, &[(&account_key, &slot1_account, 1)]); db.add_root(0); db.add_root(1); @@ -11613,7 +11680,7 @@ pub mod tests { .unwrap(); assert_eq!(account.lamports(), 1); assert_eq!(db.read_only_accounts_cache.cache_len(), 1); - db.store_cached(2, &[(&account_key, &zero_lamport_account)]); + db.store_cached(2, &[(&account_key, &zero_lamport_account, 2)]); assert_eq!(db.read_only_accounts_cache.cache_len(), 1); let account = db .load_with_fixed_root(&Ancestors::default(), &account_key) @@ -11638,8 +11705,8 @@ pub mod tests { let zero_lamport_account = AccountSharedData::new(0, 0, AccountSharedData::default().owner()); let slot1_account = AccountSharedData::new(1, 1, AccountSharedData::default().owner()); - db.store_cached(0, &[(&account_key, &zero_lamport_account)]); - db.store_cached(1, &[(&account_key, &slot1_account)]); + db.store_cached(0, &[(&account_key, &zero_lamport_account, 0)]); + db.store_cached(1, &[(&account_key, &slot1_account, 1)]); db.add_root(0); db.add_root(1); @@ -11693,16 +11760,16 @@ pub mod tests { AccountSharedData::new(0, 0, AccountSharedData::default().owner()); // Store into slot 0, and then flush the slot to storage - db.store_cached(0, &[(&zero_lamport_account_key, &slot0_account)]); + db.store_cached(0, &[(&zero_lamport_account_key, &slot0_account, 0)]); // Second key keeps other lamport account entry for slot 0 alive, // preventing clean of the zero_lamport_account in slot 1. - db.store_cached(0, &[(&other_account_key, &slot0_account)]); + db.store_cached(0, &[(&other_account_key, &slot0_account, 0)]); db.add_root(0); db.flush_accounts_cache(true, None); assert!(!db.storage.get_slot_storage_entries(0).unwrap().is_empty()); // Store into slot 1, a dummy slot that will be dead and purged before flush - db.store_cached(1, &[(&zero_lamport_account_key, &zero_lamport_account)]); + db.store_cached(1, &[(&zero_lamport_account_key, &zero_lamport_account, 1)]); // Store into slot 2, which makes all updates from slot 1 outdated. // This means slot 1 is a dead slot. Later, slot 1 will be cleaned/purged @@ -11710,7 +11777,7 @@ pub mod tests { // the refcount of `zero_lamport_account_key` because cached keys do not bump // the refcount in the index. This means clean should *not* remove // `zero_lamport_account_key` from slot 2 - db.store_cached(2, &[(&zero_lamport_account_key, &zero_lamport_account)]); + db.store_cached(2, &[(&zero_lamport_account_key, &zero_lamport_account, 2)]); db.add_root(1); db.add_root(2); @@ -11832,11 +11899,11 @@ pub mod tests { / \ 1 2 (root) */ - db.store_cached(0, &[(&account_key, &zero_lamport_account)]); - db.store_cached(1, &[(&account_key, &slot1_account)]); + db.store_cached(0, &[(&account_key, &zero_lamport_account, 0)]); + db.store_cached(1, &[(&account_key, &slot1_account, 1)]); // Fodder for the scan so that the lock on `account_key` is not held - db.store_cached(1, &[(&account_key2, &slot1_account)]); - db.store_cached(2, &[(&account_key, &slot2_account)]); + db.store_cached(1, &[(&account_key2, &slot1_account, 1)]); + db.store_cached(2, &[(&account_key, &slot2_account, 2)]); db.get_accounts_delta_hash(0); let max_scan_root = 0; @@ -11930,7 +11997,7 @@ pub mod tests { for data_size in 0..num_keys { let account = AccountSharedData::new(1, data_size, &Pubkey::default()); - accounts_db.store_cached(slot, &[(&Pubkey::new_unique(), &account)]); + accounts_db.store_cached(slot, &[(&Pubkey::new_unique(), &account, slot)]); } accounts_db.add_root(slot); @@ -11997,6 +12064,7 @@ pub mod tests { &[( &scan_stall_key, &AccountSharedData::new(1, 0, &Pubkey::default()), + stall_slot, )], ); } @@ -12008,7 +12076,11 @@ pub mod tests { let space = 1; // 1 byte allows us to track by size accounts_db.store_cached( *slot, - &[(key, &AccountSharedData::new(1, space, &Pubkey::default()))], + &[( + key, + &AccountSharedData::new(1, space, &Pubkey::default()), + *slot, + )], ); } accounts_db.add_root(*slot as Slot); @@ -12052,7 +12124,11 @@ pub mod tests { // Store a slot that overwrites all previous keys, rendering all previous keys dead accounts_db.store_cached( alive_slot, - &[(key, &AccountSharedData::new(1, 0, &Pubkey::default()))], + &[( + key, + &AccountSharedData::new(1, 0, &Pubkey::default()), + alive_slot, + )], ); accounts_db.add_root(alive_slot); } @@ -12395,8 +12471,8 @@ pub mod tests { let account1 = AccountSharedData::new(1, 0, AccountSharedData::default().owner()); // Store into slot 0 - db.store_cached(0, &[(&account_key1, &account1)]); - db.store_cached(0, &[(&account_key2, &account1)]); + db.store_cached(0, &[(&account_key1, &account1, 0)]); + db.store_cached(0, &[(&account_key2, &account1, 0)]); db.add_root(0); if !do_intra_cache_clean { // If we don't want the cache doing purges before flush, @@ -12406,11 +12482,11 @@ pub mod tests { db.flush_accounts_cache(true, None); // Add an additional ref within the same slot to pubkey 1 - db.store_uncached(0, &[(&account_key1, &account1)]); + db.store_uncached(0, &[(&account_key1, &account1, 0)]); } // Make account_key1 in slot 0 outdated by updating in rooted slot 1 - db.store_cached(1, &[(&account_key1, &account1)]); + db.store_cached(1, &[(&account_key1, &account1, 1)]); db.add_root(1); // Flushes all roots db.flush_accounts_cache(true, None); @@ -12434,7 +12510,7 @@ pub mod tests { db.shrink_candidate_slots(); // Make slot 0 dead by updating the remaining key - db.store_cached(2, &[(&account_key2, &account1)]); + db.store_cached(2, &[(&account_key2, &account1, 2)]); db.add_root(2); // Flushes all roots @@ -12476,9 +12552,9 @@ pub mod tests { let account4 = AccountSharedData::new(4, 0, AccountSharedData::default().owner()); // Store accounts into slots 0 and 1 - db.store_uncached(0, &[(&account_key1, &account1)]); - db.store_uncached(0, &[(&account_key2, &account1)]); - db.store_uncached(1, &[(&account_key1, &account2)]); + db.store_uncached(0, &[(&account_key1, &account1, 0)]); + db.store_uncached(0, &[(&account_key2, &account1, 0)]); + db.store_uncached(1, &[(&account_key1, &account2, 1)]); db.get_accounts_delta_hash(0); db.get_accounts_delta_hash(1); @@ -12500,8 +12576,8 @@ pub mod tests { db.add_root(0); // store into slot 2 - db.store_uncached(2, &[(&account_key2, &account3)]); - db.store_uncached(2, &[(&account_key1, &account3)]); + db.store_uncached(2, &[(&account_key2, &account3, 2)]); + db.store_uncached(2, &[(&account_key1, &account3, 2)]); db.get_accounts_delta_hash(2); db.clean_accounts(None, false, None); @@ -12513,7 +12589,7 @@ pub mod tests { db.print_accounts_stats("post-clean3"); - db.store_uncached(3, &[(&account_key2, &account4)]); + db.store_uncached(3, &[(&account_key2, &account4, 3)]); db.get_accounts_delta_hash(3); db.add_root(3); @@ -12662,6 +12738,7 @@ pub mod tests { &[( &pubkey, &AccountSharedData::new(1, 0, AccountSharedData::default().owner()), + 0, )], ); db.add_root(0); @@ -12681,7 +12758,7 @@ pub mod tests { return; } account.set_lamports(slot + 1); - db.store_cached(slot, &[(&pubkey, &account)]); + db.store_cached(slot, &[(&pubkey, &account, slot)]); db.add_root(slot); sleep(Duration::from_millis(RACY_SLEEP_MS)); db.flush_accounts_cache(true, None); @@ -12735,7 +12812,7 @@ pub mod tests { let lamports = 42; let mut account = AccountSharedData::new(1, 0, AccountSharedData::default().owner()); account.set_lamports(lamports); - db.store_uncached(slot, &[(&pubkey, &account)]); + db.store_uncached(slot, &[(&pubkey, &account, slot)]); // Set the slot as a root so account loads will see the contents of this slot db.add_root(slot); @@ -12822,7 +12899,7 @@ pub mod tests { if flush_trial_start_receiver.recv().is_err() { return; } - db.flush_slot_cache(10, None::<&mut fn(&_, &_) -> bool>); + db.flush_slot_cache(10, None::<&mut fn(&_, &_, _) -> bool>); flush_done_sender.send(()).unwrap(); }) .unwrap() @@ -12849,7 +12926,7 @@ pub mod tests { let num_trials = 10; for _ in 0..num_trials { let pubkey = Pubkey::new_unique(); - db.store_cached(slot, &[(&pubkey, &account)]); + db.store_cached(slot, &[(&pubkey, &account, slot)]); // Wait for both threads to finish flush_trial_start_sender.send(()).unwrap(); remove_trial_start_sender.send(()).unwrap(); @@ -12891,7 +12968,7 @@ pub mod tests { return; } for slot in 0..num_cached_slots { - db.flush_slot_cache(slot, None::<&mut fn(&_, &_) -> bool>); + db.flush_slot_cache(slot, None::<&mut fn(&_, &_, _) -> bool>); } flush_done_sender.send(()).unwrap(); }) @@ -12940,7 +13017,7 @@ pub mod tests { let slot_to_pubkey_map: HashMap = (0..num_cached_slots) .map(|slot| { let pubkey = Pubkey::new_unique(); - db.store_cached(slot, &[(&pubkey, &account)]); + db.store_cached(slot, &[(&pubkey, &account, slot)]); (slot, pubkey) }) .collect(); @@ -13046,9 +13123,9 @@ pub mod tests { let account2 = AccountSharedData::new(0, 0, &pubkey2); let account3 = AccountSharedData::new(0, 0, &pubkey3); - db.store_uncached(slot1, &[(&pubkey1, &account1)]); - db.store_uncached(slot2, &[(&pubkey2, &account2)]); - db.store_uncached(slot3, &[(&pubkey3, &account3)]); + db.store_uncached(slot1, &[(&pubkey1, &account1, slot1)]); + db.store_uncached(slot2, &[(&pubkey2, &account2, slot2)]); + db.store_uncached(slot3, &[(&pubkey3, &account3, slot3)]); db.add_root(slot1); // slot 2 is _not_ a root on purpose @@ -13104,9 +13181,9 @@ pub mod tests { let account2 = AccountSharedData::new(0, 0, &pubkey2); let account3 = AccountSharedData::new(0, 0, &pubkey3); - db.store_uncached(slot1, &[(&pubkey1, &account1)]); - db.store_uncached(slot2, &[(&pubkey2, &account2)]); - db.store_uncached(slot3, &[(&pubkey3, &account3)]); + db.store_uncached(slot1, &[(&pubkey1, &account1, slot1)]); + db.store_uncached(slot2, &[(&pubkey2, &account2, slot2)]); + db.store_uncached(slot3, &[(&pubkey3, &account3, slot3)]); // slot 1 is _not_ a root on purpose db.add_root(slot2); @@ -13193,7 +13270,7 @@ pub mod tests { let shared_key = solana_sdk::pubkey::new_rand(); let account = AccountSharedData::new(1, 1, AccountSharedData::default().owner()); let slot0 = 0; - accounts.store_uncached(slot0, &[(&shared_key, &account)]); + accounts.store_uncached(slot0, &[(&shared_key, &account, slot0)]); let storage_maps = accounts .storage @@ -13243,8 +13320,8 @@ pub mod tests { let account = AccountSharedData::new(1, 1, AccountSharedData::default().owner()); let account_big = AccountSharedData::new(1, 1000, AccountSharedData::default().owner()); let slot0 = 0; - accounts.store_uncached(slot0, &[(&keys[0], &account)]); - accounts.store_uncached(slot0, &[(&keys[1], &account_big)]); + accounts.store_uncached(slot0, &[(&keys[0], &account, slot0)]); + accounts.store_uncached(slot0, &[(&keys[1], &account_big, slot0)]); let storage_maps = accounts .storage @@ -13270,7 +13347,7 @@ pub mod tests { let shared_key = solana_sdk::pubkey::new_rand(); let account = AccountSharedData::new(1, 1, AccountSharedData::default().owner()); let slot0 = 0; - accounts.store_uncached(slot0, &[(&shared_key, &account)]); + accounts.store_uncached(slot0, &[(&shared_key, &account, slot0)]); // fake out the store count to avoid the assert for slot_stores in accounts.storage.map.iter() { @@ -13312,8 +13389,8 @@ pub mod tests { // Store accounts with greater than 0 lamports let account = AccountSharedData::new(1, 1, AccountSharedData::default().owner()); - accounts.store_uncached(slot0, &[(&shared_key, &account)]); - accounts.store_uncached(slot0, &[(&unrooted_key, &account)]); + accounts.store_uncached(slot0, &[(&shared_key, &account, slot0)]); + accounts.store_uncached(slot0, &[(&unrooted_key, &account, slot0)]); // Simulate adding dirty pubkeys on bank freeze. Note this is // not a rooted slot @@ -13322,7 +13399,7 @@ pub mod tests { // On the next *rooted* slot, update the `shared_key` account to zero lamports let zero_lamport_account = AccountSharedData::new(0, 0, AccountSharedData::default().owner()); - accounts.store_uncached(slot1, &[(&shared_key, &zero_lamport_account)]); + accounts.store_uncached(slot1, &[(&shared_key, &zero_lamport_account, slot1)]); // Simulate adding dirty pubkeys on bank freeze, set root accounts.get_accounts_delta_hash(slot1); @@ -13372,19 +13449,19 @@ pub mod tests { let slot1 = 1; let account = AccountSharedData::new(111, space, &owner); - accounts_db.store_cached(slot1, &[(&pubkey, &account)]); + accounts_db.store_cached(slot1, &[(&pubkey, &account, slot1)]); accounts_db.get_accounts_delta_hash(slot1); accounts_db.add_root(slot1); let slot2 = 2; let account = AccountSharedData::new(222, space, &owner); - accounts_db.store_cached(slot2, &[(&pubkey, &account)]); + accounts_db.store_cached(slot2, &[(&pubkey, &account, slot2)]); accounts_db.get_accounts_delta_hash(slot2); accounts_db.add_root(slot2); let slot3 = 3; let account = AccountSharedData::new(0, space, &owner); - accounts_db.store_cached(slot3, &[(&pubkey, &account)]); + accounts_db.store_cached(slot3, &[(&pubkey, &account, slot3)]); accounts_db.get_accounts_delta_hash(slot3); accounts_db.add_root(slot3); diff --git a/runtime/src/accounts_db/accountsdb_plugin_utils.rs b/runtime/src/accounts_db/accountsdb_plugin_utils.rs index 6a37684574693d..94ce4f12747434 100644 --- a/runtime/src/accounts_db/accountsdb_plugin_utils.rs +++ b/runtime/src/accounts_db/accountsdb_plugin_utils.rs @@ -216,11 +216,11 @@ pub mod tests { let account1 = AccountSharedData::new(account1_lamports, 1, AccountSharedData::default().owner()); let slot0 = 0; - accounts.store_uncached(slot0, &[(&key1, &account1)]); + accounts.store_uncached(slot0, &[(&key1, &account1, slot0)]); account1_lamports = 2; let account1 = AccountSharedData::new(account1_lamports, 1, account1.owner()); - accounts.store_uncached(slot0, &[(&key1, &account1)]); + accounts.store_uncached(slot0, &[(&key1, &account1, slot0)]); let notifier = AccountsDbTestPlugin::default(); let key2 = solana_sdk::pubkey::new_rand(); @@ -228,7 +228,7 @@ pub mod tests { let account2 = AccountSharedData::new(account2_lamports, 1, AccountSharedData::default().owner()); - accounts.store_uncached(slot0, &[(&key2, &account2)]); + accounts.store_uncached(slot0, &[(&key2, &account2, slot0)]); let notifier = Arc::new(RwLock::new(notifier)); accounts.set_accountsdb_plugin_notifer(Some(notifier.clone())); @@ -267,25 +267,25 @@ pub mod tests { let account1 = AccountSharedData::new(account1_lamports, 1, AccountSharedData::default().owner()); let slot0 = 0; - accounts.store_uncached(slot0, &[(&key1, &account1)]); + accounts.store_uncached(slot0, &[(&key1, &account1, slot0)]); let key2 = solana_sdk::pubkey::new_rand(); let account2_lamports: u64 = 200; let account2 = AccountSharedData::new(account2_lamports, 1, AccountSharedData::default().owner()); - accounts.store_uncached(slot0, &[(&key2, &account2)]); + accounts.store_uncached(slot0, &[(&key2, &account2, slot0)]); account1_lamports = 2; let slot1 = 1; let account1 = AccountSharedData::new(account1_lamports, 1, account1.owner()); - accounts.store_uncached(slot1, &[(&key1, &account1)]); + accounts.store_uncached(slot1, &[(&key1, &account1, slot1)]); let notifier = AccountsDbTestPlugin::default(); let key3 = solana_sdk::pubkey::new_rand(); let account3_lamports: u64 = 300; let account3 = AccountSharedData::new(account3_lamports, 1, AccountSharedData::default().owner()); - accounts.store_uncached(slot1, &[(&key3, &account3)]); + accounts.store_uncached(slot1, &[(&key3, &account3, slot1)]); let notifier = Arc::new(RwLock::new(notifier)); accounts.set_accountsdb_plugin_notifer(Some(notifier.clone())); @@ -337,24 +337,24 @@ pub mod tests { let account1 = AccountSharedData::new(account1_lamports1, 1, AccountSharedData::default().owner()); let slot0 = 0; - accounts.store_cached(slot0, &[(&key1, &account1)]); + accounts.store_cached(slot0, &[(&key1, &account1, slot0)]); let key2 = solana_sdk::pubkey::new_rand(); let account2_lamports: u64 = 200; let account2 = AccountSharedData::new(account2_lamports, 1, AccountSharedData::default().owner()); - accounts.store_cached(slot0, &[(&key2, &account2)]); + accounts.store_cached(slot0, &[(&key2, &account2, slot0)]); let account1_lamports2 = 2; let slot1 = 1; let account1 = AccountSharedData::new(account1_lamports2, 1, account1.owner()); - accounts.store_cached(slot1, &[(&key1, &account1)]); + accounts.store_cached(slot1, &[(&key1, &account1, slot1)]); let key3 = solana_sdk::pubkey::new_rand(); let account3_lamports: u64 = 300; let account3 = AccountSharedData::new(account3_lamports, 1, AccountSharedData::default().owner()); - accounts.store_cached(slot1, &[(&key3, &account3)]); + accounts.store_cached(slot1, &[(&key3, &account3, slot1)]); let notifier = notifier.write().unwrap(); assert_eq!(notifier.accounts_notified.get(&key1).unwrap().len(), 2); diff --git a/runtime/src/accounts_index.rs b/runtime/src/accounts_index.rs index b75c821b2dc592..33e14591b09548 100644 --- a/runtime/src/accounts_index.rs +++ b/runtime/src/accounts_index.rs @@ -1737,9 +1737,11 @@ impl AccountsIndex { // Updates the given pubkey at the given slot with the new account information. // Returns true if the pubkey was newly inserted into the index, otherwise, if the // pubkey updates an existing entry in the index, returns false. + #[allow(clippy::too_many_arguments)] pub fn upsert( &self, - slot: Slot, + new_slot: Slot, + old_slot: Slot, pubkey: &Pubkey, account_owner: &Pubkey, account_data: &[u8], @@ -1762,13 +1764,23 @@ impl AccountsIndex { // - The secondary index is never consulted as primary source of truth for gets/stores. // So, what the accounts_index sees alone is sufficient as a source of truth for other non-scan // account operations. - let new_item = - PreAllocatedAccountMapEntry::new(slot, account_info, &self.storage.storage, store_raw); + let new_item = PreAllocatedAccountMapEntry::new( + new_slot, + account_info, + &self.storage.storage, + store_raw, + ); let map = &self.account_maps[self.bin_calculator.bin_from_pubkey(pubkey)]; { let r_account_maps = map.read().unwrap(); - r_account_maps.upsert(pubkey, new_item, reclaims, previous_slot_entry_was_cached); + r_account_maps.upsert( + pubkey, + new_item, + Some(old_slot), + reclaims, + previous_slot_entry_was_cached, + ); } self.update_secondary_indexes(pubkey, account_owner, account_data, account_indexes); } @@ -2856,6 +2868,7 @@ pub mod tests { let index = AccountsIndex::::default_for_tests(); let mut gc = Vec::new(); index.upsert( + 0, 0, &key.pubkey(), &Pubkey::default(), @@ -3068,6 +3081,7 @@ pub mod tests { if upsert { // insert first entry for pubkey. This will use new_entry_after_update and not call update. index.upsert( + slot0, slot0, &key, &Pubkey::default(), @@ -3105,6 +3119,7 @@ pub mod tests { // insert second entry for pubkey. This will use update and NOT use new_entry_after_update. if upsert { index.upsert( + slot1, slot1, &key, &Pubkey::default(), @@ -3179,6 +3194,7 @@ pub mod tests { w_account_maps.upsert( &key.pubkey(), new_entry, + None, &mut SlotList::default(), UPSERT_PREVIOUS_SLOT_ENTRY_WAS_CACHED_FALSE, ); @@ -3218,6 +3234,7 @@ pub mod tests { let index = AccountsIndex::::default_for_tests(); let mut gc = Vec::new(); index.upsert( + 0, 0, &key.pubkey(), &Pubkey::default(), @@ -3250,6 +3267,7 @@ pub mod tests { let index = AccountsIndex::::default_for_tests(); let mut gc = Vec::new(); index.upsert( + 0, 0, &key.pubkey(), &Pubkey::default(), @@ -3291,6 +3309,7 @@ pub mod tests { let mut pubkeys: Vec = std::iter::repeat_with(|| { let new_pubkey = solana_sdk::pubkey::new_rand(); index.upsert( + root_slot, root_slot, &new_pubkey, &Pubkey::default(), @@ -3308,6 +3327,7 @@ pub mod tests { if num_pubkeys != 0 { pubkeys.push(Pubkey::default()); index.upsert( + root_slot, root_slot, &Pubkey::default(), &Pubkey::default(), @@ -3451,6 +3471,7 @@ pub mod tests { assert!(iter.next().is_none()); let mut gc = vec![]; index.upsert( + 0, 0, &solana_sdk::pubkey::new_rand(), &Pubkey::default(), @@ -3477,6 +3498,7 @@ pub mod tests { let index = AccountsIndex::::default_for_tests(); let mut gc = Vec::new(); index.upsert( + 0, 0, &key.pubkey(), &Pubkey::default(), @@ -3592,6 +3614,7 @@ pub mod tests { let ancestors = vec![(0, 0)].into_iter().collect(); let mut gc = Vec::new(); index.upsert( + 0, 0, &key.pubkey(), &Pubkey::default(), @@ -3610,6 +3633,7 @@ pub mod tests { let mut gc = Vec::new(); index.upsert( + 0, 0, &key.pubkey(), &Pubkey::default(), @@ -3634,6 +3658,7 @@ pub mod tests { let ancestors = vec![(0, 0)].into_iter().collect(); let mut gc = Vec::new(); index.upsert( + 0, 0, &key.pubkey(), &Pubkey::default(), @@ -3645,6 +3670,7 @@ pub mod tests { ); assert!(gc.is_empty()); index.upsert( + 1, 1, &key.pubkey(), &Pubkey::default(), @@ -3672,6 +3698,7 @@ pub mod tests { let index = AccountsIndex::::default_for_tests(); let mut gc = Vec::new(); index.upsert( + 0, 0, &key.pubkey(), &Pubkey::default(), @@ -3683,6 +3710,7 @@ pub mod tests { ); assert!(gc.is_empty()); index.upsert( + 1, 1, &key.pubkey(), &Pubkey::default(), @@ -3693,6 +3721,7 @@ pub mod tests { UPSERT_PREVIOUS_SLOT_ENTRY_WAS_CACHED_FALSE, ); index.upsert( + 2, 2, &key.pubkey(), &Pubkey::default(), @@ -3703,6 +3732,7 @@ pub mod tests { UPSERT_PREVIOUS_SLOT_ENTRY_WAS_CACHED_FALSE, ); index.upsert( + 3, 3, &key.pubkey(), &Pubkey::default(), @@ -3716,6 +3746,7 @@ pub mod tests { index.add_root(1, false); index.add_root(3, false); index.upsert( + 4, 4, &key.pubkey(), &Pubkey::default(), @@ -3761,6 +3792,7 @@ pub mod tests { let mut gc = Vec::new(); assert_eq!(0, account_maps_stats_len(&index)); index.upsert( + 1, 1, &key.pubkey(), &Pubkey::default(), @@ -3773,6 +3805,7 @@ pub mod tests { assert_eq!(1, account_maps_stats_len(&index)); index.upsert( + 1, 1, &key.pubkey(), &Pubkey::default(), @@ -3793,6 +3826,7 @@ pub mod tests { assert_eq!(1, account_maps_stats_len(&index)); index.upsert( + 1, 1, &key.pubkey(), &Pubkey::default(), @@ -3868,6 +3902,7 @@ pub mod tests { // Insert slots into secondary index for slot in &slots { index.upsert( + *slot, *slot, &account_key, // Make sure these accounts are added to secondary index @@ -4043,6 +4078,7 @@ pub mod tests { // Wrong program id index.upsert( + 0, 0, &account_key, &Pubkey::default(), @@ -4057,6 +4093,7 @@ pub mod tests { // Wrong account data size index.upsert( + 0, 0, &account_key, token_id, @@ -4174,6 +4211,7 @@ pub mod tests { // First write one mint index index.upsert( + slot, slot, &account_key, token_id, @@ -4186,6 +4224,7 @@ pub mod tests { // Now write a different mint index for the same account index.upsert( + slot, slot, &account_key, token_id, @@ -4206,6 +4245,7 @@ pub mod tests { // If a later slot also introduces secondary_key1, then it should still exist in the index let later_slot = slot + 1; index.upsert( + later_slot, later_slot, &account_key, token_id, diff --git a/runtime/src/in_mem_accounts_index.rs b/runtime/src/in_mem_accounts_index.rs index 1245143fc757cb..e68bbec19d317e 100644 --- a/runtime/src/in_mem_accounts_index.rs +++ b/runtime/src/in_mem_accounts_index.rs @@ -343,6 +343,7 @@ impl InMemAccountsIndex { &self, pubkey: &Pubkey, new_value: PreAllocatedAccountMapEntry, + other_slot: Option, reclaims: &mut SlotList, previous_slot_entry_was_cached: bool, ) { @@ -352,6 +353,7 @@ impl InMemAccountsIndex { Self::lock_and_update_slot_list( entry, new_value.into(), + other_slot, reclaims, previous_slot_entry_was_cached, ); @@ -368,6 +370,7 @@ impl InMemAccountsIndex { Self::lock_and_update_slot_list( current, new_value.into(), + other_slot, reclaims, previous_slot_entry_was_cached, ); @@ -401,6 +404,7 @@ impl InMemAccountsIndex { Self::lock_and_update_slot_list( &disk_entry, new_value.into(), + other_slot, reclaims, previous_slot_entry_was_cached, ); @@ -434,21 +438,22 @@ impl InMemAccountsIndex { Self::update_stat(count, 1); } - // Try to update an item in the slot list the given `slot` If an item for the slot - // already exists in the list, remove the older item, add it to `reclaims`, and insert - // the new item. + /// Try to update an item in the slot list the given `slot` If an item for the slot + /// already exists in the list, remove the older item, add it to `reclaims`, and insert + /// the new item. + /// if 'other_slot' is some, then also remove any entries in the slot list that are at 'other_slot' pub fn lock_and_update_slot_list( current: &AccountMapEntryInner, new_value: (Slot, T), + other_slot: Option, reclaims: &mut SlotList, previous_slot_entry_was_cached: bool, ) { let mut slot_list = current.slot_list.write().unwrap(); - let (slot, new_entry) = new_value; let addref = Self::update_slot_list( &mut slot_list, - slot, - new_entry, + new_value, + other_slot, reclaims, previous_slot_entry_was_cached, ); @@ -458,41 +463,63 @@ impl InMemAccountsIndex { current.set_dirty(true); } - // modifies slot_list - // returns true if caller should addref + /// modifies slot_list + /// any entry at slot 'new_value.0' or slot 'other_slot' is replaced with 'new_value'. + /// returns true if caller should addref fn update_slot_list( - list: &mut SlotList, - slot: Slot, - account_info: T, + slot_list: &mut SlotList, + new_value: (Slot, T), + other_slot: Option, reclaims: &mut SlotList, previous_slot_entry_was_cached: bool, ) -> bool { - let mut addref = !account_info.is_cached(); - - // find other dirty entries from the same slot - for list_index in 0..list.len() { - let (s, previous_update_value) = &list[list_index]; - if *s == slot { - let previous_was_cached = previous_update_value.is_cached(); - addref = addref && previous_was_cached; + let (new_slot, new_account_info) = new_value; + let is_new_account_cached = new_account_info.is_cached(); + + // There may be two dirty accounts found (one at new_value's slot and one at other_slot) + // that are already in the slot list. Since the first one found will be swapped with the + // new account, if a second one is found, we cannot swap again (instead, just remove it). + for slot_list_index in 0..slot_list.len() { + let (cur_slot, cur_account_info) = &slot_list[slot_list_index]; + if *cur_slot == new_slot || Some(*cur_slot) == other_slot { + let is_cur_account_cached = cur_account_info.is_cached(); + let mut new_item = (new_slot, new_account_info); + std::mem::swap(&mut new_item, &mut slot_list[slot_list_index]); - let mut new_item = (slot, account_info); - std::mem::swap(&mut new_item, &mut list[list_index]); if previous_slot_entry_was_cached { - assert!(previous_was_cached); + assert!(is_cur_account_cached); } else { reclaims.push(new_item); } - list[(list_index + 1)..] - .iter() - .for_each(|item| assert!(item.0 != slot)); - return addref; + if let Some(other_slot) = other_slot { + (slot_list_index + 1..slot_list.len()) + .into_iter() + .rev() + .for_each(|i| { + let found_slot = slot_list[i].0; + if found_slot == new_slot || found_slot == other_slot { + let removed = slot_list.remove(i); + if previous_slot_entry_was_cached { + assert!(is_cur_account_cached); + } else { + reclaims.push(removed); + } + } + }); + } else { + assert!(slot_list + .iter() + .skip(slot_list_index + 1) + .all(|(slot, _)| *slot != new_slot)); + // If there's no `old_slot`, then we've found and updated the only account. + } + return is_cur_account_cached && !is_new_account_cached; } } // if we make it here, we did not find the slot in the list - list.push((slot, account_info)); - addref + slot_list.push((new_slot, new_account_info)); + !is_new_account_cached } // convert from raw data on disk to AccountMapEntry, set to age in future @@ -529,6 +556,7 @@ impl InMemAccountsIndex { InMemAccountsIndex::lock_and_update_slot_list( occupied.get(), (slot, account_info), + None, // should be None because we don't expect a different slot # during index generation &mut Vec::default(), false, ); @@ -555,6 +583,7 @@ impl InMemAccountsIndex { InMemAccountsIndex::lock_and_update_slot_list( &disk_entry, (slot, account_info), + None, // should be None because we don't expect a different slot # during index generation &mut Vec::default(), false, ); @@ -591,8 +620,7 @@ impl InMemAccountsIndex { } } - /// return tuple: - /// true if item already existed in the index + /// return true if item already existed in the index fn upsert_on_disk( &self, vacant: VacantEntry>, @@ -609,8 +637,8 @@ impl InMemAccountsIndex { let mut slot_list = slot_list.to_vec(); let addref = Self::update_slot_list( &mut slot_list, - slot, - account_info, + (slot, account_info), + None, reclaims, previous_slot_entry_was_cached, ); diff --git a/runtime/tests/accounts.rs b/runtime/tests/accounts.rs index a055d62da14da5..34145b0754b5cd 100644 --- a/runtime/tests/accounts.rs +++ b/runtime/tests/accounts.rs @@ -58,7 +58,7 @@ fn test_shrink_and_clean() { for (pubkey, account) in alive_accounts.iter_mut() { account.checked_sub_lamports(1).unwrap(); - accounts.store_uncached(current_slot, &[(pubkey, account)]); + accounts.store_uncached(current_slot, &[(pubkey, account, current_slot)]); } accounts.add_root(current_slot); } @@ -121,11 +121,11 @@ fn test_bad_bank_hash() { let account_refs: Vec<_> = existing .iter() - .map(|idx| (&accounts_keys[*idx].0, &accounts_keys[*idx].1)) + .map(|idx| (&accounts_keys[*idx].0, &accounts_keys[*idx].1, some_slot)) .collect(); db.store_uncached(some_slot, &account_refs); - for (key, account) in &account_refs { + for (key, account, _slot) in &account_refs { assert_eq!( db.load_account_hash(&ancestors, key, None, LoadHint::Unspecified) .unwrap(),