solana-runtime 4.3.0-alpha.2

Solana runtime
Documentation
#[cfg(test)]
mod tests {
    use {
        crate::{
            bank::{Bank, test_utils as bank_test_utils},
            epoch_stakes::{
                EpochAuthorizedVoters, EpochStakes, NodeIdToVoteAccounts, VersionedEpochStakes,
            },
            genesis_utils::{
                GenesisConfigInfo, activate_all_features, create_genesis_config_with_leader,
            },
            runtime_config::RuntimeConfig,
            serde_snapshot::{self, ExtraFieldsToSerialize, SnapshotStreams},
            snapshot_bank_utils,
            snapshot_utils::{StorageAndNextAccountsFileId, create_tmp_accounts_dir_for_tests},
        },
        agave_snapshots::snapshot_config::SnapshotConfig,
        solana_accounts_db::{
            ObsoleteAccounts,
            account_storage::AccountStorageMap,
            account_storage_entry::AccountStorageEntry,
            accounts_db::{
                ACCOUNTS_DB_CONFIG_FOR_TESTING, AccountsDb, AtomicAccountsFileId,
                get_temp_accounts_paths,
            },
            accounts_file::{AccountsFile, AccountsFileError},
        },
        solana_epoch_schedule::EpochSchedule,
        solana_hash::Hash,
        solana_native_token::LAMPORTS_PER_SOL,
        solana_pubkey::Pubkey,
        std::{
            io::{BufReader, BufWriter, Cursor},
            mem,
            ops::RangeFull,
            path::Path,
            sync::{Arc, OnceLock},
        },
        tempfile::TempDir,
    };

    /// Simulates the unpacking & storage reconstruction done during snapshot unpacking
    fn copy_append_vecs<P: AsRef<Path>>(
        accounts_db: &AccountsDb,
        output_dir: P,
    ) -> Result<StorageAndNextAccountsFileId, AccountsFileError> {
        let storage_entries = accounts_db.get_storages(RangeFull).0;
        let storage: AccountStorageMap = AccountStorageMap::with_capacity(storage_entries.len());
        let mut next_append_vec_id = 0;
        for storage_entry in storage_entries.into_iter() {
            // Copy file to new directory
            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)?;

            // Read new file into append-vec and build new entry
            let (accounts_file, _num_accounts) =
                AccountsFile::new_from_file(output_path, storage_entry.accounts.len())?;
            let new_storage_entry = AccountStorageEntry::new_existing(
                storage_entry.slot(),
                storage_entry.id(),
                accounts_file,
                ObsoleteAccounts::default(),
            );
            next_append_vec_id = next_append_vec_id.max(new_storage_entry.id());
            storage.insert(new_storage_entry.slot(), Arc::new(new_storage_entry));
        }

