Makes snapshot_utils aware of Incremental Accounts Hash (#30804)

This commit is contained in:
Brooks 2023-03-21 12:34:30 -04:00 committed by GitHub
parent 6a6d5ee498
commit b64d0de771
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 269 additions and 16 deletions

View File

@ -37,6 +37,7 @@ use {
},
solana_sdk::{
clock::Slot,
feature_set,
genesis_config::{
ClusterType::{self, Development, Devnet, MainnetBeta, Testnet},
GenesisConfig,
@ -81,6 +82,7 @@ impl SnapshotTestConfig {
accounts_hash_interval_slots: Slot,
full_snapshot_archive_interval_slots: Slot,
incremental_snapshot_archive_interval_slots: Slot,
is_incremental_snapshot_only_incremental_hash_calculation_enabled: bool,
) -> SnapshotTestConfig {
let (_accounts_tmp_dir, accounts_dir) = create_tmp_accounts_dir_for_tests();
let bank_snapshots_dir = TempDir::new().unwrap();
@ -96,6 +98,12 @@ impl SnapshotTestConfig {
1, // validator_stake_lamports
);
genesis_config_info.genesis_config.cluster_type = cluster_type;
if !is_incremental_snapshot_only_incremental_hash_calculation_enabled {
genesis_config_info
.genesis_config
.accounts
.remove(&feature_set::incremental_snapshot_only_incremental_hash_calculation::id());
}
let bank0 = Bank::new_with_paths_for_tests(
&genesis_config_info.genesis_config,
Arc::<RuntimeConfig>::default(),
@ -208,6 +216,7 @@ fn run_bank_forks_snapshot_n<F>(
set_root_interval,
set_root_interval,
Slot::MAX,
true,
);
let bank_forks = &mut snapshot_test_config.bank_forks;
@ -331,7 +340,7 @@ fn test_concurrent_snapshot_packaging(
// Set up snapshotting config
let mut snapshot_test_config =
SnapshotTestConfig::new(snapshot_version, cluster_type, 1, 1, Slot::MAX);
SnapshotTestConfig::new(snapshot_version, cluster_type, 1, 1, Slot::MAX, true);
let bank_forks = &mut snapshot_test_config.bank_forks;
let snapshot_config = &snapshot_test_config.snapshot_config;
@ -583,6 +592,7 @@ fn test_slots_to_snapshot(snapshot_version: SnapshotVersion, cluster_type: Clust
(*add_root_interval * num_set_roots * 2) as Slot,
(*add_root_interval * num_set_roots * 2) as Slot,
Slot::MAX,
true,
);
let mut current_bank = snapshot_test_config.bank_forks[0].clone();
let request_sender = AbsRequestSender::new(snapshot_sender);
@ -701,6 +711,7 @@ fn test_bank_forks_incremental_snapshot(
SET_ROOT_INTERVAL,
FULL_SNAPSHOT_ARCHIVE_INTERVAL_SLOTS,
INCREMENTAL_SNAPSHOT_ARCHIVE_INTERVAL_SLOTS,
true,
);
trace!("SnapshotTestConfig:\naccounts_dir: {}\nbank_snapshots_dir: {}\nfull_snapshot_archives_dir: {}\nincremental_snapshot_archives_dir: {}",
snapshot_test_config.accounts_dir.display(), snapshot_test_config.bank_snapshots_dir.path().display(), snapshot_test_config.full_snapshot_archives_dir.path().display(), snapshot_test_config.incremental_snapshot_archives_dir.path().display());
@ -755,8 +766,6 @@ fn test_bank_forks_incremental_snapshot(
// Since AccountsBackgroundService isn't running, manually make a full snapshot archive
// at the right interval
if snapshot_utils::should_take_full_snapshot(slot, FULL_SNAPSHOT_ARCHIVE_INTERVAL_SLOTS) {
bank.force_flush_accounts_cache();
bank.update_accounts_hash(CalcAccountsHashDataSource::Storages, false, false);
make_full_snapshot_archive(&bank, &snapshot_test_config.snapshot_config).unwrap();
}
// Similarly, make an incremental snapshot archive at the right interval, but only if
@ -770,8 +779,6 @@ fn test_bank_forks_incremental_snapshot(
last_full_snapshot_slot,
) && slot != last_full_snapshot_slot.unwrap()
{
bank.force_flush_accounts_cache();
bank.update_accounts_hash(CalcAccountsHashDataSource::Storages, false, false);
make_incremental_snapshot_archive(
&bank,
last_full_snapshot_slot.unwrap(),
@ -800,6 +807,8 @@ fn make_full_snapshot_archive(
) -> snapshot_utils::Result<()> {
let slot = bank.slot();
info!("Making full snapshot archive from bank at slot: {}", slot);
bank.force_flush_accounts_cache();
bank.update_accounts_hash(CalcAccountsHashDataSource::Storages, false, false);
let bank_snapshot_info =
snapshot_utils::get_bank_snapshots_pre(&snapshot_config.bank_snapshots_dir)
.into_iter()
@ -836,6 +845,8 @@ fn make_incremental_snapshot_archive(
"Making incremental snapshot archive from bank at slot: {}, and base slot: {}",
slot, incremental_snapshot_base_slot,
);
bank.force_flush_accounts_cache();
bank.update_incremental_accounts_hash(incremental_snapshot_base_slot);
let bank_snapshot_info =
snapshot_utils::get_bank_snapshots_pre(&snapshot_config.bank_snapshots_dir)
.into_iter()
@ -940,6 +951,8 @@ fn test_snapshots_with_background_services(
BANK_SNAPSHOT_INTERVAL_SLOTS,
FULL_SNAPSHOT_ARCHIVE_INTERVAL_SLOTS,
INCREMENTAL_SNAPSHOT_ARCHIVE_INTERVAL_SLOTS,
// disable incremental accounts hash feature since AccountsHashVerifier does not support it yet
false,
);
let node_keypair = Arc::new(Keypair::new());

View File

@ -31,6 +31,7 @@ use {
clock::{DEFAULT_DEV_SLOTS_PER_EPOCH, DEFAULT_TICKS_PER_SLOT},
commitment_config::CommitmentConfig,
epoch_schedule::EpochSchedule,
feature_set,
genesis_config::{ClusterType, GenesisConfig},
message::Message,
poh_config::PohConfig,
@ -263,6 +264,11 @@ impl LocalCluster {
),
);
// disable incremental accounts hash feature since AccountsHashVerifier does not support it yet
genesis_config
.accounts
.remove(&feature_set::incremental_snapshot_only_incremental_hash_calculation::id());
let (leader_ledger_path, _blockhash) = create_new_tmp_ledger!(&genesis_config);
let leader_contact_info = leader_node.info.clone();
let mut leader_config = safe_clone_config(&config.validator_configs[0]);

View File

@ -5,10 +5,11 @@ use {
AccountShrinkThreshold, AccountStorageEntry, AccountsDbConfig, AtomicAppendVecId,
CalcAccountsHashDataSource,
},
accounts_hash::AccountsHash,
accounts_index::AccountSecondaryIndexes,
accounts_update_notifier_interface::AccountsUpdateNotifier,
append_vec::AppendVec,
bank::{Bank, BankFieldsToDeserialize, BankSlotDelta},
bank::{Bank, BankFieldsToDeserialize, BankIncrementalSnapshotPersistence, BankSlotDelta},
builtins::Builtins,
hardened_unpack::{
streaming_unpack_snapshot, unpack_snapshot, ParallelSelector, UnpackError,
@ -40,6 +41,7 @@ use {
solana_measure::{measure, measure::Measure},
solana_sdk::{
clock::Slot,
feature_set,
genesis_config::GenesisConfig,
hash::Hash,
pubkey::Pubkey,
@ -1477,12 +1479,28 @@ pub fn bank_from_snapshot_archives(
snapshot_archive_info.hash,
)?;
let base = (incremental_snapshot_archive_info.is_some()
&& bank
.feature_set
.is_active(&feature_set::incremental_snapshot_only_incremental_hash_calculation::id()))
.then(|| {
let base_slot = full_snapshot_archive_info.slot();
let base_capitalization = bank
.rc
.accounts
.accounts_db
.get_accounts_hash(base_slot)
.expect("accounts hash must exist at full snapshot's slot")
.1;
(base_slot, base_capitalization)
});
let mut measure_verify = Measure::start("verify");
if !bank.verify_snapshot_bank(
test_hash_calculation,
accounts_db_skip_shrink || !full_snapshot_archive_info.is_remote(),
full_snapshot_archive_info.slot(),
None,
base,
) && limit_load_slot_count_from_snapshot.is_none()
{
panic!("Snapshot bank for slot {} failed to verify", bank.slot());
@ -2697,7 +2715,14 @@ pub fn bank_to_incremental_snapshot_archive(
bank.squash(); // Bank may not be a root
bank.force_flush_accounts_cache();
bank.clean_accounts(Some(full_snapshot_slot));
bank.update_accounts_hash(CalcAccountsHashDataSource::Storages, false, false);
if bank
.feature_set
.is_active(&feature_set::incremental_snapshot_only_incremental_hash_calculation::id())
{
bank.update_incremental_accounts_hash(full_snapshot_slot);
} else {
bank.update_accounts_hash(CalcAccountsHashDataSource::Storages, false, false);
}
bank.rehash(); // Bank accounts may have been manually modified by the caller
let temp_dir = tempfile::tempdir_in(bank_snapshots_dir)?;
@ -2807,17 +2832,50 @@ pub fn package_and_archive_incremental_snapshot(
None,
)?;
let accounts_hash = bank
.get_accounts_hash()
.expect("accounts hash is required for snapshot");
let (accounts_hash_enum, accounts_hash_for_reserialize, bank_incremental_snapshot_persistence) =
if bank
.feature_set
.is_active(&feature_set::incremental_snapshot_only_incremental_hash_calculation::id())
{
let (base_accounts_hash, base_capitalization) = bank
.rc
.accounts
.accounts_db
.get_accounts_hash(incremental_snapshot_base_slot)
.expect("base accounts hash is required for incremental snapshot");
let (incremental_accounts_hash, incremental_capitalization) = bank
.rc
.accounts
.accounts_db
.get_incremental_accounts_hash(bank.slot())
.expect("incremental accounts hash is required for incremental snapshot");
let bank_incremental_snapshot_persistence = BankIncrementalSnapshotPersistence {
full_slot: incremental_snapshot_base_slot,
full_hash: base_accounts_hash.into(),
full_capitalization: base_capitalization,
incremental_hash: incremental_accounts_hash.into(),
incremental_capitalization,
};
(
incremental_accounts_hash.into(),
AccountsHash(Hash::default()), // value does not matter; not used for incremental snapshots
Some(bank_incremental_snapshot_persistence),
)
} else {
let accounts_hash = bank
.get_accounts_hash()
.expect("accounts hash is required for snapshot");
(accounts_hash.into(), accounts_hash, None)
};
crate::serde_snapshot::reserialize_bank_with_new_accounts_hash(
accounts_package.snapshot_links_dir(),
accounts_package.slot,
&accounts_hash,
None,
&accounts_hash_for_reserialize,
bank_incremental_snapshot_persistence.as_ref(),
);
let snapshot_package = SnapshotPackage::new(accounts_package, accounts_hash.into());
let snapshot_package = SnapshotPackage::new(accounts_package, accounts_hash_enum);
archive_snapshot_package(
&snapshot_package,
full_snapshot_archives_dir,
@ -2858,12 +2916,18 @@ pub fn create_tmp_accounts_dir_for_tests() -> (TempDir, PathBuf) {
mod tests {
use {
super::*,
crate::{accounts_db::ACCOUNTS_DB_CONFIG_FOR_TESTING, status_cache::Status},
crate::{
accounts_db::ACCOUNTS_DB_CONFIG_FOR_TESTING,
accounts_hash::{CalcAccountsHashConfig, HashStats},
genesis_utils,
sorted_storages::SortedStorages,
status_cache::Status,
},
assert_matches::assert_matches,
bincode::{deserialize_from, serialize_into},
solana_sdk::{
genesis_config::create_genesis_config,
native_token::sol_to_lamports,
native_token::{sol_to_lamports, LAMPORTS_PER_SOL},
signature::{Keypair, Signer},
slot_history::SlotHistory,
system_transaction,
@ -4869,4 +4933,174 @@ mod tests {
.iter()
.all(|dir| fs::metadata(dir).is_err()));
}
/// Test that snapshots with the Incremental Accounts Hash feature enabled can roundtrip.
///
/// This test generates banks with zero and non-zero lamport accounts then takes full and
/// incremental snapshots. A bank is deserialized from the snapshots, its incremental
/// accounts hash is recalculated, and then compared with the original.
#[test]
fn test_incremental_snapshot_with_incremental_accounts_hash() {
let bank_snapshots_dir = tempfile::TempDir::new().unwrap();
let full_snapshot_archives_dir = tempfile::TempDir::new().unwrap();
let incremental_snapshot_archives_dir = tempfile::TempDir::new().unwrap();
let genesis_config_info = genesis_utils::create_genesis_config_with_leader(
1_000_000 * LAMPORTS_PER_SOL,
&Pubkey::new_unique(),
100 * LAMPORTS_PER_SOL,
);
let mint = &genesis_config_info.mint_keypair;
let do_transfers = |bank: &Bank| {
let key1 = Keypair::new(); // lamports from mint
let key2 = Keypair::new(); // will end with ZERO lamports
let key3 = Keypair::new(); // lamports from key2
let amount = 123_456_789;
let fee = {
let blockhash = bank.last_blockhash();
let transaction = SanitizedTransaction::from_transaction_for_tests(
system_transaction::transfer(&key2, &key3.pubkey(), amount, blockhash),
);
bank.get_fee_for_message(transaction.message()).unwrap()
};
bank.transfer(amount + fee, mint, &key1.pubkey()).unwrap();
bank.transfer(amount + fee, mint, &key2.pubkey()).unwrap();
bank.transfer(amount + fee, &key2, &key3.pubkey()).unwrap();
assert_eq!(bank.get_balance(&key2.pubkey()), 0);
bank.fill_bank_with_ticks_for_tests();
};
let mut bank = Arc::new(Bank::new_for_tests(&genesis_config_info.genesis_config));
// make some banks, do some transactions, ensure there's some zero-lamport accounts
for _ in 0..5 {
bank = Arc::new(Bank::new_from_parent(
&bank,
&Pubkey::new_unique(),
bank.slot() + 1,
));
do_transfers(&bank);
}
// take full snapshot, save off the calculated accounts hash
let full_snapshot_archive = bank_to_full_snapshot_archive(
&bank_snapshots_dir,
&bank,
None,
&full_snapshot_archives_dir,
&incremental_snapshot_archives_dir,
ArchiveFormat::Tar,
DEFAULT_MAX_FULL_SNAPSHOT_ARCHIVES_TO_RETAIN,
DEFAULT_MAX_INCREMENTAL_SNAPSHOT_ARCHIVES_TO_RETAIN,
)
.unwrap();
let full_accounts_hash = bank
.rc
.accounts
.accounts_db
.get_accounts_hash(bank.slot())
.unwrap();
// make more banks, do more transactions, ensure there's more zero-lamport accounts
for _ in 0..5 {
bank = Arc::new(Bank::new_from_parent(
&bank,
&Pubkey::new_unique(),
bank.slot() + 1,
));
do_transfers(&bank);
}
// take incremental snapshot, save off the calculated incremental accounts hash
let incremental_snapshot_archive = bank_to_incremental_snapshot_archive(
&bank_snapshots_dir,
&bank,
full_snapshot_archive.slot(),
None,
&full_snapshot_archives_dir,
&incremental_snapshot_archives_dir,
ArchiveFormat::Tar,
DEFAULT_MAX_FULL_SNAPSHOT_ARCHIVES_TO_RETAIN,
DEFAULT_MAX_INCREMENTAL_SNAPSHOT_ARCHIVES_TO_RETAIN,
)
.unwrap();
let incremental_accounts_hash = bank
.rc
.accounts
.accounts_db
.get_incremental_accounts_hash(bank.slot())
.unwrap();
// reconstruct a bank from the snapshots
let other_accounts_dir = tempfile::TempDir::new().unwrap();
let other_bank_snapshots_dir = tempfile::TempDir::new().unwrap();
let (deserialized_bank, _) = bank_from_snapshot_archives(
&[other_accounts_dir.path().to_path_buf()],
&other_bank_snapshots_dir,
&full_snapshot_archive,
Some(&incremental_snapshot_archive),
&genesis_config_info.genesis_config,
&RuntimeConfig::default(),
None,
None,
AccountSecondaryIndexes::default(),
None,
AccountShrinkThreshold::default(),
false,
false,
false,
Some(ACCOUNTS_DB_CONFIG_FOR_TESTING),
None,
&Arc::default(),
)
.unwrap();
deserialized_bank.wait_for_initial_accounts_hash_verification_completed_for_tests();
assert_eq!(&deserialized_bank, bank.as_ref());
// ensure the accounts hash stored in the deserialized bank matches
let deserialized_accounts_hash = deserialized_bank
.rc
.accounts
.accounts_db
.get_accounts_hash(full_snapshot_archive.slot())
.unwrap();
assert_eq!(deserialized_accounts_hash, full_accounts_hash);
// ensure the incremental accounts hash stored in the deserialized bank matches
let deserialized_incrmental_accounts_hash = deserialized_bank
.rc
.accounts
.accounts_db
.get_incremental_accounts_hash(incremental_snapshot_archive.slot())
.unwrap();
assert_eq!(
deserialized_incrmental_accounts_hash,
incremental_accounts_hash
);
// recalculate the incremental accounts hash on the desserialized bank and ensure it matches
let other_incremental_snapshot_storages =
deserialized_bank.get_snapshot_storages(Some(full_snapshot_archive.slot()));
let other_incremental_accounts_hash = bank
.rc
.accounts
.accounts_db
.calculate_incremental_accounts_hash(
&CalcAccountsHashConfig {
use_bg_thread_pool: false,
check_hash: false,
ancestors: None,
epoch_schedule: deserialized_bank.epoch_schedule(),
rent_collector: deserialized_bank.rent_collector(),
store_detailed_debug_info_on_failure: false,
},
&SortedStorages::new(&other_incremental_snapshot_storages),
HashStats::default(),
)
.unwrap();
assert_eq!(other_incremental_accounts_hash, incremental_accounts_hash);
}
}