Purge accounts with lamports=0 on rooted forks (#6315)
This commit is contained in:
@ -559,6 +559,26 @@ impl AccountsDB {
|
||||
false
|
||||
}
|
||||
|
||||
pub fn purge_zero_lamport_accounts(&self, ancestors: &HashMap<u64, usize>) {
|
||||
let accounts_index = self.accounts_index.read().unwrap();
|
||||
let mut purges = Vec::new();
|
||||
accounts_index.scan_accounts(ancestors, |pubkey, (account_info, fork)| {
|
||||
if account_info.lamports == 0 && accounts_index.is_root(fork) {
|
||||
purges.push(*pubkey);
|
||||
}
|
||||
});
|
||||
drop(accounts_index);
|
||||
let mut reclaims = Vec::new();
|
||||
let mut accounts_index = self.accounts_index.write().unwrap();
|
||||
for purge in &purges {
|
||||
reclaims.extend(accounts_index.purge(purge));
|
||||
}
|
||||
let last_root = accounts_index.last_root;
|
||||
drop(accounts_index);
|
||||
let mut dead_forks = self.remove_dead_accounts(reclaims);
|
||||
self.cleanup_dead_forks(&mut dead_forks, last_root);
|
||||
}
|
||||
|
||||
pub fn scan_accounts<F, A>(&self, ancestors: &HashMap<Fork, usize>, scan_func: F) -> A
|
||||
where
|
||||
F: Fn(&mut A, Option<(&Pubkey, Account, Fork)>) -> (),
|
||||
@ -746,6 +766,10 @@ impl AccountsDB {
|
||||
}
|
||||
|
||||
pub fn hash_account_data(fork: Fork, lamports: u64, data: &[u8], pubkey: &Pubkey) -> Hash {
|
||||
if lamports == 0 {
|
||||
return Hash::default();
|
||||
}
|
||||
|
||||
let mut hasher = Hasher::default();
|
||||
let mut buf = [0u8; 8];
|
||||
|
||||
|
@ -29,6 +29,17 @@ impl<T: Clone> AccountsIndex<T> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn purge(&mut self, pubkey: &Pubkey) -> Vec<(Fork, T)> {
|
||||
let mut list = self.account_maps.get(&pubkey).unwrap().write().unwrap();
|
||||
let reclaims = list
|
||||
.iter()
|
||||
.filter(|(fork, _)| self.is_root(*fork))
|
||||
.cloned()
|
||||
.collect();
|
||||
list.retain(|(fork, _)| !self.is_root(*fork));
|
||||
reclaims
|
||||
}
|
||||
|
||||
// find the latest fork and T in a list for a given ancestor
|
||||
// returns index into 'list' if found, None if not.
|
||||
fn latest_fork(&self, ancestors: &HashMap<Fork, usize>, list: &[(Fork, T)]) -> Option<usize> {
|
||||
|
@ -1403,6 +1403,28 @@ impl Bank {
|
||||
.verify_hash_internal_state(self.slot(), &self.ancestors)
|
||||
}
|
||||
|
||||
/// A snapshot bank should be purged of 0 lamport accounts which are not part of the hash
|
||||
/// calculation and could shield other real accounts.
|
||||
pub fn verify_snapshot_bank(&self) -> bool {
|
||||
self.rc
|
||||
.accounts
|
||||
.verify_hash_internal_state(self.slot(), &self.ancestors)
|
||||
&& !self.has_accounts_with_zero_lamports()
|
||||
}
|
||||
|
||||
fn has_accounts_with_zero_lamports(&self) -> bool {
|
||||
self.rc.accounts.accounts_db.scan_accounts(
|
||||
&self.ancestors,
|
||||
|collector: &mut bool, option| {
|
||||
if let Some((_, account, _)) = option {
|
||||
if account.lamports == 0 {
|
||||
*collector = true;
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/// Return the number of ticks per slot
|
||||
pub fn ticks_per_slot(&self) -> u64 {
|
||||
self.ticks_per_slot
|
||||
@ -1584,6 +1606,13 @@ impl Bank {
|
||||
.accounts
|
||||
.commit_credits(&self.ancestors, self.slot());
|
||||
}
|
||||
|
||||
pub fn purge_zero_lamport_accounts(&self) {
|
||||
self.rc
|
||||
.accounts
|
||||
.accounts_db
|
||||
.purge_zero_lamport_accounts(&self.ancestors);
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Bank {
|
||||
@ -1753,6 +1782,71 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
fn assert_no_zero_balance_accounts(bank: &Arc<Bank>) {
|
||||
assert!(!bank.has_accounts_with_zero_lamports());
|
||||
}
|
||||
|
||||
// Test that purging 0 lamports accounts works.
|
||||
#[test]
|
||||
fn test_purge_empty_accounts() {
|
||||
solana_logger::setup();
|
||||
let (genesis_block, mint_keypair) = create_genesis_block(500_000);
|
||||
let parent = Arc::new(Bank::new(&genesis_block));
|
||||
let mut bank = parent;
|
||||
for _ in 0..10 {
|
||||
let blockhash = bank.last_blockhash();
|
||||
let pubkey = Pubkey::new_rand();
|
||||
let tx = system_transaction::transfer_now(&mint_keypair, &pubkey, 0, blockhash);
|
||||
bank.process_transaction(&tx).unwrap();
|
||||
bank.squash();
|
||||
bank = Arc::new(new_from_parent(&bank));
|
||||
}
|
||||
|
||||
bank.purge_zero_lamport_accounts();
|
||||
|
||||
assert_no_zero_balance_accounts(&bank);
|
||||
|
||||
let bank0 = Arc::new(new_from_parent(&bank));
|
||||
let blockhash = bank.last_blockhash();
|
||||
let keypair = Keypair::new();
|
||||
let tx = system_transaction::transfer_now(&mint_keypair, &keypair.pubkey(), 10, blockhash);
|
||||
bank0.process_transaction(&tx).unwrap();
|
||||
|
||||
let bank1 = Arc::new(new_from_parent(&bank0));
|
||||
let pubkey = Pubkey::new_rand();
|
||||
let blockhash = bank.last_blockhash();
|
||||
let tx = system_transaction::transfer_now(&keypair, &pubkey, 10, blockhash);
|
||||
bank1.process_transaction(&tx).unwrap();
|
||||
|
||||
assert_eq!(bank0.get_account(&keypair.pubkey()).unwrap().lamports, 10);
|
||||
assert_eq!(bank1.get_account(&keypair.pubkey()), None);
|
||||
bank0.purge_zero_lamport_accounts();
|
||||
|
||||
assert_eq!(bank0.get_account(&keypair.pubkey()).unwrap().lamports, 10);
|
||||
assert_eq!(bank1.get_account(&keypair.pubkey()), None);
|
||||
bank1.purge_zero_lamport_accounts();
|
||||
|
||||
assert_eq!(bank0.get_account(&keypair.pubkey()).unwrap().lamports, 10);
|
||||
assert_eq!(bank1.get_account(&keypair.pubkey()), None);
|
||||
|
||||
assert!(bank0.verify_hash_internal_state());
|
||||
|
||||
// Squash and then verify hash_internal value
|
||||
bank0.squash();
|
||||
assert!(bank0.verify_hash_internal_state());
|
||||
|
||||
bank1.squash();
|
||||
assert!(bank1.verify_hash_internal_state());
|
||||
|
||||
// keypair should have 0 tokens on both forks
|
||||
assert_eq!(bank0.get_account(&keypair.pubkey()), None);
|
||||
assert_eq!(bank1.get_account(&keypair.pubkey()), None);
|
||||
bank1.purge_zero_lamport_accounts();
|
||||
|
||||
assert!(bank1.verify_hash_internal_state());
|
||||
assert_no_zero_balance_accounts(&bank1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_two_payments_to_one_party() {
|
||||
let (genesis_block, mint_keypair) = create_genesis_block(10_000);
|
||||
|
Reference in New Issue
Block a user