Handle cleaning of zero-lamport accounts w.r.t. Incremental Snapshots (#18870)
* Handle cleaning zero-lamport accounts Handle cleaning zero-lamport accounts in slots higher than the last full snapshot slot. This is part of the Incremental Snapshot work. Fixes #18825
This commit is contained in:
@@ -796,8 +796,11 @@ pub fn bank_from_snapshot_archives(
|
||||
info!("{}", measure_rebuild);
|
||||
|
||||
let mut measure_verify = Measure::start("verify");
|
||||
if !bank.verify_snapshot_bank(test_hash_calculation, accounts_db_skip_shrink)
|
||||
&& limit_load_slot_count_from_snapshot.is_none()
|
||||
if !bank.verify_snapshot_bank(
|
||||
test_hash_calculation,
|
||||
accounts_db_skip_shrink,
|
||||
Some(full_snapshot_archive_info.slot()),
|
||||
) && limit_load_slot_count_from_snapshot.is_none()
|
||||
{
|
||||
panic!("Snapshot bank for slot {} failed to verify", bank.slot());
|
||||
}
|
||||
@@ -1582,7 +1585,7 @@ pub fn bank_to_full_snapshot_archive(
|
||||
assert!(bank.is_complete());
|
||||
bank.squash(); // Bank may not be a root
|
||||
bank.force_flush_accounts_cache();
|
||||
bank.clean_accounts(true, false);
|
||||
bank.clean_accounts(true, false, Some(bank.slot()));
|
||||
bank.update_accounts_hash();
|
||||
bank.rehash(); // Bank accounts may have been manually modified by the caller
|
||||
|
||||
@@ -1625,7 +1628,7 @@ pub fn bank_to_incremental_snapshot_archive(
|
||||
assert!(bank.slot() > full_snapshot_slot);
|
||||
bank.squash(); // Bank may not be a root
|
||||
bank.force_flush_accounts_cache();
|
||||
bank.clean_accounts(true, false);
|
||||
bank.clean_accounts(true, false, Some(full_snapshot_slot));
|
||||
bank.update_accounts_hash();
|
||||
bank.rehash(); // Bank accounts may have been manually modified by the caller
|
||||
|
||||
@@ -1820,6 +1823,7 @@ mod tests {
|
||||
use solana_sdk::{
|
||||
genesis_config::create_genesis_config,
|
||||
signature::{Keypair, Signer},
|
||||
system_transaction,
|
||||
};
|
||||
use std::mem::size_of;
|
||||
|
||||
@@ -2813,4 +2817,213 @@ mod tests {
|
||||
|
||||
assert_eq!(deserialized_bank, *bank4);
|
||||
}
|
||||
|
||||
/// Test that cleaning works well in the edge cases of zero-lamport accounts and snapshots.
|
||||
/// Here's the scenario:
|
||||
///
|
||||
/// slot 1:
|
||||
/// - send some lamports to Account1 (from Account2) to bring it to life
|
||||
/// - take a full snapshot
|
||||
/// slot 2:
|
||||
/// - make Account1 have zero lamports (send back to Account2)
|
||||
/// - take an incremental snapshot
|
||||
/// - ensure deserializing from this snapshot is equal to this bank
|
||||
/// slot 3:
|
||||
/// - remove Account2's reference back to slot 2 by transfering from the mint to Account2
|
||||
/// slot 4:
|
||||
/// - ensure `clean_accounts()` has run and that Account1 is gone
|
||||
/// - take another incremental snapshot
|
||||
/// - ensure deserializing from this snapshots is equal to this bank
|
||||
/// - ensure Account1 hasn't come back from the dead
|
||||
///
|
||||
/// The check at slot 4 will fail with the pre-incremental-snapshot cleaning logic. Because
|
||||
/// of the cleaning/purging at slot 4, the incremental snapshot at slot 4 will no longer have
|
||||
/// information about Account1, but the full snapshost _does_ have info for Account1, which is
|
||||
/// no longer correct!
|
||||
#[test]
|
||||
fn test_incremental_snapshots_handle_zero_lamport_accounts() {
|
||||
solana_logger::setup();
|
||||
|
||||
let collector = Pubkey::new_unique();
|
||||
let key1 = Keypair::new();
|
||||
let key2 = Keypair::new();
|
||||
|
||||
let accounts_dir = tempfile::TempDir::new().unwrap();
|
||||
let snapshots_dir = tempfile::TempDir::new().unwrap();
|
||||
let snapshot_archives_dir = tempfile::TempDir::new().unwrap();
|
||||
let snapshot_archive_format = ArchiveFormat::Tar;
|
||||
|
||||
let (genesis_config, mint_keypair) = create_genesis_config(1_000_000);
|
||||
|
||||
let lamports_to_transfer = 123_456;
|
||||
let bank0 = Arc::new(Bank::new_with_paths_for_tests(
|
||||
&genesis_config,
|
||||
vec![accounts_dir.path().to_path_buf()],
|
||||
&[],
|
||||
None,
|
||||
None,
|
||||
AccountSecondaryIndexes::default(),
|
||||
false,
|
||||
AccountShrinkThreshold::default(),
|
||||
false,
|
||||
));
|
||||
bank0
|
||||
.transfer(lamports_to_transfer, &mint_keypair, &key2.pubkey())
|
||||
.unwrap();
|
||||
while !bank0.is_complete() {
|
||||
bank0.register_tick(&Hash::new_unique());
|
||||
}
|
||||
|
||||
let slot = 1;
|
||||
let bank1 = Arc::new(Bank::new_from_parent(&bank0, &collector, slot));
|
||||
bank1
|
||||
.transfer(lamports_to_transfer, &key2, &key1.pubkey())
|
||||
.unwrap();
|
||||
while !bank1.is_complete() {
|
||||
bank1.register_tick(&Hash::new_unique());
|
||||
}
|
||||
|
||||
let full_snapshot_slot = slot;
|
||||
let full_snapshot_archive_info = bank_to_full_snapshot_archive(
|
||||
snapshots_dir.path(),
|
||||
&bank1,
|
||||
None,
|
||||
snapshot_archives_dir.path(),
|
||||
snapshot_archive_format,
|
||||
None,
|
||||
DEFAULT_MAX_FULL_SNAPSHOT_ARCHIVES_TO_RETAIN,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let slot = slot + 1;
|
||||
let bank2 = Arc::new(Bank::new_from_parent(&bank1, &collector, slot));
|
||||
let tx = system_transaction::transfer(
|
||||
&key1,
|
||||
&key2.pubkey(),
|
||||
lamports_to_transfer,
|
||||
bank2.last_blockhash(),
|
||||
);
|
||||
let (_blockhash, fee_calculator) = bank2.last_blockhash_with_fee_calculator();
|
||||
let fee = fee_calculator.calculate_fee(tx.message());
|
||||
let tx = system_transaction::transfer(
|
||||
&key1,
|
||||
&key2.pubkey(),
|
||||
lamports_to_transfer - fee,
|
||||
bank2.last_blockhash(),
|
||||
);
|
||||
bank2.process_transaction(&tx).unwrap();
|
||||
assert_eq!(
|
||||
bank2.get_balance(&key1.pubkey()),
|
||||
0,
|
||||
"Ensure Account1's balance is zero"
|
||||
);
|
||||
while !bank2.is_complete() {
|
||||
bank2.register_tick(&Hash::new_unique());
|
||||
}
|
||||
|
||||
// Take an incremental snapshot and then do a roundtrip on the bank and ensure it
|
||||
// deserializes correctly.
|
||||
let incremental_snapshot_archive_info = bank_to_incremental_snapshot_archive(
|
||||
snapshots_dir.path(),
|
||||
&bank2,
|
||||
full_snapshot_slot,
|
||||
None,
|
||||
snapshot_archives_dir.path(),
|
||||
snapshot_archive_format,
|
||||
None,
|
||||
DEFAULT_MAX_FULL_SNAPSHOT_ARCHIVES_TO_RETAIN,
|
||||
)
|
||||
.unwrap();
|
||||
let (deserialized_bank, _) = bank_from_snapshot_archives(
|
||||
&[accounts_dir.path().to_path_buf()],
|
||||
&[],
|
||||
snapshots_dir.path(),
|
||||
&full_snapshot_archive_info,
|
||||
Some(&incremental_snapshot_archive_info),
|
||||
&genesis_config,
|
||||
None,
|
||||
None,
|
||||
AccountSecondaryIndexes::default(),
|
||||
false,
|
||||
None,
|
||||
AccountShrinkThreshold::default(),
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
crate::accounts_index::BINS_FOR_TESTING,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
deserialized_bank, *bank2,
|
||||
"Ensure rebuilding from an incremental snapshot works"
|
||||
);
|
||||
|
||||
let slot = slot + 1;
|
||||
let bank3 = Arc::new(Bank::new_from_parent(&bank2, &collector, slot));
|
||||
// Update Account2 so that it no longer holds a reference to slot2
|
||||
bank3
|
||||
.transfer(lamports_to_transfer, &mint_keypair, &key2.pubkey())
|
||||
.unwrap();
|
||||
while !bank3.is_complete() {
|
||||
bank3.register_tick(&Hash::new_unique());
|
||||
}
|
||||
|
||||
let slot = slot + 1;
|
||||
let bank4 = Arc::new(Bank::new_from_parent(&bank3, &collector, slot));
|
||||
while !bank4.is_complete() {
|
||||
bank4.register_tick(&Hash::new_unique());
|
||||
}
|
||||
|
||||
// Ensure account1 has been cleaned/purged from everywhere
|
||||
bank4.squash();
|
||||
bank4.clean_accounts(true, false, Some(full_snapshot_slot));
|
||||
assert!(
|
||||
bank4.get_account_modified_slot(&key1.pubkey()).is_none(),
|
||||
"Ensure Account1 has been cleaned and purged from AccountsDb"
|
||||
);
|
||||
|
||||
// Take an incremental snapshot and then do a roundtrip on the bank and ensure it
|
||||
// deserializes correctly
|
||||
let incremental_snapshot_archive_info = bank_to_incremental_snapshot_archive(
|
||||
snapshots_dir.path(),
|
||||
&bank4,
|
||||
full_snapshot_slot,
|
||||
None,
|
||||
snapshot_archives_dir.path(),
|
||||
snapshot_archive_format,
|
||||
None,
|
||||
DEFAULT_MAX_FULL_SNAPSHOT_ARCHIVES_TO_RETAIN,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let (deserialized_bank, _) = bank_from_snapshot_archives(
|
||||
&[accounts_dir.path().to_path_buf()],
|
||||
&[],
|
||||
snapshots_dir.path(),
|
||||
&full_snapshot_archive_info,
|
||||
Some(&incremental_snapshot_archive_info),
|
||||
&genesis_config,
|
||||
None,
|
||||
None,
|
||||
AccountSecondaryIndexes::default(),
|
||||
false,
|
||||
None,
|
||||
AccountShrinkThreshold::default(),
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
crate::accounts_index::BINS_FOR_TESTING,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
deserialized_bank, *bank4,
|
||||
"Ensure rebuilding from an incremental snapshot works",
|
||||
);
|
||||
assert!(
|
||||
deserialized_bank
|
||||
.get_account_modified_slot(&key1.pubkey())
|
||||
.is_none(),
|
||||
"Ensure Account1 has not been brought back from the dead"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user