Purge accounts with lamports=0 on rooted forks (#6315)

This commit is contained in:
sakridge
2019-10-23 12:46:48 -07:00
committed by GitHub
parent 6ce115ec95
commit f1172617cc
6 changed files with 188 additions and 39 deletions

View File

@ -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];

View File

@ -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> {

View File

@ -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);