Credit-Only Accounts: Cache account balance for thread-safe load/store (#4691)

* Implement CreditOnlyLocks

* Update credit-only atomic on account load

* Update credit-only atomic after bank.freeze_lock; store credits if all credit-only lock references are dropped

* Commit credit-only credits on bank freeze

* Update core to CreditAccountLocks

* Impl credit-only in System Transfer

* Rework CreditAccountLocks, test, and fix bugs

* Review comments: Pass CreditAccountLocks by reference; Tighten up insert block

* Only store credits on completed slot

* Check balance in bench_exchange funding to ensure commit_credits has completed

* Add is_debitable info to KeyedAccount meta to pass into programs

* Reinstate CreditOnlyLocks check on lock_account

* Rework CreditAccountLocks to remove strong_count usage

* Add multi-threaded credit-only locks test

* Improve RwLocks usage

* Review comments: panic if bad things happen; tighter code

* Assert lock_accounts race does not happen

* Revert panic if bad things happen; not a bad thing
This commit is contained in:
Tyera Eulberg
2019-06-27 17:25:10 -04:00
committed by GitHub
parent 979df17328
commit 66552d7047
12 changed files with 657 additions and 177 deletions

View File

@ -480,11 +480,11 @@ impl AccountsDB {
fn store_accounts(
&self,
fork_id: Fork,
accounts: &HashMap<&Pubkey, (&Account, LamportCredit)>,
accounts: &HashMap<&Pubkey, &Account>,
) -> Vec<AccountInfo> {
let with_meta: Vec<(StorageMeta, &Account, u64)> = accounts
let with_meta: Vec<(StorageMeta, &Account)> = accounts
.iter()
.map(|(pubkey, (account, credit))| {
.map(|(pubkey, account)| {
let write_version = self.write_version.fetch_add(1, Ordering::Relaxed) as u64;
let data_len = if account.lamports == 0 {
0
@ -497,14 +497,7 @@ impl AccountsDB {
data_len,
};
let mut lamports: u64 = account.lamports;
if *credit > 0 {
// Only credit-only accounts with multiple transaction credits will have credit
// values greater than 0. Update lamports to collect all credits.
lamports += credit;
}
(meta, *account, lamports)
(meta, *account)
})
.collect();
let mut infos: Vec<AccountInfo> = vec![];
@ -515,12 +508,12 @@ impl AccountsDB {
storage.set_status(AccountStorageStatus::Full);
continue;
}
for (offset, (_, _, lamports)) in rvs.iter().zip(&with_meta[infos.len()..]) {
for (offset, (_, account)) in rvs.iter().zip(&with_meta[infos.len()..]) {
storage.add_account();
infos.push(AccountInfo {
id: storage.id,
offset: *offset,
lamports: *lamports,
lamports: account.lamports,
});
}
// restore the state to available
@ -533,7 +526,7 @@ impl AccountsDB {
&self,
fork_id: Fork,
infos: Vec<AccountInfo>,
accounts: &HashMap<&Pubkey, (&Account, LamportCredit)>,
accounts: &HashMap<&Pubkey, &Account>,
) -> (Vec<(Fork, AccountInfo)>, u64) {
let mut reclaims: Vec<(Fork, AccountInfo)> = Vec::with_capacity(infos.len() * 2);
let mut index = self.accounts_index.write().unwrap();
@ -588,7 +581,7 @@ impl AccountsDB {
}
/// Store the account update.
pub fn store(&self, fork_id: Fork, accounts: &HashMap<&Pubkey, (&Account, LamportCredit)>) {
pub fn store(&self, fork_id: Fork, accounts: &HashMap<&Pubkey, &Account>) {
let infos = self.store_accounts(fork_id, accounts);
let (reclaims, last_root) = self.update_index(fork_id, infos, accounts);
@ -746,7 +739,7 @@ mod tests {
let key = Pubkey::default();
let account0 = Account::new(1, 0, &key);
db.store(0, &hashmap!(&key => (&account0, 0)));
db.store(0, &hashmap!(&key => &account0));
db.add_root(0);
let ancestors = vec![(1, 1)].into_iter().collect();
assert_eq!(db.load_slow(&ancestors, &key), Some((account0, 0)));
@ -760,10 +753,10 @@ mod tests {
let key = Pubkey::default();
let account0 = Account::new(1, 0, &key);
db.store(0, &hashmap!(&key => (&account0, 0)));
db.store(0, &hashmap!(&key => &account0));
let account1 = Account::new(0, 0, &key);
db.store(1, &hashmap!(&key => (&account1, 0)));
db.store(1, &hashmap!(&key => &account1));
let ancestors = vec![(1, 1)].into_iter().collect();
assert_eq!(&db.load_slow(&ancestors, &key).unwrap().0, &account1);
@ -780,10 +773,10 @@ mod tests {
let key = Pubkey::default();
let account0 = Account::new(1, 0, &key);
db.store(0, &hashmap!(&key => (&account0, 0)));
db.store(0, &hashmap!(&key => &account0));
let account1 = Account::new(0, 0, &key);
db.store(1, &hashmap!(&key => (&account1, 0)));
db.store(1, &hashmap!(&key => &account1));
db.add_root(0);
let ancestors = vec![(1, 1)].into_iter().collect();
@ -802,7 +795,7 @@ mod tests {
let account0 = Account::new(1, 0, &key);
// store value 1 in the "root", i.e. db zero
db.store(0, &hashmap!(&key => (&account0, 0)));
db.store(0, &hashmap!(&key => &account0));
// now we have:
//
@ -815,7 +808,7 @@ mod tests {
// store value 0 in one child
let account1 = Account::new(0, 0, &key);
db.store(1, &hashmap!(&key => (&account1, 0)));
db.store(1, &hashmap!(&key => &account1));
// masking accounts is done at the Accounts level, at accountsDB we see
// original account (but could also accept "None", which is implemented
@ -886,8 +879,8 @@ mod tests {
let pubkey = Pubkey::new_rand();
let account = Account::new(1, ACCOUNT_DATA_FILE_SIZE as usize / 3, &pubkey);
db.store(1, &hashmap!(&pubkey => (&account, 0)));
db.store(1, &hashmap!(&pubkeys[0] => (&account, 0)));
db.store(1, &hashmap!(&pubkey => &account));
db.store(1, &hashmap!(&pubkeys[0] => &account));
{
let stores = db.storage.read().unwrap();
let fork_0_stores = &stores.0.get(&0).unwrap();
@ -917,11 +910,11 @@ mod tests {
let paths = get_tmp_accounts_path!();
let db0 = AccountsDB::new(&paths.paths);
let account0 = Account::new(1, 0, &key);
db0.store(0, &hashmap!(&key => (&account0, 0)));
db0.store(0, &hashmap!(&key => &account0));
// 0 lamports in the child
let account1 = Account::new(0, 0, &key);
db0.store(1, &hashmap!(&key => (&account1, 0)));
db0.store(1, &hashmap!(&key => &account1));
// masking accounts is done at the Accounts level, at accountsDB we see
// original account
@ -931,23 +924,6 @@ mod tests {
assert_eq!(db0.load_slow(&ancestors, &key), Some((account0, 0)));
}
#[test]
fn test_credit_check() {
let paths = get_tmp_accounts_path!();
let db = AccountsDB::new(&paths.paths);
let pubkey = Pubkey::default();
let account = Account::new(1, 0, &pubkey);
db.store(0, &hashmap!(&pubkey => (&account, 0)));
// Load account with lamport credit
let account_new = Account::new(5, 0, &pubkey);
db.store(0, &hashmap!(&pubkey => (&account_new, 4)));
let ancestors = vec![(0, 0)].into_iter().collect();
let (account_load, _) = db.load_slow(&ancestors, &pubkey).unwrap();
assert_eq!(account_load.lamports, 9);
}
fn create_account(
accounts: &AccountsDB,
pubkeys: &mut Vec<Pubkey>,
@ -962,7 +938,7 @@ mod tests {
pubkeys.push(pubkey.clone());
let ancestors = vec![(fork, 0)].into_iter().collect();
assert!(accounts.load_slow(&ancestors, &pubkey).is_none());
accounts.store(fork, &hashmap!(&pubkey => (&account, 0)));
accounts.store(fork, &hashmap!(&pubkey => &account));
}
for t in 0..num_vote {
let pubkey = Pubkey::new_rand();
@ -970,7 +946,7 @@ mod tests {
pubkeys.push(pubkey.clone());
let ancestors = vec![(fork, 0)].into_iter().collect();
assert!(accounts.load_slow(&ancestors, &pubkey).is_none());
accounts.store(fork, &hashmap!(&pubkey => (&account, 0)));
accounts.store(fork, &hashmap!(&pubkey => &account));
}
}
@ -980,7 +956,7 @@ mod tests {
let ancestors = vec![(fork, 0)].into_iter().collect();
if let Some((mut account, _)) = accounts.load_slow(&ancestors, &pubkeys[idx]) {
account.lamports = account.lamports + 1;
accounts.store(fork, &hashmap!(&pubkeys[idx] => (&account, 0)));
accounts.store(fork, &hashmap!(&pubkeys[idx] => &account));
if account.lamports == 0 {
let ancestors = vec![(fork, 0)].into_iter().collect();
assert!(accounts.load_slow(&ancestors, &pubkeys[idx]).is_none());
@ -1031,7 +1007,7 @@ mod tests {
) {
for idx in 0..num {
let account = Account::new((idx + count) as u64, 0, &Account::default().owner);
accounts.store(fork, &hashmap!(&pubkeys[idx] => (&account, 0)));
accounts.store(fork, &hashmap!(&pubkeys[idx] => &account));
}
}
@ -1076,7 +1052,7 @@ mod tests {
for i in 0..9 {
let key = Pubkey::new_rand();
let account = Account::new(i + 1, size as usize / 4, &key);
accounts.store(0, &hashmap!(&key => (&account, 0)));
accounts.store(0, &hashmap!(&key => &account));
keys.push(key);
}
for (i, key) in keys.iter().enumerate() {
@ -1111,7 +1087,7 @@ mod tests {
let status = [AccountStorageStatus::Available, AccountStorageStatus::Full];
let pubkey1 = Pubkey::new_rand();
let account1 = Account::new(1, ACCOUNT_DATA_FILE_SIZE as usize / 2, &pubkey1);
accounts.store(0, &hashmap!(&pubkey1 => (&account1, 0)));
accounts.store(0, &hashmap!(&pubkey1 => &account1));
{
let stores = accounts.storage.read().unwrap();
assert_eq!(stores.0.len(), 1);
@ -1121,7 +1097,7 @@ mod tests {
let pubkey2 = Pubkey::new_rand();
let account2 = Account::new(1, ACCOUNT_DATA_FILE_SIZE as usize / 2, &pubkey2);
accounts.store(0, &hashmap!(&pubkey2 => (&account2, 0)));
accounts.store(0, &hashmap!(&pubkey2 => &account2));
{
let stores = accounts.storage.read().unwrap();
assert_eq!(stores.0.len(), 1);
@ -1144,7 +1120,7 @@ mod tests {
// lots of stores, but 3 storages should be enough for everything
for i in 0..25 {
let index = i % 2;
accounts.store(0, &hashmap!(&pubkey1 => (&account1, 0)));
accounts.store(0, &hashmap!(&pubkey1 => &account1));
{
let stores = accounts.storage.read().unwrap();
assert_eq!(stores.0.len(), 1);
@ -1202,7 +1178,7 @@ mod tests {
let pubkey = Pubkey::new_rand();
let account = Account::new(1, 0, &Account::default().owner);
//store an account
accounts.store(0, &hashmap!(&pubkey => (&account, 0)));
accounts.store(0, &hashmap!(&pubkey => &account));
let ancestors = vec![(0, 0)].into_iter().collect();
let info = accounts
.accounts_index
@ -1222,7 +1198,7 @@ mod tests {
.is_some());
//store causes cleanup
accounts.store(1, &hashmap!(&pubkey => (&account, 0)));
accounts.store(1, &hashmap!(&pubkey => &account));
//fork is gone
assert!(accounts.storage.read().unwrap().0.get(&0).is_none());
@ -1296,7 +1272,7 @@ mod tests {
loop {
let account_bal = thread_rng().gen_range(1, 99);
account.lamports = account_bal;
db.store(fork_id, &hashmap!(&pubkey => (&account, 0)));
db.store(fork_id, &hashmap!(&pubkey => &account));
let (account, fork) = db.load_slow(&HashMap::new(), &pubkey).expect(
&format!("Could not fetch stored account {}, iter {}", pubkey, i),
);