Prevent scans on unrooted slots from seeing partial clean (#13628)
Co-authored-by: Carl Lin <carl@solana.com>
This commit is contained in:
@@ -1775,7 +1775,6 @@ impl Bank {
|
||||
//this bank and all its parents are now on the rooted path
|
||||
let mut roots = vec![self.slot()];
|
||||
roots.append(&mut self.parents().iter().map(|p| p.slot()).collect());
|
||||
*self.rc.parent.write().unwrap() = None;
|
||||
|
||||
let mut squash_accounts_time = Measure::start("squash_accounts_time");
|
||||
for slot in roots.iter().rev() {
|
||||
@@ -1784,6 +1783,8 @@ impl Bank {
|
||||
}
|
||||
squash_accounts_time.stop();
|
||||
|
||||
*self.rc.parent.write().unwrap() = None;
|
||||
|
||||
let mut squash_cache_time = Measure::start("squash_cache_time");
|
||||
roots
|
||||
.iter()
|
||||
@@ -3506,6 +3507,13 @@ impl Bank {
|
||||
parents
|
||||
}
|
||||
|
||||
/// Compute all the parents of the bank including this bank itself
|
||||
pub fn parents_inclusive(self: &Arc<Self>) -> Vec<Arc<Bank>> {
|
||||
let mut all = vec![self.clone()];
|
||||
all.extend(self.parents().into_iter());
|
||||
all
|
||||
}
|
||||
|
||||
pub fn store_account(&self, pubkey: &Pubkey, account: &Account) {
|
||||
self.rc.accounts.store_slow(self.slot(), pubkey, account);
|
||||
|
||||
@@ -3686,7 +3694,7 @@ impl Bank {
|
||||
}
|
||||
|
||||
pub fn get_largest_accounts(
|
||||
&self,
|
||||
self: &Arc<Self>,
|
||||
num: usize,
|
||||
filter_by_address: &HashSet<Pubkey>,
|
||||
filter: AccountAddressFilter,
|
||||
@@ -10670,6 +10678,86 @@ pub(crate) mod tests {
|
||||
update_thread.join().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_store_scan_consistency_unrooted() {
|
||||
test_store_scan_consistency(
|
||||
|bank0, bank_to_scan_sender, pubkeys_to_modify, program_id, starting_lamports| {
|
||||
let mut current_major_fork_bank = bank0;
|
||||
loop {
|
||||
let mut current_minor_fork_bank = current_major_fork_bank.clone();
|
||||
let num_new_banks = 2;
|
||||
let lamports = current_minor_fork_bank.slot() + starting_lamports + 1;
|
||||
// Modify banks on the two banks on the minor fork
|
||||
for pubkeys_to_modify in &pubkeys_to_modify
|
||||
.iter()
|
||||
.chunks(pubkeys_to_modify.len() / num_new_banks)
|
||||
{
|
||||
current_minor_fork_bank = Arc::new(Bank::new_from_parent(
|
||||
¤t_minor_fork_bank,
|
||||
&solana_sdk::pubkey::new_rand(),
|
||||
current_minor_fork_bank.slot() + 2,
|
||||
));
|
||||
let account = Account::new(lamports, 0, &program_id);
|
||||
// Write partial updates to each of the banks in the minor fork so if any of them
|
||||
// get cleaned up, there will be keys with the wrong account value/missing.
|
||||
for key in pubkeys_to_modify {
|
||||
current_minor_fork_bank.store_account(key, &account);
|
||||
}
|
||||
current_minor_fork_bank.freeze();
|
||||
}
|
||||
|
||||
// All the parent banks made in this iteration of the loop
|
||||
// are currently discoverable, previous parents should have
|
||||
// been squashed
|
||||
assert_eq!(
|
||||
current_minor_fork_bank.parents_inclusive().len(),
|
||||
num_new_banks + 1,
|
||||
);
|
||||
|
||||
// `next_major_bank` needs to be sandwiched between the minor fork banks
|
||||
// That way, after the squash(), the minor fork has the potential to see a
|
||||
// *partial* clean of the banks < `next_major_bank`.
|
||||
current_major_fork_bank = Arc::new(Bank::new_from_parent(
|
||||
¤t_major_fork_bank,
|
||||
&solana_sdk::pubkey::new_rand(),
|
||||
current_minor_fork_bank.slot() - 1,
|
||||
));
|
||||
let lamports = current_major_fork_bank.slot() + starting_lamports + 1;
|
||||
let account = Account::new(lamports, 0, &program_id);
|
||||
for key in pubkeys_to_modify.iter() {
|
||||
// Store rooted updates to these pubkeys such that the minor
|
||||
// fork updates to the same keys will be deleted by clean
|
||||
current_major_fork_bank.store_account(key, &account);
|
||||
}
|
||||
|
||||
// Send the last new bank to the scan thread to perform the scan.
|
||||
// Meanwhile this thread will continually set roots on a separate fork
|
||||
// and squash.
|
||||
/*
|
||||
bank 0
|
||||
/ \
|
||||
minor bank 1 \
|
||||
/ current_major_fork_bank
|
||||
minor bank 2
|
||||
|
||||
*/
|
||||
// The capacity of the channel is 1 so that this thread will wait for the scan to finish before starting
|
||||
// the next iteration, allowing the scan to stay in sync with these updates
|
||||
// such that every scan will see this interruption.
|
||||
current_major_fork_bank.freeze();
|
||||
current_major_fork_bank.squash();
|
||||
if bank_to_scan_sender.send(current_minor_fork_bank).is_err() {
|
||||
// Channel was disconnected, exit
|
||||
return;
|
||||
}
|
||||
|
||||
// Try to get clean to overlap with the scan
|
||||
current_major_fork_bank.clean_accounts(false);
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_store_scan_consistency_root() {
|
||||
test_store_scan_consistency(
|
||||
|
Reference in New Issue
Block a user