Slimmer implementation of credit-only accounts (#4592)
* Add credit-only debit/data check to verify_instruction * Store credits and pass to accounts_db * Add InstructionErrors and tests * Relax account locks for credit-only accounts * Collect credit-only account credits before passing to accounts_db to store properly * Convert System Transfer accounts to credit-only, and fixup test * Functionalize collect_accounts to unit test * Review comments * Rebase
This commit is contained in:
@ -28,7 +28,7 @@ use rayon::ThreadPool;
|
||||
use serde::de::{MapAccess, Visitor};
|
||||
use serde::ser::{SerializeMap, Serializer};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use solana_sdk::account::Account;
|
||||
use solana_sdk::account::{Account, LamportCredit};
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::fmt;
|
||||
@ -74,6 +74,7 @@ pub struct AccountInfo {
|
||||
/// An offset into the AccountsDB::storage vector
|
||||
pub type AppendVecId = usize;
|
||||
pub type InstructionAccounts = Vec<Account>;
|
||||
pub type InstructionCredits = Vec<LamportCredit>;
|
||||
pub type InstructionLoaders = Vec<Vec<(Pubkey, Account)>>;
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
@ -429,10 +430,14 @@ impl AccountsDB {
|
||||
}
|
||||
}
|
||||
|
||||
fn store_accounts(&self, fork_id: Fork, accounts: &[(&Pubkey, &Account)]) -> Vec<AccountInfo> {
|
||||
let with_meta: Vec<(StorageMeta, &Account)> = accounts
|
||||
fn store_accounts(
|
||||
&self,
|
||||
fork_id: Fork,
|
||||
accounts: &HashMap<&Pubkey, (&Account, LamportCredit)>,
|
||||
) -> Vec<AccountInfo> {
|
||||
let with_meta: Vec<(StorageMeta, &Pubkey, &Account, u64)> = accounts
|
||||
.iter()
|
||||
.map(|(pubkey, account)| {
|
||||
.map(|(pubkey, (account, credit))| {
|
||||
let write_version = self.write_version.fetch_add(1, Ordering::Relaxed) as u64;
|
||||
let data_len = if account.lamports == 0 {
|
||||
0
|
||||
@ -444,7 +449,15 @@ impl AccountsDB {
|
||||
pubkey: **pubkey,
|
||||
data_len,
|
||||
};
|
||||
(meta, *account)
|
||||
|
||||
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, *pubkey, *account, lamports)
|
||||
})
|
||||
.collect();
|
||||
let mut infos: Vec<AccountInfo> = vec![];
|
||||
@ -455,12 +468,12 @@ impl AccountsDB {
|
||||
storage.set_status(AccountStorageStatus::Full);
|
||||
continue;
|
||||
}
|
||||
for (offset, (_, account)) in rvs.iter().zip(&with_meta[infos.len()..]) {
|
||||
for (offset, (_, _, _, lamports)) in rvs.iter().zip(&with_meta[infos.len()..]) {
|
||||
storage.add_account();
|
||||
infos.push(AccountInfo {
|
||||
id: storage.id,
|
||||
offset: *offset,
|
||||
lamports: account.lamports,
|
||||
lamports: *lamports,
|
||||
});
|
||||
}
|
||||
// restore the state to available
|
||||
@ -473,12 +486,12 @@ impl AccountsDB {
|
||||
&self,
|
||||
fork_id: Fork,
|
||||
infos: Vec<AccountInfo>,
|
||||
accounts: &[(&Pubkey, &Account)],
|
||||
accounts: &HashMap<&Pubkey, (&Account, LamportCredit)>,
|
||||
) -> (Vec<(Fork, AccountInfo)>, u64) {
|
||||
let mut reclaims = Vec::with_capacity(infos.len() * 2);
|
||||
let mut reclaims: Vec<(Fork, AccountInfo)> = Vec::with_capacity(infos.len() * 2);
|
||||
let mut index = self.accounts_index.write().unwrap();
|
||||
for (i, info) in infos.into_iter().enumerate() {
|
||||
let key = &accounts[i].0;
|
||||
for (info, account) in infos.into_iter().zip(accounts.iter()) {
|
||||
let key = &account.0;
|
||||
index.insert(fork_id, key, info, &mut reclaims);
|
||||
}
|
||||
(reclaims, index.last_root)
|
||||
@ -528,7 +541,7 @@ impl AccountsDB {
|
||||
}
|
||||
|
||||
/// Store the account update.
|
||||
pub fn store(&self, fork_id: Fork, accounts: &[(&Pubkey, &Account)]) {
|
||||
pub fn store(&self, fork_id: Fork, accounts: &HashMap<&Pubkey, (&Account, LamportCredit)>) {
|
||||
let infos = self.store_accounts(fork_id, accounts);
|
||||
|
||||
let (reclaims, last_root) = self.update_index(fork_id, infos, accounts);
|
||||
@ -689,6 +702,7 @@ mod tests {
|
||||
// TODO: all the bank tests are bank specific, issue: 2194
|
||||
use super::*;
|
||||
use bincode::{deserialize_from, serialize_into, serialized_size};
|
||||
use maplit::hashmap;
|
||||
use rand::{thread_rng, Rng};
|
||||
use solana_sdk::account::Account;
|
||||
|
||||
@ -743,7 +757,7 @@ mod tests {
|
||||
let key = Pubkey::default();
|
||||
let account0 = Account::new(1, 0, &key);
|
||||
|
||||
db.store(0, &[(&key, &account0)]);
|
||||
db.store(0, &hashmap!(&key => (&account0, 0)));
|
||||
db.add_root(0);
|
||||
let ancestors = vec![(1, 1)].into_iter().collect();
|
||||
assert_eq!(db.load_slow(&ancestors, &key), Some((account0, 0)));
|
||||
@ -757,10 +771,10 @@ mod tests {
|
||||
let key = Pubkey::default();
|
||||
let account0 = Account::new(1, 0, &key);
|
||||
|
||||
db.store(0, &[(&key, &account0)]);
|
||||
db.store(0, &hashmap!(&key => (&account0, 0)));
|
||||
|
||||
let account1 = Account::new(0, 0, &key);
|
||||
db.store(1, &[(&key, &account1)]);
|
||||
db.store(1, &hashmap!(&key => (&account1, 0)));
|
||||
|
||||
let ancestors = vec![(1, 1)].into_iter().collect();
|
||||
assert_eq!(&db.load_slow(&ancestors, &key).unwrap().0, &account1);
|
||||
@ -777,10 +791,10 @@ mod tests {
|
||||
let key = Pubkey::default();
|
||||
let account0 = Account::new(1, 0, &key);
|
||||
|
||||
db.store(0, &[(&key, &account0)]);
|
||||
db.store(0, &hashmap!(&key => (&account0, 0)));
|
||||
|
||||
let account1 = Account::new(0, 0, &key);
|
||||
db.store(1, &[(&key, &account1)]);
|
||||
db.store(1, &hashmap!(&key => (&account1, 0)));
|
||||
db.add_root(0);
|
||||
|
||||
let ancestors = vec![(1, 1)].into_iter().collect();
|
||||
@ -799,7 +813,7 @@ mod tests {
|
||||
let account0 = Account::new(1, 0, &key);
|
||||
|
||||
// store value 1 in the "root", i.e. db zero
|
||||
db.store(0, &[(&key, &account0)]);
|
||||
db.store(0, &hashmap!(&key => (&account0, 0)));
|
||||
|
||||
// now we have:
|
||||
//
|
||||
@ -812,7 +826,7 @@ mod tests {
|
||||
|
||||
// store value 0 in one child
|
||||
let account1 = Account::new(0, 0, &key);
|
||||
db.store(1, &[(&key, &account1)]);
|
||||
db.store(1, &hashmap!(&key => (&account1, 0)));
|
||||
|
||||
// masking accounts is done at the Accounts level, at accountsDB we see
|
||||
// original account (but could also accept "None", which is implemented
|
||||
@ -883,8 +897,8 @@ mod tests {
|
||||
|
||||
let pubkey = Pubkey::new_rand();
|
||||
let account = Account::new(1, ACCOUNT_DATA_FILE_SIZE as usize / 3, &pubkey);
|
||||
db.store(1, &[(&pubkey, &account)]);
|
||||
db.store(1, &[(&pubkeys[0], &account)]);
|
||||
db.store(1, &hashmap!(&pubkey => (&account, 0)));
|
||||
db.store(1, &hashmap!(&pubkeys[0] => (&account, 0)));
|
||||
{
|
||||
let stores = db.storage.read().unwrap();
|
||||
let fork_0_stores = &stores.0.get(&0).unwrap();
|
||||
@ -914,11 +928,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, &[(&key, &account0)]);
|
||||
db0.store(0, &hashmap!(&key => (&account0, 0)));
|
||||
|
||||
// 0 lamports in the child
|
||||
let account1 = Account::new(0, 0, &key);
|
||||
db0.store(1, &[(&key, &account1)]);
|
||||
db0.store(1, &hashmap!(&key => (&account1, 0)));
|
||||
|
||||
// masking accounts is done at the Accounts level, at accountsDB we see
|
||||
// original account
|
||||
@ -928,6 +942,23 @@ 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>,
|
||||
@ -942,7 +973,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, &[(&pubkey, &account)]);
|
||||
accounts.store(fork, &hashmap!(&pubkey => (&account, 0)));
|
||||
}
|
||||
for t in 0..num_vote {
|
||||
let pubkey = Pubkey::new_rand();
|
||||
@ -950,7 +981,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, &[(&pubkey, &account)]);
|
||||
accounts.store(fork, &hashmap!(&pubkey => (&account, 0)));
|
||||
}
|
||||
}
|
||||
|
||||
@ -960,7 +991,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, &[(&pubkeys[idx], &account)]);
|
||||
accounts.store(fork, &hashmap!(&pubkeys[idx] => (&account, 0)));
|
||||
if account.lamports == 0 {
|
||||
let ancestors = vec![(fork, 0)].into_iter().collect();
|
||||
assert!(accounts.load_slow(&ancestors, &pubkeys[idx]).is_none());
|
||||
@ -1005,7 +1036,7 @@ mod tests {
|
||||
) {
|
||||
for idx in 0..num {
|
||||
let account = Account::new((idx + count) as u64, 0, &Account::default().owner);
|
||||
accounts.store(fork, &[(&pubkeys[idx], &account)]);
|
||||
accounts.store(fork, &hashmap!(&pubkeys[idx] => (&account, 0)));
|
||||
}
|
||||
}
|
||||
|
||||
@ -1050,7 +1081,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, &[(&key, &account)]);
|
||||
accounts.store(0, &hashmap!(&key => (&account, 0)));
|
||||
keys.push(key);
|
||||
}
|
||||
for (i, key) in keys.iter().enumerate() {
|
||||
@ -1085,7 +1116,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, &[(&pubkey1, &account1)]);
|
||||
accounts.store(0, &hashmap!(&pubkey1 => (&account1, 0)));
|
||||
{
|
||||
let stores = accounts.storage.read().unwrap();
|
||||
assert_eq!(stores.0.len(), 1);
|
||||
@ -1095,7 +1126,7 @@ mod tests {
|
||||
|
||||
let pubkey2 = Pubkey::new_rand();
|
||||
let account2 = Account::new(1, ACCOUNT_DATA_FILE_SIZE as usize / 2, &pubkey2);
|
||||
accounts.store(0, &[(&pubkey2, &account2)]);
|
||||
accounts.store(0, &hashmap!(&pubkey2 => (&account2, 0)));
|
||||
{
|
||||
let stores = accounts.storage.read().unwrap();
|
||||
assert_eq!(stores.0.len(), 1);
|
||||
@ -1118,7 +1149,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, &[(&pubkey1, &account1)]);
|
||||
accounts.store(0, &hashmap!(&pubkey1 => (&account1, 0)));
|
||||
{
|
||||
let stores = accounts.storage.read().unwrap();
|
||||
assert_eq!(stores.0.len(), 1);
|
||||
@ -1176,7 +1207,7 @@ mod tests {
|
||||
let pubkey = Pubkey::new_rand();
|
||||
let account = Account::new(1, 0, &Account::default().owner);
|
||||
//store an account
|
||||
accounts.store(0, &[(&pubkey, &account)]);
|
||||
accounts.store(0, &hashmap!(&pubkey => (&account, 0)));
|
||||
let ancestors = vec![(0, 0)].into_iter().collect();
|
||||
let info = accounts
|
||||
.accounts_index
|
||||
@ -1203,7 +1234,7 @@ mod tests {
|
||||
.is_some());
|
||||
|
||||
//store causes cleanup
|
||||
accounts.store(1, &[(&pubkey, &account)]);
|
||||
accounts.store(1, &hashmap!(&pubkey => (&account, 0)));
|
||||
|
||||
//fork is gone
|
||||
assert!(accounts.storage.read().unwrap().0.get(&0).is_none());
|
||||
@ -1268,7 +1299,7 @@ mod tests {
|
||||
loop {
|
||||
let account_bal = thread_rng().gen_range(1, 99);
|
||||
account.lamports = account_bal;
|
||||
db.store(fork_id, &[(&pubkey, &account)]);
|
||||
db.store(fork_id, &hashmap!(&pubkey => (&account, 0)));
|
||||
let (account, fork) = db.load_slow(&HashMap::new(), &pubkey).expect(
|
||||
&format!("Could not fetch stored account {}, iter {}", pubkey, i),
|
||||
);
|
||||
|
Reference in New Issue
Block a user