#[cfg(test)]
mod tests {
use {
crate::{
bank::{
epoch_accounts_hash_utils, test_utils as bank_test_utils, Bank, EpochRewardStatus,
},
epoch_stakes::{
EpochAuthorizedVoters, EpochStakes, NodeIdToVoteAccounts, VersionedEpochStakes,
},
genesis_utils::activate_all_features,
runtime_config::RuntimeConfig,
serde_snapshot::{
self, BankIncrementalSnapshotPersistence, ExtraFieldsToSerialize,
SerdeAccountsHash, SerdeIncrementalAccountsHash, SnapshotStreams,
},
snapshot_bank_utils,
snapshot_utils::{
create_tmp_accounts_dir_for_tests, get_storages_to_serialize, ArchiveFormat,
StorageAndNextAccountsFileId,
},
stakes::{SerdeStakesToStakeFormat, Stakes, StakesEnum},
},
clone_solana_accounts_db::{
account_storage::{AccountStorageMap, AccountStorageReference},
accounts_db::{
get_temp_accounts_paths, AccountStorageEntry, AccountsDb, AccountsDbConfig,
AtomicAccountsFileId, ACCOUNTS_DB_CONFIG_FOR_TESTING,
},
accounts_file::{AccountsFile, AccountsFileError, StorageAccess},
accounts_hash::{AccountsDeltaHash, AccountsHash},
epoch_accounts_hash::EpochAccountsHash,
},
clone_solana_sdk::{
epoch_schedule::EpochSchedule, genesis_config::create_genesis_config, hash::Hash,
pubkey::Pubkey, stake::state::Stake,
},
solana_nohash_hasher::BuildNoHashHasher,
std::{
io::{BufReader, BufWriter, Cursor},
mem,
ops::RangeFull,
path::Path,
sync::{atomic::Ordering, Arc},
},
tempfile::TempDir,
test_case::test_case,
};
fn copy_append_vecs<P: AsRef<Path>>(
accounts_db: &AccountsDb,
output_dir: P,
storage_access: StorageAccess,
) -> Result<StorageAndNextAccountsFileId, AccountsFileError> {
let storage_entries = accounts_db.get_storages(RangeFull).0;
let storage: AccountStorageMap = AccountStorageMap::with_capacity_and_hasher(
storage_entries.len(),
BuildNoHashHasher::default(),
);
let mut next_append_vec_id = 0;
for storage_entry in storage_entries.into_iter() {
let storage_path = storage_entry.path();
let file_name = AccountsFile::file_name(storage_entry.slot(), storage_entry.id());
let output_path = output_dir.as_ref().join(file_name);
std::fs::copy(storage_path, &output_path)?;
let (accounts_file, num_accounts) = AccountsFile::new_from_file(
output_path,
storage_entry.accounts.len(),
storage_access,
)?;
let new_storage_entry = AccountStorageEntry::new_existing(
storage_entry.slot(),
storage_entry.id(),
accounts_file,
num_accounts,
);
next_append_vec_id = next_append_vec_id.max(new_storage_entry.id());
storage.insert(
new_storage_entry.slot(),
AccountStorageReference {
id: new_storage_entry.id(),
storage: Arc::new(new_storage_entry),
},
);
}
Ok(StorageAndNextAccountsFileId {
storage,
next_append_vec_id: AtomicAccountsFileId::new(next_append_vec_id + 1),
})
}
#[test]
fn test_serialize_bank_snapshot() {
let storage_access_iter = [StorageAccess::Mmap, StorageAccess::File].into_iter();
let has_incremental_snapshot_persistence_iter = [false, true].into_iter();
let has_epoch_accounts_hash_iter = [false, true].into_iter();
let has_accounts_lt_hash_iter = [false, true].into_iter();
for (
storage_access,
has_incremental_snapshot_persistence,
has_epoch_accounts_hash,
has_accounts_lt_hash,
) in itertools::iproduct!(
storage_access_iter,
has_incremental_snapshot_persistence_iter,
has_epoch_accounts_hash_iter,
has_accounts_lt_hash_iter
) {
do_serialize_bank_snapshot(
storage_access,
has_incremental_snapshot_persistence,
has_epoch_accounts_hash,
has_accounts_lt_hash,
);
}
fn do_serialize_bank_snapshot(
storage_access: StorageAccess,
has_incremental_snapshot_persistence: bool,
has_epoch_accounts_hash: bool,
has_accounts_lt_hash: bool,
) {
let (mut genesis_config, _) = create_genesis_config(500);
genesis_config.epoch_schedule = EpochSchedule::custom(400, 400, false);
let bank0 = Arc::new(Bank::new_for_tests(&genesis_config));
bank0
.rc
.accounts
.accounts_db
.set_is_experimental_accumulator_hash_enabled(has_accounts_lt_hash);
let deposit_amount = bank0.get_minimum_balance_for_rent_exemption(0);
let eah_start_slot = epoch_accounts_hash_utils::calculation_start(&bank0);
let bank1 = Bank::new_from_parent(bank0.clone(), &Pubkey::default(), 1);
let key1 = Pubkey::new_unique();
bank_test_utils::deposit(&bank1, &key1, deposit_amount).unwrap();
let bank2_slot = if has_epoch_accounts_hash {
eah_start_slot
} else {
0
} + 2;
let mut bank2 = Bank::new_from_parent(bank0, &Pubkey::default(), bank2_slot);
let key2 = Pubkey::new_unique();
bank_test_utils::deposit(&bank2, &key2, deposit_amount).unwrap();
assert_eq!(bank2.get_balance(&key2), deposit_amount);
let key3 = Pubkey::new_unique();
bank_test_utils::deposit(&bank2, &key3, 0).unwrap();
let accounts_db = &bank2.rc.accounts.accounts_db;
bank2.squash();
bank2.force_flush_accounts_cache();
let expected_accounts_hash = AccountsHash(Hash::new_unique());
accounts_db.set_accounts_hash(bank2_slot, (expected_accounts_hash, 30));
let expected_incremental_snapshot_persistence = has_incremental_snapshot_persistence
.then(|| BankIncrementalSnapshotPersistence {
full_slot: bank2_slot - 1,
full_hash: SerdeAccountsHash(Hash::new_unique()),
full_capitalization: 31,
incremental_hash: SerdeIncrementalAccountsHash(Hash::new_unique()),
incremental_capitalization: 32,
});
let expected_epoch_accounts_hash = has_epoch_accounts_hash.then(|| {
let epoch_accounts_hash = EpochAccountsHash::new(Hash::new_unique());
accounts_db
.epoch_accounts_hash_manager
.set_valid(epoch_accounts_hash, eah_start_slot);
epoch_accounts_hash
});
let expected_accounts_lt_hash =
has_accounts_lt_hash.then(|| bank2.accounts_lt_hash.lock().unwrap().clone());
{
assert_eq!(bank2.epoch_stakes.len(), 2);
assert!(bank2
.epoch_stakes
.values()
.all(|epoch_stakes| matches!(epoch_stakes.stakes(), &StakesEnum::Accounts(_))));
let StakesEnum::Accounts(stake_accounts) =
bank2.epoch_stakes.remove(&0).unwrap().stakes().clone()
else {
panic!("expected the epoch 0 stakes entry to have stake accounts");
};
bank2.epoch_stakes.insert(
0,
EpochStakes::new(Arc::new(StakesEnum::Delegations(stake_accounts.into())), 0),
);
}
let mut buf = Vec::new();
let cursor = Cursor::new(&mut buf);
let mut writer = BufWriter::new(cursor);
{
let mut bank_fields = bank2.get_fields_to_serialize();
assert!(!bank_fields.epoch_stakes.is_empty());
assert!(!bank_fields.versioned_epoch_stakes.is_empty());
let versioned_epoch_stakes = mem::take(&mut bank_fields.versioned_epoch_stakes);
let accounts_lt_hash = bank_fields.accounts_lt_hash.clone().map(Into::into);
serde_snapshot::serialize_bank_snapshot_into(
&mut writer,
bank_fields,
bank2.get_bank_hash_stats(),
accounts_db.get_accounts_delta_hash(bank2_slot).unwrap(),
expected_accounts_hash,
&get_storages_to_serialize(&bank2.get_snapshot_storages(None)),
ExtraFieldsToSerialize {
lamports_per_signature: bank2.fee_rate_governor.lamports_per_signature,
incremental_snapshot_persistence: expected_incremental_snapshot_persistence
.as_ref(),
epoch_accounts_hash: expected_epoch_accounts_hash,
versioned_epoch_stakes,
accounts_lt_hash,
},
accounts_db.write_version.load(Ordering::Acquire),
)
.unwrap();
}
drop(writer);
let (_accounts_dir, dbank_paths) = get_temp_accounts_paths(4).unwrap();
let copied_accounts = TempDir::new().unwrap();
let storage_and_next_append_vec_id =
copy_append_vecs(accounts_db, copied_accounts.path(), storage_access).unwrap();
let cursor = Cursor::new(buf.as_slice());
let mut reader = BufReader::new(cursor);
let mut snapshot_streams = SnapshotStreams {
full_snapshot_stream: &mut reader,
incremental_snapshot_stream: None,
};
let accounts_db_config = AccountsDbConfig {
enable_experimental_accumulator_hash: has_accounts_lt_hash,
..ACCOUNTS_DB_CONFIG_FOR_TESTING
};
let (dbank, _) = serde_snapshot::bank_from_streams(
&mut snapshot_streams,
&dbank_paths,
storage_and_next_append_vec_id,
&genesis_config,
&RuntimeConfig::default(),
None,
None,
None,
false,
Some(accounts_db_config),
None,
Arc::default(),
)
.unwrap();
assert_eq!(dbank.get_balance(&key1), 0);
assert_eq!(dbank.get_balance(&key2), deposit_amount);
assert_eq!(dbank.get_balance(&key3), 0);
if let Some(incremental_snapshot_persistence) =
expected_incremental_snapshot_persistence.as_ref()
{
assert_eq!(dbank.get_accounts_hash(), None);
assert_eq!(
dbank.get_incremental_accounts_hash(),
Some(
incremental_snapshot_persistence
.incremental_hash
.clone()
.into()
),
);
} else {
assert_eq!(dbank.get_accounts_hash(), Some(expected_accounts_hash));
assert_eq!(dbank.get_incremental_accounts_hash(), None);
}
assert_eq!(
dbank.get_epoch_accounts_hash_to_serialize(),
expected_epoch_accounts_hash,
);
assert_eq!(
dbank.is_accounts_lt_hash_enabled().then(|| dbank
.accounts_lt_hash
.lock()
.unwrap()
.clone()),
expected_accounts_lt_hash,
);
assert_eq!(dbank.get_bank_hash_stats(), bank2.get_bank_hash_stats());
assert_eq!(dbank, bank2);
}
}
fn add_root_and_flush_write_cache(bank: &Bank) {
bank.rc.accounts.add_root(bank.slot());
bank.flush_accounts_cache_slot_for_tests()
}
#[test_case(StorageAccess::Mmap)]
#[test_case(StorageAccess::File)]
fn test_extra_fields_eof(storage_access: StorageAccess) {
clone_solana_logger::setup();
let (genesis_config, _) = create_genesis_config(500);
let bank0 = Arc::new(Bank::new_for_tests(&genesis_config));
bank0.squash();
let mut bank = Bank::new_from_parent(bank0.clone(), &Pubkey::default(), 1);
bank.freeze();
add_root_and_flush_write_cache(&bank0);
bank.rc
.accounts
.accounts_db
.set_accounts_delta_hash(bank.slot(), AccountsDeltaHash(Hash::new_unique()));
bank.rc.accounts.accounts_db.set_accounts_hash(
bank.slot(),
(AccountsHash(Hash::new_unique()), u64::default()),
);
bank.fee_rate_governor.lamports_per_signature = 7000;
bank.epoch_stakes.insert(
42,
EpochStakes::from(VersionedEpochStakes::Current {
stakes: SerdeStakesToStakeFormat::Stake(Stakes::<Stake>::default()),
total_stake: 42,
node_id_to_vote_accounts: Arc::<NodeIdToVoteAccounts>::default(),
epoch_authorized_voters: Arc::<EpochAuthorizedVoters>::default(),
}),
);
assert_eq!(bank.epoch_stakes.len(), 3);
let snapshot_storages = bank.get_snapshot_storages(None);
let mut buf = vec![];
let mut writer = Cursor::new(&mut buf);
crate::serde_snapshot::bank_to_stream(
&mut std::io::BufWriter::new(&mut writer),
&bank,
&get_storages_to_serialize(&snapshot_storages),
)
.unwrap();
let rdr = Cursor::new(&buf[..]);
let mut reader = std::io::BufReader::new(&buf[rdr.position() as usize..]);
let mut snapshot_streams = SnapshotStreams {
full_snapshot_stream: &mut reader,
incremental_snapshot_stream: None,
};
let (_accounts_dir, dbank_paths) = get_temp_accounts_paths(4).unwrap();
let copied_accounts = TempDir::new().unwrap();
let storage_and_next_append_vec_id = copy_append_vecs(
&bank.rc.accounts.accounts_db,
copied_accounts.path(),
storage_access,
)
.unwrap();
let (dbank, _) = crate::serde_snapshot::bank_from_streams(
&mut snapshot_streams,
&dbank_paths,
storage_and_next_append_vec_id,
&genesis_config,
&RuntimeConfig::default(),
None,
None,
None,
false,
Some(clone_solana_accounts_db::accounts_db::ACCOUNTS_DB_CONFIG_FOR_TESTING),
None,
Arc::default(),
)
.unwrap();
assert_eq!(bank.epoch_stakes, dbank.epoch_stakes);
assert_eq!(
bank.fee_rate_governor.lamports_per_signature,
dbank.fee_rate_governor.lamports_per_signature
);
}
#[test]
fn test_extra_fields_full_snapshot_archive() {
clone_solana_logger::setup();
let (mut genesis_config, _) = create_genesis_config(500);
activate_all_features(&mut genesis_config);
let bank0 = Arc::new(Bank::new_for_tests(&genesis_config));
let mut bank = Bank::new_from_parent(bank0, &Pubkey::default(), 1);
while !bank.is_complete() {
bank.fill_bank_with_ticks_for_tests();
}
bank.fee_rate_governor.lamports_per_signature = 7000;
let (_tmp_dir, accounts_dir) = create_tmp_accounts_dir_for_tests();
let bank_snapshots_dir = TempDir::new().unwrap();
let full_snapshot_archives_dir = TempDir::new().unwrap();
let incremental_snapshot_archives_dir = TempDir::new().unwrap();
let snapshot_archive_info = snapshot_bank_utils::bank_to_full_snapshot_archive(
&bank_snapshots_dir,
&bank,
None,
full_snapshot_archives_dir.path(),
incremental_snapshot_archives_dir.path(),
ArchiveFormat::Tar,
)
.unwrap();
let (dbank, _) = snapshot_bank_utils::bank_from_snapshot_archives(
&[accounts_dir],
bank_snapshots_dir.path(),
&snapshot_archive_info,
None,
&genesis_config,
&RuntimeConfig::default(),
None,
None,
None,
false,
false,
false,
false,
Some(clone_solana_accounts_db::accounts_db::ACCOUNTS_DB_CONFIG_FOR_TESTING),
None,
Arc::default(),
)
.unwrap();
assert_eq!(
bank.fee_rate_governor.lamports_per_signature,
dbank.fee_rate_governor.lamports_per_signature
);
}
#[test_case(StorageAccess::Mmap)]
#[test_case(StorageAccess::File)]
fn test_blank_extra_fields(storage_access: StorageAccess) {
clone_solana_logger::setup();
let (genesis_config, _) = create_genesis_config(500);
let bank0 = Arc::new(Bank::new_for_tests(&genesis_config));
bank0.squash();
let mut bank = Bank::new_from_parent(bank0.clone(), &Pubkey::default(), 1);
bank.freeze();
add_root_and_flush_write_cache(&bank0);
bank.rc
.accounts
.accounts_db
.set_accounts_delta_hash(bank.slot(), AccountsDeltaHash(Hash::new_unique()));
bank.rc.accounts.accounts_db.set_accounts_hash(
bank.slot(),
(AccountsHash(Hash::new_unique()), u64::default()),
);
bank.fee_rate_governor.lamports_per_signature = 7000;
let snapshot_storages = bank.get_snapshot_storages(None);
let mut buf = vec![];
let mut writer = Cursor::new(&mut buf);
crate::serde_snapshot::bank_to_stream_no_extra_fields(
&mut std::io::BufWriter::new(&mut writer),
&bank,
&get_storages_to_serialize(&snapshot_storages),
)
.unwrap();
let rdr = Cursor::new(&buf[..]);
let mut reader = std::io::BufReader::new(&buf[rdr.position() as usize..]);
let mut snapshot_streams = SnapshotStreams {
full_snapshot_stream: &mut reader,
incremental_snapshot_stream: None,
};
let (_accounts_dir, dbank_paths) = get_temp_accounts_paths(4).unwrap();
let copied_accounts = TempDir::new().unwrap();
let storage_and_next_append_vec_id = copy_append_vecs(
&bank.rc.accounts.accounts_db,
copied_accounts.path(),
storage_access,
)
.unwrap();
let (dbank, _) = crate::serde_snapshot::bank_from_streams(
&mut snapshot_streams,
&dbank_paths,
storage_and_next_append_vec_id,
&genesis_config,
&RuntimeConfig::default(),
None,
None,
None,
false,
Some(clone_solana_accounts_db::accounts_db::ACCOUNTS_DB_CONFIG_FOR_TESTING),
None,
Arc::default(),
)
.unwrap();
assert_eq!(0, dbank.fee_rate_governor.lamports_per_signature);
assert_eq!(dbank.epoch_reward_status, EpochRewardStatus::Inactive);
}
#[cfg(feature = "frozen-abi")]
mod test_bank_serialize {
use {
super::*,
crate::bank::BankHashStats,
clone_solana_accounts_db::{
account_storage::meta::StoredMetaWriteVersion, accounts_hash::AccountsLtHash,
},
clone_solana_frozen_abi::abi_example::AbiExample,
clone_solana_lattice_hash::lt_hash::LtHash,
clone_solana_sdk::clock::Slot,
std::marker::PhantomData,
};
#[cfg_attr(
feature = "frozen-abi",
derive(AbiExample),
frozen_abi(digest = "3PsrjAtyWBU3KPopGoM1UK1sa8HjVzehjBi7M2v6wW1Q")
)]
#[derive(Serialize)]
pub struct BankAbiTestWrapper {
#[serde(serialize_with = "wrapper")]
bank: PhantomData<Bank>,
}
pub fn wrapper<S>(_bank: &PhantomData<Bank>, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let bank = Bank::default_for_tests();
let snapshot_storages = AccountsDb::example().get_storages(0..1).0;
assert!(!snapshot_storages.is_empty());
let incremental_snapshot_persistence = BankIncrementalSnapshotPersistence {
full_slot: Slot::default(),
full_hash: SerdeAccountsHash(Hash::new_unique()),
full_capitalization: u64::default(),
incremental_hash: SerdeIncrementalAccountsHash(Hash::new_unique()),
incremental_capitalization: u64::default(),
};
let mut bank_fields = bank.get_fields_to_serialize();
let versioned_epoch_stakes = std::mem::take(&mut bank_fields.versioned_epoch_stakes);
serde_snapshot::serialize_bank_snapshot_with(
serializer,
bank_fields,
BankHashStats::default(),
AccountsDeltaHash(Hash::new_unique()),
AccountsHash(Hash::new_unique()),
&get_storages_to_serialize(&snapshot_storages),
ExtraFieldsToSerialize {
lamports_per_signature: bank.fee_rate_governor.lamports_per_signature,
incremental_snapshot_persistence: Some(&incremental_snapshot_persistence),
epoch_accounts_hash: Some(EpochAccountsHash::new(Hash::new_unique())),
versioned_epoch_stakes,
accounts_lt_hash: Some(AccountsLtHash(LtHash::identity()).into()),
},
StoredMetaWriteVersion::default(),
)
}
}
}