solana_runtime/
snapshot_minimizer.rs

1//! Used to create minimal snapshots - separated here to keep accounts_db simpler
2
3use {
4    crate::{
5        accounts_db::{
6            AccountStorageEntry, AccountsDb, GetUniqueAccountsResult, PurgeStats, StoreReclaims,
7        },
8        bank::Bank,
9        builtins, static_ids,
10    },
11    dashmap::DashSet,
12    log::info,
13    rayon::{
14        iter::{IntoParallelIterator, IntoParallelRefIterator, ParallelIterator},
15        prelude::ParallelSlice,
16    },
17    safecoin_measure::measure,
18    solana_sdk::{
19        account::ReadableAccount,
20        account_utils::StateMut,
21        bpf_loader_upgradeable::{self, UpgradeableLoaderState},
22        clock::Slot,
23        pubkey::Pubkey,
24        sdk_ids,
25    },
26    std::{
27        collections::HashSet,
28        sync::{
29            atomic::{AtomicUsize, Ordering},
30            Arc, Mutex,
31        },
32    },
33};
34
35/// Used to modify bank and accounts_db to create a minimized snapshot
36pub struct SnapshotMinimizer<'a> {
37    bank: &'a Bank,
38    starting_slot: Slot,
39    ending_slot: Slot,
40    minimized_account_set: DashSet<Pubkey>,
41}
42
43impl<'a> SnapshotMinimizer<'a> {
44    /// Removes all accounts not necessary for replaying slots in the range [starting_slot, ending_slot].
45    /// `transaction_account_set` should contain accounts used in transactions in the slot range [starting_slot, ending_slot].
46    /// This function will accumulate other accounts (rent colleciton, builtins, etc) necessary to replay transactions.
47    ///
48    /// This function will modify accounts_db by removing accounts not needed to replay [starting_slot, ending_slot],
49    /// and update the bank's capitalization.
50    pub fn minimize(
51        bank: &'a Bank,
52        starting_slot: Slot,
53        ending_slot: Slot,
54        transaction_account_set: DashSet<Pubkey>,
55    ) {
56        let minimizer = SnapshotMinimizer {
57            bank,
58            starting_slot,
59            ending_slot,
60            minimized_account_set: transaction_account_set,
61        };
62
63        minimizer.add_accounts(Self::get_active_bank_features, "active bank features");
64        minimizer.add_accounts(Self::get_inactive_bank_features, "inactive bank features");
65        minimizer.add_accounts(Self::get_builtins, "builtin accounts");
66        minimizer.add_accounts(Self::get_static_runtime_accounts, "static runtime accounts");
67        minimizer.add_accounts(Self::get_sdk_accounts, "sdk accounts");
68
69        minimizer.add_accounts(
70            Self::get_rent_collection_accounts,
71            "rent collection accounts",
72        );
73        minimizer.add_accounts(Self::get_vote_accounts, "vote accounts");
74        minimizer.add_accounts(Self::get_stake_accounts, "stake accounts");
75        minimizer.add_accounts(Self::get_owner_accounts, "owner accounts");
76        minimizer.add_accounts(Self::get_programdata_accounts, "programdata accounts");
77
78        minimizer.minimize_accounts_db();
79
80        // Update accounts_cache and capitalization
81        minimizer.bank.force_flush_accounts_cache();
82        minimizer.bank.set_capitalization();
83    }
84
85    /// Helper function to measure time and number of accounts added
86    fn add_accounts<F>(&self, add_accounts_fn: F, name: &'static str)
87    where
88        F: Fn(&SnapshotMinimizer<'a>),
89    {
90        let initial_accounts_len = self.minimized_account_set.len();
91        let (_, measure) = measure!(add_accounts_fn(self), name);
92        let total_accounts_len = self.minimized_account_set.len();
93        let added_accounts = total_accounts_len - initial_accounts_len;
94
95        info!(
96            "Added {added_accounts} {name} for total of {total_accounts_len} accounts. get {measure}"
97        );
98    }
99
100    /// Used to get active bank feature accounts in `minimize`.
101    fn get_active_bank_features(&self) {
102        self.bank.feature_set.active.iter().for_each(|(pubkey, _)| {
103            self.minimized_account_set.insert(*pubkey);
104        });
105    }
106
107    /// Used to get inactive bank feature accounts in `minimize`
108    fn get_inactive_bank_features(&self) {
109        self.bank.feature_set.inactive.iter().for_each(|pubkey| {
110            self.minimized_account_set.insert(*pubkey);
111        });
112    }
113
114    /// Used to get builtin accounts in `minimize`
115    fn get_builtins(&self) {
116        builtins::get_pubkeys().iter().for_each(|pubkey| {
117            self.minimized_account_set.insert(*pubkey);
118        });
119    }
120
121    /// Used to get static runtime accounts in `minimize`
122    fn get_static_runtime_accounts(&self) {
123        static_ids::STATIC_IDS.iter().for_each(|pubkey| {
124            self.minimized_account_set.insert(*pubkey);
125        });
126    }
127
128    /// Used to get sdk accounts in `minimize`
129    fn get_sdk_accounts(&self) {
130        sdk_ids::SDK_IDS.iter().for_each(|pubkey| {
131            self.minimized_account_set.insert(*pubkey);
132        });
133    }
134
135    /// Used to get rent collection accounts in `minimize`
136    /// Add all pubkeys we would collect rent from or rewrite to `minimized_account_set`.
137    /// related to Bank::rent_collection_partitions
138    fn get_rent_collection_accounts(&self) {
139        let partitions = if !self.bank.use_fixed_collection_cycle() {
140            self.bank
141                .variable_cycle_partitions_between_slots(self.starting_slot, self.ending_slot)
142        } else {
143            self.bank
144                .fixed_cycle_partitions_between_slots(self.starting_slot, self.ending_slot)
145        };
146
147        partitions.into_iter().for_each(|partition| {
148            let subrange = Bank::pubkey_range_from_partition(partition);
149            // This may be overkill since we just need the pubkeys and don't need to actually load the accounts.
150            // Leaving it for now as this is only used by ledger-tool. If used in runtime, we will need to instead use
151            // some of the guts of `load_to_collect_rent_eagerly`.
152            self.bank
153                .accounts()
154                .load_to_collect_rent_eagerly(&self.bank.ancestors, subrange)
155                .into_par_iter()
156                .for_each(|(pubkey, ..)| {
157                    self.minimized_account_set.insert(pubkey);
158                })
159        });
160    }
161
162    /// Used to get vote and node pubkeys in `minimize`
163    /// Add all pubkeys from vote accounts and nodes to `minimized_account_set`
164    fn get_vote_accounts(&self) {
165        self.bank
166            .vote_accounts()
167            .par_iter()
168            .for_each(|(pubkey, (_stake, vote_account))| {
169                self.minimized_account_set.insert(*pubkey);
170                if let Ok(vote_state) = vote_account.vote_state().as_ref() {
171                    self.minimized_account_set.insert(vote_state.node_pubkey);
172                }
173            });
174    }
175
176    /// Used to get stake accounts in `minimize`
177    /// Add all pubkeys from stake accounts to `minimized_account_set`
178    fn get_stake_accounts(&self) {
179        self.bank.get_stake_accounts(&self.minimized_account_set);
180    }
181
182    /// Used to get owner accounts in `minimize`
183    /// For each account in `minimized_account_set` adds the owner account's pubkey to `minimized_account_set`.
184    fn get_owner_accounts(&self) {
185        let owner_accounts: HashSet<_> = self
186            .minimized_account_set
187            .par_iter()
188            .filter_map(|pubkey| self.bank.get_account(&pubkey))
189            .map(|account| *account.owner())
190            .collect();
191        owner_accounts.into_par_iter().for_each(|pubkey| {
192            self.minimized_account_set.insert(pubkey);
193        });
194    }
195
196    /// Used to get program data accounts in `minimize`
197    /// For each upgradable bpf program, adds the programdata account pubkey to `minimized_account_set`
198    fn get_programdata_accounts(&self) {
199        let programdata_accounts: HashSet<_> = self
200            .minimized_account_set
201            .par_iter()
202            .filter_map(|pubkey| self.bank.get_account(&pubkey))
203            .filter(|account| account.executable())
204            .filter(|account| bpf_loader_upgradeable::check_id(account.owner()))
205            .filter_map(|account| {
206                if let Ok(UpgradeableLoaderState::Program {
207                    programdata_address,
208                }) = account.state()
209                {
210                    Some(programdata_address)
211                } else {
212                    None
213                }
214            })
215            .collect();
216        programdata_accounts.into_par_iter().for_each(|pubkey| {
217            self.minimized_account_set.insert(pubkey);
218        });
219    }
220
221    /// Remove accounts not in `minimized_accoun_set` from accounts_db
222    fn minimize_accounts_db(&self) {
223        let (minimized_slot_set, minimized_slot_set_measure) =
224            measure!(self.get_minimized_slot_set(), "generate minimized slot set");
225        info!("{minimized_slot_set_measure}");
226
227        let ((dead_slots, dead_storages), process_snapshot_storages_measure) = measure!(
228            self.process_snapshot_storages(minimized_slot_set),
229            "process snapshot storages"
230        );
231        info!("{process_snapshot_storages_measure}");
232
233        // Avoid excessive logging
234        self.accounts_db()
235            .log_dead_slots
236            .store(false, Ordering::Relaxed);
237
238        let (_, purge_dead_slots_measure) =
239            measure!(self.purge_dead_slots(dead_slots), "purge dead slots");
240        info!("{purge_dead_slots_measure}");
241
242        let (_, drop_or_recycle_stores_measure) = measure!(
243            self.accounts_db().drop_or_recycle_stores(dead_storages),
244            "drop or recycle stores"
245        );
246        info!("{drop_or_recycle_stores_measure}");
247
248        // Turn logging back on after minimization
249        self.accounts_db()
250            .log_dead_slots
251            .store(true, Ordering::Relaxed);
252    }
253
254    /// Determines minimum set of slots that accounts in `minimized_account_set` are in
255    fn get_minimized_slot_set(&self) -> DashSet<Slot> {
256        let minimized_slot_set = DashSet::new();
257        self.minimized_account_set.par_iter().for_each(|pubkey| {
258            if let Some(read_entry) = self
259                .accounts_db()
260                .accounts_index
261                .get_account_read_entry(&pubkey)
262            {
263                if let Some(max_slot) = read_entry.slot_list().iter().map(|(slot, _)| *slot).max() {
264                    minimized_slot_set.insert(max_slot);
265                }
266            }
267        });
268        minimized_slot_set
269    }
270
271    /// Process all snapshot storages to during `minimize`
272    fn process_snapshot_storages(
273        &self,
274        minimized_slot_set: DashSet<Slot>,
275    ) -> (Vec<Slot>, Vec<Arc<AccountStorageEntry>>) {
276        let snapshot_storages = self
277            .accounts_db()
278            .get_snapshot_storages(self.starting_slot, None, None)
279            .0;
280
281        let dead_slots = Mutex::new(Vec::new());
282        let dead_storages = Mutex::new(Vec::new());
283
284        snapshot_storages.into_par_iter().for_each(|storages| {
285            let slot = storages.first().unwrap().slot();
286            if slot != self.starting_slot {
287                if minimized_slot_set.contains(&slot) {
288                    self.filter_storages(storages, &dead_storages);
289                } else {
290                    dead_slots.lock().unwrap().push(slot);
291                }
292            }
293        });
294
295        let dead_slots = dead_slots.into_inner().unwrap();
296        let dead_storages = dead_storages.into_inner().unwrap();
297        (dead_slots, dead_storages)
298    }
299
300    /// Creates new storage replacing `storages` that contains only accounts in `minimized_account_set`.
301    fn filter_storages(
302        &self,
303        storages: Vec<Arc<AccountStorageEntry>>,
304        dead_storages: &Mutex<Vec<Arc<AccountStorageEntry>>>,
305    ) {
306        let slot = storages.first().unwrap().slot();
307        let GetUniqueAccountsResult {
308            stored_accounts, ..
309        } = self
310            .accounts_db()
311            .get_unique_accounts_from_storages(storages.iter());
312        let mut stored_accounts = stored_accounts.into_iter().collect::<Vec<_>>();
313        stored_accounts.sort_unstable_by(|a, b| a.0.cmp(&b.0));
314
315        let keep_accounts_collect = Mutex::new(Vec::with_capacity(stored_accounts.len()));
316        let purge_pubkeys_collect = Mutex::new(Vec::with_capacity(stored_accounts.len()));
317        let total_bytes_collect = AtomicUsize::new(0);
318        const CHUNK_SIZE: usize = 50;
319        stored_accounts.par_chunks(CHUNK_SIZE).for_each(|chunk| {
320            let mut chunk_bytes = 0;
321            let mut keep_accounts = Vec::with_capacity(CHUNK_SIZE);
322            let mut purge_pubkeys = Vec::with_capacity(CHUNK_SIZE);
323            chunk.iter().for_each(|(pubkey, account)| {
324                if self.minimized_account_set.contains(pubkey) {
325                    chunk_bytes += account.account.stored_size;
326                    keep_accounts.push((pubkey, account));
327                } else if self
328                    .accounts_db()
329                    .accounts_index
330                    .get_account_read_entry(pubkey)
331                    .is_some()
332                {
333                    purge_pubkeys.push(pubkey);
334                }
335            });
336
337            keep_accounts_collect
338                .lock()
339                .unwrap()
340                .append(&mut keep_accounts);
341            purge_pubkeys_collect
342                .lock()
343                .unwrap()
344                .append(&mut purge_pubkeys);
345            total_bytes_collect.fetch_add(chunk_bytes, Ordering::Relaxed);
346        });
347
348        let keep_accounts = keep_accounts_collect.into_inner().unwrap();
349        let remove_pubkeys = purge_pubkeys_collect.into_inner().unwrap();
350        let total_bytes = total_bytes_collect.load(Ordering::Relaxed);
351
352        let purge_pubkeys: Vec<_> = remove_pubkeys
353            .into_iter()
354            .map(|pubkey| (*pubkey, slot))
355            .collect();
356        let _ = self.accounts_db().purge_keys_exact(purge_pubkeys.iter());
357
358        let aligned_total: u64 = AccountsDb::page_align(total_bytes as u64);
359        if aligned_total > 0 {
360            let mut accounts = Vec::with_capacity(keep_accounts.len());
361            let mut hashes = Vec::with_capacity(keep_accounts.len());
362            let mut write_versions = Vec::with_capacity(keep_accounts.len());
363
364            for (pubkey, alive_account) in keep_accounts {
365                accounts.push((pubkey, &alive_account.account));
366                hashes.push(alive_account.account.hash);
367                write_versions.push(alive_account.account.meta.write_version);
368            }
369
370            let (new_storage, _time) = self.accounts_db().get_store_for_shrink(slot, aligned_total);
371
372            self.accounts_db().store_accounts_frozen(
373                (slot, &accounts[..]),
374                Some(&hashes),
375                Some(&new_storage),
376                Some(Box::new(write_versions.into_iter())),
377                StoreReclaims::Default,
378            );
379
380            new_storage.flush().unwrap();
381        }
382
383        let append_vec_set: HashSet<_> = storages
384            .iter()
385            .map(|storage| storage.append_vec_id())
386            .collect();
387        self.accounts_db().mark_dirty_dead_stores(
388            slot,
389            &mut dead_storages.lock().unwrap(),
390            |store| !append_vec_set.contains(&store.append_vec_id()),
391        );
392    }
393
394    /// Purge dead slots from storage and cache
395    fn purge_dead_slots(&self, dead_slots: Vec<Slot>) {
396        let stats = PurgeStats::default();
397        self.accounts_db()
398            .purge_slots_from_cache_and_store(dead_slots.iter(), &stats, false);
399    }
400
401    /// Convenience function for getting accounts_db
402    fn accounts_db(&self) -> &AccountsDb {
403        &self.bank.rc.accounts.accounts_db
404    }
405}
406
407#[cfg(test)]
408mod tests {
409    use {
410        crate::{
411            bank::Bank, genesis_utils::create_genesis_config_with_leader,
412            snapshot_minimizer::SnapshotMinimizer,
413        },
414        dashmap::DashSet,
415        solana_sdk::{
416            account::{AccountSharedData, ReadableAccount, WritableAccount},
417            bpf_loader_upgradeable::{self, UpgradeableLoaderState},
418            genesis_config::{create_genesis_config, GenesisConfig},
419            pubkey::Pubkey,
420            signer::Signer,
421            stake,
422        },
423        std::sync::Arc,
424    };
425
426    #[test]
427    fn test_get_rent_collection_accounts() {
428        solana_logger::setup();
429
430        let genesis_config = GenesisConfig::default();
431        let bank = Arc::new(Bank::new_for_tests(&genesis_config));
432
433        // Slots correspond to subrange: A52Kf8KJNVhs1y61uhkzkSF82TXCLxZekqmFwiFXLnHu..=ChWNbfHUHLvFY3uhXj6kQhJ7a9iZB4ykh34WRGS5w9NE
434        // Initially, there are no existing keys in this range
435        {
436            let minimizer = SnapshotMinimizer {
437                bank: &bank,
438                starting_slot: 100_000,
439                ending_slot: 110_000,
440                minimized_account_set: DashSet::new(),
441            };
442            minimizer.get_rent_collection_accounts();
443            assert!(
444                minimizer.minimized_account_set.is_empty(),
445                "rent collection accounts should be empty: len={}",
446                minimizer.minimized_account_set.len()
447            );
448        }
449
450        // Add a key in the subrange
451        let pubkey: Pubkey = "ChWNbfHUHLvFY3uhXj6kQhJ7a9iZB4ykh34WRGS5w9ND"
452            .parse()
453            .unwrap();
454        bank.store_account(&pubkey, &AccountSharedData::new(1, 0, &Pubkey::default()));
455
456        {
457            let minimizer = SnapshotMinimizer {
458                bank: &bank,
459                starting_slot: 100_000,
460                ending_slot: 110_000,
461                minimized_account_set: DashSet::new(),
462            };
463            minimizer.get_rent_collection_accounts();
464            assert_eq!(
465                1,
466                minimizer.minimized_account_set.len(),
467                "rent collection accounts should have len=1: len={}",
468                minimizer.minimized_account_set.len()
469            );
470            assert!(minimizer.minimized_account_set.contains(&pubkey));
471        }
472
473        // Slots correspond to subrange: ChXFtoKuDvQum4HvtgiqGWrgUYbtP1ZzGFGMnT8FuGaB..=FKzRYCFeCC8e48jP9kSW4xM77quv1BPrdEMktpceXWSa
474        // The previous key is not contained in this range, so is not added
475        {
476            let minimizer = SnapshotMinimizer {
477                bank: &bank,
478                starting_slot: 110_001,
479                ending_slot: 120_000,
480                minimized_account_set: DashSet::new(),
481            };
482            assert!(
483                minimizer.minimized_account_set.is_empty(),
484                "rent collection accounts should be empty: len={}",
485                minimizer.minimized_account_set.len()
486            );
487        }
488    }
489
490    #[test]
491    fn test_minimization_get_vote_accounts() {
492        solana_logger::setup();
493
494        let bootstrap_validator_pubkey = solana_sdk::pubkey::new_rand();
495        let bootstrap_validator_stake_lamports = 30;
496        let genesis_config_info = create_genesis_config_with_leader(
497            10,
498            &bootstrap_validator_pubkey,
499            bootstrap_validator_stake_lamports,
500        );
501
502        let bank = Arc::new(Bank::new_for_tests(&genesis_config_info.genesis_config));
503
504        let minimizer = SnapshotMinimizer {
505            bank: &bank,
506            starting_slot: 0,
507            ending_slot: 0,
508            minimized_account_set: DashSet::new(),
509        };
510        minimizer.get_vote_accounts();
511
512        assert!(minimizer
513            .minimized_account_set
514            .contains(&genesis_config_info.voting_keypair.pubkey()));
515        assert!(minimizer
516            .minimized_account_set
517            .contains(&genesis_config_info.validator_pubkey));
518    }
519
520    #[test]
521    fn test_minimization_get_stake_accounts() {
522        solana_logger::setup();
523
524        let bootstrap_validator_pubkey = solana_sdk::pubkey::new_rand();
525        let bootstrap_validator_stake_lamports = 30;
526        let genesis_config_info = create_genesis_config_with_leader(
527            10,
528            &bootstrap_validator_pubkey,
529            bootstrap_validator_stake_lamports,
530        );
531
532        let bank = Arc::new(Bank::new_for_tests(&genesis_config_info.genesis_config));
533        let minimizer = SnapshotMinimizer {
534            bank: &bank,
535            starting_slot: 0,
536            ending_slot: 0,
537            minimized_account_set: DashSet::new(),
538        };
539        minimizer.get_stake_accounts();
540
541        let mut expected_stake_accounts: Vec<_> = genesis_config_info
542            .genesis_config
543            .accounts
544            .iter()
545            .filter_map(|(pubkey, account)| {
546                stake::program::check_id(account.owner()).then(|| *pubkey)
547            })
548            .collect();
549        expected_stake_accounts.push(bootstrap_validator_pubkey);
550
551        assert_eq!(
552            minimizer.minimized_account_set.len(),
553            expected_stake_accounts.len()
554        );
555        for stake_pubkey in expected_stake_accounts {
556            assert!(minimizer.minimized_account_set.contains(&stake_pubkey));
557        }
558    }
559
560    #[test]
561    fn test_minimization_get_owner_accounts() {
562        solana_logger::setup();
563
564        let (genesis_config, _) = create_genesis_config(1_000_000);
565        let bank = Arc::new(Bank::new_for_tests(&genesis_config));
566
567        let pubkey = solana_sdk::pubkey::new_rand();
568        let owner_pubkey = solana_sdk::pubkey::new_rand();
569        bank.store_account(&pubkey, &AccountSharedData::new(1, 0, &owner_pubkey));
570
571        let owner_accounts = DashSet::new();
572        owner_accounts.insert(pubkey);
573        let minimizer = SnapshotMinimizer {
574            bank: &bank,
575            starting_slot: 0,
576            ending_slot: 0,
577            minimized_account_set: owner_accounts,
578        };
579
580        minimizer.get_owner_accounts();
581        assert!(minimizer.minimized_account_set.contains(&pubkey));
582        assert!(minimizer.minimized_account_set.contains(&owner_pubkey));
583    }
584
585    #[test]
586    fn test_minimization_add_programdata_accounts() {
587        solana_logger::setup();
588
589        let (genesis_config, _) = create_genesis_config(1_000_000);
590        let bank = Arc::new(Bank::new_for_tests(&genesis_config));
591
592        let non_program_id = solana_sdk::pubkey::new_rand();
593        let program_id = solana_sdk::pubkey::new_rand();
594        let programdata_address = solana_sdk::pubkey::new_rand();
595
596        let program = UpgradeableLoaderState::Program {
597            programdata_address,
598        };
599
600        let non_program_acount = AccountSharedData::new(1, 0, &non_program_id);
601        let mut program_account =
602            AccountSharedData::new_data(40, &program, &bpf_loader_upgradeable::id()).unwrap();
603        program_account.set_executable(true);
604
605        bank.store_account(&non_program_id, &non_program_acount);
606        bank.store_account(&program_id, &program_account);
607
608        // Non-program account does not add any additional keys
609        let programdata_accounts = DashSet::new();
610        programdata_accounts.insert(non_program_id);
611        let minimizer = SnapshotMinimizer {
612            bank: &bank,
613            starting_slot: 0,
614            ending_slot: 0,
615            minimized_account_set: programdata_accounts,
616        };
617        minimizer.get_programdata_accounts();
618        assert_eq!(minimizer.minimized_account_set.len(), 1);
619        assert!(minimizer.minimized_account_set.contains(&non_program_id));
620
621        // Programdata account adds the programdata address to the set
622        minimizer.minimized_account_set.insert(program_id);
623        minimizer.get_programdata_accounts();
624        assert_eq!(minimizer.minimized_account_set.len(), 3);
625        assert!(minimizer.minimized_account_set.contains(&non_program_id));
626        assert!(minimizer.minimized_account_set.contains(&program_id));
627        assert!(minimizer
628            .minimized_account_set
629            .contains(&programdata_address));
630    }
631
632    #[test]
633    fn test_minimize_accounts_db() {
634        solana_logger::setup();
635
636        let (genesis_config, _) = create_genesis_config(1_000_000);
637        let bank = Arc::new(Bank::new_for_tests(&genesis_config));
638        let accounts = &bank.accounts().accounts_db;
639
640        let num_slots = 5;
641        let num_accounts_per_slot = 300;
642
643        let mut current_slot = 0;
644        let minimized_account_set = DashSet::new();
645        for _ in 0..num_slots {
646            let pubkeys: Vec<_> = (0..num_accounts_per_slot)
647                .map(|_| solana_sdk::pubkey::new_rand())
648                .collect();
649
650            let some_lamport = 223;
651            let no_data = 0;
652            let owner = *AccountSharedData::default().owner();
653            let account = AccountSharedData::new(some_lamport, no_data, &owner);
654
655            current_slot += 1;
656
657            for (index, pubkey) in pubkeys.iter().enumerate() {
658                accounts.store_uncached(current_slot, &[(pubkey, &account)]);
659
660                if current_slot % 2 == 0 && index % 100 == 0 {
661                    minimized_account_set.insert(*pubkey);
662                }
663            }
664            accounts.get_accounts_delta_hash(current_slot);
665            accounts.add_root(current_slot);
666        }
667
668        assert_eq!(minimized_account_set.len(), 6);
669        let minimizer = SnapshotMinimizer {
670            bank: &bank,
671            starting_slot: current_slot,
672            ending_slot: current_slot,
673            minimized_account_set,
674        };
675        minimizer.minimize_accounts_db();
676
677        let snapshot_storages = accounts.get_snapshot_storages(current_slot, None, None).0;
678        assert_eq!(snapshot_storages.len(), 3);
679
680        let mut account_count = 0;
681        snapshot_storages.into_iter().for_each(|storages| {
682            storages.into_iter().for_each(|storage| {
683                account_count += storage.accounts.account_iter().count();
684            });
685        });
686
687        assert_eq!(
688            account_count,
689            minimizer.minimized_account_set.len() + num_accounts_per_slot
690        ); // snapshot slot is untouched, so still has all 300 accounts
691    }
692}