solana/runtime/src/accounts.rs

3904 lines
139 KiB
Rust
Raw Normal View History

use {
crate::{
account_overrides::AccountOverrides,
account_rent_state::{check_rent_state_with_account, RentState},
accounts_db::{
AccountShrinkThreshold, AccountsAddRootTiming, AccountsDb, AccountsDbConfig,
BankHashInfo, CalcAccountsHashDataSource, IncludeSlotInHash, LoadHint, LoadedAccount,
ScanStorageResult, ACCOUNTS_DB_CONFIG_FOR_BENCHMARKS, ACCOUNTS_DB_CONFIG_FOR_TESTING,
},
accounts_index::{
AccountSecondaryIndexes, IndexKey, ScanConfig, ScanError, ScanResult, ZeroLamport,
},
accounts_update_notifier_interface::AccountsUpdateNotifier,
ancestors::Ancestors,
bank::{
2022-04-19 09:29:29 -07:00
Bank, NonceFull, NonceInfo, RentDebits, Rewrites, TransactionCheckResult,
TransactionExecutionResult,
},
blockhash_queue::BlockhashQueue,
rent_collector::RentCollector,
storable_accounts::StorableAccounts,
system_instruction_processor::{get_system_account_kind, SystemAccountKind},
transaction_error_metrics::TransactionErrorMetrics,
},
dashmap::{
mapref::entry::Entry::{Occupied, Vacant},
DashMap,
},
log::*,
rand::{thread_rng, Rng},
solana_address_lookup_table_program::{error::AddressLookupError, state::AddressLookupTable},
solana_sdk::{
account::{Account, AccountSharedData, ReadableAccount, WritableAccount},
account_utils::StateMut,
bpf_loader_upgradeable::{self, UpgradeableLoaderState},
clock::{BankId, Slot},
feature_set::{
self, remove_deprecated_request_unit_ix, use_default_units_in_fee_calculation,
FeatureSet,
},
2022-02-11 16:23:16 -08:00
fee::FeeStructure,
genesis_config::ClusterType,
hash::Hash,
message::{
v0::{LoadedAddresses, MessageAddressTableLookup},
SanitizedMessage,
},
native_loader,
nonce::{
state::{DurableNonce, Versions as NonceVersions},
State as NonceState,
},
pubkey::Pubkey,
signature::Signature,
slot_hashes::SlotHashes,
system_program,
sysvar::{self, epoch_schedule::EpochSchedule, instructions::construct_instructions_data},
transaction::{Result, SanitizedTransaction, TransactionAccountLocks, TransactionError},
transaction_context::{IndexOfAccount, TransactionAccount},
},
std::{
cmp::Reverse,
collections::{hash_map, BinaryHeap, HashMap, HashSet},
ops::RangeBounds,
path::PathBuf,
sync::{
atomic::{AtomicBool, AtomicUsize, Ordering},
Arc, Mutex,
},
},
2020-01-28 17:03:20 -08:00
};
2022-04-19 19:34:00 -07:00
pub type PubkeyAccountSlot = (Pubkey, AccountSharedData, Slot);
#[derive(Debug, Default, AbiExample)]
pub struct AccountLocks {
write_locks: HashSet<Pubkey>,
readonly_locks: HashMap<Pubkey, u64>,
}
impl AccountLocks {
fn is_locked_readonly(&self, key: &Pubkey) -> bool {
self.readonly_locks
.get(key)
.map_or(false, |count| *count > 0)
}
fn is_locked_write(&self, key: &Pubkey) -> bool {
self.write_locks.contains(key)
}
fn insert_new_readonly(&mut self, key: &Pubkey) {
assert!(self.readonly_locks.insert(*key, 1).is_none());
}
fn lock_readonly(&mut self, key: &Pubkey) -> bool {
self.readonly_locks.get_mut(key).map_or(false, |count| {
*count += 1;
true
})
}
fn unlock_readonly(&mut self, key: &Pubkey) {
if let hash_map::Entry::Occupied(mut occupied_entry) = self.readonly_locks.entry(*key) {
let count = occupied_entry.get_mut();
*count -= 1;
if *count == 0 {
occupied_entry.remove_entry();
}
}
}
fn unlock_write(&mut self, key: &Pubkey) {
self.write_locks.remove(key);
}
}
/// This structure handles synchronization for db
#[derive(Debug, AbiExample)]
pub struct Accounts {
2021-02-18 23:42:09 -08:00
/// Single global AccountsDb
pub accounts_db: Arc<AccountsDb>,
/// set of read-only and writable accounts which are currently
/// being processed by banking/replay threads
pub(crate) account_locks: Mutex<AccountLocks>,
}
// for the load instructions
pub type TransactionRent = u64;
pub type TransactionProgramIndices = Vec<Vec<IndexOfAccount>>;
#[derive(PartialEq, Eq, Debug, Clone)]
pub struct LoadedTransaction {
pub accounts: Vec<TransactionAccount>,
pub program_indices: TransactionProgramIndices,
pub rent: TransactionRent,
pub rent_debits: RentDebits,
}
2021-11-18 16:07:17 -08:00
pub type TransactionLoadResult = (Result<LoadedTransaction>, Option<NonceFull>);
2019-09-08 11:13:59 -07:00
pub enum AccountAddressFilter {
Exclude, // exclude all addresses matching the filter
Include, // only include addresses matching the filter
}
impl Accounts {
pub fn default_for_tests() -> Self {
Self {
accounts_db: Arc::new(AccountsDb::default_for_tests()),
account_locks: Mutex::default(),
}
}
pub fn new_with_config_for_tests(
paths: Vec<PathBuf>,
cluster_type: &ClusterType,
account_indexes: AccountSecondaryIndexes,
caching_enabled: bool,
shrink_ratio: AccountShrinkThreshold,
) -> Self {
Self::new_with_config(
paths,
cluster_type,
account_indexes,
caching_enabled,
shrink_ratio,
Some(ACCOUNTS_DB_CONFIG_FOR_TESTING),
None,
&Arc::default(),
)
}
pub fn new_with_config_for_benches(
paths: Vec<PathBuf>,
cluster_type: &ClusterType,
account_indexes: AccountSecondaryIndexes,
caching_enabled: bool,
shrink_ratio: AccountShrinkThreshold,
) -> Self {
Self::new_with_config(
paths,
cluster_type,
account_indexes,
caching_enabled,
shrink_ratio,
Some(ACCOUNTS_DB_CONFIG_FOR_BENCHMARKS),
None,
&Arc::default(),
)
}
pub fn new_with_config(
2020-12-31 18:06:03 -08:00
paths: Vec<PathBuf>,
cluster_type: &ClusterType,
account_indexes: AccountSecondaryIndexes,
caching_enabled: bool,
shrink_ratio: AccountShrinkThreshold,
accounts_db_config: Option<AccountsDbConfig>,
accounts_update_notifier: Option<AccountsUpdateNotifier>,
exit: &Arc<AtomicBool>,
2020-12-31 18:06:03 -08:00
) -> Self {
Multi-version snapshot support (#9980) * Multi-version snapshot support * rustfmt * Remove CLI options and runtime support for selection output snapshot version. Address some clippy complaints. * Muzzle clippy type complexity warning. Despite clippy's suggestion, it is not currently possible to create type aliases for traits and so everything within the 'Box<...>' cannot be type aliased. This then leaves creating full blown traits, and either implementing said traits by closure (somehow) or moving the closures into new structs implementing said traits which seems a bit of a palaver. Alternatively it is possible to define and use the type alias 'type ResultBox<T> = Result<Box<T>>' which does seems rather pointless and not a great reduction in complexity but is enough to keep clippy happy. In the end I simply went with squelching the clippy warning. * Remove now unused Serialize/Deserialize trait implementations for AccountStorageEntry and AppendVec * refactor versioned de/serialisers * rename serde_utils to serde_snapshot * move call to accounts_db.generate_index() back down to context_accountsdb_from_stream() * update version 1.1.1 to 1.2.0 remove nested use of serialize_bytes * cleanups * Add back measurement of account storage entry serialization. Remove construction of Vec and HashMap temporaries during serialization. * consolidate serialisation test cases into serde_snapshot. clean up leakage of implementation details in serde_snapshot. * move short term / legacy snapshot code into child module * add serialize_iter_as_tuple * preliminary integration of following commit commit 6d58b73c47294bfb93465d5a83cd2175660b6e6d Author: Ryo Onodera <ryoqun@gmail.com> Date: Wed May 20 14:02:02 2020 +0900 Confine snapshot 1.1 relic to versioned codepath * refactored serde_snapshot, rustfmt legacy accounts_db format now "owns" both leading u64s, legacy bank_rc format has none * reduce type complexity (clippy)
2020-05-22 10:54:24 -07:00
Self {
2021-02-18 23:42:09 -08:00
accounts_db: Arc::new(AccountsDb::new_with_config(
2020-12-31 18:06:03 -08:00
paths,
cluster_type,
account_indexes,
caching_enabled,
shrink_ratio,
accounts_db_config,
accounts_update_notifier,
exit,
2020-12-31 18:06:03 -08:00
)),
account_locks: Mutex::new(AccountLocks::default()),
Multi-version snapshot support (#9980) * Multi-version snapshot support * rustfmt * Remove CLI options and runtime support for selection output snapshot version. Address some clippy complaints. * Muzzle clippy type complexity warning. Despite clippy's suggestion, it is not currently possible to create type aliases for traits and so everything within the 'Box<...>' cannot be type aliased. This then leaves creating full blown traits, and either implementing said traits by closure (somehow) or moving the closures into new structs implementing said traits which seems a bit of a palaver. Alternatively it is possible to define and use the type alias 'type ResultBox<T> = Result<Box<T>>' which does seems rather pointless and not a great reduction in complexity but is enough to keep clippy happy. In the end I simply went with squelching the clippy warning. * Remove now unused Serialize/Deserialize trait implementations for AccountStorageEntry and AppendVec * refactor versioned de/serialisers * rename serde_utils to serde_snapshot * move call to accounts_db.generate_index() back down to context_accountsdb_from_stream() * update version 1.1.1 to 1.2.0 remove nested use of serialize_bytes * cleanups * Add back measurement of account storage entry serialization. Remove construction of Vec and HashMap temporaries during serialization. * consolidate serialisation test cases into serde_snapshot. clean up leakage of implementation details in serde_snapshot. * move short term / legacy snapshot code into child module * add serialize_iter_as_tuple * preliminary integration of following commit commit 6d58b73c47294bfb93465d5a83cd2175660b6e6d Author: Ryo Onodera <ryoqun@gmail.com> Date: Wed May 20 14:02:02 2020 +0900 Confine snapshot 1.1 relic to versioned codepath * refactored serde_snapshot, rustfmt legacy accounts_db format now "owns" both leading u64s, legacy bank_rc format has none * reduce type complexity (clippy)
2020-05-22 10:54:24 -07:00
}
}
pub fn new_from_parent(parent: &Accounts, slot: Slot, parent_slot: Slot) -> Self {
let accounts_db = parent.accounts_db.clone();
accounts_db.set_hash(slot, parent_slot);
Self {
accounts_db,
account_locks: Mutex::new(AccountLocks::default()),
}
}
2021-02-18 23:42:09 -08:00
pub(crate) fn new_empty(accounts_db: AccountsDb) -> Self {
Self {
accounts_db: Arc::new(accounts_db),
account_locks: Mutex::new(AccountLocks::default()),
}
}
fn construct_instructions_account(
message: &SanitizedMessage,
is_owned_by_sysvar: bool,
) -> AccountSharedData {
let data = construct_instructions_data(&message.decompile_instructions());
let owner = if is_owned_by_sysvar {
sysvar::id()
} else {
system_program::id()
};
AccountSharedData::from(Account {
data,
owner,
..Account::default()
})
}
fn load_transaction(
&self,
ancestors: &Ancestors,
tx: &SanitizedTransaction,
fee: u64,
error_counters: &mut TransactionErrorMetrics,
rent_collector: &RentCollector,
feature_set: &FeatureSet,
account_overrides: Option<&AccountOverrides>,
) -> Result<LoadedTransaction> {
// Copy all the accounts
2019-03-29 09:05:06 -07:00
let message = tx.message();
// NOTE: this check will never fail because `tx` is sanitized
if tx.signatures().is_empty() && fee != 0 {
2019-03-13 12:58:44 -07:00
Err(TransactionError::MissingSignatureForFee)
} else {
// 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
let mut validated_fee_payer = false;
let mut tx_rent: TransactionRent = 0;
let account_keys = message.account_keys();
let mut accounts = Vec::with_capacity(account_keys.len());
let mut account_deps = Vec::with_capacity(account_keys.len());
let mut rent_debits = RentDebits::default();
for (i, key) in account_keys.iter().enumerate() {
let account = if !message.is_non_loader_key(i) {
// Fill in an empty account for the program slots.
AccountSharedData::default()
} else {
#[allow(clippy::collapsible_else_if)]
2021-08-31 14:51:26 -07:00
if solana_sdk::sysvar::instructions::check_id(key) {
Self::construct_instructions_account(
message,
feature_set
.is_active(&feature_set::instructions_sysvar_owned_by_sysvar::id()),
)
} else {
let (mut account, rent) = if let Some(account_override) =
account_overrides.and_then(|overrides| overrides.get(key))
{
(account_override.clone(), 0)
} else {
self.accounts_db
.load_with_fixed_root(ancestors, key)
.map(|(mut account, _)| {
if message.is_writable(i) {
let rent_due = rent_collector
.collect_from_existing_account(
key,
&mut account,
self.accounts_db.filler_account_suffix.as_ref(),
)
.rent_amount;
(account, rent_due)
} else {
(account, 0)
}
})
.unwrap_or_default()
};
if !validated_fee_payer {
if i != 0 {
warn!("Payer index should be 0! {:?}", tx);
}
Self::validate_fee_payer(
key,
&mut account,
i as IndexOfAccount,
error_counters,
rent_collector,
feature_set,
fee,
)?;
validated_fee_payer = true;
}
if bpf_loader_upgradeable::check_id(account.owner()) {
if message.is_writable(i) && !message.is_upgradeable_loader_present() {
error_counters.invalid_writable_account += 1;
return Err(TransactionError::InvalidWritableAccount);
}
if account.executable() {
// The upgradeable loader requires the derived ProgramData account
if let Ok(UpgradeableLoaderState::Program {
programdata_address,
}) = account.state()
{
if let Some((programdata_account, _)) = self
.accounts_db
.load_with_fixed_root(ancestors, &programdata_address)
{
account_deps
.push((programdata_address, programdata_account));
} else {
error_counters.account_not_found += 1;
return Err(TransactionError::ProgramAccountNotFound);
}
} else {
error_counters.invalid_program_for_execution += 1;
return Err(TransactionError::InvalidProgramForExecution);
}
}
} else if account.executable() && message.is_writable(i) {
error_counters.invalid_writable_account += 1;
return Err(TransactionError::InvalidWritableAccount);
}
tx_rent += rent;
rent_debits.insert(key, rent, account.lamports());
account
}
};
accounts.push((*key, account));
}
debug_assert_eq!(accounts.len(), account_keys.len());
// Appends the account_deps at the end of the accounts,
// this way they can be accessed in a uniform way.
// At places where only the accounts are needed,
// the account_deps are truncated using e.g:
// accounts.iter().take(message.account_keys.len())
accounts.append(&mut account_deps);
if validated_fee_payer {
let program_indices = message
.instructions()
.iter()
.map(|instruction| {
self.load_executable_accounts(
ancestors,
&mut accounts,
instruction.program_id_index as IndexOfAccount,
error_counters,
)
})
.collect::<Result<Vec<Vec<IndexOfAccount>>>>()?;
Ok(LoadedTransaction {
accounts,
program_indices,
rent: tx_rent,
rent_debits,
})
} else {
error_counters.account_not_found += 1;
Err(TransactionError::AccountNotFound)
}
}
}
2019-01-04 16:39:04 -08:00
fn validate_fee_payer(
payer_address: &Pubkey,
payer_account: &mut AccountSharedData,
payer_index: IndexOfAccount,
error_counters: &mut TransactionErrorMetrics,
rent_collector: &RentCollector,
feature_set: &FeatureSet,
fee: u64,
) -> Result<()> {
if payer_account.lamports() == 0 {
error_counters.account_not_found += 1;
return Err(TransactionError::AccountNotFound);
}
let min_balance = match get_system_account_kind(payer_account).ok_or_else(|| {
error_counters.invalid_account_for_fee += 1;
TransactionError::InvalidAccountForFee
})? {
SystemAccountKind::System => 0,
SystemAccountKind::Nonce => {
// Should we ever allow a fees charge to zero a nonce account's
// balance. The state MUST be set to uninitialized in that case
rent_collector.rent.minimum_balance(NonceState::size())
}
};
if payer_account.lamports() < fee + min_balance {
error_counters.insufficient_funds += 1;
return Err(TransactionError::InsufficientFundsForFee);
}
let payer_pre_rent_state = RentState::from_account(payer_account, &rent_collector.rent);
payer_account
.checked_sub_lamports(fee)
.map_err(|_| TransactionError::InsufficientFundsForFee)?;
let payer_post_rent_state = RentState::from_account(payer_account, &rent_collector.rent);
check_rent_state_with_account(
&payer_pre_rent_state,
&payer_post_rent_state,
payer_address,
payer_account,
feature_set
.is_active(&feature_set::include_account_index_in_rent_error::ID)
.then_some(payer_index),
feature_set
.is_active(&feature_set::prevent_crediting_accounts_that_end_rent_paying::id()),
)
}
fn load_executable_accounts(
&self,
ancestors: &Ancestors,
accounts: &mut Vec<TransactionAccount>,
mut program_account_index: IndexOfAccount,
error_counters: &mut TransactionErrorMetrics,
) -> Result<Vec<IndexOfAccount>> {
let mut account_indices = Vec::new();
let mut program_id = match accounts.get(program_account_index as usize) {
2022-03-23 17:03:01 -07:00
Some(program_account) => program_account.0,
None => {
error_counters.account_not_found += 1;
return Err(TransactionError::ProgramAccountNotFound);
}
};
let mut depth = 0;
while !native_loader::check_id(&program_id) {
if depth >= 5 {
error_counters.call_chain_too_deep += 1;
2019-03-13 12:58:44 -07:00
return Err(TransactionError::CallChainTooDeep);
}
depth += 1;
program_account_index = match self
.accounts_db
.load_with_fixed_root(ancestors, &program_id)
{
Some((program_account, _)) => {
let account_index = accounts.len() as IndexOfAccount;
accounts.push((program_id, program_account));
account_index
}
None => {
error_counters.account_not_found += 1;
return Err(TransactionError::ProgramAccountNotFound);
}
};
let program = &accounts[program_account_index as usize].1;
if !program.executable() {
error_counters.invalid_program_for_execution += 1;
return Err(TransactionError::InvalidProgramForExecution);
}
2020-12-14 15:35:10 -08:00
// Add loader to chain
2021-04-23 15:49:47 -07:00
let program_owner = *program.owner();
account_indices.insert(0, program_account_index);
2020-12-14 15:35:10 -08:00
if bpf_loader_upgradeable::check_id(&program_owner) {
// The upgradeable loader requires the derived ProgramData account
if let Ok(UpgradeableLoaderState::Program {
programdata_address,
}) = program.state()
{
let programdata_account_index = match self
.accounts_db
.load_with_fixed_root(ancestors, &programdata_address)
{
Some((programdata_account, _)) => {
let account_index = accounts.len() as IndexOfAccount;
accounts.push((programdata_address, programdata_account));
account_index
}
None => {
error_counters.account_not_found += 1;
return Err(TransactionError::ProgramAccountNotFound);
}
};
account_indices.insert(0, programdata_account_index);
2020-12-14 15:35:10 -08:00
} else {
error_counters.invalid_program_for_execution += 1;
return Err(TransactionError::InvalidProgramForExecution);
}
}
2019-09-24 04:09:53 -07:00
program_id = program_owner;
}
Ok(account_indices)
}
#[allow(clippy::too_many_arguments)]
pub fn load_accounts(
&self,
ancestors: &Ancestors,
txs: &[SanitizedTransaction],
lock_results: Vec<TransactionCheckResult>,
hash_queue: &BlockhashQueue,
error_counters: &mut TransactionErrorMetrics,
rent_collector: &RentCollector,
feature_set: &FeatureSet,
2022-02-11 16:23:16 -08:00
fee_structure: &FeeStructure,
account_overrides: Option<&AccountOverrides>,
) -> Vec<TransactionLoadResult> {
txs.iter()
.zip(lock_results)
.map(|etx| match etx {
(tx, (Ok(()), nonce)) => {
2021-11-18 16:07:17 -08:00
let lamports_per_signature = nonce
.as_ref()
2021-11-18 16:07:17 -08:00
.map(|nonce| nonce.lamports_per_signature())
.unwrap_or_else(|| {
hash_queue.get_lamports_per_signature(tx.message().recent_blockhash())
});
let fee = if let Some(lamports_per_signature) = lamports_per_signature {
2022-02-11 16:23:16 -08:00
Bank::calculate_fee(
tx.message(),
lamports_per_signature,
fee_structure,
feature_set.is_active(&use_default_units_in_fee_calculation::id()),
!feature_set.is_active(&remove_deprecated_request_unit_ix::id()),
2022-02-11 16:23:16 -08:00
)
} else {
return (Err(TransactionError::BlockhashNotFound), None);
};
let loaded_transaction = match self.load_transaction(
ancestors,
tx,
fee,
error_counters,
rent_collector,
feature_set,
account_overrides,
) {
Ok(loaded_transaction) => loaded_transaction,
Err(e) => return (Err(e), None),
};
2021-11-18 16:07:17 -08:00
// Update nonce with fee-subtracted accounts
let nonce = if let Some(nonce) = nonce {
match NonceFull::from_partial(
nonce,
tx.message(),
&loaded_transaction.accounts,
&loaded_transaction.rent_debits,
) {
2021-11-18 16:07:17 -08:00
Ok(nonce) => Some(nonce),
Err(e) => return (Err(e), None),
}
} else {
None
};
2021-11-18 16:07:17 -08:00
(Ok(loaded_transaction), nonce)
}
2021-11-18 16:07:17 -08:00
(_, (Err(e), _nonce)) => (Err(e), None),
})
.collect()
}
2019-01-04 16:04:31 -08:00
pub fn load_lookup_table_addresses(
&self,
ancestors: &Ancestors,
address_table_lookup: &MessageAddressTableLookup,
slot_hashes: &SlotHashes,
) -> std::result::Result<LoadedAddresses, AddressLookupError> {
let table_account = self
.accounts_db
.load_with_fixed_root(ancestors, &address_table_lookup.account_key)
.map(|(account, _rent)| account)
.ok_or(AddressLookupError::LookupTableAccountNotFound)?;
if table_account.owner() == &solana_address_lookup_table_program::id() {
let current_slot = ancestors.max_slot();
let lookup_table = AddressLookupTable::deserialize(table_account.data())
.map_err(|_ix_err| AddressLookupError::InvalidAccountData)?;
Ok(LoadedAddresses {
writable: lookup_table.lookup(
current_slot,
&address_table_lookup.writable_indexes,
slot_hashes,
)?,
readonly: lookup_table.lookup(
current_slot,
&address_table_lookup.readonly_indexes,
slot_hashes,
)?,
})
} else {
Err(AddressLookupError::InvalidAccountOwner)
}
}
/// Slow because lock is held for 1 operation instead of many
/// This always returns None for zero-lamport accounts.
fn load_slow(
&self,
ancestors: &Ancestors,
pubkey: &Pubkey,
load_hint: LoadHint,
) -> Option<(AccountSharedData, Slot)> {
self.accounts_db.load(ancestors, pubkey, load_hint)
}
pub fn load_with_fixed_root(
&self,
ancestors: &Ancestors,
pubkey: &Pubkey,
) -> Option<(AccountSharedData, Slot)> {
self.load_slow(ancestors, pubkey, LoadHint::FixedMaxRoot)
}
pub fn load_without_fixed_root(
&self,
ancestors: &Ancestors,
pubkey: &Pubkey,
) -> Option<(AccountSharedData, Slot)> {
self.load_slow(ancestors, pubkey, LoadHint::Unspecified)
}
/// scans underlying accounts_db for this delta (slot) with a map function
/// from LoadedAccount to B
/// returns only the latest/current version of B for this slot
pub fn scan_slot<F, B>(&self, slot: Slot, func: F) -> Vec<B>
where
F: Fn(LoadedAccount) -> Option<B> + Send + Sync,
B: Sync + Send + Default + std::cmp::Eq,
{
let scan_result = self.accounts_db.scan_account_storage(
slot,
|loaded_account: LoadedAccount| {
// Cache only has one version per key, don't need to worry about versioning
func(loaded_account)
},
|accum: &DashMap<Pubkey, (u64, B)>, loaded_account: LoadedAccount| {
let loaded_account_pubkey = *loaded_account.pubkey();
let loaded_write_version = loaded_account.write_version();
let should_insert = accum
.get(&loaded_account_pubkey)
.map(|existing_entry| loaded_write_version > existing_entry.value().0)
.unwrap_or(true);
if should_insert {
if let Some(val) = func(loaded_account) {
// Detected insertion is necessary, grabs the write lock to commit the write,
match accum.entry(loaded_account_pubkey) {
// Double check in case another thread interleaved a write between the read + write.
Occupied(mut occupied_entry) => {
if loaded_write_version > occupied_entry.get().0 {
occupied_entry.insert((loaded_write_version, val));
}
}
Vacant(vacant_entry) => {
vacant_entry.insert((loaded_write_version, val));
}
}
}
}
},
);
match scan_result {
ScanStorageResult::Cached(cached_result) => cached_result,
ScanStorageResult::Stored(stored_result) => stored_result
.into_iter()
.map(|(_pubkey, (_latest_write_version, val))| val)
.collect(),
}
}
pub fn load_by_program_slot(
&self,
slot: Slot,
program_id: Option<&Pubkey>,
) -> Vec<TransactionAccount> {
self.scan_slot(slot, |stored_account| {
let hit = match program_id {
None => true,
Some(program_id) => stored_account.owner() == program_id,
};
if hit {
Some((*stored_account.pubkey(), stored_account.take_account()))
} else {
None
}
})
}
pub fn load_largest_accounts(
&self,
ancestors: &Ancestors,
bank_id: BankId,
num: usize,
filter_by_address: &HashSet<Pubkey>,
filter: AccountAddressFilter,
) -> ScanResult<Vec<(Pubkey, u64)>> {
if num == 0 {
return Ok(vec![]);
}
let mut account_balances = BinaryHeap::new();
self.accounts_db.scan_accounts(
ancestors,
bank_id,
|option| {
if let Some((pubkey, account, _slot)) = option {
2021-04-29 08:44:46 -07:00
if account.lamports() == 0 {
return;
}
let contains_address = filter_by_address.contains(pubkey);
let collect = match filter {
AccountAddressFilter::Exclude => !contains_address,
AccountAddressFilter::Include => contains_address,
};
if !collect {
return;
}
if account_balances.len() == num {
let Reverse(entry) = account_balances
.peek()
.expect("BinaryHeap::peek should succeed when len > 0");
2021-04-29 08:44:46 -07:00
if *entry >= (account.lamports(), *pubkey) {
return;
}
account_balances.pop();
}
account_balances.push(Reverse((account.lamports(), *pubkey)));
}
},
&ScanConfig::default(),
)?;
Ok(account_balances
.into_sorted_vec()
.into_iter()
.map(|Reverse((balance, pubkey))| (pubkey, balance))
.collect())
}
/// only called at startup vs steady-state runtime
pub fn calculate_capitalization(
&self,
ancestors: &Ancestors,
slot: Slot,
debug_verify: bool,
epoch_schedule: &EpochSchedule,
rent_collector: &RentCollector,
) -> u64 {
let is_startup = true;
self.accounts_db
.verify_accounts_hash_in_bg
.wait_for_complete();
self.accounts_db
.update_accounts_hash(
CalcAccountsHashDataSource::Storages,
debug_verify,
slot,
ancestors,
None,
epoch_schedule,
rent_collector,
is_startup,
)
.1
}
/// Only called from startup or test code.
#[must_use]
#[allow(clippy::too_many_arguments)]
pub fn verify_bank_hash_and_lamports(
&self,
slot: Slot,
ancestors: &Ancestors,
total_lamports: u64,
test_hash_calculation: bool,
epoch_schedule: &EpochSchedule,
rent_collector: &RentCollector,
ignore_mismatch: bool,
store_detailed_debug_info: bool,
use_bg_thread_pool: bool,
) -> bool {
if let Err(err) = self.accounts_db.verify_bank_hash_and_lamports_new(
slot,
ancestors,
total_lamports,
test_hash_calculation,
epoch_schedule,
rent_collector,
ignore_mismatch,
store_detailed_debug_info,
use_bg_thread_pool,
) {
2022-07-01 11:12:52 -07:00
warn!("verify_bank_hash failed: {:?}, slot: {}", err, slot);
false
} else {
true
}
2019-09-20 13:21:12 -07:00
}
fn is_loadable(lamports: u64) -> bool {
// Don't ever load zero lamport accounts into runtime because
// the existence of zero-lamport accounts are never deterministic!!
lamports > 0
}
2021-03-09 13:06:07 -08:00
fn load_while_filtering<F: Fn(&AccountSharedData) -> bool>(
collector: &mut Vec<TransactionAccount>,
2021-03-09 13:06:07 -08:00
some_account_tuple: Option<(&Pubkey, AccountSharedData, Slot)>,
filter: F,
) {
if let Some(mapped_account_tuple) = some_account_tuple
2021-04-29 08:44:46 -07:00
.filter(|(_, account, _)| Self::is_loadable(account.lamports()) && filter(account))
.map(|(pubkey, account, _slot)| (*pubkey, account))
{
collector.push(mapped_account_tuple)
}
}
fn load_with_slot(
collector: &mut Vec<PubkeyAccountSlot>,
some_account_tuple: Option<(&Pubkey, AccountSharedData, Slot)>,
) {
if let Some(mapped_account_tuple) = some_account_tuple
.filter(|(_, account, _)| Self::is_loadable(account.lamports()))
.map(|(pubkey, account, slot)| (*pubkey, account, slot))
{
collector.push(mapped_account_tuple)
}
}
pub fn load_by_program(
&self,
ancestors: &Ancestors,
bank_id: BankId,
program_id: &Pubkey,
config: &ScanConfig,
) -> ScanResult<Vec<TransactionAccount>> {
let mut collector = Vec::new();
self.accounts_db
.scan_accounts(
ancestors,
bank_id,
|some_account_tuple| {
Self::load_while_filtering(&mut collector, some_account_tuple, |account| {
account.owner() == program_id
})
},
config,
)
.map(|_| collector)
}
2021-03-09 13:06:07 -08:00
pub fn load_by_program_with_filter<F: Fn(&AccountSharedData) -> bool>(
2020-12-31 18:06:03 -08:00
&self,
ancestors: &Ancestors,
bank_id: BankId,
2020-12-31 18:06:03 -08:00
program_id: &Pubkey,
filter: F,
config: &ScanConfig,
) -> ScanResult<Vec<TransactionAccount>> {
let mut collector = Vec::new();
self.accounts_db
.scan_accounts(
ancestors,
bank_id,
|some_account_tuple| {
Self::load_while_filtering(&mut collector, some_account_tuple, |account| {
account.owner() == program_id && filter(account)
})
},
config,
)
.map(|_| collector)
2020-12-31 18:06:03 -08:00
}
fn calc_scan_result_size(account: &AccountSharedData) -> usize {
account.data().len()
+ std::mem::size_of::<AccountSharedData>()
+ std::mem::size_of::<Pubkey>()
}
/// Accumulate size of (pubkey + account) into sum.
/// Return true iff sum > 'byte_limit_for_scan'
fn accumulate_and_check_scan_result_size(
sum: &AtomicUsize,
account: &AccountSharedData,
byte_limit_for_scan: &Option<usize>,
) -> bool {
if let Some(byte_limit_for_scan) = byte_limit_for_scan.as_ref() {
let added = Self::calc_scan_result_size(account);
sum.fetch_add(added, Ordering::Relaxed)
.saturating_add(added)
> *byte_limit_for_scan
} else {
false
}
}
fn maybe_abort_scan(
result: ScanResult<Vec<TransactionAccount>>,
config: &ScanConfig,
) -> ScanResult<Vec<TransactionAccount>> {
if config.is_aborted() {
ScanResult::Err(ScanError::Aborted(
"The accumulated scan results exceeded the limit".to_string(),
))
} else {
result
}
}
2021-03-09 13:06:07 -08:00
pub fn load_by_index_key_with_filter<F: Fn(&AccountSharedData) -> bool>(
2020-12-31 18:06:03 -08:00
&self,
ancestors: &Ancestors,
bank_id: BankId,
2020-12-31 18:06:03 -08:00
index_key: &IndexKey,
filter: F,
config: &ScanConfig,
byte_limit_for_scan: Option<usize>,
) -> ScanResult<Vec<TransactionAccount>> {
let sum = AtomicUsize::default();
let config = config.recreate_with_abort();
let mut collector = Vec::new();
let result = self
.accounts_db
.index_scan_accounts(
ancestors,
bank_id,
*index_key,
|some_account_tuple| {
Self::load_while_filtering(&mut collector, some_account_tuple, |account| {
let use_account = filter(account);
if use_account
&& Self::accumulate_and_check_scan_result_size(
&sum,
account,
&byte_limit_for_scan,
)
{
// total size of results exceeds size limit, so abort scan
config.abort();
}
use_account
});
},
&config,
)
.map(|_| collector);
Self::maybe_abort_scan(result, &config)
2020-12-31 18:06:03 -08:00
}
pub fn account_indexes_include_key(&self, key: &Pubkey) -> bool {
self.accounts_db.account_indexes.include_key(key)
}
pub fn load_all(
&self,
ancestors: &Ancestors,
bank_id: BankId,
2022-04-19 19:34:00 -07:00
) -> ScanResult<Vec<PubkeyAccountSlot>> {
let mut collector = Vec::new();
self.accounts_db
.scan_accounts(
ancestors,
bank_id,
|some_account_tuple| {
if let Some((pubkey, account, slot)) = some_account_tuple
.filter(|(_, account, _)| Self::is_loadable(account.lamports()))
{
collector.push((*pubkey, account, slot))
}
},
&ScanConfig::default(),
)
.map(|_| collector)
}
pub fn hold_range_in_memory<R>(
&self,
range: &R,
start_holding: bool,
thread_pool: &rayon::ThreadPool,
) where
R: RangeBounds<Pubkey> + std::fmt::Debug + Sync,
2021-09-17 15:19:29 -07:00
{
self.accounts_db
.accounts_index
.hold_range_in_memory(range, start_holding, thread_pool)
2021-09-17 15:19:29 -07:00
}
pub fn load_to_collect_rent_eagerly<R: RangeBounds<Pubkey> + std::fmt::Debug>(
&self,
ancestors: &Ancestors,
range: R,
) -> Vec<PubkeyAccountSlot> {
let mut collector = Vec::new();
self.accounts_db.range_scan_accounts(
"", // disable logging of this. We now parallelize it and this results in multiple parallel logs
ancestors,
range,
&ScanConfig::new(true),
|option| Self::load_with_slot(&mut collector, option),
);
collector
}
/// Slow because lock is held for 1 operation instead of many.
/// WARNING: This noncached version is only to be used for tests/benchmarking
/// as bypassing the cache in general is not supported
2021-03-09 13:06:07 -08:00
pub fn store_slow_uncached(&self, slot: Slot, pubkey: &Pubkey, account: &AccountSharedData) {
self.accounts_db.store_uncached(slot, &[(pubkey, account)]);
}
fn lock_account(
&self,
account_locks: &mut AccountLocks,
writable_keys: Vec<&Pubkey>,
readonly_keys: Vec<&Pubkey>,
) -> Result<()> {
for k in writable_keys.iter() {
if account_locks.is_locked_write(k) || account_locks.is_locked_readonly(k) {
debug!("Writable account in use: {:?}", k);
return Err(TransactionError::AccountInUse);
}
}
for k in readonly_keys.iter() {
if account_locks.is_locked_write(k) {
debug!("Read-only account in use: {:?}", k);
2019-03-13 12:58:44 -07:00
return Err(TransactionError::AccountInUse);
}
}
for k in writable_keys {
account_locks.write_locks.insert(*k);
}
for k in readonly_keys {
if !account_locks.lock_readonly(k) {
account_locks.insert_new_readonly(k);
}
}
Ok(())
}
fn unlock_account(
&self,
account_locks: &mut AccountLocks,
writable_keys: Vec<&Pubkey>,
readonly_keys: Vec<&Pubkey>,
) {
for k in writable_keys {
account_locks.unlock_write(k);
}
for k in readonly_keys {
account_locks.unlock_readonly(k);
}
}
2019-01-04 16:04:31 -08:00
2022-04-19 09:29:29 -07:00
pub fn bank_hash_at(&self, slot: Slot, rewrites: &Rewrites) -> Hash {
self.bank_hash_info_at(slot, rewrites).hash
}
2022-04-19 09:29:29 -07:00
pub fn bank_hash_info_at(&self, slot: Slot, rewrites: &Rewrites) -> BankHashInfo {
let delta_hash = self
.accounts_db
.get_accounts_delta_hash_with_rewrites(slot, rewrites);
let bank_hashes = self.accounts_db.bank_hashes.read().unwrap();
let mut hash_info = bank_hashes
.get(&slot)
.expect("No bank hash was found for this bank, that should not be possible")
.clone();
hash_info.hash = delta_hash;
hash_info
}
/// This function will prevent multiple threads from modifying the same account state at the
/// same time
#[must_use]
#[allow(clippy::needless_collect)]
pub fn lock_accounts<'a>(
&self,
txs: impl Iterator<Item = &'a SanitizedTransaction>,
tx_account_lock_limit: usize,
) -> Vec<Result<()>> {
let tx_account_locks_results: Vec<Result<_>> = txs
.map(|tx| tx.get_account_locks(tx_account_lock_limit))
.collect();
self.lock_accounts_inner(tx_account_locks_results)
}
#[must_use]
#[allow(clippy::needless_collect)]
pub fn lock_accounts_with_results<'a>(
&self,
txs: impl Iterator<Item = &'a SanitizedTransaction>,
results: impl Iterator<Item = &'a Result<()>>,
tx_account_lock_limit: usize,
) -> Vec<Result<()>> {
let tx_account_locks_results: Vec<Result<_>> = txs
.zip(results)
.map(|(tx, result)| match result {
Ok(()) => tx.get_account_locks(tx_account_lock_limit),
Err(err) => Err(err.clone()),
})
.collect();
self.lock_accounts_inner(tx_account_locks_results)
}
#[must_use]
fn lock_accounts_inner(
&self,
tx_account_locks_results: Vec<Result<TransactionAccountLocks>>,
) -> Vec<Result<()>> {
let account_locks = &mut self.account_locks.lock().unwrap();
tx_account_locks_results
.into_iter()
.map(|tx_account_locks_result| match tx_account_locks_result {
Ok(tx_account_locks) => self.lock_account(
account_locks,
tx_account_locks.writable,
tx_account_locks.readonly,
),
Err(err) => Err(err),
})
.collect()
}
/// Once accounts are unlocked, new transactions that modify that state can enter the pipeline
#[allow(clippy::needless_collect)]
pub fn unlock_accounts<'a>(
&self,
txs: impl Iterator<Item = &'a SanitizedTransaction>,
results: &[Result<()>],
) {
let keys: Vec<_> = txs
.zip(results)
.filter_map(|(tx, res)| match res {
Err(TransactionError::AccountLoadedTwice)
| Err(TransactionError::AccountInUse)
| Err(TransactionError::SanitizeFailure)
| Err(TransactionError::TooManyAccountLocks)
| Err(TransactionError::WouldExceedMaxBlockCostLimit)
| Err(TransactionError::WouldExceedMaxVoteCostLimit)
| Err(TransactionError::WouldExceedMaxAccountCostLimit)
| Err(TransactionError::WouldExceedAccountDataBlockLimit)
| Err(TransactionError::WouldExceedAccountDataTotalLimit) => None,
_ => Some(tx.get_account_locks_unchecked()),
})
.collect();
let mut account_locks = self.account_locks.lock().unwrap();
debug!("bank unlock accounts");
keys.into_iter().for_each(|keys| {
self.unlock_account(&mut account_locks, keys.writable, keys.readonly);
});
}
/// Store the accounts into the DB
// allow(clippy) needed for various gating flags
#[allow(clippy::too_many_arguments)]
pub(crate) fn store_cached(
&self,
slot: Slot,
txs: &[SanitizedTransaction],
res: &[TransactionExecutionResult],
loaded: &mut [TransactionLoadResult],
rent_collector: &RentCollector,
durable_nonce: &DurableNonce,
lamports_per_signature: u64,
include_slot_in_hash: IncludeSlotInHash,
2019-02-19 17:11:43 -08:00
) {
let (accounts_to_store, txn_signatures) = self.collect_accounts_to_store(
txs,
res,
loaded,
rent_collector,
durable_nonce,
lamports_per_signature,
);
self.accounts_db.store_cached(
(slot, &accounts_to_store[..], include_slot_in_hash),
Some(&txn_signatures),
);
}
pub fn store_accounts_cached<'a, T: ReadableAccount + Sync + ZeroLamport>(
&self,
accounts: impl StorableAccounts<'a, T>,
) {
self.accounts_db.store_cached(accounts, None)
}
/// Add a slot to root. Root slots cannot be purged
pub fn add_root(&self, slot: Slot) -> AccountsAddRootTiming {
self.accounts_db.add_root(slot)
}
#[allow(clippy::too_many_arguments)]
2019-08-19 13:00:37 -07:00
fn collect_accounts_to_store<'a>(
&self,
txs: &'a [SanitizedTransaction],
2021-11-18 16:07:17 -08:00
execution_results: &'a [TransactionExecutionResult],
load_results: &'a mut [TransactionLoadResult],
_rent_collector: &RentCollector,
durable_nonce: &DurableNonce,
lamports_per_signature: u64,
) -> (
Vec<(&'a Pubkey, &'a AccountSharedData)>,
Vec<Option<&'a Signature>>,
) {
2021-11-18 16:07:17 -08:00
let mut accounts = Vec::with_capacity(load_results.len());
let mut signatures = Vec::with_capacity(load_results.len());
for (i, ((tx_load_result, nonce), tx)) in load_results.iter_mut().zip(txs).enumerate() {
2021-11-18 16:07:17 -08:00
if tx_load_result.is_err() {
// Don't store any accounts if tx failed to load
continue;
}
2021-11-18 16:07:17 -08:00
let execution_status = match &execution_results[i] {
TransactionExecutionResult::Executed { details, .. } => &details.status,
// Don't store any accounts if tx wasn't executed
TransactionExecutionResult::NotExecuted(_) => continue,
};
let maybe_nonce = match (execution_status, &*nonce) {
(Ok(_), _) => None, // Success, don't do any additional nonce processing
(Err(_), Some(nonce)) => {
Some((nonce, true /* rollback */))
}
(Err(_), None) => {
// Fees for failed transactions which don't use durable nonces are
// deducted in Bank::filter_program_errors_and_collect_fee
continue;
}
};
let message = tx.message();
2021-11-18 16:07:17 -08:00
let loaded_transaction = tx_load_result.as_mut().unwrap();
let mut fee_payer_index = None;
for (i, (address, account)) in (0..message.account_keys().len())
.zip(loaded_transaction.accounts.iter_mut())
2021-11-18 16:07:17 -08:00
.filter(|(i, _)| message.is_non_loader_key(*i))
{
if fee_payer_index.is_none() {
fee_payer_index = Some(i);
}
let is_fee_payer = Some(i) == fee_payer_index;
if message.is_writable(i) {
let is_nonce_account = prepare_if_nonce_account(
address,
account,
execution_status,
is_fee_payer,
maybe_nonce,
durable_nonce,
lamports_per_signature,
);
if execution_status.is_ok() || is_nonce_account || is_fee_payer {
// Add to the accounts to store
accounts.push((&*address, &*account));
signatures.push(Some(tx.signature()));
}
}
}
}
(accounts, signatures)
}
}
fn prepare_if_nonce_account<'a>(
2021-11-18 16:07:17 -08:00
address: &Pubkey,
2021-03-09 13:06:07 -08:00
account: &mut AccountSharedData,
2021-11-18 16:07:17 -08:00
execution_result: &Result<()>,
is_fee_payer: bool,
maybe_nonce: Option<(&'a NonceFull, bool)>,
&durable_nonce: &DurableNonce,
lamports_per_signature: u64,
) -> bool {
if let Some((nonce, rollback)) = maybe_nonce {
if address == nonce.address() {
if rollback {
2021-11-18 16:07:17 -08:00
// The transaction failed which would normally drop the account
// processing changes, since this account is now being included
// in the accounts written back to the db, roll it back to
2021-11-18 16:07:17 -08:00
// pre-processing state.
*account = nonce.account().clone();
}
// Advance the stored blockhash to prevent fee theft by someone
// replaying nonce transactions that have failed with an
// `InstructionError`.
//
// Since we know we are dealing with a valid nonce account,
// unwrap is safe here
let nonce_versions = StateMut::<NonceVersions>::state(nonce.account()).unwrap();
if let NonceState::Initialized(ref data) = nonce_versions.state() {
let nonce_state = NonceState::new_initialized(
&data.authority,
durable_nonce,
lamports_per_signature,
);
let nonce_versions = NonceVersions::new(nonce_state);
account.set_state(&nonce_versions).unwrap();
}
true
} else {
if execution_result.is_err() && is_fee_payer {
if let Some(fee_payer_account) = nonce.fee_payer_account() {
// Instruction error and fee-payer for this nonce tx is not
// the nonce account itself, rollback the fee payer to the
// fee-paid original state.
*account = fee_payer_account.clone();
}
}
false
}
} else {
false
}
}
/// A set of utility functions used for testing and benchmarking
pub mod test_utils {
use super::*;
pub fn create_test_accounts(
accounts: &Accounts,
pubkeys: &mut Vec<Pubkey>,
num: usize,
slot: Slot,
) {
for t in 0..num {
let pubkey = solana_sdk::pubkey::new_rand();
let account =
AccountSharedData::new((t + 1) as u64, 0, AccountSharedData::default().owner());
accounts.store_slow_uncached(slot, &pubkey, &account);
pubkeys.push(pubkey);
}
}
// Only used by bench, not safe to call otherwise accounts can conflict with the
// accounts cache!
pub fn update_accounts_bench(accounts: &Accounts, pubkeys: &[Pubkey], slot: u64) {
for pubkey in pubkeys {
let amount = thread_rng().gen_range(0, 10);
let account = AccountSharedData::new(amount, 0, AccountSharedData::default().owner());
accounts.store_slow_uncached(slot, pubkey, &account);
}
}
}
#[cfg(test)]
mod tests {
use {
super::*,
crate::{
bank::{DurableNonceFee, TransactionExecutionDetails},
rent_collector::RentCollector,
},
assert_matches::assert_matches,
solana_address_lookup_table_program::state::LookupTableMeta,
solana_program_runtime::executor_cache::TransactionExecutorCache,
solana_sdk::{
account::{AccountSharedData, WritableAccount},
epoch_schedule::EpochSchedule,
genesis_config::ClusterType,
hash::Hash,
instruction::{CompiledInstruction, InstructionError},
message::{Message, MessageHeader},
nonce, nonce_account,
rent::Rent,
signature::{keypair_from_seed, signers::Signers, Keypair, Signer},
system_instruction, system_program,
transaction::{Transaction, MAX_TX_ACCOUNT_LOCKS},
},
std::{
borrow::Cow,
cell::RefCell,
convert::TryFrom,
rc::Rc,
sync::atomic::{AtomicBool, AtomicU64, Ordering},
thread, time,
},
2020-01-28 17:03:20 -08:00
};
fn new_sanitized_tx<T: Signers>(
from_keypairs: &T,
message: Message,
recent_blockhash: Hash,
) -> SanitizedTransaction {
SanitizedTransaction::from_transaction_for_tests(Transaction::new(
from_keypairs,
message,
recent_blockhash,
))
}
fn new_execution_result(
status: Result<()>,
nonce: Option<&NonceFull>,
) -> TransactionExecutionResult {
TransactionExecutionResult::Executed {
details: TransactionExecutionDetails {
status,
log_messages: None,
inner_instructions: None,
durable_nonce_fee: nonce.map(DurableNonceFee::from),
return_data: None,
executed_units: 0,
accounts_data_len_delta: 0,
},
tx_executor_cache: Rc::new(RefCell::new(TransactionExecutorCache::default())),
}
}
fn load_accounts_with_fee_and_rent(
tx: Transaction,
ka: &[TransactionAccount],
lamports_per_signature: u64,
rent_collector: &RentCollector,
error_counters: &mut TransactionErrorMetrics,
2022-02-11 16:23:16 -08:00
feature_set: &FeatureSet,
fee_structure: &FeeStructure,
) -> Vec<TransactionLoadResult> {
let mut hash_queue = BlockhashQueue::new(100);
hash_queue.register_hash(&tx.message().recent_blockhash, lamports_per_signature);
let accounts = Accounts::new_with_config_for_tests(
Vec::new(),
&ClusterType::Development,
AccountSecondaryIndexes::default(),
false,
AccountShrinkThreshold::default(),
);
for ka in ka.iter() {
accounts.store_slow_uncached(0, &ka.0, &ka.1);
}
let ancestors = vec![(0, 0)].into_iter().collect();
let sanitized_tx = SanitizedTransaction::from_transaction_for_tests(tx);
accounts.load_accounts(
&ancestors,
&[sanitized_tx],
vec![(Ok(()), None)],
&hash_queue,
error_counters,
rent_collector,
2022-02-11 16:23:16 -08:00
feature_set,
fee_structure,
None,
)
}
fn load_accounts_with_fee(
tx: Transaction,
ka: &[TransactionAccount],
lamports_per_signature: u64,
error_counters: &mut TransactionErrorMetrics,
) -> Vec<TransactionLoadResult> {
load_accounts_with_fee_and_rent(
tx,
ka,
lamports_per_signature,
&RentCollector::default(),
error_counters,
2022-02-11 16:23:16 -08:00
&FeatureSet::all_enabled(),
&FeeStructure::default(),
)
}
fn load_accounts(
tx: Transaction,
ka: &[TransactionAccount],
error_counters: &mut TransactionErrorMetrics,
) -> Vec<TransactionLoadResult> {
load_accounts_with_fee(tx, ka, 0, error_counters)
}
2021-09-17 15:19:29 -07:00
#[test]
fn test_hold_range_in_memory() {
let accts = Accounts::default_for_tests();
let range = Pubkey::new(&[0; 32])..=Pubkey::new(&[0xff; 32]);
accts.hold_range_in_memory(&range, true, &test_thread_pool());
accts.hold_range_in_memory(&range, false, &test_thread_pool());
accts.hold_range_in_memory(&range, true, &test_thread_pool());
accts.hold_range_in_memory(&range, true, &test_thread_pool());
accts.hold_range_in_memory(&range, false, &test_thread_pool());
accts.hold_range_in_memory(&range, false, &test_thread_pool());
2021-09-17 15:19:29 -07:00
}
#[test]
fn test_hold_range_in_memory2() {
let accts = Accounts::default_for_tests();
let range = Pubkey::new(&[0; 32])..=Pubkey::new(&[0xff; 32]);
let idx = &accts.accounts_db.accounts_index;
let bins = idx.account_maps.len();
// use bins * 2 to get the first half of the range within bin 0
let bins_2 = bins * 2;
2021-10-18 12:05:16 -07:00
let binner = crate::pubkey_bins::PubkeyBinCalculator24::new(bins_2);
2021-09-17 15:19:29 -07:00
let range2 =
binner.lowest_pubkey_from_bin(0, bins_2)..binner.lowest_pubkey_from_bin(1, bins_2);
let range2_inclusive = range2.start..=range2.end;
assert_eq!(0, idx.bin_calculator.bin_from_pubkey(&range2.start));
assert_eq!(0, idx.bin_calculator.bin_from_pubkey(&range2.end));
accts.hold_range_in_memory(&range, true, &test_thread_pool());
idx.account_maps.iter().for_each(|map| {
2021-09-17 15:19:29 -07:00
assert_eq!(
map.cache_ranges_held.read().unwrap().to_vec(),
vec![range.clone()]
2021-09-17 15:19:29 -07:00
);
});
accts.hold_range_in_memory(&range2, true, &test_thread_pool());
2021-09-17 15:19:29 -07:00
idx.account_maps.iter().enumerate().for_each(|(bin, map)| {
let expected = if bin == 0 {
vec![range.clone(), range2_inclusive.clone()]
2021-09-17 15:19:29 -07:00
} else {
vec![range.clone()]
2021-09-17 15:19:29 -07:00
};
assert_eq!(
map.cache_ranges_held.read().unwrap().to_vec(),
expected,
"bin: {}",
bin
);
});
accts.hold_range_in_memory(&range, false, &test_thread_pool());
accts.hold_range_in_memory(&range2, false, &test_thread_pool());
}
fn test_thread_pool() -> rayon::ThreadPool {
crate::accounts_db::make_min_priority_thread_pool()
2021-09-17 15:19:29 -07:00
}
#[test]
fn test_load_accounts_no_account_0_exists() {
let accounts: Vec<TransactionAccount> = Vec::new();
let mut error_counters = TransactionErrorMetrics::default();
let keypair = Keypair::new();
let instructions = vec![CompiledInstruction::new(1, &(), vec![0])];
2019-03-15 09:02:28 -07:00
let tx = Transaction::new_with_compiled_instructions(
&[&keypair],
&[],
Hash::default(),
vec![native_loader::id()],
instructions,
);
let loaded_accounts = load_accounts(tx, &accounts, &mut error_counters);
assert_eq!(error_counters.account_not_found, 1);
assert_eq!(loaded_accounts.len(), 1);
assert_eq!(
loaded_accounts[0],
(Err(TransactionError::AccountNotFound), None,),
);
}
#[test]
fn test_load_accounts_unknown_program_id() {
let mut accounts: Vec<TransactionAccount> = Vec::new();
let mut error_counters = TransactionErrorMetrics::default();
let keypair = Keypair::new();
let key0 = keypair.pubkey();
let key1 = Pubkey::new(&[5u8; 32]);
2021-03-09 13:06:07 -08:00
let account = AccountSharedData::new(1, 0, &Pubkey::default());
accounts.push((key0, account));
2021-03-09 13:06:07 -08:00
let account = AccountSharedData::new(2, 1, &Pubkey::default());
accounts.push((key1, account));
let instructions = vec![CompiledInstruction::new(1, &(), vec![0])];
2019-03-15 09:02:28 -07:00
let tx = Transaction::new_with_compiled_instructions(
&[&keypair],
&[],
Hash::default(),
vec![Pubkey::default()],
instructions,
);
let loaded_accounts = load_accounts(tx, &accounts, &mut error_counters);
assert_eq!(error_counters.account_not_found, 1);
assert_eq!(loaded_accounts.len(), 1);
assert_eq!(
loaded_accounts[0],
(Err(TransactionError::ProgramAccountNotFound), None,)
);
}
#[test]
fn test_load_accounts_insufficient_funds() {
let lamports_per_signature = 5000;
let mut accounts: Vec<TransactionAccount> = Vec::new();
let mut error_counters = TransactionErrorMetrics::default();
let keypair = Keypair::new();
let key0 = keypair.pubkey();
2021-03-09 13:06:07 -08:00
let account = AccountSharedData::new(1, 0, &Pubkey::default());
accounts.push((key0, account));
let instructions = vec![CompiledInstruction::new(1, &(), vec![0])];
2019-03-15 09:02:28 -07:00
let tx = Transaction::new_with_compiled_instructions(
&[&keypair],
&[],
Hash::default(),
vec![native_loader::id()],
instructions,
);
let fee = Bank::calculate_fee(
&SanitizedMessage::try_from(tx.message().clone()).unwrap(),
lamports_per_signature,
2022-02-11 16:23:16 -08:00
&FeeStructure::default(),
true,
false,
);
assert_eq!(fee, lamports_per_signature);
let loaded_accounts =
load_accounts_with_fee(tx, &accounts, lamports_per_signature, &mut error_counters);
assert_eq!(error_counters.insufficient_funds, 1);
assert_eq!(loaded_accounts.len(), 1);
2019-03-13 12:58:44 -07:00
assert_eq!(
loaded_accounts[0].clone(),
(Err(TransactionError::InsufficientFundsForFee), None,),
);
}
#[test]
fn test_load_accounts_invalid_account_for_fee() {
let mut accounts: Vec<TransactionAccount> = Vec::new();
let mut error_counters = TransactionErrorMetrics::default();
let keypair = Keypair::new();
let key0 = keypair.pubkey();
2021-03-09 13:06:07 -08:00
let account = AccountSharedData::new(1, 1, &solana_sdk::pubkey::new_rand()); // <-- owner is not the system program
accounts.push((key0, account));
let instructions = vec![CompiledInstruction::new(1, &(), vec![0])];
let tx = Transaction::new_with_compiled_instructions(
&[&keypair],
&[],
Hash::default(),
vec![native_loader::id()],
instructions,
);
let loaded_accounts = load_accounts(tx, &accounts, &mut error_counters);
assert_eq!(error_counters.invalid_account_for_fee, 1);
assert_eq!(loaded_accounts.len(), 1);
assert_eq!(
loaded_accounts[0],
(Err(TransactionError::InvalidAccountForFee), None,),
2019-03-13 12:58:44 -07:00
);
}
#[test]
fn test_load_accounts_fee_payer_is_nonce() {
let lamports_per_signature = 5000;
let mut error_counters = TransactionErrorMetrics::default();
let rent_collector = RentCollector::new(
0,
EpochSchedule::default(),
500_000.0,
Rent {
lamports_per_byte_year: 42,
..Rent::default()
},
);
2021-11-18 16:07:17 -08:00
let min_balance = rent_collector.rent.minimum_balance(NonceState::size());
let nonce = Keypair::new();
let mut accounts = vec![(
nonce.pubkey(),
2021-03-09 13:06:07 -08:00
AccountSharedData::new_data(
min_balance + lamports_per_signature,
&NonceVersions::new(NonceState::Initialized(nonce::state::Data::default())),
&system_program::id(),
)
.unwrap(),
)];
let instructions = vec![CompiledInstruction::new(1, &(), vec![0])];
let tx = Transaction::new_with_compiled_instructions(
&[&nonce],
&[],
Hash::default(),
vec![native_loader::id()],
instructions,
);
// Fee leaves min_balance balance succeeds
let loaded_accounts = load_accounts_with_fee_and_rent(
tx.clone(),
&accounts,
lamports_per_signature,
&rent_collector,
&mut error_counters,
&FeatureSet::all_enabled(),
2022-02-11 16:23:16 -08:00
&FeeStructure::default(),
);
assert_eq!(loaded_accounts.len(), 1);
2021-11-18 16:07:17 -08:00
let (load_res, _nonce) = &loaded_accounts[0];
let loaded_transaction = load_res.as_ref().unwrap();
assert_eq!(loaded_transaction.accounts[0].1.lamports(), min_balance);
// Fee leaves zero balance fails
accounts[0].1.set_lamports(lamports_per_signature);
let loaded_accounts = load_accounts_with_fee_and_rent(
tx.clone(),
&accounts,
lamports_per_signature,
&rent_collector,
&mut error_counters,
&FeatureSet::all_enabled(),
2022-02-11 16:23:16 -08:00
&FeeStructure::default(),
);
assert_eq!(loaded_accounts.len(), 1);
2021-11-18 16:07:17 -08:00
let (load_res, _nonce) = &loaded_accounts[0];
assert_eq!(*load_res, Err(TransactionError::InsufficientFundsForFee));
// Fee leaves non-zero, but sub-min_balance balance fails
accounts[0]
.1
.set_lamports(lamports_per_signature + min_balance / 2);
let loaded_accounts = load_accounts_with_fee_and_rent(
tx,
&accounts,
lamports_per_signature,
&rent_collector,
&mut error_counters,
&FeatureSet::all_enabled(),
2022-02-11 16:23:16 -08:00
&FeeStructure::default(),
);
assert_eq!(loaded_accounts.len(), 1);
2021-11-18 16:07:17 -08:00
let (load_res, _nonce) = &loaded_accounts[0];
assert_eq!(*load_res, Err(TransactionError::InsufficientFundsForFee));
}
#[test]
fn test_load_accounts_no_loaders() {
let mut accounts: Vec<TransactionAccount> = Vec::new();
let mut error_counters = TransactionErrorMetrics::default();
let keypair = Keypair::new();
let key0 = keypair.pubkey();
let key1 = Pubkey::new(&[5u8; 32]);
2021-03-09 13:06:07 -08:00
let mut account = AccountSharedData::new(1, 0, &Pubkey::default());
account.set_rent_epoch(1);
accounts.push((key0, account));
2021-03-09 13:06:07 -08:00
let mut account = AccountSharedData::new(2, 1, &Pubkey::default());
account.set_rent_epoch(1);
accounts.push((key1, account));
let instructions = vec![CompiledInstruction::new(2, &(), vec![0, 1])];
2019-03-15 09:02:28 -07:00
let tx = Transaction::new_with_compiled_instructions(
&[&keypair],
&[key1],
Hash::default(),
vec![native_loader::id()],
instructions,
);
let loaded_accounts = load_accounts(tx, &accounts, &mut error_counters);
assert_eq!(error_counters.account_not_found, 0);
assert_eq!(loaded_accounts.len(), 1);
match &loaded_accounts[0] {
2021-11-18 16:07:17 -08:00
(Ok(loaded_transaction), _nonce) => {
assert_eq!(loaded_transaction.accounts.len(), 3);
assert_eq!(loaded_transaction.accounts[0].1, accounts[0].1);
assert_eq!(loaded_transaction.program_indices.len(), 1);
assert_eq!(loaded_transaction.program_indices[0].len(), 0);
}
2021-11-18 16:07:17 -08:00
(Err(e), _nonce) => Err(e).unwrap(),
}
}
#[test]
fn test_load_accounts_max_call_depth() {
let mut accounts: Vec<TransactionAccount> = Vec::new();
let mut error_counters = TransactionErrorMetrics::default();
let keypair = Keypair::new();
let key0 = keypair.pubkey();
let key1 = Pubkey::new(&[5u8; 32]);
let key2 = Pubkey::new(&[6u8; 32]);
let key3 = Pubkey::new(&[7u8; 32]);
let key4 = Pubkey::new(&[8u8; 32]);
let key5 = Pubkey::new(&[9u8; 32]);
let key6 = Pubkey::new(&[10u8; 32]);
2021-03-09 13:06:07 -08:00
let account = AccountSharedData::new(1, 0, &Pubkey::default());
accounts.push((key0, account));
2021-03-09 13:06:07 -08:00
let mut account = AccountSharedData::new(40, 1, &Pubkey::default());
account.set_executable(true);
account.set_owner(native_loader::id());
accounts.push((key1, account));
2021-03-09 13:06:07 -08:00
let mut account = AccountSharedData::new(41, 1, &Pubkey::default());
account.set_executable(true);
account.set_owner(key1);
accounts.push((key2, account));
2021-03-09 13:06:07 -08:00
let mut account = AccountSharedData::new(42, 1, &Pubkey::default());
account.set_executable(true);
account.set_owner(key2);
accounts.push((key3, account));
2021-03-09 13:06:07 -08:00
let mut account = AccountSharedData::new(43, 1, &Pubkey::default());
account.set_executable(true);
account.set_owner(key3);
accounts.push((key4, account));
2021-03-09 13:06:07 -08:00
let mut account = AccountSharedData::new(44, 1, &Pubkey::default());
account.set_executable(true);
account.set_owner(key4);
accounts.push((key5, account));
2021-03-09 13:06:07 -08:00
let mut account = AccountSharedData::new(45, 1, &Pubkey::default());
account.set_executable(true);
account.set_owner(key5);
accounts.push((key6, account));
let instructions = vec![CompiledInstruction::new(1, &(), vec![0])];
2019-03-15 09:02:28 -07:00
let tx = Transaction::new_with_compiled_instructions(
&[&keypair],
&[],
Hash::default(),
vec![key6],
instructions,
);
let loaded_accounts = load_accounts(tx, &accounts, &mut error_counters);
assert_eq!(error_counters.call_chain_too_deep, 1);
assert_eq!(loaded_accounts.len(), 1);
assert_eq!(
loaded_accounts[0],
(Err(TransactionError::CallChainTooDeep), None,)
);
}
2020-03-04 10:40:41 -08:00
#[test]
fn test_load_accounts_bad_owner() {
let mut accounts: Vec<TransactionAccount> = Vec::new();
let mut error_counters = TransactionErrorMetrics::default();
2020-03-04 10:40:41 -08:00
let keypair = Keypair::new();
let key0 = keypair.pubkey();
let key1 = Pubkey::new(&[5u8; 32]);
2021-03-09 13:06:07 -08:00
let account = AccountSharedData::new(1, 0, &Pubkey::default());
2020-03-04 10:40:41 -08:00
accounts.push((key0, account));
2021-03-09 13:06:07 -08:00
let mut account = AccountSharedData::new(40, 1, &Pubkey::default());
account.set_executable(true);
2020-03-04 10:40:41 -08:00
accounts.push((key1, account));
let instructions = vec![CompiledInstruction::new(1, &(), vec![0])];
let tx = Transaction::new_with_compiled_instructions(
&[&keypair],
&[],
Hash::default(),
vec![key1],
instructions,
);
let loaded_accounts = load_accounts(tx, &accounts, &mut error_counters);
assert_eq!(error_counters.account_not_found, 1);
assert_eq!(loaded_accounts.len(), 1);
assert_eq!(
loaded_accounts[0],
(Err(TransactionError::ProgramAccountNotFound), None,)
);
}
#[test]
fn test_load_accounts_not_executable() {
let mut accounts: Vec<TransactionAccount> = Vec::new();
let mut error_counters = TransactionErrorMetrics::default();
let keypair = Keypair::new();
let key0 = keypair.pubkey();
let key1 = Pubkey::new(&[5u8; 32]);
2021-03-09 13:06:07 -08:00
let account = AccountSharedData::new(1, 0, &Pubkey::default());
accounts.push((key0, account));
2021-03-09 13:06:07 -08:00
let account = AccountSharedData::new(40, 1, &native_loader::id());
accounts.push((key1, account));
let instructions = vec![CompiledInstruction::new(1, &(), vec![0])];
2019-03-15 09:02:28 -07:00
let tx = Transaction::new_with_compiled_instructions(
&[&keypair],
&[],
Hash::default(),
vec![key1],
instructions,
);
let loaded_accounts = load_accounts(tx, &accounts, &mut error_counters);
assert_eq!(error_counters.invalid_program_for_execution, 1);
assert_eq!(loaded_accounts.len(), 1);
assert_eq!(
loaded_accounts[0],
(Err(TransactionError::InvalidProgramForExecution), None,)
);
}
#[test]
fn test_load_accounts_multiple_loaders() {
let mut accounts: Vec<TransactionAccount> = Vec::new();
let mut error_counters = TransactionErrorMetrics::default();
let keypair = Keypair::new();
let key0 = keypair.pubkey();
let key1 = Pubkey::new(&[5u8; 32]);
let key2 = Pubkey::new(&[6u8; 32]);
2021-03-09 13:06:07 -08:00
let mut account = AccountSharedData::new(1, 0, &Pubkey::default());
account.set_rent_epoch(1);
accounts.push((key0, account));
2021-03-09 13:06:07 -08:00
let mut account = AccountSharedData::new(40, 1, &Pubkey::default());
account.set_executable(true);
account.set_rent_epoch(1);
account.set_owner(native_loader::id());
accounts.push((key1, account));
2021-03-09 13:06:07 -08:00
let mut account = AccountSharedData::new(41, 1, &Pubkey::default());
account.set_executable(true);
account.set_rent_epoch(1);
account.set_owner(key1);
accounts.push((key2, account));
let instructions = vec![
CompiledInstruction::new(1, &(), vec![0]),
CompiledInstruction::new(2, &(), vec![0]),
];
2019-03-15 09:02:28 -07:00
let tx = Transaction::new_with_compiled_instructions(
&[&keypair],
&[],
Hash::default(),
vec![key1, key2],
instructions,
);
let loaded_accounts = load_accounts(tx, &accounts, &mut error_counters);
assert_eq!(error_counters.account_not_found, 0);
assert_eq!(loaded_accounts.len(), 1);
match &loaded_accounts[0] {
2021-11-18 16:07:17 -08:00
(Ok(loaded_transaction), _nonce) => {
assert_eq!(loaded_transaction.accounts.len(), 6);
assert_eq!(loaded_transaction.accounts[0].1, accounts[0].1);
assert_eq!(loaded_transaction.program_indices.len(), 2);
assert_eq!(loaded_transaction.program_indices[0].len(), 1);
assert_eq!(loaded_transaction.program_indices[1].len(), 2);
for program_indices in loaded_transaction.program_indices.iter() {
for (i, program_index) in program_indices.iter().enumerate() {
// +1 to skip first not loader account
assert_eq!(
loaded_transaction.accounts[*program_index as usize].0,
accounts[i + 1].0
);
assert_eq!(
loaded_transaction.accounts[*program_index as usize].1,
accounts[i + 1].1
);
}
}
}
2021-11-18 16:07:17 -08:00
(Err(e), _nonce) => Err(e).unwrap(),
}
}
#[test]
fn test_load_lookup_table_addresses_account_not_found() {
let ancestors = vec![(0, 0)].into_iter().collect();
let accounts = Accounts::new_with_config_for_tests(
Vec::new(),
&ClusterType::Development,
AccountSecondaryIndexes::default(),
false,
AccountShrinkThreshold::default(),
);
let invalid_table_key = Pubkey::new_unique();
let address_table_lookup = MessageAddressTableLookup {
account_key: invalid_table_key,
writable_indexes: vec![],
readonly_indexes: vec![],
};
assert_eq!(
accounts.load_lookup_table_addresses(
&ancestors,
&address_table_lookup,
&SlotHashes::default(),
),
Err(AddressLookupError::LookupTableAccountNotFound),
);
}
#[test]
fn test_load_lookup_table_addresses_invalid_account_owner() {
let ancestors = vec![(0, 0)].into_iter().collect();
let accounts = Accounts::new_with_config_for_tests(
Vec::new(),
&ClusterType::Development,
AccountSecondaryIndexes::default(),
false,
AccountShrinkThreshold::default(),
);
let invalid_table_key = Pubkey::new_unique();
let mut invalid_table_account = AccountSharedData::default();
invalid_table_account.set_lamports(1);
accounts.store_slow_uncached(0, &invalid_table_key, &invalid_table_account);
let address_table_lookup = MessageAddressTableLookup {
account_key: invalid_table_key,
writable_indexes: vec![],
readonly_indexes: vec![],
};
assert_eq!(
accounts.load_lookup_table_addresses(
&ancestors,
&address_table_lookup,
&SlotHashes::default(),
),
Err(AddressLookupError::InvalidAccountOwner),
);
}
#[test]
fn test_load_lookup_table_addresses_invalid_account_data() {
let ancestors = vec![(0, 0)].into_iter().collect();
let accounts = Accounts::new_with_config_for_tests(
Vec::new(),
&ClusterType::Development,
AccountSecondaryIndexes::default(),
false,
AccountShrinkThreshold::default(),
);
let invalid_table_key = Pubkey::new_unique();
let invalid_table_account =
AccountSharedData::new(1, 0, &solana_address_lookup_table_program::id());
accounts.store_slow_uncached(0, &invalid_table_key, &invalid_table_account);
let address_table_lookup = MessageAddressTableLookup {
account_key: invalid_table_key,
writable_indexes: vec![],
readonly_indexes: vec![],
};
assert_eq!(
accounts.load_lookup_table_addresses(
&ancestors,
&address_table_lookup,
&SlotHashes::default(),
),
Err(AddressLookupError::InvalidAccountData),
);
}
#[test]
fn test_load_lookup_table_addresses() {
let ancestors = vec![(1, 1), (0, 0)].into_iter().collect();
let accounts = Accounts::new_with_config_for_tests(
Vec::new(),
&ClusterType::Development,
AccountSecondaryIndexes::default(),
false,
AccountShrinkThreshold::default(),
);
let table_key = Pubkey::new_unique();
let table_addresses = vec![Pubkey::new_unique(), Pubkey::new_unique()];
let table_account = {
let table_state = AddressLookupTable {
meta: LookupTableMeta::default(),
addresses: Cow::Owned(table_addresses.clone()),
};
AccountSharedData::create(
1,
table_state.serialize_for_tests().unwrap(),
solana_address_lookup_table_program::id(),
false,
0,
)
};
accounts.store_slow_uncached(0, &table_key, &table_account);
let address_table_lookup = MessageAddressTableLookup {
account_key: table_key,
writable_indexes: vec![0],
readonly_indexes: vec![1],
};
assert_eq!(
accounts.load_lookup_table_addresses(
&ancestors,
&address_table_lookup,
&SlotHashes::default(),
),
Ok(LoadedAddresses {
writable: vec![table_addresses[0]],
readonly: vec![table_addresses[1]],
}),
);
}
#[test]
fn test_load_by_program_slot() {
let accounts = Accounts::new_with_config_for_tests(
Vec::new(),
&ClusterType::Development,
AccountSecondaryIndexes::default(),
false,
AccountShrinkThreshold::default(),
);
2021-02-18 23:42:09 -08:00
// Load accounts owned by various programs into AccountsDb
let pubkey0 = solana_sdk::pubkey::new_rand();
2021-03-09 13:06:07 -08:00
let account0 = AccountSharedData::new(1, 0, &Pubkey::new(&[2; 32]));
accounts.store_slow_uncached(0, &pubkey0, &account0);
let pubkey1 = solana_sdk::pubkey::new_rand();
2021-03-09 13:06:07 -08:00
let account1 = AccountSharedData::new(1, 0, &Pubkey::new(&[2; 32]));
accounts.store_slow_uncached(0, &pubkey1, &account1);
let pubkey2 = solana_sdk::pubkey::new_rand();
2021-03-09 13:06:07 -08:00
let account2 = AccountSharedData::new(1, 0, &Pubkey::new(&[3; 32]));
accounts.store_slow_uncached(0, &pubkey2, &account2);
2019-02-27 21:42:14 -08:00
let loaded = accounts.load_by_program_slot(0, Some(&Pubkey::new(&[2; 32])));
assert_eq!(loaded.len(), 2);
let loaded = accounts.load_by_program_slot(0, Some(&Pubkey::new(&[3; 32])));
assert_eq!(loaded, vec![(pubkey2, account2)]);
let loaded = accounts.load_by_program_slot(0, Some(&Pubkey::new(&[4; 32])));
assert_eq!(loaded, vec![]);
}
#[test]
fn test_load_accounts_executable_with_write_lock() {
let mut accounts: Vec<TransactionAccount> = Vec::new();
let mut error_counters = TransactionErrorMetrics::default();
let keypair = Keypair::new();
let key0 = keypair.pubkey();
let key1 = Pubkey::new(&[5u8; 32]);
let key2 = Pubkey::new(&[6u8; 32]);
let mut account = AccountSharedData::new(1, 0, &Pubkey::default());
account.set_rent_epoch(1);
accounts.push((key0, account));
let mut account = AccountSharedData::new(40, 1, &native_loader::id());
account.set_executable(true);
account.set_rent_epoch(1);
accounts.push((key1, account));
let mut account = AccountSharedData::new(40, 1, &native_loader::id());
account.set_executable(true);
account.set_rent_epoch(1);
accounts.push((key2, account));
let instructions = vec![CompiledInstruction::new(2, &(), vec![0, 1])];
let mut message = Message::new_with_compiled_instructions(
1,
0,
1, // only one executable marked as readonly
vec![key0, key1, key2],
Hash::default(),
instructions,
);
let tx = Transaction::new(&[&keypair], message.clone(), Hash::default());
let loaded_accounts = load_accounts(tx, &accounts, &mut error_counters);
assert_eq!(error_counters.invalid_writable_account, 1);
assert_eq!(loaded_accounts.len(), 1);
assert_eq!(
loaded_accounts[0],
(Err(TransactionError::InvalidWritableAccount), None)
);
// Mark executables as readonly
message.account_keys = vec![key0, key1, key2]; // revert key change
message.header.num_readonly_unsigned_accounts = 2; // mark both executables as readonly
let tx = Transaction::new(&[&keypair], message, Hash::default());
let loaded_accounts = load_accounts(tx, &accounts, &mut error_counters);
assert_eq!(error_counters.invalid_writable_account, 1);
assert_eq!(loaded_accounts.len(), 1);
let result = loaded_accounts[0].0.as_ref().unwrap();
assert_eq!(result.accounts[..2], accounts[..2]);
assert_eq!(
result.accounts[result.program_indices[0][0] as usize],
accounts[2]
);
}
#[test]
fn test_load_accounts_upgradeable_with_write_lock() {
let mut accounts: Vec<TransactionAccount> = Vec::new();
let mut error_counters = TransactionErrorMetrics::default();
let keypair = Keypair::new();
let key0 = keypair.pubkey();
let key1 = Pubkey::new(&[5u8; 32]);
let key2 = Pubkey::new(&[6u8; 32]);
let programdata_key1 = Pubkey::new(&[7u8; 32]);
let programdata_key2 = Pubkey::new(&[8u8; 32]);
let mut account = AccountSharedData::new(1, 0, &Pubkey::default());
account.set_rent_epoch(1);
accounts.push((key0, account));
let program_data = UpgradeableLoaderState::ProgramData {
slot: 42,
upgrade_authority_address: None,
};
let program = UpgradeableLoaderState::Program {
programdata_address: programdata_key1,
};
let mut account =
AccountSharedData::new_data(40, &program, &bpf_loader_upgradeable::id()).unwrap();
account.set_executable(true);
account.set_rent_epoch(1);
accounts.push((key1, account));
let mut account =
AccountSharedData::new_data(40, &program_data, &bpf_loader_upgradeable::id()).unwrap();
account.set_rent_epoch(1);
accounts.push((programdata_key1, account));
let program = UpgradeableLoaderState::Program {
programdata_address: programdata_key2,
};
let mut account =
AccountSharedData::new_data(40, &program, &bpf_loader_upgradeable::id()).unwrap();
account.set_executable(true);
account.set_rent_epoch(1);
accounts.push((key2, account));
let mut account =
AccountSharedData::new_data(40, &program_data, &bpf_loader_upgradeable::id()).unwrap();
account.set_rent_epoch(1);
accounts.push((programdata_key2, account));
let mut account = AccountSharedData::new(40, 1, &native_loader::id()); // create mock bpf_loader_upgradeable
account.set_executable(true);
account.set_rent_epoch(1);
accounts.push((bpf_loader_upgradeable::id(), account));
let instructions = vec![CompiledInstruction::new(2, &(), vec![0, 1])];
let mut message = Message::new_with_compiled_instructions(
1,
0,
1, // only one executable marked as readonly
vec![key0, key1, key2],
Hash::default(),
instructions,
);
let tx = Transaction::new(&[&keypair], message.clone(), Hash::default());
let loaded_accounts = load_accounts(tx, &accounts, &mut error_counters);
assert_eq!(error_counters.invalid_writable_account, 1);
assert_eq!(loaded_accounts.len(), 1);
assert_eq!(
loaded_accounts[0],
(Err(TransactionError::InvalidWritableAccount), None)
);
// Solution 1: include bpf_loader_upgradeable account
message.account_keys = vec![key0, key1, bpf_loader_upgradeable::id()];
let tx = Transaction::new(&[&keypair], message.clone(), Hash::default());
let loaded_accounts = load_accounts(tx, &accounts, &mut error_counters);
assert_eq!(error_counters.invalid_writable_account, 1);
assert_eq!(loaded_accounts.len(), 1);
let result = loaded_accounts[0].0.as_ref().unwrap();
assert_eq!(result.accounts[..2], accounts[..2]);
assert_eq!(
result.accounts[result.program_indices[0][0] as usize],
accounts[5]
);
// Solution 2: mark programdata as readonly
message.account_keys = vec![key0, key1, key2]; // revert key change
message.header.num_readonly_unsigned_accounts = 2; // mark both executables as readonly
let tx = Transaction::new(&[&keypair], message, Hash::default());
let loaded_accounts = load_accounts(tx, &accounts, &mut error_counters);
assert_eq!(error_counters.invalid_writable_account, 1);
assert_eq!(loaded_accounts.len(), 1);
let result = loaded_accounts[0].0.as_ref().unwrap();
assert_eq!(result.accounts[..2], accounts[..2]);
assert_eq!(
result.accounts[result.program_indices[0][0] as usize],
accounts[5]
);
assert_eq!(
result.accounts[result.program_indices[0][1] as usize],
accounts[4]
);
assert_eq!(
result.accounts[result.program_indices[0][2] as usize],
accounts[3]
);
}
#[test]
fn test_load_accounts_programdata_with_write_lock() {
let mut accounts: Vec<TransactionAccount> = Vec::new();
let mut error_counters = TransactionErrorMetrics::default();
let keypair = Keypair::new();
let key0 = keypair.pubkey();
let key1 = Pubkey::new(&[5u8; 32]);
let key2 = Pubkey::new(&[6u8; 32]);
let mut account = AccountSharedData::new(1, 0, &Pubkey::default());
account.set_rent_epoch(1);
accounts.push((key0, account));
let program_data = UpgradeableLoaderState::ProgramData {
slot: 42,
upgrade_authority_address: None,
};
let mut account =
AccountSharedData::new_data(40, &program_data, &bpf_loader_upgradeable::id()).unwrap();
account.set_rent_epoch(1);
accounts.push((key1, account));
let mut account = AccountSharedData::new(40, 1, &native_loader::id());
account.set_executable(true);
account.set_rent_epoch(1);
accounts.push((key2, account));
let instructions = vec![CompiledInstruction::new(2, &(), vec![0, 1])];
let mut message = Message::new_with_compiled_instructions(
1,
0,
1, // only the program marked as readonly
vec![key0, key1, key2],
Hash::default(),
instructions,
);
let tx = Transaction::new(&[&keypair], message.clone(), Hash::default());
let loaded_accounts = load_accounts(tx, &accounts, &mut error_counters);
assert_eq!(error_counters.invalid_writable_account, 1);
assert_eq!(loaded_accounts.len(), 1);
assert_eq!(
loaded_accounts[0],
(Err(TransactionError::InvalidWritableAccount), None)
);
// Solution 1: include bpf_loader_upgradeable account
let mut account = AccountSharedData::new(40, 1, &native_loader::id()); // create mock bpf_loader_upgradeable
account.set_executable(true);
account.set_rent_epoch(1);
let accounts_with_upgradeable_loader = vec![
accounts[0].clone(),
accounts[1].clone(),
(bpf_loader_upgradeable::id(), account),
];
message.account_keys = vec![key0, key1, bpf_loader_upgradeable::id()];
let tx = Transaction::new(&[&keypair], message.clone(), Hash::default());
let loaded_accounts =
load_accounts(tx, &accounts_with_upgradeable_loader, &mut error_counters);
assert_eq!(error_counters.invalid_writable_account, 1);
assert_eq!(loaded_accounts.len(), 1);
let result = loaded_accounts[0].0.as_ref().unwrap();
assert_eq!(result.accounts[..2], accounts_with_upgradeable_loader[..2]);
assert_eq!(
result.accounts[result.program_indices[0][0] as usize],
accounts_with_upgradeable_loader[2]
);
// Solution 2: mark programdata as readonly
message.account_keys = vec![key0, key1, key2]; // revert key change
message.header.num_readonly_unsigned_accounts = 2; // extend readonly set to include programdata
let tx = Transaction::new(&[&keypair], message, Hash::default());
let loaded_accounts = load_accounts(tx, &accounts, &mut error_counters);
assert_eq!(error_counters.invalid_writable_account, 1);
assert_eq!(loaded_accounts.len(), 1);
let result = loaded_accounts[0].0.as_ref().unwrap();
assert_eq!(result.accounts[..2], accounts[..2]);
assert_eq!(
result.accounts[result.program_indices[0][0] as usize],
accounts[2]
);
}
#[test]
fn test_accounts_account_not_found() {
let accounts = Accounts::new_with_config_for_tests(
Vec::new(),
&ClusterType::Development,
AccountSecondaryIndexes::default(),
false,
AccountShrinkThreshold::default(),
);
let mut error_counters = TransactionErrorMetrics::default();
let ancestors = vec![(0, 0)].into_iter().collect();
let keypair = Keypair::new();
let mut account = AccountSharedData::new(1, 0, &Pubkey::default());
account.set_executable(true);
accounts.store_slow_uncached(0, &keypair.pubkey(), &account);
assert_eq!(
accounts.load_executable_accounts(
&ancestors,
&mut vec![(keypair.pubkey(), account)],
0,
&mut error_counters,
),
Err(TransactionError::ProgramAccountNotFound)
);
assert_eq!(error_counters.account_not_found, 1);
}
#[test]
#[should_panic]
fn test_accounts_empty_bank_hash() {
let accounts = Accounts::new_with_config_for_tests(
Vec::new(),
&ClusterType::Development,
AccountSecondaryIndexes::default(),
false,
AccountShrinkThreshold::default(),
);
2022-04-19 09:29:29 -07:00
accounts.bank_hash_at(1, &Rewrites::default());
}
#[test]
fn test_lock_accounts_with_duplicates() {
let accounts = Accounts::new_with_config_for_tests(
Vec::new(),
&ClusterType::Development,
AccountSecondaryIndexes::default(),
false,
AccountShrinkThreshold::default(),
);
let keypair = Keypair::new();
let message = Message {
header: MessageHeader {
num_required_signatures: 1,
..MessageHeader::default()
},
account_keys: vec![keypair.pubkey(), keypair.pubkey()],
..Message::default()
};
let tx = new_sanitized_tx(&[&keypair], message, Hash::default());
let results = accounts.lock_accounts([tx].iter(), MAX_TX_ACCOUNT_LOCKS);
assert_eq!(results[0], Err(TransactionError::AccountLoadedTwice));
}
#[test]
fn test_lock_accounts_with_too_many_accounts() {
let accounts = Accounts::new_with_config_for_tests(
Vec::new(),
&ClusterType::Development,
AccountSecondaryIndexes::default(),
false,
AccountShrinkThreshold::default(),
);
let keypair = Keypair::new();
// Allow up to MAX_TX_ACCOUNT_LOCKS
{
let num_account_keys = MAX_TX_ACCOUNT_LOCKS;
let mut account_keys: Vec<_> = (0..num_account_keys)
.map(|_| Pubkey::new_unique())
.collect();
account_keys[0] = keypair.pubkey();
let message = Message {
header: MessageHeader {
num_required_signatures: 1,
..MessageHeader::default()
},
account_keys,
..Message::default()
};
let txs = vec![new_sanitized_tx(&[&keypair], message, Hash::default())];
let results = accounts.lock_accounts(txs.iter(), MAX_TX_ACCOUNT_LOCKS);
assert_eq!(results[0], Ok(()));
accounts.unlock_accounts(txs.iter(), &results);
}
// Disallow over MAX_TX_ACCOUNT_LOCKS
{
let num_account_keys = MAX_TX_ACCOUNT_LOCKS + 1;
let mut account_keys: Vec<_> = (0..num_account_keys)
.map(|_| Pubkey::new_unique())
.collect();
account_keys[0] = keypair.pubkey();
let message = Message {
header: MessageHeader {
num_required_signatures: 1,
..MessageHeader::default()
},
account_keys,
..Message::default()
};
let txs = vec![new_sanitized_tx(&[&keypair], message, Hash::default())];
let results = accounts.lock_accounts(txs.iter(), MAX_TX_ACCOUNT_LOCKS);
assert_eq!(results[0], Err(TransactionError::TooManyAccountLocks));
}
}
#[test]
fn test_accounts_locks() {
let keypair0 = Keypair::new();
let keypair1 = Keypair::new();
let keypair2 = Keypair::new();
let keypair3 = Keypair::new();
2021-03-09 13:06:07 -08:00
let account0 = AccountSharedData::new(1, 0, &Pubkey::default());
let account1 = AccountSharedData::new(2, 0, &Pubkey::default());
let account2 = AccountSharedData::new(3, 0, &Pubkey::default());
let account3 = AccountSharedData::new(4, 0, &Pubkey::default());
let accounts = Accounts::new_with_config_for_tests(
Vec::new(),
&ClusterType::Development,
AccountSecondaryIndexes::default(),
false,
AccountShrinkThreshold::default(),
);
accounts.store_slow_uncached(0, &keypair0.pubkey(), &account0);
accounts.store_slow_uncached(0, &keypair1.pubkey(), &account1);
accounts.store_slow_uncached(0, &keypair2.pubkey(), &account2);
accounts.store_slow_uncached(0, &keypair3.pubkey(), &account3);
let instructions = vec![CompiledInstruction::new(2, &(), vec![0, 1])];
let message = Message::new_with_compiled_instructions(
1,
0,
2,
vec![keypair0.pubkey(), keypair1.pubkey(), native_loader::id()],
Hash::default(),
instructions,
);
let tx = new_sanitized_tx(&[&keypair0], message, Hash::default());
let results0 = accounts.lock_accounts([tx.clone()].iter(), MAX_TX_ACCOUNT_LOCKS);
assert!(results0[0].is_ok());
assert_eq!(
*accounts
.account_locks
.lock()
.unwrap()
.readonly_locks
.get(&keypair1.pubkey())
.unwrap(),
1
);
let instructions = vec![CompiledInstruction::new(2, &(), vec![0, 1])];
let message = Message::new_with_compiled_instructions(
1,
0,
2,
vec![keypair2.pubkey(), keypair1.pubkey(), native_loader::id()],
Hash::default(),
instructions,
);
let tx0 = new_sanitized_tx(&[&keypair2], message, Hash::default());
let instructions = vec![CompiledInstruction::new(2, &(), vec![0, 1])];
let message = Message::new_with_compiled_instructions(
1,
0,
2,
vec![keypair1.pubkey(), keypair3.pubkey(), native_loader::id()],
Hash::default(),
instructions,
);
let tx1 = new_sanitized_tx(&[&keypair1], message, Hash::default());
let txs = vec![tx0, tx1];
let results1 = accounts.lock_accounts(txs.iter(), MAX_TX_ACCOUNT_LOCKS);
assert!(results1[0].is_ok()); // Read-only account (keypair1) can be referenced multiple times
assert!(results1[1].is_err()); // Read-only account (keypair1) cannot also be locked as writable
assert_eq!(
*accounts
.account_locks
.lock()
.unwrap()
.readonly_locks
.get(&keypair1.pubkey())
.unwrap(),
2
);
accounts.unlock_accounts([tx].iter(), &results0);
accounts.unlock_accounts(txs.iter(), &results1);
let instructions = vec![CompiledInstruction::new(2, &(), vec![0, 1])];
let message = Message::new_with_compiled_instructions(
1,
0,
2,
vec![keypair1.pubkey(), keypair3.pubkey(), native_loader::id()],
Hash::default(),
instructions,
);
let tx = new_sanitized_tx(&[&keypair1], message, Hash::default());
let results2 = accounts.lock_accounts([tx].iter(), MAX_TX_ACCOUNT_LOCKS);
assert!(results2[0].is_ok()); // Now keypair1 account can be locked as writable
// Check that read-only lock with zero references is deleted
assert!(accounts
.account_locks
.lock()
.unwrap()
.readonly_locks
.get(&keypair1.pubkey())
.is_none());
}
#[test]
fn test_accounts_locks_multithreaded() {
let counter = Arc::new(AtomicU64::new(0));
let exit = Arc::new(AtomicBool::new(false));
let keypair0 = Keypair::new();
let keypair1 = Keypair::new();
let keypair2 = Keypair::new();
2021-03-09 13:06:07 -08:00
let account0 = AccountSharedData::new(1, 0, &Pubkey::default());
let account1 = AccountSharedData::new(2, 0, &Pubkey::default());
let account2 = AccountSharedData::new(3, 0, &Pubkey::default());
let accounts = Accounts::new_with_config_for_tests(
Vec::new(),
&ClusterType::Development,
AccountSecondaryIndexes::default(),
false,
AccountShrinkThreshold::default(),
);
accounts.store_slow_uncached(0, &keypair0.pubkey(), &account0);
accounts.store_slow_uncached(0, &keypair1.pubkey(), &account1);
accounts.store_slow_uncached(0, &keypair2.pubkey(), &account2);
let accounts_arc = Arc::new(accounts);
let instructions = vec![CompiledInstruction::new(2, &(), vec![0, 1])];
let readonly_message = Message::new_with_compiled_instructions(
1,
0,
2,
vec![keypair0.pubkey(), keypair1.pubkey(), native_loader::id()],
Hash::default(),
instructions,
);
let readonly_tx = new_sanitized_tx(&[&keypair0], readonly_message, Hash::default());
let instructions = vec![CompiledInstruction::new(2, &(), vec![0, 1])];
let writable_message = Message::new_with_compiled_instructions(
1,
0,
2,
vec![keypair1.pubkey(), keypair2.pubkey(), native_loader::id()],
Hash::default(),
instructions,
);
let writable_tx = new_sanitized_tx(&[&keypair1], writable_message, Hash::default());
let counter_clone = counter.clone();
let accounts_clone = accounts_arc.clone();
let exit_clone = exit.clone();
thread::spawn(move || {
let counter_clone = counter_clone.clone();
let exit_clone = exit_clone.clone();
loop {
let txs = vec![writable_tx.clone()];
let results = accounts_clone
.clone()
.lock_accounts(txs.iter(), MAX_TX_ACCOUNT_LOCKS);
for result in results.iter() {
if result.is_ok() {
counter_clone.clone().fetch_add(1, Ordering::SeqCst);
}
}
accounts_clone.unlock_accounts(txs.iter(), &results);
if exit_clone.clone().load(Ordering::Relaxed) {
break;
}
}
});
let counter_clone = counter;
for _ in 0..5 {
let txs = vec![readonly_tx.clone()];
let results = accounts_arc
.clone()
.lock_accounts(txs.iter(), MAX_TX_ACCOUNT_LOCKS);
if results[0].is_ok() {
let counter_value = counter_clone.clone().load(Ordering::SeqCst);
thread::sleep(time::Duration::from_millis(50));
assert_eq!(counter_value, counter_clone.clone().load(Ordering::SeqCst));
}
accounts_arc.unlock_accounts(txs.iter(), &results);
thread::sleep(time::Duration::from_millis(50));
}
exit.store(true, Ordering::Relaxed);
}
#[test]
fn test_demote_program_write_locks() {
let keypair0 = Keypair::new();
let keypair1 = Keypair::new();
let keypair2 = Keypair::new();
let keypair3 = Keypair::new();
let account0 = AccountSharedData::new(1, 0, &Pubkey::default());
let account1 = AccountSharedData::new(2, 0, &Pubkey::default());
let account2 = AccountSharedData::new(3, 0, &Pubkey::default());
let account3 = AccountSharedData::new(4, 0, &Pubkey::default());
let accounts = Accounts::new_with_config_for_tests(
Vec::new(),
&ClusterType::Development,
AccountSecondaryIndexes::default(),
false,
AccountShrinkThreshold::default(),
);
accounts.store_slow_uncached(0, &keypair0.pubkey(), &account0);
accounts.store_slow_uncached(0, &keypair1.pubkey(), &account1);
accounts.store_slow_uncached(0, &keypair2.pubkey(), &account2);
accounts.store_slow_uncached(0, &keypair3.pubkey(), &account3);
let instructions = vec![CompiledInstruction::new(2, &(), vec![0, 1])];
let message = Message::new_with_compiled_instructions(
1,
0,
0, // All accounts marked as writable
vec![keypair0.pubkey(), keypair1.pubkey(), native_loader::id()],
Hash::default(),
instructions,
);
let tx = new_sanitized_tx(&[&keypair0], message, Hash::default());
let results0 = accounts.lock_accounts([tx].iter(), MAX_TX_ACCOUNT_LOCKS);
assert!(results0[0].is_ok());
// Instruction program-id account demoted to readonly
assert_eq!(
*accounts
.account_locks
.lock()
.unwrap()
.readonly_locks
.get(&native_loader::id())
.unwrap(),
1
);
// Non-program accounts remain writable
assert!(accounts
.account_locks
.lock()
.unwrap()
.write_locks
.contains(&keypair0.pubkey()));
assert!(accounts
.account_locks
.lock()
.unwrap()
.write_locks
.contains(&keypair1.pubkey()));
}
#[test]
fn test_accounts_locks_with_results() {
let keypair0 = Keypair::new();
let keypair1 = Keypair::new();
let keypair2 = Keypair::new();
let keypair3 = Keypair::new();
let account0 = AccountSharedData::new(1, 0, &Pubkey::default());
let account1 = AccountSharedData::new(2, 0, &Pubkey::default());
let account2 = AccountSharedData::new(3, 0, &Pubkey::default());
let account3 = AccountSharedData::new(4, 0, &Pubkey::default());
let accounts = Accounts::new_with_config_for_tests(
Vec::new(),
&ClusterType::Development,
AccountSecondaryIndexes::default(),
false,
AccountShrinkThreshold::default(),
);
accounts.store_slow_uncached(0, &keypair0.pubkey(), &account0);
accounts.store_slow_uncached(0, &keypair1.pubkey(), &account1);
accounts.store_slow_uncached(0, &keypair2.pubkey(), &account2);
accounts.store_slow_uncached(0, &keypair3.pubkey(), &account3);
let instructions = vec![CompiledInstruction::new(2, &(), vec![0, 1])];
let message = Message::new_with_compiled_instructions(
1,
0,
2,
vec![keypair1.pubkey(), keypair0.pubkey(), native_loader::id()],
Hash::default(),
instructions,
);
let tx0 = new_sanitized_tx(&[&keypair1], message, Hash::default());
let instructions = vec![CompiledInstruction::new(2, &(), vec![0, 1])];
let message = Message::new_with_compiled_instructions(
1,
0,
2,
vec![keypair2.pubkey(), keypair0.pubkey(), native_loader::id()],
Hash::default(),
instructions,
);
let tx1 = new_sanitized_tx(&[&keypair2], message, Hash::default());
let instructions = vec![CompiledInstruction::new(2, &(), vec![0, 1])];
let message = Message::new_with_compiled_instructions(
1,
0,
2,
vec![keypair3.pubkey(), keypair0.pubkey(), native_loader::id()],
Hash::default(),
instructions,
);
let tx2 = new_sanitized_tx(&[&keypair3], message, Hash::default());
let txs = vec![tx0, tx1, tx2];
let qos_results = vec![
Ok(()),
Err(TransactionError::WouldExceedMaxBlockCostLimit),
Ok(()),
];
let results = accounts.lock_accounts_with_results(
txs.iter(),
qos_results.iter(),
MAX_TX_ACCOUNT_LOCKS,
);
assert!(results[0].is_ok()); // Read-only account (keypair0) can be referenced multiple times
assert!(results[1].is_err()); // is not locked due to !qos_results[1].is_ok()
assert!(results[2].is_ok()); // Read-only account (keypair0) can be referenced multiple times
// verify that keypair0 read-only lock twice (for tx0 and tx2)
assert_eq!(
*accounts
.account_locks
.lock()
.unwrap()
.readonly_locks
.get(&keypair0.pubkey())
.unwrap(),
2
);
// verify that keypair2 (for tx1) is not write-locked
assert!(accounts
.account_locks
.lock()
.unwrap()
.write_locks
.get(&keypair2.pubkey())
.is_none());
accounts.unlock_accounts(txs.iter(), &results);
// check all locks to be removed
assert!(accounts
.account_locks
.lock()
.unwrap()
.readonly_locks
.is_empty());
assert!(accounts
.account_locks
.lock()
.unwrap()
.write_locks
.is_empty());
}
#[test]
2019-08-19 13:00:37 -07:00
fn test_collect_accounts_to_store() {
let keypair0 = Keypair::new();
let keypair1 = Keypair::new();
let pubkey = solana_sdk::pubkey::new_rand();
let account0 = AccountSharedData::new(1, 0, &Pubkey::default());
let account1 = AccountSharedData::new(2, 0, &Pubkey::default());
let account2 = AccountSharedData::new(3, 0, &Pubkey::default());
let rent_collector = RentCollector::default();
let instructions = vec![CompiledInstruction::new(2, &(), vec![0, 1])];
let message = Message::new_with_compiled_instructions(
1,
0,
2,
vec![keypair0.pubkey(), pubkey, native_loader::id()],
Hash::default(),
instructions,
);
let transaction_accounts0 = vec![
(message.account_keys[0], account0),
(message.account_keys[1], account2.clone()),
];
let tx0 = new_sanitized_tx(&[&keypair0], message, Hash::default());
let tx0_sign = *tx0.signature();
let instructions = vec![CompiledInstruction::new(2, &(), vec![0, 1])];
let message = Message::new_with_compiled_instructions(
1,
0,
2,
vec![keypair1.pubkey(), pubkey, native_loader::id()],
Hash::default(),
instructions,
);
let transaction_accounts1 = vec![
(message.account_keys[0], account1),
(message.account_keys[1], account2),
];
let tx1 = new_sanitized_tx(&[&keypair1], message, Hash::default());
let tx1_sign = *tx1.signature();
let loaded0 = (
Ok(LoadedTransaction {
accounts: transaction_accounts0,
program_indices: vec![],
rent: 0,
rent_debits: RentDebits::default(),
}),
None,
);
let loaded1 = (
Ok(LoadedTransaction {
accounts: transaction_accounts1,
program_indices: vec![],
rent: 0,
rent_debits: RentDebits::default(),
}),
None,
);
let mut loaded = vec![loaded0, loaded1];
let accounts = Accounts::new_with_config_for_tests(
Vec::new(),
&ClusterType::Development,
AccountSecondaryIndexes::default(),
false,
AccountShrinkThreshold::default(),
);
{
accounts
.account_locks
.lock()
.unwrap()
.insert_new_readonly(&pubkey);
}
let txs = vec![tx0, tx1];
let execution_results = vec![new_execution_result(Ok(()), None); 2];
let (collected_accounts, txn_signatures) = accounts.collect_accounts_to_store(
&txs,
&execution_results,
loaded.as_mut_slice(),
&rent_collector,
&DurableNonce::default(),
0,
);
2019-08-19 13:00:37 -07:00
assert_eq!(collected_accounts.len(), 2);
assert!(collected_accounts
.iter()
.any(|(pubkey, _account)| *pubkey == &keypair0.pubkey()));
2019-08-19 13:00:37 -07:00
assert!(collected_accounts
.iter()
.any(|(pubkey, _account)| *pubkey == &keypair1.pubkey()));
2019-08-19 13:00:37 -07:00
assert_eq!(txn_signatures.len(), 2);
assert!(txn_signatures
.iter()
.any(|signature| signature.unwrap().to_string().eq(&tx0_sign.to_string())));
assert!(txn_signatures
.iter()
.any(|signature| signature.unwrap().to_string().eq(&tx1_sign.to_string())));
// Ensure readonly_lock reflects lock
assert_eq!(
*accounts
.account_locks
.lock()
.unwrap()
.readonly_locks
.get(&pubkey)
.unwrap(),
1
);
}
#[test]
fn huge_clean() {
solana_logger::setup();
let accounts = Accounts::new_with_config_for_tests(
Vec::new(),
&ClusterType::Development,
AccountSecondaryIndexes::default(),
false,
AccountShrinkThreshold::default(),
);
let mut old_pubkey = Pubkey::default();
let zero_account = AccountSharedData::new(0, 0, AccountSharedData::default().owner());
info!("storing..");
for i in 0..2_000 {
let pubkey = solana_sdk::pubkey::new_rand();
2022-11-09 11:39:38 -08:00
let account = AccountSharedData::new(i + 1, 0, AccountSharedData::default().owner());
accounts.store_slow_uncached(i, &pubkey, &account);
accounts.store_slow_uncached(i, &old_pubkey, &zero_account);
old_pubkey = pubkey;
accounts.add_root(i);
if i % 1_000 == 0 {
info!(" store {}", i);
}
}
info!("done..cleaning..");
accounts.accounts_db.clean_accounts_for_tests();
}
fn load_accounts_no_store(
accounts: &Accounts,
tx: Transaction,
account_overrides: Option<&AccountOverrides>,
) -> Vec<TransactionLoadResult> {
let tx = SanitizedTransaction::from_transaction_for_tests(tx);
let rent_collector = RentCollector::default();
let mut hash_queue = BlockhashQueue::new(100);
hash_queue.register_hash(tx.message().recent_blockhash(), 10);
let ancestors = vec![(0, 0)].into_iter().collect();
let mut error_counters = TransactionErrorMetrics::default();
accounts.load_accounts(
&ancestors,
&[tx],
vec![(Ok(()), None)],
&hash_queue,
&mut error_counters,
&rent_collector,
&FeatureSet::all_enabled(),
2022-02-11 16:23:16 -08:00
&FeeStructure::default(),
account_overrides,
)
}
#[test]
fn test_instructions() {
solana_logger::setup();
let accounts = Accounts::new_with_config_for_tests(
Vec::new(),
&ClusterType::Development,
AccountSecondaryIndexes::default(),
false,
AccountShrinkThreshold::default(),
);
let instructions_key = solana_sdk::sysvar::instructions::id();
let keypair = Keypair::new();
let instructions = vec![CompiledInstruction::new(1, &(), vec![0, 1])];
let tx = Transaction::new_with_compiled_instructions(
&[&keypair],
&[solana_sdk::pubkey::new_rand(), instructions_key],
Hash::default(),
vec![native_loader::id()],
instructions,
);
let loaded_accounts = load_accounts_no_store(&accounts, tx, None);
assert_eq!(loaded_accounts.len(), 1);
assert!(loaded_accounts[0].0.is_err());
}
#[test]
fn test_overrides() {
solana_logger::setup();
let accounts = Accounts::new_with_config_for_tests(
Vec::new(),
&ClusterType::Development,
AccountSecondaryIndexes::default(),
false,
AccountShrinkThreshold::default(),
);
let mut account_overrides = AccountOverrides::default();
let slot_history_id = sysvar::slot_history::id();
let account = AccountSharedData::new(42, 0, &Pubkey::default());
account_overrides.set_slot_history(Some(account));
let keypair = Keypair::new();
let account = AccountSharedData::new(1_000_000, 0, &Pubkey::default());
accounts.store_slow_uncached(0, &keypair.pubkey(), &account);
let instructions = vec![CompiledInstruction::new(2, &(), vec![0])];
let tx = Transaction::new_with_compiled_instructions(
&[&keypair],
&[slot_history_id],
Hash::default(),
vec![native_loader::id()],
instructions,
);
let loaded_accounts = load_accounts_no_store(&accounts, tx, Some(&account_overrides));
assert_eq!(loaded_accounts.len(), 1);
let loaded_transaction = loaded_accounts[0].0.as_ref().unwrap();
assert_eq!(loaded_transaction.accounts[0].0, keypair.pubkey());
assert_eq!(loaded_transaction.accounts[1].0, slot_history_id);
assert_eq!(loaded_transaction.accounts[1].1.lamports(), 42);
}
fn create_accounts_prepare_if_nonce_account() -> (
Pubkey,
2021-03-09 13:06:07 -08:00
AccountSharedData,
AccountSharedData,
DurableNonce,
u64,
2021-03-09 13:06:07 -08:00
Option<AccountSharedData>,
) {
let data = NonceVersions::new(NonceState::Initialized(nonce::state::Data::default()));
2021-03-09 13:06:07 -08:00
let account = AccountSharedData::new_data(42, &data, &system_program::id()).unwrap();
let mut pre_account = account.clone();
pre_account.set_lamports(43);
let durable_nonce = DurableNonce::from_blockhash(&Hash::new(&[1u8; 32]));
(
Pubkey::default(),
pre_account,
account,
durable_nonce,
1234,
None,
)
}
fn run_prepare_if_nonce_account_test(
2021-11-18 16:07:17 -08:00
account_address: &Pubkey,
2021-03-09 13:06:07 -08:00
account: &mut AccountSharedData,
tx_result: &Result<()>,
is_fee_payer: bool,
maybe_nonce: Option<(&NonceFull, bool)>,
durable_nonce: &DurableNonce,
lamports_per_signature: u64,
2021-03-09 13:06:07 -08:00
expect_account: &AccountSharedData,
) -> bool {
// Verify expect_account's relationship
if !is_fee_payer {
match maybe_nonce {
Some((nonce, _)) if nonce.address() == account_address => {
assert_ne!(expect_account, nonce.account())
}
_ => assert_eq!(expect_account, account),
}
}
prepare_if_nonce_account(
2021-11-18 16:07:17 -08:00
account_address,
account,
tx_result,
is_fee_payer,
2021-11-18 16:07:17 -08:00
maybe_nonce,
durable_nonce,
lamports_per_signature,
);
assert_eq!(expect_account, account);
expect_account == account
}
#[test]
fn test_prepare_if_nonce_account_expected() {
let (
2021-11-18 16:07:17 -08:00
pre_account_address,
pre_account,
mut post_account,
blockhash,
lamports_per_signature,
2021-11-18 16:07:17 -08:00
maybe_fee_payer_account,
) = create_accounts_prepare_if_nonce_account();
2021-11-18 16:07:17 -08:00
let post_account_address = pre_account_address;
let nonce = NonceFull::new(
pre_account_address,
pre_account.clone(),
maybe_fee_payer_account,
);
let mut expect_account = pre_account;
expect_account
.set_state(&NonceVersions::new(NonceState::Initialized(
nonce::state::Data::new(Pubkey::default(), blockhash, lamports_per_signature),
)))
.unwrap();
assert!(run_prepare_if_nonce_account_test(
2021-11-18 16:07:17 -08:00
&post_account_address,
&mut post_account,
&Ok(()),
false,
Some((&nonce, true)),
&blockhash,
lamports_per_signature,
&expect_account,
));
}
#[test]
fn test_prepare_if_nonce_account_not_nonce_tx() {
let (
2021-11-18 16:07:17 -08:00
pre_account_address,
_pre_account,
_post_account,
blockhash,
lamports_per_signature,
2021-11-18 16:07:17 -08:00
_maybe_fee_payer_account,
) = create_accounts_prepare_if_nonce_account();
2021-11-18 16:07:17 -08:00
let post_account_address = pre_account_address;
2021-03-09 13:06:07 -08:00
let mut post_account = AccountSharedData::default();
let expect_account = post_account.clone();
assert!(run_prepare_if_nonce_account_test(
2021-11-18 16:07:17 -08:00
&post_account_address,
&mut post_account,
&Ok(()),
false,
None,
&blockhash,
lamports_per_signature,
&expect_account,
));
}
#[test]
2021-11-18 16:07:17 -08:00
fn test_prepare_if_nonce_account_not_nonce_address() {
let (
2021-11-18 16:07:17 -08:00
pre_account_address,
pre_account,
mut post_account,
blockhash,
lamports_per_signature,
2021-11-18 16:07:17 -08:00
maybe_fee_payer_account,
) = create_accounts_prepare_if_nonce_account();
let nonce = NonceFull::new(pre_account_address, pre_account, maybe_fee_payer_account);
let expect_account = post_account.clone();
// Wrong key
assert!(run_prepare_if_nonce_account_test(
&Pubkey::new(&[1u8; 32]),
2021-11-18 16:07:17 -08:00
&mut post_account,
&Ok(()),
false,
Some((&nonce, true)),
&blockhash,
lamports_per_signature,
&expect_account,
));
}
#[test]
fn test_prepare_if_nonce_account_tx_error() {
let (
2021-11-18 16:07:17 -08:00
pre_account_address,
pre_account,
mut post_account,
blockhash,
lamports_per_signature,
2021-11-18 16:07:17 -08:00
maybe_fee_payer_account,
) = create_accounts_prepare_if_nonce_account();
2021-11-18 16:07:17 -08:00
let post_account_address = pre_account_address;
let mut expect_account = pre_account.clone();
let nonce = NonceFull::new(pre_account_address, pre_account, maybe_fee_payer_account);
expect_account
.set_state(&NonceVersions::new(NonceState::Initialized(
nonce::state::Data::new(Pubkey::default(), blockhash, lamports_per_signature),
)))
.unwrap();
assert!(run_prepare_if_nonce_account_test(
2021-11-18 16:07:17 -08:00
&post_account_address,
&mut post_account,
&Err(TransactionError::InstructionError(
0,
InstructionError::InvalidArgument,
)),
false,
Some((&nonce, true)),
&blockhash,
lamports_per_signature,
&expect_account,
));
}
#[test]
fn test_rollback_nonce_fee_payer() {
let nonce_account = AccountSharedData::new_data(1, &(), &system_program::id()).unwrap();
let pre_fee_payer_account =
AccountSharedData::new_data(42, &(), &system_program::id()).unwrap();
let mut post_fee_payer_account =
AccountSharedData::new_data(84, &[1, 2, 3, 4], &system_program::id()).unwrap();
let nonce = NonceFull::new(
Pubkey::new_unique(),
nonce_account,
Some(pre_fee_payer_account.clone()),
);
assert!(run_prepare_if_nonce_account_test(
&Pubkey::new_unique(),
&mut post_fee_payer_account.clone(),
&Err(TransactionError::InstructionError(
0,
InstructionError::InvalidArgument,
)),
false,
Some((&nonce, true)),
&DurableNonce::default(),
1,
&post_fee_payer_account.clone(),
));
assert!(run_prepare_if_nonce_account_test(
&Pubkey::new_unique(),
&mut post_fee_payer_account.clone(),
&Ok(()),
true,
Some((&nonce, true)),
&DurableNonce::default(),
1,
&post_fee_payer_account.clone(),
));
assert!(run_prepare_if_nonce_account_test(
&Pubkey::new_unique(),
&mut post_fee_payer_account.clone(),
&Err(TransactionError::InstructionError(
0,
InstructionError::InvalidArgument,
)),
true,
None,
&DurableNonce::default(),
1,
&post_fee_payer_account.clone(),
));
assert!(run_prepare_if_nonce_account_test(
&Pubkey::new_unique(),
&mut post_fee_payer_account,
&Err(TransactionError::InstructionError(
0,
InstructionError::InvalidArgument,
)),
true,
Some((&nonce, true)),
&DurableNonce::default(),
1,
&pre_fee_payer_account,
));
}
#[test]
fn test_nonced_failure_accounts_rollback_from_pays() {
let rent_collector = RentCollector::default();
let nonce_address = Pubkey::new_unique();
let nonce_authority = keypair_from_seed(&[0; 32]).unwrap();
let from = keypair_from_seed(&[1; 32]).unwrap();
let from_address = from.pubkey();
let to_address = Pubkey::new_unique();
let durable_nonce = DurableNonce::from_blockhash(&Hash::new_unique());
let nonce_state = NonceVersions::new(NonceState::Initialized(nonce::state::Data::new(
nonce_authority.pubkey(),
durable_nonce,
0,
)));
let nonce_account_post =
AccountSharedData::new_data(43, &nonce_state, &system_program::id()).unwrap();
let from_account_post = AccountSharedData::new(4199, 0, &Pubkey::default());
let to_account = AccountSharedData::new(2, 0, &Pubkey::default());
let nonce_authority_account = AccountSharedData::new(3, 0, &Pubkey::default());
let recent_blockhashes_sysvar_account = AccountSharedData::new(4, 0, &Pubkey::default());
let instructions = vec![
system_instruction::advance_nonce_account(&nonce_address, &nonce_authority.pubkey()),
system_instruction::transfer(&from_address, &to_address, 42),
];
let message = Message::new(&instructions, Some(&from_address));
let blockhash = Hash::new_unique();
let transaction_accounts = vec![
(message.account_keys[0], from_account_post),
(message.account_keys[1], nonce_authority_account),
(message.account_keys[2], nonce_account_post),
(message.account_keys[3], to_account),
(message.account_keys[4], recent_blockhashes_sysvar_account),
];
let tx = new_sanitized_tx(&[&nonce_authority, &from], message, blockhash);
let durable_nonce = DurableNonce::from_blockhash(&Hash::new_unique());
let nonce_state = NonceVersions::new(NonceState::Initialized(nonce::state::Data::new(
nonce_authority.pubkey(),
durable_nonce,
0,
)));
2021-03-09 13:06:07 -08:00
let nonce_account_pre =
AccountSharedData::new_data(42, &nonce_state, &system_program::id()).unwrap();
let from_account_pre = AccountSharedData::new(4242, 0, &Pubkey::default());
2021-11-18 16:07:17 -08:00
let nonce = Some(NonceFull::new(
nonce_address,
nonce_account_pre.clone(),
Some(from_account_pre.clone()),
));
let loaded = (
Ok(LoadedTransaction {
accounts: transaction_accounts,
program_indices: vec![],
rent: 0,
rent_debits: RentDebits::default(),
}),
2021-11-18 16:07:17 -08:00
nonce.clone(),
);
let mut loaded = vec![loaded];
let durable_nonce = DurableNonce::from_blockhash(&Hash::new_unique());
let accounts = Accounts::new_with_config_for_tests(
Vec::new(),
&ClusterType::Development,
AccountSecondaryIndexes::default(),
false,
AccountShrinkThreshold::default(),
);
let txs = vec![tx];
let execution_results = vec![new_execution_result(
Err(TransactionError::InstructionError(
1,
InstructionError::InvalidArgument,
)),
nonce.as_ref(),
)];
let (collected_accounts, _) = accounts.collect_accounts_to_store(
&txs,
&execution_results,
loaded.as_mut_slice(),
&rent_collector,
&durable_nonce,
0,
);
assert_eq!(collected_accounts.len(), 2);
assert_eq!(
collected_accounts
.iter()
.find(|(pubkey, _account)| *pubkey == &from_address)
.map(|(_pubkey, account)| *account)
.cloned()
.unwrap(),
from_account_pre,
);
let collected_nonce_account = collected_accounts
.iter()
.find(|(pubkey, _account)| *pubkey == &nonce_address)
.map(|(_pubkey, account)| *account)
.cloned()
.unwrap();
2021-05-03 08:45:54 -07:00
assert_eq!(
collected_nonce_account.lamports(),
nonce_account_pre.lamports(),
);
assert_matches!(
nonce_account::verify_nonce_account(&collected_nonce_account, durable_nonce.as_hash()),
Some(_)
);
}
#[test]
fn test_nonced_failure_accounts_rollback_nonce_pays() {
let rent_collector = RentCollector::default();
let nonce_authority = keypair_from_seed(&[0; 32]).unwrap();
let nonce_address = nonce_authority.pubkey();
let from = keypair_from_seed(&[1; 32]).unwrap();
let from_address = from.pubkey();
let to_address = Pubkey::new_unique();
let durable_nonce = DurableNonce::from_blockhash(&Hash::new_unique());
let nonce_state = NonceVersions::new(NonceState::Initialized(nonce::state::Data::new(
nonce_authority.pubkey(),
durable_nonce,
0,
)));
let nonce_account_post =
AccountSharedData::new_data(43, &nonce_state, &system_program::id()).unwrap();
let from_account_post = AccountSharedData::new(4200, 0, &Pubkey::default());
let to_account = AccountSharedData::new(2, 0, &Pubkey::default());
let nonce_authority_account = AccountSharedData::new(3, 0, &Pubkey::default());
let recent_blockhashes_sysvar_account = AccountSharedData::new(4, 0, &Pubkey::default());
let instructions = vec![
system_instruction::advance_nonce_account(&nonce_address, &nonce_authority.pubkey()),
system_instruction::transfer(&from_address, &to_address, 42),
];
let message = Message::new(&instructions, Some(&nonce_address));
let blockhash = Hash::new_unique();
let transaction_accounts = vec![
(message.account_keys[0], from_account_post),
(message.account_keys[1], nonce_authority_account),
(message.account_keys[2], nonce_account_post),
(message.account_keys[3], to_account),
(message.account_keys[4], recent_blockhashes_sysvar_account),
];
let tx = new_sanitized_tx(&[&nonce_authority, &from], message, blockhash);
let durable_nonce = DurableNonce::from_blockhash(&Hash::new_unique());
let nonce_state = NonceVersions::new(NonceState::Initialized(nonce::state::Data::new(
nonce_authority.pubkey(),
durable_nonce,
0,
)));
2021-03-09 13:06:07 -08:00
let nonce_account_pre =
AccountSharedData::new_data(42, &nonce_state, &system_program::id()).unwrap();
2021-11-18 16:07:17 -08:00
let nonce = Some(NonceFull::new(
nonce_address,
nonce_account_pre.clone(),
None,
));
let loaded = (
Ok(LoadedTransaction {
accounts: transaction_accounts,
program_indices: vec![],
rent: 0,
rent_debits: RentDebits::default(),
}),
2021-11-18 16:07:17 -08:00
nonce.clone(),
);
let mut loaded = vec![loaded];
let durable_nonce = DurableNonce::from_blockhash(&Hash::new_unique());
let accounts = Accounts::new_with_config_for_tests(
Vec::new(),
&ClusterType::Development,
AccountSecondaryIndexes::default(),
false,
AccountShrinkThreshold::default(),
);
let txs = vec![tx];
let execution_results = vec![new_execution_result(
Err(TransactionError::InstructionError(
1,
InstructionError::InvalidArgument,
)),
nonce.as_ref(),
)];
let (collected_accounts, _) = accounts.collect_accounts_to_store(
&txs,
&execution_results,
loaded.as_mut_slice(),
&rent_collector,
&durable_nonce,
0,
);
assert_eq!(collected_accounts.len(), 1);
let collected_nonce_account = collected_accounts
.iter()
.find(|(pubkey, _account)| *pubkey == &nonce_address)
.map(|(_pubkey, account)| *account)
.cloned()
.unwrap();
2021-05-03 08:45:54 -07:00
assert_eq!(
collected_nonce_account.lamports(),
nonce_account_pre.lamports()
);
assert_matches!(
nonce_account::verify_nonce_account(&collected_nonce_account, durable_nonce.as_hash()),
Some(_)
);
}
#[test]
fn test_load_largest_accounts() {
let accounts = Accounts::new_with_config_for_tests(
Vec::new(),
&ClusterType::Development,
AccountSecondaryIndexes::default(),
false,
AccountShrinkThreshold::default(),
);
/* This test assumes pubkey0 < pubkey1 < pubkey2.
* But the keys created with new_unique() does not gurantee this
* order because of the endianness. new_unique() calls add 1 at each
* key generaration as the little endian integer. A pubkey stores its
* value in a 32-byte array bytes, and its eq-partial trait considers
* the lower-address bytes more significant, which is the big-endian
* order.
* So, sort first to ensure the order assumption holds.
*/
let mut keys = vec![];
for _idx in 0..3 {
keys.push(Pubkey::new_unique());
}
keys.sort();
let pubkey2 = keys.pop().unwrap();
let pubkey1 = keys.pop().unwrap();
let pubkey0 = keys.pop().unwrap();
let account0 = AccountSharedData::new(42, 0, &Pubkey::default());
accounts.store_slow_uncached(0, &pubkey0, &account0);
let account1 = AccountSharedData::new(42, 0, &Pubkey::default());
accounts.store_slow_uncached(0, &pubkey1, &account1);
let account2 = AccountSharedData::new(41, 0, &Pubkey::default());
accounts.store_slow_uncached(0, &pubkey2, &account2);
let ancestors = vec![(0, 0)].into_iter().collect();
let all_pubkeys: HashSet<_> = vec![pubkey0, pubkey1, pubkey2].into_iter().collect();
// num == 0 should always return empty set
let bank_id = 0;
assert_eq!(
accounts
.load_largest_accounts(
&ancestors,
bank_id,
0,
&HashSet::new(),
AccountAddressFilter::Exclude
)
.unwrap(),
vec![]
);
assert_eq!(
accounts
.load_largest_accounts(
&ancestors,
bank_id,
0,
&all_pubkeys,
AccountAddressFilter::Include
)
.unwrap(),
vec![]
);
// list should be sorted by balance, then pubkey, descending
assert!(pubkey1 > pubkey0);
assert_eq!(
accounts
.load_largest_accounts(
&ancestors,
bank_id,
1,
&HashSet::new(),
AccountAddressFilter::Exclude
)
.unwrap(),
vec![(pubkey1, 42)]
);
assert_eq!(
accounts
.load_largest_accounts(
&ancestors,
bank_id,
2,
&HashSet::new(),
AccountAddressFilter::Exclude
)
.unwrap(),
vec![(pubkey1, 42), (pubkey0, 42)]
);
assert_eq!(
accounts
.load_largest_accounts(
&ancestors,
bank_id,
3,
&HashSet::new(),
AccountAddressFilter::Exclude
)
.unwrap(),
vec![(pubkey1, 42), (pubkey0, 42), (pubkey2, 41)]
);
// larger num should not affect results
assert_eq!(
accounts
.load_largest_accounts(
&ancestors,
bank_id,
6,
&HashSet::new(),
AccountAddressFilter::Exclude
)
.unwrap(),
vec![(pubkey1, 42), (pubkey0, 42), (pubkey2, 41)]
);
// AccountAddressFilter::Exclude should exclude entry
let exclude1: HashSet<_> = vec![pubkey1].into_iter().collect();
assert_eq!(
accounts
.load_largest_accounts(
&ancestors,
bank_id,
1,
&exclude1,
AccountAddressFilter::Exclude
)
.unwrap(),
vec![(pubkey0, 42)]
);
assert_eq!(
accounts
.load_largest_accounts(
&ancestors,
bank_id,
2,
&exclude1,
AccountAddressFilter::Exclude
)
.unwrap(),
vec![(pubkey0, 42), (pubkey2, 41)]
);
assert_eq!(
accounts
.load_largest_accounts(
&ancestors,
bank_id,
3,
&exclude1,
AccountAddressFilter::Exclude
)
.unwrap(),
vec![(pubkey0, 42), (pubkey2, 41)]
);
// AccountAddressFilter::Include should limit entries
let include1_2: HashSet<_> = vec![pubkey1, pubkey2].into_iter().collect();
assert_eq!(
accounts
.load_largest_accounts(
&ancestors,
bank_id,
1,
&include1_2,
AccountAddressFilter::Include
)
.unwrap(),
vec![(pubkey1, 42)]
);
assert_eq!(
accounts
.load_largest_accounts(
&ancestors,
bank_id,
2,
&include1_2,
AccountAddressFilter::Include
)
.unwrap(),
vec![(pubkey1, 42), (pubkey2, 41)]
);
assert_eq!(
accounts
.load_largest_accounts(
&ancestors,
bank_id,
3,
&include1_2,
AccountAddressFilter::Include
)
.unwrap(),
vec![(pubkey1, 42), (pubkey2, 41)]
);
}
fn zero_len_account_size() -> usize {
std::mem::size_of::<AccountSharedData>() + std::mem::size_of::<Pubkey>()
}
#[test]
fn test_calc_scan_result_size() {
for len in 0..3 {
assert_eq!(
Accounts::calc_scan_result_size(&AccountSharedData::new(
0,
len,
&Pubkey::default()
)),
zero_len_account_size() + len
);
}
}
#[test]
fn test_maybe_abort_scan() {
assert!(Accounts::maybe_abort_scan(ScanResult::Ok(vec![]), &ScanConfig::default()).is_ok());
let config = ScanConfig::default().recreate_with_abort();
assert!(Accounts::maybe_abort_scan(ScanResult::Ok(vec![]), &config).is_ok());
config.abort();
assert!(Accounts::maybe_abort_scan(ScanResult::Ok(vec![]), &config).is_err());
}
#[test]
fn test_accumulate_and_check_scan_result_size() {
for (account, byte_limit_for_scan, result) in [
(AccountSharedData::default(), zero_len_account_size(), false),
(
AccountSharedData::new(0, 1, &Pubkey::default()),
zero_len_account_size(),
true,
),
(
AccountSharedData::new(0, 2, &Pubkey::default()),
zero_len_account_size() + 3,
false,
),
] {
let sum = AtomicUsize::default();
assert_eq!(
result,
Accounts::accumulate_and_check_scan_result_size(
&sum,
&account,
&Some(byte_limit_for_scan)
)
);
// calling a second time should accumulate above the threshold
assert!(Accounts::accumulate_and_check_scan_result_size(
&sum,
&account,
&Some(byte_limit_for_scan)
));
assert!(!Accounts::accumulate_and_check_scan_result_size(
&sum, &account, &None
));
}
}
}