add filler accounts to bloat validator and predict failure (#20491)

* add filler accounts to bloat validator and predict failure

* assert no accounts match filler

* cleanup magic numbers

* panic if can't load from snapshot with filler accounts specified

* some renames

* renames

* into_par_iter

* clean filler accts, too
This commit is contained in:
Jeff Washington (jwash) 2021-10-11 12:46:27 -05:00 committed by GitHub
parent 61ba8d1ecf
commit a8e000a2a6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 303 additions and 74 deletions

View File

@ -134,6 +134,7 @@ impl AccountsHashVerifier {
HashStats::default(),
false,
None,
None, // this will fail with filler accounts
)
.unwrap();

View File

@ -893,6 +893,12 @@ fn main() {
.validator(is_parsable::<usize>)
.takes_value(true)
.help("How much memory the accounts index can consume. If this is exceeded, some account index entries will be stored on disk. If missing, the entire index is stored in memory.");
let accounts_filler_count = Arg::with_name("accounts_filler_count")
.long("accounts-filler-count")
.value_name("COUNT")
.validator(is_parsable::<usize>)
.takes_value(true)
.help("How many accounts to add to stress the system. Accounts are ignored in operations related to correctness.");
let account_paths_arg = Arg::with_name("account_paths")
.long("accounts")
.value_name("PATHS")
@ -1217,6 +1223,7 @@ fn main() {
.arg(&limit_load_slot_count_from_snapshot_arg)
.arg(&accounts_index_bins)
.arg(&accounts_index_limit)
.arg(&accounts_filler_count)
.arg(&verify_index_arg)
.arg(&hard_forks_arg)
.arg(&no_accounts_db_caching_arg)
@ -1961,9 +1968,12 @@ fn main() {
accounts_index_config.drives = Some(accounts_index_paths);
}
let filler_account_count = value_t!(arg_matches, "accounts_filler_count", usize).ok();
let accounts_db_config = Some(AccountsDbConfig {
index: Some(accounts_index_config),
accounts_hash_cache_path: Some(ledger_path.clone()),
filler_account_count,
});
let process_options = ProcessOptions {

View File

@ -97,6 +97,16 @@ pub fn load(
info!("Snapshots disabled; will load from genesis");
}
if process_options
.accounts_db_config
.as_ref()
.and_then(|config| config.filler_account_count)
.unwrap_or_default()
> 0
{
panic!("filler accounts specified, but not loading from snapshot");
}
load_from_genesis(
genesis_config,
blockstore,

View File

@ -273,6 +273,7 @@ impl Accounts {
key,
&mut account,
rent_for_sysvars,
self.accounts_db.filler_account_suffix.as_ref(),
);
(account, rent_due)
} else {

View File

@ -52,7 +52,7 @@ use solana_measure::measure::Measure;
use solana_rayon_threadlimit::get_thread_count;
use solana_sdk::{
account::{AccountSharedData, ReadableAccount},
clock::{BankId, Epoch, Slot},
clock::{BankId, Epoch, Slot, SlotCount},
genesis_config::ClusterType,
hash::{Hash, Hasher},
pubkey::Pubkey,
@ -68,6 +68,7 @@ use std::{
io::{Error as IoError, Result as IoResult},
ops::{Range, RangeBounds},
path::{Path, PathBuf},
str::FromStr,
sync::atomic::{AtomicBool, AtomicU64, AtomicUsize, Ordering},
sync::{Arc, Condvar, Mutex, MutexGuard, RwLock},
thread::Builder,
@ -129,10 +130,12 @@ const CACHE_VIRTUAL_STORED_SIZE: usize = 0;
pub const ACCOUNTS_DB_CONFIG_FOR_TESTING: AccountsDbConfig = AccountsDbConfig {
index: Some(ACCOUNTS_INDEX_CONFIG_FOR_TESTING),
accounts_hash_cache_path: None,
filler_account_count: None,
};
pub const ACCOUNTS_DB_CONFIG_FOR_BENCHMARKS: AccountsDbConfig = AccountsDbConfig {
index: Some(ACCOUNTS_INDEX_CONFIG_FOR_BENCHMARKS),
accounts_hash_cache_path: None,
filler_account_count: None,
};
pub type BinnedHashData = Vec<Vec<CalculateHashIntermediate>>;
@ -141,6 +144,7 @@ pub type BinnedHashData = Vec<Vec<CalculateHashIntermediate>>;
pub struct AccountsDbConfig {
pub index: Option<AccountsIndexConfig>,
pub accounts_hash_cache_path: Option<PathBuf>,
pub filler_account_count: Option<usize>,
}
struct FoundStoredAccount<'a> {
@ -1044,6 +1048,9 @@ pub struct AccountsDb {
/// AccountsDbPlugin accounts update notifier
accounts_update_notifier: Option<AccountsUpdateNotifier>,
filler_account_count: usize,
pub filler_account_suffix: Option<Pubkey>,
}
#[derive(Debug, Default)]
@ -1536,6 +1543,8 @@ impl AccountsDb {
dirty_stores: DashMap::default(),
zero_lamport_accounts_to_purge_after_full_snapshot: DashSet::default(),
accounts_update_notifier: None,
filler_account_count: 0,
filler_account_suffix: None,
}
}
@ -1565,6 +1574,15 @@ impl AccountsDb {
let accounts_hash_cache_path = accounts_db_config
.as_ref()
.and_then(|x| x.accounts_hash_cache_path.clone());
let filler_account_count = accounts_db_config
.as_ref()
.and_then(|cfg| cfg.filler_account_count)
.unwrap_or_default();
let filler_account_suffix = if filler_account_count > 0 {
Some(solana_sdk::pubkey::new_rand())
} else {
None
};
let paths_is_empty = paths.is_empty();
let mut new = Self {
paths,
@ -1573,6 +1591,8 @@ impl AccountsDb {
caching_enabled,
shrink_ratio,
accounts_update_notifier,
filler_account_count,
filler_account_suffix,
..Self::default_with_accounts_index(accounts_index, accounts_hash_cache_path)
};
if paths_is_empty {
@ -4855,6 +4875,9 @@ impl AccountsDb {
let result: Vec<Hash> = pubkeys
.iter()
.filter_map(|pubkey| {
if self.is_filler_account(pubkey) {
return None;
}
if let AccountIndexGetResult::Found(lock, index) =
self.accounts_index.get(pubkey, Some(ancestors), Some(slot))
{
@ -4880,7 +4903,7 @@ impl AccountsDb {
|loaded_account| {
let loaded_hash = loaded_account.loaded_hash();
let balance = account_info.lamports;
if check_hash {
if check_hash && !self.is_filler_account(pubkey) {
let computed_hash =
loaded_account.compute_hash(*slot, pubkey);
if computed_hash != loaded_hash {
@ -5233,6 +5256,11 @@ impl AccountsDb {
timings,
check_hash,
accounts_cache_and_ancestors,
if self.filler_account_count > 0 {
self.filler_account_suffix.as_ref()
} else {
None
},
)
} else {
self.calculate_accounts_hash(slot, ancestors, check_hash)
@ -5318,6 +5346,7 @@ impl AccountsDb {
&Ancestors,
&AccountInfoAccountsIndex,
)>,
filler_account_suffix: Option<&Pubkey>,
) -> Result<Vec<BinnedHashData>, BankHashVerificationError> {
let bin_calculator = PubkeyBinCalculator16::new(bins);
assert!(bin_range.start < bins && bin_range.end <= bins && bin_range.start < bin_range.end);
@ -5352,7 +5381,7 @@ impl AccountsDb {
let source_item =
CalculateHashIntermediate::new(loaded_account.loaded_hash(), balance, *pubkey);
if check_hash {
if check_hash && !Self::is_filler_account_helper(pubkey, filler_account_suffix) {
let computed_hash = loaded_account.compute_hash(slot, pubkey);
if computed_hash != source_item.hash {
info!(
@ -5425,6 +5454,7 @@ impl AccountsDb {
&Ancestors,
&AccountInfoAccountsIndex,
)>,
filler_account_suffix: Option<&Pubkey>,
) -> Result<(Hash, u64), BankHashVerificationError> {
let mut scan_and_hash = move || {
assert_eq!(
@ -5450,9 +5480,13 @@ impl AccountsDb {
&bounds,
check_hash,
accounts_cache_and_ancestors,
filler_account_suffix,
)?;
let (hash, lamports, for_next_pass) = AccountsHash::rest_of_hash_calculation(
let hash = AccountsHash {
filler_account_suffix: filler_account_suffix.cloned(),
};
let (hash, lamports, for_next_pass) = hash.rest_of_hash_calculation(
result,
&mut stats,
pass == NUM_SCAN_PASSES - 1,
@ -5570,12 +5604,19 @@ impl AccountsDb {
.scan_account_storage(
slot,
|loaded_account: LoadedAccount| {
// Cache only has one version per key, don't need to worry about versioning
Some((*loaded_account.pubkey(), loaded_account.loaded_hash()))
if self.is_filler_account(loaded_account.pubkey()) {
None
} else {
// Cache only has one version per key, don't need to worry about versioning
Some((*loaded_account.pubkey(), loaded_account.loaded_hash()))
}
},
|accum: &DashMap<Pubkey, (u64, Hash)>, loaded_account: LoadedAccount| {
let loaded_write_version = loaded_account.write_version();
let loaded_hash = loaded_account.loaded_hash();
if self.is_filler_account(loaded_account.pubkey()) {
return;
}
let should_insert =
if let Some(existing_entry) = accum.get(loaded_account.pubkey()) {
loaded_write_version > existing_entry.value().version()
@ -6369,9 +6410,10 @@ impl AccountsDb {
(result, slots)
}
fn process_storage_slot(
storage_maps: &[Arc<AccountStorageEntry>],
) -> GenerateIndexAccountsMap<'_> {
fn process_storage_slot<'a>(
&self,
storage_maps: &'a [Arc<AccountStorageEntry>],
) -> GenerateIndexAccountsMap<'a> {
let num_accounts = storage_maps
.iter()
.map(|storage| storage.approx_stored_count())
@ -6381,7 +6423,9 @@ impl AccountsDb {
let accounts = storage.all_accounts();
accounts.into_iter().for_each(|stored_account| {
let this_version = stored_account.meta.write_version;
match accounts_map.entry(stored_account.meta.pubkey) {
let pubkey = stored_account.meta.pubkey;
assert!(!self.is_filler_account(&pubkey));
match accounts_map.entry(pubkey) {
std::collections::hash_map::Entry::Vacant(entry) => {
entry.insert(IndexAccountMapEntry {
write_version: this_version,
@ -6398,7 +6442,7 @@ impl AccountsDb {
stored_account,
});
} else {
assert!(occupied_version != this_version);
assert_ne!(occupied_version, this_version);
}
}
}
@ -6462,6 +6506,123 @@ impl AccountsDb {
insert_us
}
fn filler_unique_id_bytes() -> usize {
std::mem::size_of::<u32>()
}
fn filler_rent_partition_prefix_bytes() -> usize {
std::mem::size_of::<u64>()
}
fn filler_prefix_bytes() -> usize {
Self::filler_unique_id_bytes() + Self::filler_rent_partition_prefix_bytes()
}
pub fn is_filler_account_helper(
pubkey: &Pubkey,
filler_account_suffix: Option<&Pubkey>,
) -> bool {
let offset = Self::filler_prefix_bytes();
filler_account_suffix
.as_ref()
.map(|filler_account_suffix| {
pubkey.as_ref()[offset..] == filler_account_suffix.as_ref()[offset..]
})
.unwrap_or_default()
}
pub fn is_filler_account(&self, pubkey: &Pubkey) -> bool {
Self::is_filler_account_helper(pubkey, self.filler_account_suffix.as_ref())
}
/// filler accounts are space-holding accounts which are ignored by hash calculations and rent.
/// They are designed to allow a validator to run against a network successfully while simulating having many more accounts present.
/// All filler accounts share a common pubkey suffix. The suffix is randomly generated per validator on startup.
/// The filler accounts are added to each slot in the snapshot after index generation.
/// The accounts added in a slot are setup to have pubkeys such that rent will be collected from them before (or when?) their slot becomes an epoch old.
/// Thus, the filler accounts are rewritten by rent and the old slot can be thrown away successfully.
pub fn maybe_add_filler_accounts(&self, ticks_per_slot: SlotCount) {
if self.filler_account_count == 0 {
return;
}
info!("adding {} filler accounts", self.filler_account_count);
// break this up to force the accounts out of memory after each pass
let passes = 100;
let roots = self.storage.all_slots();
let root_count = roots.len();
let per_pass = std::cmp::max(1, root_count / passes);
let overall_index = AtomicUsize::new(0);
let string = "FiLLERACCoUNTooooooooooooooooooooooooooooooo";
let hash = Hash::from_str(string).unwrap();
let owner = Pubkey::from_str(string).unwrap();
let lamports = 100_000_000;
let space = 0;
let account = AccountSharedData::new(lamports, space, &owner);
let added = AtomicUsize::default();
for pass in 0..=passes {
self.accounts_index.set_startup(true);
let roots_in_this_pass = roots
.iter()
.skip(pass * per_pass)
.take(per_pass)
.collect::<Vec<_>>();
let slot_count_in_two_day =
crate::bank::Bank::slot_count_in_two_day_helper(ticks_per_slot);
self.thread_pool.install(|| {
roots_in_this_pass.into_par_iter().for_each(|slot| {
let storage_maps: Vec<Arc<AccountStorageEntry>> = self
.storage
.get_slot_storage_entries(*slot)
.unwrap_or_default();
if storage_maps.is_empty() {
return;
}
let partition = *crate::bank::Bank::get_partitions(
*slot,
slot.saturating_sub(1),
slot_count_in_two_day,
)
.last()
.unwrap();
let subrange = crate::bank::Bank::pubkey_range_from_partition(partition);
let idx = overall_index.fetch_add(1, Ordering::Relaxed);
let filler_entries = (idx + 1) * self.filler_account_count / root_count
- idx * self.filler_account_count / root_count;
let accounts = (0..filler_entries)
.map(|_| {
let my_id = added.fetch_add(1, Ordering::Relaxed);
let my_id_bytes = u32::to_be_bytes(my_id as u32);
// pubkey begins life as entire filler 'suffix' pubkey
let mut key = self.filler_account_suffix.unwrap();
let rent_prefix_bytes = Self::filler_rent_partition_prefix_bytes();
// first bytes are replaced with rent partition range: filler_rent_partition_prefix_bytes
key.as_mut()[0..rent_prefix_bytes]
.copy_from_slice(&subrange.start().as_ref()[0..rent_prefix_bytes]);
// next bytes are replaced with my_id: filler_unique_id_bytes
key.as_mut()[rent_prefix_bytes
..(rent_prefix_bytes + Self::filler_unique_id_bytes())]
.copy_from_slice(&my_id_bytes);
assert!(subrange.contains(&key));
key
})
.collect::<Vec<_>>();
let add = accounts
.iter()
.map(|key| (key, &account))
.collect::<Vec<_>>();
let hashes = (0..filler_entries).map(|_| hash).collect::<Vec<_>>();
self.store_accounts_frozen(*slot, &add[..], Some(&hashes[..]), None, None);
})
});
self.accounts_index.set_startup(false);
}
info!("added {} filler accounts", added.load(Ordering::Relaxed));
}
#[allow(clippy::needless_collect)]
pub fn generate_index(&self, limit_load_slot_count_from_snapshot: Option<usize>, verify: bool) {
let mut slots = self.storage.all_slots();
@ -6501,7 +6662,7 @@ impl AccountsDb {
.storage
.get_slot_storage_entries(*slot)
.unwrap_or_default();
let accounts_map = Self::process_storage_slot(&storage_maps);
let accounts_map = self.process_storage_slot(&storage_maps);
scan_time.stop();
scan_time_sum += scan_time.as_us();
Self::update_storage_info(
@ -7005,6 +7166,7 @@ pub mod tests {
bin_range,
check_hash,
None,
None,
)
}
}
@ -7351,6 +7513,7 @@ pub mod tests {
HashStats::default(),
false,
None,
None,
)
.unwrap();
let expected_hash = Hash::from_str("GKot5hBsd81kMupNCXHaqbhv3huEbxAFMLnpcX2hniwn").unwrap();
@ -7374,6 +7537,7 @@ pub mod tests {
HashStats::default(),
false,
None,
None,
)
.unwrap();
@ -12736,7 +12900,7 @@ pub mod tests {
.get_slot_storage_entries(slot0)
.unwrap_or_default();
let storage_info = StorageSizeAndCountMap::default();
let accounts_map = AccountsDb::process_storage_slot(&storage_maps[..]);
let accounts_map = accounts.process_storage_slot(&storage_maps[..]);
AccountsDb::update_storage_info(&storage_info, &accounts_map, &Mutex::default());
assert_eq!(storage_info.len(), 1);
for entry in storage_info.iter() {
@ -12749,9 +12913,10 @@ pub mod tests {
#[test]
fn test_calculate_storage_count_and_alive_bytes_0_accounts() {
let accounts = AccountsDb::new_single_for_tests();
let storage_maps = vec![];
let storage_info = StorageSizeAndCountMap::default();
let accounts_map = AccountsDb::process_storage_slot(&storage_maps[..]);
let accounts_map = accounts.process_storage_slot(&storage_maps[..]);
AccountsDb::update_storage_info(&storage_info, &accounts_map, &Mutex::default());
assert!(storage_info.is_empty());
}
@ -12786,7 +12951,7 @@ pub mod tests {
.get_slot_storage_entries(slot0)
.unwrap_or_default();
let storage_info = StorageSizeAndCountMap::default();
let accounts_map = AccountsDb::process_storage_slot(&storage_maps[..]);
let accounts_map = accounts.process_storage_slot(&storage_maps[..]);
AccountsDb::update_storage_info(&storage_info, &accounts_map, &Mutex::default());
assert_eq!(storage_info.len(), 1);
for entry in storage_info.iter() {

View File

@ -195,9 +195,9 @@ impl CumulativeOffsets {
}
}
#[derive(Debug)]
#[derive(Debug, Default)]
pub struct AccountsHash {
pub dummy: i32,
pub filler_account_suffix: Option<Pubkey>,
}
impl AccountsHash {
@ -504,6 +504,7 @@ impl AccountsHash {
}
fn de_dup_and_eliminate_zeros(
&self,
sorted_data_by_pubkey: Vec<Vec<Vec<CalculateHashIntermediate>>>,
stats: &mut HashStats,
max_bin: usize,
@ -520,7 +521,7 @@ impl AccountsHash {
.into_par_iter()
.map(|bin| {
let (hashes, lamports_bin, unreduced_entries_count) =
Self::de_dup_accounts_in_parallel(&sorted_data_by_pubkey, bin);
self.de_dup_accounts_in_parallel(&sorted_data_by_pubkey, bin);
{
let mut lock = min_max_sum_entries_hashes.lock().unwrap();
let (mut min, mut max, mut lamports_sum, mut entries, mut hash_total) = *lock;
@ -594,6 +595,7 @@ impl AccountsHash {
// b. lamport sum
// c. unreduced count (ie. including duplicates and zero lamport)
fn de_dup_accounts_in_parallel(
&self,
pubkey_division: &[Vec<Vec<CalculateHashIntermediate>>],
pubkey_bin: usize,
) -> (Vec<Hash>, u64, usize) {
@ -658,7 +660,8 @@ impl AccountsHash {
pubkey_division,
&mut indexes,
);
if item.lamports != ZERO_RAW_LAMPORTS_SENTINEL {
if !self.is_filler_account(&item.pubkey) && item.lamports != ZERO_RAW_LAMPORTS_SENTINEL
{
overall_sum = Self::checked_cast_for_capitalization(
item.lamports as u128 + overall_sum as u128,
);
@ -668,11 +671,19 @@ impl AccountsHash {
(hashes, overall_sum, item_len)
}
fn is_filler_account(&self, pubkey: &Pubkey) -> bool {
crate::accounts_db::AccountsDb::is_filler_account_helper(
pubkey,
self.filler_account_suffix.as_ref(),
)
}
// input:
// vec: group of slot data, ordered by Slot (low to high)
// vec: [0..bins] - where bins are pubkey ranges (these are ordered by Pubkey range)
// vec: [..] - items which fit in the containing bin. Sorted by: Pubkey, higher Slot, higher Write version (if pubkey =)
pub fn rest_of_hash_calculation(
&self,
data_sections_by_pubkey: Vec<Vec<Vec<CalculateHashIntermediate>>>,
mut stats: &mut HashStats,
is_last_pass: bool,
@ -680,7 +691,7 @@ impl AccountsHash {
max_bin: usize,
) -> (Hash, u64, PreviousPass) {
let (mut hashes, mut total_lamports) =
Self::de_dup_and_eliminate_zeros(data_sections_by_pubkey, stats, max_bin);
self.de_dup_and_eliminate_zeros(data_sections_by_pubkey, stats, max_bin);
total_lamports += previous_state.lamports;
@ -823,7 +834,8 @@ pub mod tests {
let val = CalculateHashIntermediate::new(hash, ZERO_RAW_LAMPORTS_SENTINEL, key);
account_maps.push(val);
let result = AccountsHash::rest_of_hash_calculation(
let accounts_hash = AccountsHash::default();
let result = accounts_hash.rest_of_hash_calculation(
for_rest(account_maps.clone()),
&mut HashStats::default(),
true,
@ -839,7 +851,7 @@ pub mod tests {
let val = CalculateHashIntermediate::new(hash, 20, key);
account_maps.insert(0, val);
let result = AccountsHash::rest_of_hash_calculation(
let result = accounts_hash.rest_of_hash_calculation(
for_rest(account_maps.clone()),
&mut HashStats::default(),
true,
@ -855,7 +867,7 @@ pub mod tests {
let val = CalculateHashIntermediate::new(hash, 30, key);
account_maps.insert(1, val);
let result = AccountsHash::rest_of_hash_calculation(
let result = accounts_hash.rest_of_hash_calculation(
for_rest(account_maps),
&mut HashStats::default(),
true,
@ -898,9 +910,10 @@ pub mod tests {
let mut previous_pass = PreviousPass::default();
let accounts_index = AccountsHash::default();
if pass == 0 {
// first pass that is not last and is empty
let result = AccountsHash::rest_of_hash_calculation(
let result = accounts_index.rest_of_hash_calculation(
vec![vec![vec![]]],
&mut HashStats::default(),
false, // not last pass
@ -915,7 +928,7 @@ pub mod tests {
assert_eq!(previous_pass.lamports, 0);
}
let result = AccountsHash::rest_of_hash_calculation(
let result = accounts_index.rest_of_hash_calculation(
for_rest(account_maps.clone()),
&mut HashStats::default(),
false, // not last pass
@ -932,8 +945,9 @@ pub mod tests {
let expected_hash =
Hash::from_str("8j9ARGFv4W2GfML7d3sVJK2MePwrikqYnu6yqer28cCa").unwrap();
let accounts_index = AccountsHash::default();
if pass == 2 {
let result = AccountsHash::rest_of_hash_calculation(
let result = accounts_index.rest_of_hash_calculation(
vec![vec![vec![]]],
&mut HashStats::default(),
false,
@ -947,7 +961,7 @@ pub mod tests {
assert_eq!(previous_pass.lamports, account_maps[0].lamports);
}
let result = AccountsHash::rest_of_hash_calculation(
let result = accounts_index.rest_of_hash_calculation(
vec![vec![vec![]]],
&mut HashStats::default(),
true, // finally, last pass
@ -979,8 +993,8 @@ pub mod tests {
let hash = Hash::new(&[2u8; 32]);
let val = CalculateHashIntermediate::new(hash, 20, key);
account_maps.push(val);
let result = AccountsHash::rest_of_hash_calculation(
let accounts_hash = AccountsHash::default();
let result = accounts_hash.rest_of_hash_calculation(
for_rest(vec![account_maps[0].clone()]),
&mut HashStats::default(),
false, // not last pass
@ -995,7 +1009,7 @@ pub mod tests {
assert_eq!(previous_pass.reduced_hashes.len(), 0);
assert_eq!(previous_pass.lamports, account_maps[0].lamports);
let result = AccountsHash::rest_of_hash_calculation(
let result = accounts_hash.rest_of_hash_calculation(
for_rest(vec![account_maps[1].clone()]),
&mut HashStats::default(),
false, // not last pass
@ -1014,7 +1028,7 @@ pub mod tests {
let total_lamports_expected = account_maps[0].lamports + account_maps[1].lamports;
assert_eq!(previous_pass.lamports, total_lamports_expected);
let result = AccountsHash::rest_of_hash_calculation(
let result = accounts_hash.rest_of_hash_calculation(
vec![vec![vec![]]],
&mut HashStats::default(),
true,
@ -1046,6 +1060,7 @@ pub mod tests {
solana_logger::setup();
let mut account_maps = Vec::new();
let accounts_hash = AccountsHash::default();
const TARGET_FANOUT_LEVEL: usize = 3;
let target_fanout = MERKLE_FANOUT.pow(TARGET_FANOUT_LEVEL as u32);
@ -1065,7 +1080,7 @@ pub mod tests {
let sorted = chunk.clone();
// first 4097 hashes (1 left over)
let result = AccountsHash::rest_of_hash_calculation(
let result = accounts_hash.rest_of_hash_calculation(
for_rest(chunk),
&mut HashStats::default(),
false, // not last pass
@ -1110,7 +1125,7 @@ pub mod tests {
);
// second 4097 hashes (2 left over)
let result = AccountsHash::rest_of_hash_calculation(
let result = accounts_hash.rest_of_hash_calculation(
for_rest(chunk),
&mut HashStats::default(),
false, // not last pass
@ -1138,7 +1153,7 @@ pub mod tests {
.sum::<u64>()
);
let result = AccountsHash::rest_of_hash_calculation(
let result = accounts_hash.rest_of_hash_calculation(
vec![vec![vec![]]],
&mut HashStats::default(),
true,
@ -1169,10 +1184,8 @@ pub mod tests {
#[test]
fn test_accountsdb_de_dup_accounts_zero_chunks() {
let (hashes, lamports, _) = AccountsHash::de_dup_accounts_in_parallel(
&[vec![vec![CalculateHashIntermediate::default()]]],
0,
);
let (hashes, lamports, _) = AccountsHash::default()
.de_dup_accounts_in_parallel(&[vec![vec![CalculateHashIntermediate::default()]]], 0);
assert_eq!(vec![Hash::default()], hashes);
assert_eq!(lamports, 0);
}
@ -1180,8 +1193,9 @@ pub mod tests {
#[test]
fn test_accountsdb_de_dup_accounts_empty() {
solana_logger::setup();
let accounts_hash = AccountsHash::default();
let (hashes, lamports) = AccountsHash::de_dup_and_eliminate_zeros(
let (hashes, lamports) = accounts_hash.de_dup_and_eliminate_zeros(
vec![vec![], vec![]],
&mut HashStats::default(),
one_range(),
@ -1192,7 +1206,7 @@ pub mod tests {
);
assert_eq!(lamports, 0);
let (hashes, lamports) = AccountsHash::de_dup_and_eliminate_zeros(
let (hashes, lamports) = accounts_hash.de_dup_and_eliminate_zeros(
vec![],
&mut HashStats::default(),
zero_range(),
@ -1201,11 +1215,11 @@ pub mod tests {
assert_eq!(empty, hashes);
assert_eq!(lamports, 0);
let (hashes, lamports, _) = AccountsHash::de_dup_accounts_in_parallel(&[], 1);
let (hashes, lamports, _) = accounts_hash.de_dup_accounts_in_parallel(&[], 1);
assert_eq!(vec![Hash::default(); 0], hashes);
assert_eq!(lamports, 0);
let (hashes, lamports, _) = AccountsHash::de_dup_accounts_in_parallel(&[], 2);
let (hashes, lamports, _) = accounts_hash.de_dup_accounts_in_parallel(&[], 2);
assert_eq!(vec![Hash::default(); 0], hashes);
assert_eq!(lamports, 0);
}
@ -1276,6 +1290,7 @@ pub mod tests {
result
}).collect();
let hash = AccountsHash::default();
let mut expected_index = 0;
for last_slice in 0..2 {
for start in 0..COUNT {
@ -1286,21 +1301,19 @@ pub mod tests {
let slice2 = vec![vec![slice.to_vec()]];
let slice = &slice2[..];
let (hashes2, lamports2, _) =
AccountsHash::de_dup_accounts_in_parallel(slice, 0);
let (hashes3, lamports3, _) =
AccountsHash::de_dup_accounts_in_parallel(slice, 0);
let (hashes4, lamports4) = AccountsHash::de_dup_and_eliminate_zeros(
let (hashes2, lamports2, _) = hash.de_dup_accounts_in_parallel(slice, 0);
let (hashes3, lamports3, _) = hash.de_dup_accounts_in_parallel(slice, 0);
let (hashes4, lamports4) = hash.de_dup_and_eliminate_zeros(
slice.to_vec(),
&mut HashStats::default(),
end - start,
);
let (hashes5, lamports5) = AccountsHash::de_dup_and_eliminate_zeros(
let (hashes5, lamports5) = hash.de_dup_and_eliminate_zeros(
slice.to_vec(),
&mut HashStats::default(),
end - start,
);
let (hashes6, lamports6) = AccountsHash::de_dup_and_eliminate_zeros(
let (hashes6, lamports6) = hash.de_dup_and_eliminate_zeros(
slice.to_vec(),
&mut HashStats::default(),
end - start,
@ -1413,7 +1426,8 @@ pub mod tests {
fn test_de_dup_accounts_in_parallel(
account_maps: &[CalculateHashIntermediate],
) -> (Vec<Hash>, u64, usize) {
AccountsHash::de_dup_accounts_in_parallel(&vec![vec![account_maps.to_vec()]][..], 0)
AccountsHash::default()
.de_dup_accounts_in_parallel(&vec![vec![account_maps.to_vec()]][..], 0)
}
#[test]
@ -1803,7 +1817,7 @@ pub mod tests {
),
CalculateHashIntermediate::new(Hash::new(&[2u8; 32]), offset + 1, Pubkey::new_unique()),
];
AccountsHash::de_dup_accounts_in_parallel(&[vec![input]], 0);
AccountsHash::default().de_dup_accounts_in_parallel(&[vec![input]], 0);
}
#[test]
@ -1824,7 +1838,7 @@ pub mod tests {
Pubkey::new_unique(),
)],
];
AccountsHash::de_dup_and_eliminate_zeros(
AccountsHash::default().de_dup_and_eliminate_zeros(
vec![input],
&mut HashStats::default(),
2, // accounts above are in 2 groups

View File

@ -4410,6 +4410,7 @@ impl Bank {
&pubkey,
&mut account,
rent_for_sysvars,
self.rc.accounts.accounts_db.filler_account_suffix.as_ref(),
);
total_rent += rent;
// Store all of them unconditionally to purge old AppendVec,
@ -4431,7 +4432,7 @@ impl Bank {
// start_index..=end_index. But it has some exceptional cases, including
// this important and valid one:
// 0..=0: the first partition in the new epoch when crossing epochs
fn pubkey_range_from_partition(
pub fn pubkey_range_from_partition(
(start_index, end_index, partition_count): Partition,
) -> RangeInclusive<Pubkey> {
assert!(start_index <= end_index);
@ -4496,13 +4497,15 @@ impl Bank {
Pubkey::new_from_array(start_pubkey)..=Pubkey::new_from_array(end_pubkey)
}
fn fixed_cycle_partitions(&self) -> Vec<Partition> {
let slot_count_in_two_day = self.slot_count_in_two_day();
let parent_cycle = self.parent_slot() / slot_count_in_two_day;
let current_cycle = self.slot() / slot_count_in_two_day;
let mut parent_cycle_index = self.parent_slot() % slot_count_in_two_day;
let current_cycle_index = self.slot() % slot_count_in_two_day;
pub fn get_partitions(
slot: Slot,
parent_slot: Slot,
slot_count_in_two_day: SlotCount,
) -> Vec<Partition> {
let parent_cycle = parent_slot / slot_count_in_two_day;
let current_cycle = slot / slot_count_in_two_day;
let mut parent_cycle_index = parent_slot % slot_count_in_two_day;
let current_cycle_index = slot % slot_count_in_two_day;
let mut partitions = vec![];
if parent_cycle < current_cycle {
if current_cycle_index > 0 {
@ -4531,6 +4534,11 @@ impl Bank {
partitions
}
fn fixed_cycle_partitions(&self) -> Vec<Partition> {
let slot_count_in_two_day = self.slot_count_in_two_day();
Self::get_partitions(self.slot(), self.parent_slot(), slot_count_in_two_day)
}
fn variable_cycle_partitions(&self) -> Vec<Partition> {
let (current_epoch, current_slot_index) = self.get_epoch_and_slot_index(self.slot());
let (parent_epoch, mut parent_slot_index) =
@ -4722,11 +4730,15 @@ impl Bank {
&& self.slot_count_per_normal_epoch() < self.slot_count_in_two_day()
}
fn slot_count_in_two_day(&self) -> SlotCount {
Self::slot_count_in_two_day_helper(self.ticks_per_slot)
}
// This value is specially chosen to align with slots per epoch in mainnet-beta and testnet
// Also, assume 500GB account data set as the extreme, then for 2 day (=48 hours) to collect
// rent eagerly, we'll consume 5.7 MB/s IO bandwidth, bidirectionally.
fn slot_count_in_two_day(&self) -> SlotCount {
2 * DEFAULT_TICKS_PER_SECOND * SECONDS_PER_DAY / self.ticks_per_slot
pub fn slot_count_in_two_day_helper(ticks_per_slot: SlotCount) -> SlotCount {
2 * DEFAULT_TICKS_PER_SECOND * SECONDS_PER_DAY / ticks_per_slot
}
fn slot_count_per_normal_epoch(&self) -> SlotCount {

View File

@ -61,11 +61,13 @@ impl RentCollector {
address: &Pubkey,
account: &mut AccountSharedData,
rent_for_sysvars: bool,
filler_account_suffix: Option<&Pubkey>,
) -> u64 {
if account.executable() // executable accounts must be rent-exempt balance
|| account.rent_epoch() > self.epoch
|| (!rent_for_sysvars && sysvar::check_id(account.owner()))
|| *address == incinerator::id()
|| crate::accounts_db::AccountsDb::is_filler_account_helper(address, filler_account_suffix)
{
0
} else {
@ -120,7 +122,7 @@ impl RentCollector {
) -> u64 {
// initialize rent_epoch as created at this epoch
account.set_rent_epoch(self.epoch);
self.collect_from_existing_account(address, account, rent_for_sysvars)
self.collect_from_existing_account(address, account, rent_for_sysvars, None)
}
}
@ -162,6 +164,7 @@ mod tests {
&solana_sdk::pubkey::new_rand(),
&mut existing_account,
true,
None,
);
assert!(existing_account.lamports() < old_lamports);
assert_eq!(existing_account.lamports() + collected, old_lamports);
@ -188,7 +191,7 @@ mod tests {
let rent_collector = RentCollector::default().clone_with_epoch(epoch);
// first mark account as being collected while being rent-exempt
collected = rent_collector.collect_from_existing_account(&pubkey, &mut account, true);
collected = rent_collector.collect_from_existing_account(&pubkey, &mut account, true, None);
assert_eq!(account.lamports(), huge_lamports);
assert_eq!(collected, 0);
@ -196,7 +199,7 @@ mod tests {
account.set_lamports(tiny_lamports);
// ... and trigger another rent collection on the same epoch and check that rent is working
collected = rent_collector.collect_from_existing_account(&pubkey, &mut account, true);
collected = rent_collector.collect_from_existing_account(&pubkey, &mut account, true, None);
assert_eq!(account.lamports(), tiny_lamports - collected);
assert_ne!(collected, 0);
}
@ -216,12 +219,14 @@ mod tests {
let rent_collector = RentCollector::default().clone_with_epoch(epoch);
// old behavior: sysvars are special-cased
let collected = rent_collector.collect_from_existing_account(&pubkey, &mut account, false);
let collected =
rent_collector.collect_from_existing_account(&pubkey, &mut account, false, None);
assert_eq!(account.lamports(), tiny_lamports);
assert_eq!(collected, 0);
// new behavior: sysvars are NOT special-cased
let collected = rent_collector.collect_from_existing_account(&pubkey, &mut account, true);
let collected =
rent_collector.collect_from_existing_account(&pubkey, &mut account, true, None);
assert_eq!(account.lamports(), 0);
assert_eq!(collected, 1);
}

View File

@ -29,7 +29,6 @@ use {
clock::{Epoch, Slot, UnixTimestamp},
epoch_schedule::EpochSchedule,
fee_calculator::{FeeCalculator, FeeRateGovernor},
genesis_config::ClusterType,
genesis_config::GenesisConfig,
hard_forks::HardForks,
hash::Hash,
@ -347,7 +346,7 @@ where
snapshot_accounts_db_fields,
account_paths,
unpacked_append_vec_map,
&genesis_config.cluster_type,
genesis_config,
account_secondary_indexes,
caching_enabled,
limit_load_slot_count_from_snapshot,
@ -402,7 +401,7 @@ fn reconstruct_accountsdb_from_fields<E>(
snapshot_accounts_db_fields: SnapshotAccountsDbFields<E>,
account_paths: &[PathBuf],
unpacked_append_vec_map: UnpackedAppendVecMap,
cluster_type: &ClusterType,
genesis_config: &GenesisConfig,
account_secondary_indexes: AccountSecondaryIndexes,
caching_enabled: bool,
limit_load_slot_count_from_snapshot: Option<usize>,
@ -416,7 +415,7 @@ where
{
let mut accounts_db = AccountsDb::new_with_config(
account_paths.to_vec(),
cluster_type,
&genesis_config.cluster_type,
account_secondary_indexes,
caching_enabled,
shrink_ratio,
@ -536,6 +535,7 @@ where
.write_version
.fetch_add(snapshot_version, Ordering::Relaxed);
accounts_db.generate_index(limit_load_slot_count_from_snapshot, verify_index);
accounts_db.maybe_add_filler_accounts(genesis_config.ticks_per_slot());
let mut measure_notify = Measure::start("accounts_notify");
accounts_db.notify_account_restore_from_snapshot();

View File

@ -77,7 +77,10 @@ where
snapshot_accounts_db_fields,
account_paths,
unpacked_append_vec_map,
&ClusterType::Development,
&GenesisConfig {
cluster_type: ClusterType::Development,
..GenesisConfig::default()
},
AccountSecondaryIndexes::default(),
false,
None,

View File

@ -1380,8 +1380,14 @@ pub fn main() {
.help("Persistent accounts-index location. \
May be specified multiple times. \
[default: [ledger]/accounts_index]"),
)
.arg(
)
.arg(Arg::with_name("accounts_filler_count")
.long("accounts-filler-count")
.value_name("COUNT")
.validator(is_parsable::<usize>)
.takes_value(true)
.help("How many accounts to add to stress the system. Accounts are ignored in operations related to correctness."))
.arg(
Arg::with_name("accounts_db_test_hash_calculation")
.long("accounts-db-test-hash-calculation")
.help("Enables testing of hash calculation using stores in \
@ -1949,9 +1955,11 @@ pub fn main() {
accounts_index_config.drives = Some(accounts_index_paths);
}
let filler_account_count = value_t!(matches, "accounts_filler_count", usize).ok();
let accounts_db_config = Some(AccountsDbConfig {
index: Some(accounts_index_config),
accounts_hash_cache_path: Some(ledger_path.clone()),
filler_account_count,
});
let accountsdb_repl_service_config = if matches.is_present("enable_accountsdb_repl") {