        Ok(StorageAndNextAccountsFileId {
            storage,
            next_append_vec_id: AtomicAccountsFileId::new(next_append_vec_id + 1),
        })
    }

    /// Test roundtrip serialize/deserialize of a bank
    #[test]
    fn test_serialize_bank_snapshot() {
        let leader_id = Pubkey::new_unique();
        let GenesisConfigInfo {
            mut genesis_config, ..
        } = create_genesis_config_with_leader(500, &leader_id, LAMPORTS_PER_SOL);
        genesis_config.epoch_schedule = EpochSchedule::custom(400, 400, false);
        let (bank0, bank_forks) =
            Bank::new_for_tests(&genesis_config).wrap_with_bank_forks_for_tests();
        let deposit_amount = bank0.get_minimum_balance_for_rent_exemption(0);
        let bank1 = Bank::new_from_parent_with_bank_forks(
            bank_forks.as_ref(),
            bank0.clone(),
            *bank0.leader(),
            1,
        );

        // Create an account on a non-root fork
        let key1 = Pubkey::new_unique();
        bank_test_utils::deposit(&bank1, &key1, deposit_amount).unwrap();

        let bank2_slot = 2;
        let bank0_leader = *bank0.leader();
        let bank2 = Bank::new_from_parent_with_bank_forks(
            bank_forks.as_ref(),
            bank0,
            bank0_leader,
            bank2_slot,
        );

        // Test new account
        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.set_block_id(Some(Hash::default()));
        bank2.squash();
        bank2.force_flush_accounts_cache();

        let expected_accounts_lt_hash = bank2.accounts_lt_hash.lock().unwrap().clone();

        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();
            let versioned_epoch_stakes = mem::take(&mut bank_fields.versioned_epoch_stakes);
            let accounts_lt_hash = Some(bank_fields.accounts_lt_hash.clone().into());
            let block_id = Some(bank_fields.block_id);
            serde_snapshot::serialize_bank_snapshot_into(
                &mut writer,
                bank_fields,
                bank2.get_bank_hash_stats(),
                &bank2.get_snapshot_storages(None),
                ExtraFieldsToSerialize {
                    lamports_per_signature: bank2.fee_rate_governor.lamports_per_signature,
                    unused_incremental_snapshot_persistence: None,
                    unused_epoch_accounts_hash: None,
                    versioned_epoch_stakes,
                    accounts_lt_hash,
                    block_id,
                },
            )
            .unwrap();
        }
        drop(writer);

        // The wincode serialization path must produce byte-for-byte identical output to bincode.
        {
            let mut buf_wincode = Vec::new();
            let mut bank_fields = bank2.get_fields_to_serialize();
            let versioned_epoch_stakes = mem::take(&mut bank_fields.versioned_epoch_stakes);
            let accounts_lt_hash = Some(bank_fields.accounts_lt_hash.clone().into());
            let block_id = Some(bank_fields.block_id);
            serde_snapshot::serialize_bank_snapshot_into_wincode(
                &mut buf_wincode,
                bank_fields,
                bank2.get_bank_hash_stats(),
                &bank2.get_snapshot_storages(None),
                ExtraFieldsToSerialize {
                    lamports_per_signature: bank2.fee_rate_governor.lamports_per_signature,
                    unused_incremental_snapshot_persistence: None,
                    unused_epoch_accounts_hash: None,
                    versioned_epoch_stakes,
                    accounts_lt_hash,
                    block_id,
                },
            )
            .unwrap();
            assert_eq!(buf, buf_wincode);
        }

        // Now deserialize the serialized bank and ensure it matches the original bank

        // Create a new set of directories for this bank's accounts
        let (_accounts_dir, dbank_paths) = get_temp_accounts_paths(4).unwrap();
        // Create a directory to simulate AppendVecs unpackaged from a snapshot tar
        let copied_accounts = TempDir::new().unwrap();
        let storage_and_next_append_vec_id =
            copy_append_vecs(accounts_db, copied_accounts.path()).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 (dbank, _) = serde_snapshot::bank_from_streams(
            &mut snapshot_streams,
            &dbank_paths,
            storage_and_next_append_vec_id,
            &genesis_config,
            &RuntimeConfig::default(),
            None,
            None,
            false,
            ACCOUNTS_DB_CONFIG_FOR_TESTING,
            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);
        assert_eq!(
            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.as_ref());
    }

    fn add_root_and_flush_write_cache(bank: &Bank) {
        bank.rc.accounts.add_root(bank.slot());
        bank.force_flush_accounts_cache();
    }

    #[test]
    fn test_extra_fields_eof() {
        agave_logger::setup();
        let leader_id = Pubkey::new_unique();
        let GenesisConfigInfo { genesis_config, .. } =
            create_genesis_config_with_leader(500, &leader_id, LAMPORTS_PER_SOL);

        let (bank0, _bank_forks) =
            Bank::new_for_tests(&genesis_config).wrap_with_bank_forks_for_tests();
        bank0.squash();
        let mut bank = Bank::new_from_parent(bank0.clone(), *bank0.leader(), 1);
        bank.set_block_id(Some(Hash::default()));
        bank.freeze();
        add_root_and_flush_write_cache(&bank0);

        // Set extra fields
        bank.fee_rate_governor.lamports_per_signature = 7000;
        // Note that epoch_stakes already has two epoch stakes entries for epochs 0 and 1
        // which will also be serialized to the versioned epoch stakes extra field, so add a
        // third entry to exercise round-tripping the extra field.
        bank.epoch_stakes.insert(
            42,
            VersionedEpochStakes::Current {
                stakes: EpochStakes::default(),
                total_stake: 42,
                node_id_to_vote_accounts: Arc::<NodeIdToVoteAccounts>::default(),
                epoch_authorized_voters: Arc::<EpochAuthorizedVoters>::default(),
                bls_pubkey_to_rank_map: OnceLock::new(),
            },
        );
        assert_eq!(bank.epoch_stakes.len(), 3);

        // Serialize
        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,
            &snapshot_storages,
        )
        .unwrap();

        // Deserialize
        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()).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,
            false,
            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() {
        agave_logger::setup();

        let leader_id = Pubkey::new_unique();
        let GenesisConfigInfo {
            mut genesis_config, ..
        } = create_genesis_config_with_leader(500, &leader_id, LAMPORTS_PER_SOL);
        activate_all_features(&mut genesis_config);

        let (bank0, _bank_forks) =
            Bank::new_for_tests(&genesis_config).wrap_with_bank_forks_for_tests();
        let bank0_leader = *bank0.leader();
        let mut bank = Bank::new_from_parent(bank0, bank0_leader, 1);
        while !bank.is_complete() {
            bank.fill_bank_with_ticks_for_tests();
        }
        bank.set_block_id(Some(Hash::default()));

        // Set extra field
        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();

        // Serialize
        let snapshot_config = SnapshotConfig {
            full_snapshot_archives_dir: full_snapshot_archives_dir.path().to_path_buf(),
            incremental_snapshot_archives_dir: incremental_snapshot_archives_dir
                .path()
                .to_path_buf(),
            bank_snapshots_dir: bank_snapshots_dir.path().to_path_buf(),
            ..SnapshotConfig::default()
        };
        let snapshot_archive_info =
            snapshot_bank_utils::bank_to_full_snapshot_archive(&snapshot_config, &bank).unwrap();

        // Deserialize
        let dbank = snapshot_bank_utils::bank_from_snapshot_archives(
            &[accounts_dir],
            &snapshot_archive_info,
            None,
            &snapshot_config,
            &genesis_config,
            &RuntimeConfig::default(),
            None,
            None, // leader_for_tests
            None,
            false,
            false,
            false,
            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
        );
    }
}