Skip to content

Commit

Permalink
Purge accounts with lamports=0 on rooted forks
Browse files Browse the repository at this point in the history
  • Loading branch information
sakridge committed Oct 22, 2019
1 parent 0b2d4f3 commit d82b25c
Show file tree
Hide file tree
Showing 4 changed files with 133 additions and 1 deletion.
3 changes: 2 additions & 1 deletion ledger/src/snapshot_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ where
}

pub fn add_snapshot<P: AsRef<Path>>(snapshot_path: P, bank: &Bank) -> Result<()> {
bank.purge_zero_lamport_accounts();
let slot = bank.slot();
// snapshot_path/slot
let slot_snapshot_dir = get_bank_snapshot_dir(snapshot_path, slot);
Expand Down Expand Up @@ -220,7 +221,7 @@ pub fn bank_from_archive<P: AsRef<Path>>(
unpacked_accounts_dir,
)?;

if !bank.verify_hash_internal_state() {
if !bank.verify_snapshot_bank() {
panic!("Snapshot bank failed to verify");
}

Expand Down
24 changes: 24 additions & 0 deletions runtime/src/accounts_db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)>) -> (),
Expand Down Expand Up @@ -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];

Expand Down
11 changes: 11 additions & 0 deletions runtime/src/accounts_index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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> {
Expand Down
96 changes: 96 additions & 0 deletions runtime/src/bank.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1379,6 +1379,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
Expand Down Expand Up @@ -1560,6 +1582,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 {
Expand Down Expand Up @@ -1729,6 +1758,73 @@ mod tests {
);
}

fn assert_no_zero_balance_accounts(bank: &Arc<Bank>) {
assert!(!bank.has_accounts_with_zero_lamports());
}

#[test]
fn create_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!(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);

info!("verifying hash_internal 0..");
assert!(bank0.verify_hash_internal_state());

bank0.squash();
assert!(bank0.verify_hash_internal_state());

bank1.squash();

info!("verifying hash_internal post squash..");

assert!(bank1.verify_hash_internal_state());

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);
Expand Down

0 comments on commit d82b25c

Please sign in to comment.