Accounts refactoring for forking.

* Move last_id and signature dup handling out of Accounts.
* Accounts that handle overlays.
This commit is contained in:
Anatoly Yakovenko 2019-01-29 16:33:28 -08:00 committed by Grimes
parent 668d353add
commit 1c0758e3bd
2 changed files with 201 additions and 199 deletions

View File

@ -1,7 +1,6 @@
use crate::bank::BankError;
use crate::bank::Result;
use crate::counter::Counter;
use crate::status_deque::{StatusDeque, StatusDequeError};
use bincode::serialize;
use hashbrown::{HashMap, HashSet};
use log::Level;
@ -10,6 +9,7 @@ use solana_sdk::hash::{hash, Hash};
use solana_sdk::pubkey::Pubkey;
use solana_sdk::transaction::Transaction;
use std::collections::BTreeMap;
use std::ops::Deref;
use std::sync::atomic::AtomicUsize;
use std::sync::{Mutex, RwLock};
@ -21,6 +21,7 @@ pub struct ErrorCounters {
pub account_not_found: usize,
pub account_in_use: usize,
pub last_id_not_found: usize,
pub last_id_too_old: usize,
pub reserve_last_id: usize,
pub insufficient_funds: usize,
pub duplicate_signature: usize,
@ -65,10 +66,6 @@ impl Default for Accounts {
}
impl AccountsDB {
pub fn keys(&self) -> Vec<Pubkey> {
self.accounts.keys().cloned().collect()
}
pub fn hash_internal_state(&self) -> Hash {
let mut ordered_accounts = BTreeMap::new();
@ -81,16 +78,28 @@ impl AccountsDB {
hash(&serialize(&ordered_accounts).unwrap())
}
fn load(&self, pubkey: &Pubkey) -> Option<&Account> {
if let Some(account) = self.accounts.get(pubkey) {
return Some(account);
fn load<U>(checkpoints: &[U], pubkey: &Pubkey) -> Option<Account>
where
U: Deref<Target = Self>,
{
for db in checkpoints {
if let Some(account) = db.accounts.get(pubkey) {
return Some(account.clone());
}
}
None
}
pub fn store(&mut self, pubkey: &Pubkey, account: &Account) {
/// Store the account update. If the update is to delete the account because the token balance
/// is 0, purge needs to be set to true for the delete to occur in place.
pub fn store(&mut self, purge: bool, pubkey: &Pubkey, account: &Account) {
if account.tokens == 0 {
self.accounts.remove(pubkey);
if purge {
// purge if balance is 0 and no checkpoints
self.accounts.remove(pubkey);
} else {
// store default account if balance is 0 and there's a checkpoint
self.accounts.insert(pubkey.clone(), Account::default());
}
} else {
self.accounts.insert(pubkey.clone(), account.clone());
}
@ -98,6 +107,7 @@ impl AccountsDB {
pub fn store_accounts(
&mut self,
purge: bool,
txs: &[Transaction],
res: &[Result<()>],
loaded: &[Result<(InstructionAccounts, InstructionLoaders)>],
@ -108,65 +118,51 @@ impl AccountsDB {
}
let tx = &txs[i];
let accs = raccs.as_ref().unwrap();
for (key, account) in tx.account_keys.iter().zip(accs.0.iter()) {
self.store(key, account);
let acc = raccs.as_ref().unwrap();
for (key, account) in tx.account_keys.iter().zip(acc.0.iter()) {
self.store(purge, key, account);
}
}
}
fn load_tx_accounts(
&self,
fn load_tx_accounts<U>(
checkpoints: &[U],
tx: &Transaction,
last_ids: &mut StatusDeque<Result<()>>,
max_age: usize,
error_counters: &mut ErrorCounters,
) -> Result<Vec<Account>> {
) -> Result<Vec<Account>>
where
U: Deref<Target = Self>,
{
// Copy all the accounts
if tx.signatures.is_empty() && tx.fee != 0 {
Err(BankError::MissingSignatureForFee)
} else if tx.account_keys.is_empty() || self.load(&tx.account_keys[0]).is_none() {
error_counters.account_not_found += 1;
Err(BankError::AccountNotFound)
} else if self.load(&tx.account_keys[0]).unwrap().tokens < tx.fee {
error_counters.insufficient_funds += 1;
Err(BankError::InsufficientFundsForFee)
} else {
if !last_ids.check_entry_id_age(tx.last_id, max_age) {
error_counters.last_id_not_found += 1;
return Err(BankError::LastIdNotFound);
}
// There is no way to predict what program will execute without an error
// If a fee can pay for execution then the program will be scheduled
last_ids
.reserve_signature_with_last_id(&tx.last_id, &tx.signatures[0])
.map_err(|err| match err {
StatusDequeError::LastIdNotFound => {
error_counters.reserve_last_id += 1;
BankError::LastIdNotFound
}
StatusDequeError::DuplicateSignature => {
error_counters.duplicate_signature += 1;
BankError::DuplicateSignature
}
})?;
let mut called_accounts: Vec<Account> = tx
.account_keys
.iter()
.map(|key| self.load(key).cloned().unwrap_or_default())
.collect();
called_accounts[0].tokens -= tx.fee;
Ok(called_accounts)
let mut called_accounts: Vec<Account> = vec![];
for key in &tx.account_keys {
called_accounts.push(Self::load(checkpoints, key).unwrap_or_default());
}
if called_accounts.is_empty() || called_accounts[0].tokens == 0 {
error_counters.account_not_found += 1;
Err(BankError::AccountNotFound)
} else if called_accounts[0].tokens < tx.fee {
error_counters.insufficient_funds += 1;
Err(BankError::InsufficientFundsForFee)
} else {
called_accounts[0].tokens -= tx.fee;
Ok(called_accounts)
}
}
}
fn load_executable_accounts(
&self,
fn load_executable_accounts<U>(
checkpoints: &[U],
mut program_id: Pubkey,
error_counters: &mut ErrorCounters,
) -> Result<Vec<(Pubkey, Account)>> {
) -> Result<Vec<(Pubkey, Account)>>
where
U: Deref<Target = Self>,
{
let mut accounts = Vec::new();
let mut depth = 0;
loop {
@ -181,7 +177,7 @@ impl AccountsDB {
}
depth += 1;
let program = match self.load(&program_id) {
let program = match Self::load(checkpoints, &program_id) {
Some(program) => program,
None => {
error_counters.account_not_found += 1;
@ -202,11 +198,14 @@ impl AccountsDB {
}
/// For each program_id in the transaction, load its loaders.
fn load_loaders(
&self,
fn load_loaders<U>(
checkpoints: &[U],
tx: &Transaction,
error_counters: &mut ErrorCounters,
) -> Result<Vec<Vec<(Pubkey, Account)>>> {
) -> Result<Vec<Vec<(Pubkey, Account)>>>
where
U: Deref<Target = Self>,
{
tx.instructions
.iter()
.map(|ix| {
@ -215,25 +214,26 @@ impl AccountsDB {
return Err(BankError::AccountNotFound);
}
let program_id = tx.program_ids[ix.program_ids_index as usize];
self.load_executable_accounts(program_id, error_counters)
Self::load_executable_accounts(checkpoints, program_id, error_counters)
})
.collect()
}
fn load_accounts(
&self,
fn load_accounts<U>(
checkpoints: &[U],
txs: &[Transaction],
last_ids: &mut StatusDeque<Result<()>>,
lock_results: Vec<Result<()>>,
max_age: usize,
error_counters: &mut ErrorCounters,
) -> Vec<Result<(InstructionAccounts, InstructionLoaders)>> {
) -> Vec<Result<(InstructionAccounts, InstructionLoaders)>>
where
U: Deref<Target = Self>,
{
txs.iter()
.zip(lock_results.into_iter())
.map(|etx| match etx {
(tx, Ok(())) => {
let accounts = self.load_tx_accounts(tx, last_ids, max_age, error_counters)?;
let loaders = self.load_loaders(tx, error_counters)?;
let accounts = Self::load_tx_accounts(checkpoints, tx, error_counters)?;
let loaders = Self::load_loaders(checkpoints, tx, error_counters)?;
Ok((accounts, loaders))
}
(_, Err(e)) => Err(e),
@ -248,36 +248,35 @@ impl AccountsDB {
pub fn transaction_count(&self) -> u64 {
self.transaction_count
}
pub fn account_values_slow(&self) -> Vec<(Pubkey, solana_sdk::account::Account)> {
self.accounts.iter().map(|(x, y)| (*x, y.clone())).collect()
}
fn merge(&mut self, other: Self) {
self.transaction_count += other.transaction_count;
self.accounts.extend(other.accounts)
}
}
impl Accounts {
// TODO use a fork
pub fn copy_for_tpu(&self) -> Self {
let copy = Accounts::default();
{
let mut accounts_db = copy.accounts_db.write().unwrap();
for (key, val) in self.accounts_db.read().unwrap().accounts.iter() {
accounts_db.accounts.insert(key.clone(), val.clone());
}
accounts_db.transaction_count = self.transaction_count();
}
copy
/// Slow because lock is held for 1 operation insted of many
pub fn load_slow<U>(checkpoints: &[U], pubkey: &Pubkey) -> Option<Account>
where
U: Deref<Target = Self>,
{
let dbs: Vec<_> = checkpoints
.iter()
.map(|obj| obj.accounts_db.read().unwrap())
.collect();
AccountsDB::load(&dbs, pubkey)
}
pub fn keys(&self) -> Vec<Pubkey> {
self.accounts_db.read().unwrap().keys()
}
/// Slow because lock is held for 1 operation instead of many
pub fn load_slow(&self, pubkey: &Pubkey) -> Option<Account> {
self.accounts_db.read().unwrap().load(pubkey).cloned()
}
/// Slow because lock is held for 1 operation instead of many
pub fn store_slow(&self, pubkey: &Pubkey, account: &Account) {
self.accounts_db.write().unwrap().store(pubkey, account)
/// Slow because lock is held for 1 operation insted of many
/// * purge - if the account token value is 0 and purge is true then delete the account.
/// purge should be set to false for overlays, and true for the root checkpoint.
pub fn store_slow(&self, purge: bool, pubkey: &Pubkey, account: &Account) {
self.accounts_db
.write()
.unwrap()
.store(purge, pubkey, account)
}
fn lock_account(
@ -341,25 +340,28 @@ impl Accounts {
.for_each(|(tx, result)| Self::unlock_account(tx, result, &mut account_locks));
}
pub fn load_accounts(
&self,
pub fn load_accounts<U>(
checkpoints: &[U],
txs: &[Transaction],
last_ids: &mut StatusDeque<Result<()>>,
lock_results: Vec<Result<()>>,
max_age: usize,
results: Vec<Result<()>>,
error_counters: &mut ErrorCounters,
) -> Vec<Result<(InstructionAccounts, InstructionLoaders)>> {
self.accounts_db.read().unwrap().load_accounts(
txs,
last_ids,
lock_results,
max_age,
error_counters,
)
) -> Vec<Result<(InstructionAccounts, InstructionLoaders)>>
where
U: Deref<Target = Self>,
{
let dbs: Vec<_> = checkpoints
.iter()
.map(|obj| obj.accounts_db.read().unwrap())
.collect();
AccountsDB::load_accounts(&dbs, txs, results, error_counters)
}
/// Store the accounts into the DB
/// * purge - if the account token value is 0 and purge is true then delete the account.
/// purge should be set to false for overlays, and true for the root checkpoint.
pub fn store_accounts(
&self,
purge: bool,
txs: &[Transaction],
res: &[Result<()>],
loaded: &[Result<(InstructionAccounts, InstructionLoaders)>],
@ -367,7 +369,7 @@ impl Accounts {
self.accounts_db
.write()
.unwrap()
.store_accounts(txs, res, loaded)
.store_accounts(purge, txs, res, loaded)
}
pub fn increment_transaction_count(&self, tx_count: usize) {
@ -380,6 +382,27 @@ impl Accounts {
pub fn transaction_count(&self) -> u64 {
self.accounts_db.read().unwrap().transaction_count()
}
/// accounts starts with an empty data structure for every fork
/// self is root, merge the fork into self
pub fn merge_into_root(&self, other: Self) {
assert!(other.account_locks.lock().unwrap().is_empty());
let db = other.accounts_db.into_inner().unwrap();
let mut mydb = self.accounts_db.write().unwrap();
mydb.merge(db)
}
pub fn copy_for_tpu(&self) -> Self {
//TODO: deprecate this in favor of forks and merge_into_root
let copy = Accounts::default();
{
let mut accounts_db = copy.accounts_db.write().unwrap();
for (key, val) in self.accounts_db.read().unwrap().accounts.iter() {
accounts_db.accounts.insert(key.clone(), val.clone());
}
accounts_db.transaction_count = self.transaction_count();
}
copy
}
}
#[cfg(test)]
@ -394,22 +417,30 @@ mod tests {
use solana_sdk::transaction::Instruction;
use solana_sdk::transaction::Transaction;
#[test]
fn test_purge() {
let mut db = AccountsDB::default();
let key = Pubkey::default();
let account = Account::new(0, 0, Pubkey::default());
// accounts are deleted when their token value is 0 and purge is true
db.store(false, &key, &account);
assert_eq!(AccountsDB::load(&[&db], &key), Some(account.clone()));
// purge should be set to true for the root checkpoint
db.store(true, &key, &account);
assert_eq!(AccountsDB::load(&[&db], &key), None);
}
fn load_accounts(
tx: Transaction,
ka: &Vec<(Pubkey, Account)>,
error_counters: &mut ErrorCounters,
max_age: usize,
) -> Vec<Result<(InstructionAccounts, InstructionLoaders)>> {
let accounts = Accounts::default();
for ka in ka.iter() {
accounts.store_slow(&ka.0, &ka.1);
accounts.store_slow(true, &ka.0, &ka.1);
}
let id = Default::default();
let mut last_ids: StatusDeque<Result<()>> = StatusDeque::default();
last_ids.register_tick(&id);
accounts.load_accounts(&[tx], &mut last_ids, vec![Ok(())], max_age, error_counters)
Accounts::load_accounts(&[&accounts], &[tx], vec![Ok(())], error_counters)
}
fn assert_counters(error_counters: &ErrorCounters, expected: [usize; 8]) {
@ -423,34 +454,6 @@ mod tests {
assert_eq!(error_counters.missing_signature_for_fee, expected[7]);
}
#[test]
fn test_load_accounts_index_out_of_bounds() {
let mut accounts: Vec<(Pubkey, Account)> = Vec::new();
let mut error_counters = ErrorCounters::default();
let keypair = Keypair::new();
let key0 = keypair.pubkey();
let account = Account::new(1, 1, Pubkey::default());
accounts.push((key0, account));
let instructions = vec![Instruction::new(1, &(), vec![0, 1])];
let tx = Transaction::new_with_instructions(
&[&keypair],
&[], // TODO this should contain a key, should fail
Hash::default(),
0,
vec![solana_native_loader::id()],
instructions,
);
let loaded_accounts = load_accounts(tx, &accounts, &mut error_counters, 10);
assert_counters(&error_counters, [1, 0, 0, 0, 0, 0, 0, 0]);
assert_eq!(loaded_accounts.len(), 1);
assert_eq!(loaded_accounts[0], Err(BankError::AccountNotFound));
}
#[test]
fn test_load_accounts_no_key() {
let accounts: Vec<(Pubkey, Account)> = Vec::new();
@ -466,7 +469,7 @@ mod tests {
instructions,
);
let loaded_accounts = load_accounts(tx, &accounts, &mut error_counters, 10);
let loaded_accounts = load_accounts(tx, &accounts, &mut error_counters);
assert_counters(&error_counters, [1, 0, 0, 0, 0, 0, 0, 0]);
assert_eq!(loaded_accounts.len(), 1);
@ -490,7 +493,7 @@ mod tests {
instructions,
);
let loaded_accounts = load_accounts(tx, &accounts, &mut error_counters, 10);
let loaded_accounts = load_accounts(tx, &accounts, &mut error_counters);
assert_counters(&error_counters, [1, 0, 0, 0, 0, 0, 0, 0]);
assert_eq!(loaded_accounts.len(), 1);
@ -522,45 +525,13 @@ mod tests {
instructions,
);
let loaded_accounts = load_accounts(tx, &accounts, &mut error_counters, 10);
let loaded_accounts = load_accounts(tx, &accounts, &mut error_counters);
assert_counters(&error_counters, [1, 0, 0, 0, 0, 0, 0, 0]);
assert_eq!(loaded_accounts.len(), 1);
assert_eq!(loaded_accounts[0], Err(BankError::AccountNotFound));
}
#[test]
fn test_load_accounts_max_age() {
let mut accounts: Vec<(Pubkey, Account)> = Vec::new();
let mut error_counters = ErrorCounters::default();
let keypair = Keypair::new();
let key0 = keypair.pubkey();
let key1 = Pubkey::new(&[5u8; 32]);
let account = Account::new(1, 1, Pubkey::default());
accounts.push((key0, account));
let account = Account::new(2, 1, Pubkey::default());
accounts.push((key1, account));
let instructions = vec![Instruction::new(1, &(), vec![0])];
let tx = Transaction::new_with_instructions(
&[&keypair],
&[],
Hash::default(),
0,
vec![solana_native_loader::id()],
instructions,
);
let loaded_accounts = load_accounts(tx, &accounts, &mut error_counters, 0);
assert_counters(&error_counters, [0, 0, 1, 0, 0, 0, 0, 0]);
assert_eq!(loaded_accounts.len(), 1);
assert_eq!(loaded_accounts[0], Err(BankError::LastIdNotFound));
}
#[test]
fn test_load_accounts_insufficient_funds() {
let mut accounts: Vec<(Pubkey, Account)> = Vec::new();
@ -582,7 +553,7 @@ mod tests {
instructions,
);
let loaded_accounts = load_accounts(tx, &accounts, &mut error_counters, 10);
let loaded_accounts = load_accounts(tx, &accounts, &mut error_counters);
assert_counters(&error_counters, [0, 0, 0, 0, 1, 0, 0, 0]);
assert_eq!(loaded_accounts.len(), 1);
@ -614,7 +585,7 @@ mod tests {
instructions,
);
let loaded_accounts = load_accounts(tx, &accounts, &mut error_counters, 10);
let loaded_accounts = load_accounts(tx, &accounts, &mut error_counters);
assert_counters(&error_counters, [0, 0, 0, 0, 0, 0, 0, 0]);
assert_eq!(loaded_accounts.len(), 1);
@ -686,7 +657,7 @@ mod tests {
instructions,
);
let loaded_accounts = load_accounts(tx, &accounts, &mut error_counters, 10);
let loaded_accounts = load_accounts(tx, &accounts, &mut error_counters);
assert_counters(&error_counters, [0, 0, 0, 0, 0, 0, 1, 0]);
assert_eq!(loaded_accounts.len(), 1);
@ -720,7 +691,7 @@ mod tests {
instructions,
);
let loaded_accounts = load_accounts(tx, &accounts, &mut error_counters, 10);
let loaded_accounts = load_accounts(tx, &accounts, &mut error_counters);
assert_counters(&error_counters, [1, 0, 0, 0, 0, 0, 0, 0]);
assert_eq!(loaded_accounts.len(), 1);
@ -753,7 +724,7 @@ mod tests {
instructions,
);
let loaded_accounts = load_accounts(tx, &accounts, &mut error_counters, 10);
let loaded_accounts = load_accounts(tx, &accounts, &mut error_counters);
assert_counters(&error_counters, [1, 0, 0, 0, 0, 0, 0, 0]);
assert_eq!(loaded_accounts.len(), 1);
@ -802,7 +773,7 @@ mod tests {
instructions,
);
let loaded_accounts = load_accounts(tx, &accounts, &mut error_counters, 10);
let loaded_accounts = load_accounts(tx, &accounts, &mut error_counters);
assert_counters(&error_counters, [0, 0, 0, 0, 0, 0, 0, 0]);
assert_eq!(loaded_accounts.len(), 1);

View File

@ -11,7 +11,7 @@ use crate::genesis_block::GenesisBlock;
use crate::leader_scheduler::LeaderScheduler;
use crate::poh_recorder::PohRecorder;
use crate::runtime::{self, RuntimeError};
use crate::status_deque::{Status, StatusDeque, MAX_ENTRY_IDS};
use crate::status_deque::{Status, StatusDeque, StatusDequeError, MAX_ENTRY_IDS};
use bincode::deserialize;
use itertools::Itertools;
use log::Level;
@ -159,13 +159,14 @@ impl Bank {
mint_account.tokens -= genesis_block.bootstrap_leader_tokens;
bootstrap_leader_account.tokens += genesis_block.bootstrap_leader_tokens;
self.accounts.store_slow(
true,
&genesis_block.bootstrap_leader_id,
&bootstrap_leader_account,
);
};
self.accounts
.store_slow(&genesis_block.mint_id, &mint_account);
.store_slow(true, &genesis_block.mint_id, &mint_account);
self.register_tick(&genesis_block.last_id());
}
@ -178,7 +179,7 @@ impl Bank {
loader: solana_native_loader::id(),
};
self.accounts
.store_slow(&system_program::id(), &system_program_account);
.store_slow(true, &system_program::id(), &system_program_account);
}
fn add_builtin_programs(&self) {
@ -193,7 +194,7 @@ impl Bank {
loader: solana_native_loader::id(),
};
self.accounts
.store_slow(&vote_program::id(), &vote_program_account);
.store_slow(true, &vote_program::id(), &vote_program_account);
// Storage program
let storage_program_account = Account {
@ -204,7 +205,7 @@ impl Bank {
loader: solana_native_loader::id(),
};
self.accounts
.store_slow(&storage_program::id(), &storage_program_account);
.store_slow(true, &storage_program::id(), &storage_program_account);
let storage_system_account = Account {
tokens: 1,
@ -214,7 +215,7 @@ impl Bank {
loader: Pubkey::default(),
};
self.accounts
.store_slow(&storage_program::system_id(), &storage_system_account);
.store_slow(true, &storage_program::system_id(), &storage_system_account);
// Bpf Loader
let bpf_loader_account = Account {
@ -226,7 +227,7 @@ impl Bank {
};
self.accounts
.store_slow(&bpf_loader::id(), &bpf_loader_account);
.store_slow(true, &bpf_loader::id(), &bpf_loader_account);
// Budget program
let budget_program_account = Account {
@ -237,7 +238,7 @@ impl Bank {
loader: solana_native_loader::id(),
};
self.accounts
.store_slow(&budget_program::id(), &budget_program_account);
.store_slow(true, &budget_program::id(), &budget_program_account);
// Erc20 token program
let erc20_account = Account {
@ -249,7 +250,7 @@ impl Bank {
};
self.accounts
.store_slow(&token_program::id(), &erc20_account);
.store_slow(true, &token_program::id(), &erc20_account);
}
/// Return the last entry ID registered.
@ -428,17 +429,46 @@ impl Bank {
}
fn load_accounts(
&self,
txs: &[Transaction],
results: Vec<Result<()>>,
error_counters: &mut ErrorCounters,
) -> Vec<Result<(InstructionAccounts, InstructionLoaders)>> {
Accounts::load_accounts(&[&self.accounts], txs, results, error_counters)
}
fn check_signatures(
&self,
txs: &[Transaction],
lock_results: Vec<Result<()>>,
max_age: usize,
error_counters: &mut ErrorCounters,
) -> Vec<Result<(InstructionAccounts, InstructionLoaders)>> {
) -> Vec<Result<()>> {
let mut last_ids = self.last_ids.write().unwrap();
self.accounts
.load_accounts(txs, &mut last_ids, lock_results, max_age, error_counters)
txs.iter()
.zip(lock_results.into_iter())
.map(|(tx, lock_res)| {
if lock_res.is_ok() {
let r = if !last_ids.check_entry_id_age(tx.last_id, max_age) {
Err(StatusDequeError::LastIdNotFound)
} else {
last_ids.reserve_signature_with_last_id(&tx.last_id, &tx.signatures[0])
};
r.map_err(|err| match err {
StatusDequeError::LastIdNotFound => {
error_counters.reserve_last_id += 1;
BankError::LastIdNotFound
}
StatusDequeError::DuplicateSignature => {
error_counters.duplicate_signature += 1;
BankError::DuplicateSignature
}
})
} else {
lock_res
}
})
.collect()
}
#[allow(clippy::type_complexity)]
fn load_and_execute_transactions(
&self,
@ -452,8 +482,8 @@ impl Bank {
debug!("processing transactions: {}", txs.len());
let mut error_counters = ErrorCounters::default();
let now = Instant::now();
let mut loaded_accounts =
self.load_accounts(txs, lock_results, max_age, &mut error_counters);
let sig_results = self.check_signatures(txs, lock_results, max_age, &mut error_counters);
let mut loaded_accounts = self.load_accounts(txs, sig_results, &mut error_counters);
let tick_height = self.tick_height();
let load_elapsed = now.elapsed();
@ -539,7 +569,8 @@ impl Bank {
executed: &[Result<()>],
) {
let now = Instant::now();
self.accounts.store_accounts(txs, executed, loaded_accounts);
self.accounts
.store_accounts(true, txs, executed, loaded_accounts);
// Check account subscriptions and send notifications
self.send_account_notifications(txs, executed, loaded_accounts);
@ -753,7 +784,7 @@ impl Bank {
}
pub fn get_account(&self, pubkey: &Pubkey) -> Option<Account> {
self.accounts.load_slow(pubkey)
Accounts::load_slow(&[&self.accounts], pubkey)
}
pub fn transaction_count(&self) -> u64 {