1use {
4 crate::{bank::Bank, static_ids},
5 agave_reserved_account_keys::ReservedAccountKeys,
6 dashmap::DashSet,
7 log::info,
8 rayon::{
9 iter::{IntoParallelIterator, IntoParallelRefIterator, ParallelIterator},
10 prelude::ParallelSlice,
11 },
12 solana_account::{state_traits::StateMut, ReadableAccount},
13 solana_accounts_db::{
14 accounts_db::{
15 stats::PurgeStats, AccountStorageEntry, AccountsDb, GetUniqueAccountsResult,
16 },
17 accounts_partition,
18 storable_accounts::StorableAccountsBySlot,
19 },
20 solana_clock::Slot,
21 solana_loader_v3_interface::state::UpgradeableLoaderState,
22 solana_measure::measure_time,
23 solana_pubkey::Pubkey,
24 solana_sdk_ids::bpf_loader_upgradeable,
25 std::{
26 collections::HashSet,
27 sync::{
28 atomic::{AtomicUsize, Ordering},
29 Arc, Mutex,
30 },
31 },
32};
33
34pub struct SnapshotMinimizer<'a> {
36 bank: &'a Bank,
37 starting_slot: Slot,
38 ending_slot: Slot,
39 minimized_account_set: DashSet<Pubkey>,
40}
41
42impl<'a> SnapshotMinimizer<'a> {
43 pub fn minimize(
50 bank: &'a Bank,
51 starting_slot: Slot,
52 ending_slot: Slot,
53 transaction_account_set: DashSet<Pubkey>,
54 ) {
55 let minimizer = SnapshotMinimizer {
56 bank,
57 starting_slot,
58 ending_slot,
59 minimized_account_set: transaction_account_set,
60 };
61
62 minimizer.add_accounts(Self::get_active_bank_features, "active bank features");
63 minimizer.add_accounts(Self::get_inactive_bank_features, "inactive bank features");
64 minimizer.add_accounts(Self::get_static_runtime_accounts, "static runtime accounts");
65 minimizer.add_accounts(Self::get_reserved_accounts, "reserved accounts");
66
67 minimizer.add_accounts(
68 Self::get_rent_collection_accounts,
69 "rent collection accounts",
70 );
71 minimizer.add_accounts(Self::get_vote_accounts, "vote accounts");
72 minimizer.add_accounts(Self::get_stake_accounts, "stake accounts");
73 minimizer.add_accounts(Self::get_owner_accounts, "owner accounts");
74 minimizer.add_accounts(Self::get_programdata_accounts, "programdata accounts");
75
76 minimizer.minimize_accounts_db();
77
78 minimizer.bank.force_flush_accounts_cache();
80 minimizer.bank.set_capitalization();
81 }
82
83 fn add_accounts<F>(&self, add_accounts_fn: F, name: &'static str)
85 where
86 F: Fn(&SnapshotMinimizer<'a>),
87 {
88 let initial_accounts_len = self.minimized_account_set.len();
89 let (_, measure) = measure_time!(add_accounts_fn(self), name);
90 let total_accounts_len = self.minimized_account_set.len();
91 let added_accounts = total_accounts_len - initial_accounts_len;
92
93 info!(
94 "Added {added_accounts} {name} for total of {total_accounts_len} accounts. get {measure}"
95 );
96 }
97
98 fn get_active_bank_features(&self) {
100 self.bank
101 .feature_set
102 .active()
103 .iter()
104 .for_each(|(pubkey, _)| {
105 self.minimized_account_set.insert(*pubkey);
106 });
107 }
108
109 fn get_inactive_bank_features(&self) {
111 self.bank.feature_set.inactive().iter().for_each(|pubkey| {
112 self.minimized_account_set.insert(*pubkey);
113 });
114 }
115
116 fn get_static_runtime_accounts(&self) {
118 static_ids::STATIC_IDS.iter().for_each(|pubkey| {
119 self.minimized_account_set.insert(*pubkey);
120 });
121 }
122
123 fn get_reserved_accounts(&self) {
125 ReservedAccountKeys::all_keys_iter().for_each(|pubkey| {
126 self.minimized_account_set.insert(*pubkey);
127 })
128 }
129
130 fn get_rent_collection_accounts(&self) {
134 let partitions = if !self.bank.use_fixed_collection_cycle() {
135 self.bank
136 .variable_cycle_partitions_between_slots(self.starting_slot, self.ending_slot)
137 } else {
138 self.bank
139 .fixed_cycle_partitions_between_slots(self.starting_slot, self.ending_slot)
140 };
141
142 partitions.into_iter().for_each(|partition| {
143 let subrange = accounts_partition::pubkey_range_from_partition(partition);
144 self.bank
148 .accounts()
149 .load_to_collect_rent_eagerly(&self.bank.ancestors, subrange)
150 .into_par_iter()
151 .for_each(|(pubkey, ..)| {
152 self.minimized_account_set.insert(pubkey);
153 })
154 });
155 }
156
157 fn get_vote_accounts(&self) {
160 self.bank
161 .vote_accounts()
162 .par_iter()
163 .for_each(|(pubkey, (_stake, vote_account))| {
164 self.minimized_account_set.insert(*pubkey);
165 self.minimized_account_set
166 .insert(*vote_account.node_pubkey());
167 });
168 }
169
170 fn get_stake_accounts(&self) {
173 self.bank.get_stake_accounts(&self.minimized_account_set);
174 }
175
176 fn get_owner_accounts(&self) {
179 let owner_accounts: HashSet<_> = self
180 .minimized_account_set
181 .par_iter()
182 .filter_map(|pubkey| self.bank.get_account(&pubkey))
183 .map(|account| *account.owner())
184 .collect();
185 owner_accounts.into_par_iter().for_each(|pubkey| {
186 self.minimized_account_set.insert(pubkey);
187 });
188 }
189
190 fn get_programdata_accounts(&self) {
193 let programdata_accounts: HashSet<_> = self
194 .minimized_account_set
195 .par_iter()
196 .filter_map(|pubkey| self.bank.get_account(&pubkey))
197 .filter(|account| account.executable())
198 .filter(|account| bpf_loader_upgradeable::check_id(account.owner()))
199 .filter_map(|account| {
200 if let Ok(UpgradeableLoaderState::Program {
201 programdata_address,
202 }) = account.state()
203 {
204 Some(programdata_address)
205 } else {
206 None
207 }
208 })
209 .collect();
210 programdata_accounts.into_par_iter().for_each(|pubkey| {
211 self.minimized_account_set.insert(pubkey);
212 });
213 }
214
215 fn minimize_accounts_db(&self) {
217 let (minimized_slot_set, minimized_slot_set_measure) =
218 measure_time!(self.get_minimized_slot_set(), "generate minimized slot set");
219 info!("{minimized_slot_set_measure}");
220
221 let ((dead_slots, dead_storages), process_snapshot_storages_measure) = measure_time!(
222 self.process_snapshot_storages(minimized_slot_set),
223 "process snapshot storages"
224 );
225 info!("{process_snapshot_storages_measure}");
226
227 self.accounts_db()
229 .log_dead_slots
230 .store(false, Ordering::Relaxed);
231
232 let (_, purge_dead_slots_measure) =
233 measure_time!(self.purge_dead_slots(dead_slots), "purge dead slots");
234 info!("{purge_dead_slots_measure}");
235
236 let (_, drop_storages_measure) = measure_time!(drop(dead_storages), "drop storages");
237 info!("{drop_storages_measure}");
238
239 self.accounts_db()
241 .log_dead_slots
242 .store(true, Ordering::Relaxed);
243 }
244
245 fn get_minimized_slot_set(&self) -> DashSet<Slot> {
247 let minimized_slot_set = DashSet::new();
248 self.minimized_account_set.par_iter().for_each(|pubkey| {
249 self.accounts_db()
250 .accounts_index
251 .get_and_then(&pubkey, |entry| {
252 if let Some(entry) = entry {
253 let max_slot = entry
254 .slot_list
255 .read()
256 .unwrap()
257 .iter()
258 .map(|(slot, _)| *slot)
259 .max();
260 if let Some(max_slot) = max_slot {
261 minimized_slot_set.insert(max_slot);
262 }
263 }
264 (false, ())
265 });
266 });
267 minimized_slot_set
268 }
269
270 fn process_snapshot_storages(
272 &self,
273 minimized_slot_set: DashSet<Slot>,
274 ) -> (Vec<Slot>, Vec<Arc<AccountStorageEntry>>) {
275 let snapshot_storages = self.accounts_db().get_storages(..=self.starting_slot).0;
276
277 let dead_slots = Mutex::new(Vec::new());
278 let dead_storages = Mutex::new(Vec::new());
279
280 snapshot_storages.into_par_iter().for_each(|storage| {
281 let slot = storage.slot();
282 if slot != self.starting_slot {
283 if minimized_slot_set.contains(&slot) {
284 self.filter_storage(&storage, &dead_storages);
285 } else {
286 dead_slots.lock().unwrap().push(slot);
287 }
288 }
289 });
290
291 let dead_slots = dead_slots.into_inner().unwrap();
292 let dead_storages = dead_storages.into_inner().unwrap();
293 (dead_slots, dead_storages)
294 }
295
296 fn filter_storage(
298 &self,
299 storage: &Arc<AccountStorageEntry>,
300 dead_storages: &Mutex<Vec<Arc<AccountStorageEntry>>>,
301 ) {
302 let slot = storage.slot();
303 let GetUniqueAccountsResult {
304 stored_accounts, ..
305 } = self.accounts_db().get_unique_accounts_from_storage(storage);
306
307 let keep_accounts_collect = Mutex::new(Vec::with_capacity(stored_accounts.len()));
308 let purge_pubkeys_collect = Mutex::new(Vec::with_capacity(stored_accounts.len()));
309 let total_bytes_collect = AtomicUsize::new(0);
310 const CHUNK_SIZE: usize = 50;
311 stored_accounts.par_chunks(CHUNK_SIZE).for_each(|chunk| {
312 let mut chunk_bytes = 0;
313 let mut keep_accounts = Vec::with_capacity(CHUNK_SIZE);
314 let mut purge_pubkeys = Vec::with_capacity(CHUNK_SIZE);
315 chunk.iter().for_each(|account| {
316 if self.minimized_account_set.contains(account.pubkey()) {
317 chunk_bytes += account.stored_size();
318 keep_accounts.push(account);
319 } else if self.accounts_db().accounts_index.contains(account.pubkey()) {
320 purge_pubkeys.push(account.pubkey());
321 }
322 });
323
324 keep_accounts_collect
325 .lock()
326 .unwrap()
327 .append(&mut keep_accounts);
328 purge_pubkeys_collect
329 .lock()
330 .unwrap()
331 .append(&mut purge_pubkeys);
332 total_bytes_collect.fetch_add(chunk_bytes, Ordering::Relaxed);
333 });
334
335 let keep_accounts = keep_accounts_collect.into_inner().unwrap();
336 let remove_pubkeys = purge_pubkeys_collect.into_inner().unwrap();
337 let total_bytes = total_bytes_collect.load(Ordering::Relaxed);
338
339 let purge_pubkeys: Vec<_> = remove_pubkeys
340 .into_iter()
341 .map(|pubkey| (*pubkey, slot))
342 .collect();
343 let _ = self.accounts_db().purge_keys_exact(purge_pubkeys.iter());
344
345 let mut shrink_in_progress = None;
346 if total_bytes > 0 {
347 shrink_in_progress = Some(
348 self.accounts_db()
349 .get_store_for_shrink(slot, total_bytes as u64),
350 );
351 let new_storage = shrink_in_progress.as_ref().unwrap().new_storage();
352
353 let accounts = [(slot, &keep_accounts[..])];
354 let storable_accounts =
355 StorableAccountsBySlot::new(slot, &accounts, self.accounts_db());
356
357 self.accounts_db()
358 .store_accounts_frozen(storable_accounts, new_storage);
359
360 new_storage.flush().unwrap();
361 }
362
363 let mut dead_storages_this_time = self.accounts_db().mark_dirty_dead_stores(
364 slot,
365 true, shrink_in_progress,
367 false,
368 );
369 dead_storages
370 .lock()
371 .unwrap()
372 .append(&mut dead_storages_this_time);
373 }
374
375 fn purge_dead_slots(&self, dead_slots: Vec<Slot>) {
377 let stats = PurgeStats::default();
378 self.accounts_db()
379 .purge_slots_from_cache_and_store(dead_slots.iter(), &stats, false);
380 }
381
382 fn accounts_db(&self) -> &AccountsDb {
384 &self.bank.rc.accounts.accounts_db
385 }
386}
387
388#[cfg(test)]
389mod tests {
390 use {
391 crate::{
392 bank::Bank, genesis_utils::create_genesis_config_with_leader,
393 snapshot_minimizer::SnapshotMinimizer,
394 },
395 dashmap::DashSet,
396 solana_account::{AccountSharedData, ReadableAccount, WritableAccount},
397 solana_genesis_config::{create_genesis_config, GenesisConfig},
398 solana_loader_v3_interface::state::UpgradeableLoaderState,
399 solana_pubkey::Pubkey,
400 solana_sdk_ids::bpf_loader_upgradeable,
401 solana_signer::Signer,
402 solana_stake_interface as stake,
403 std::sync::Arc,
404 };
405
406 #[test]
407 fn test_get_rent_collection_accounts() {
408 solana_logger::setup();
409
410 let genesis_config = GenesisConfig::default();
411 let bank = Arc::new(Bank::new_for_tests(&genesis_config));
412
413 {
416 let minimizer = SnapshotMinimizer {
417 bank: &bank,
418 starting_slot: 100_000,
419 ending_slot: 110_000,
420 minimized_account_set: DashSet::new(),
421 };
422 minimizer.get_rent_collection_accounts();
423 assert!(
424 minimizer.minimized_account_set.is_empty(),
425 "rent collection accounts should be empty: len={}",
426 minimizer.minimized_account_set.len()
427 );
428 }
429
430 let pubkey: Pubkey = "ChWNbfHUHLvFY3uhXj6kQhJ7a9iZB4ykh34WRGS5w9ND"
432 .parse()
433 .unwrap();
434 bank.store_account(&pubkey, &AccountSharedData::new(1, 0, &Pubkey::default()));
435
436 {
437 let minimizer = SnapshotMinimizer {
438 bank: &bank,
439 starting_slot: 100_000,
440 ending_slot: 110_000,
441 minimized_account_set: DashSet::new(),
442 };
443 minimizer.get_rent_collection_accounts();
444 assert_eq!(
445 1,
446 minimizer.minimized_account_set.len(),
447 "rent collection accounts should have len=1: len={}",
448 minimizer.minimized_account_set.len()
449 );
450 assert!(minimizer.minimized_account_set.contains(&pubkey));
451 }
452
453 {
456 let minimizer = SnapshotMinimizer {
457 bank: &bank,
458 starting_slot: 110_001,
459 ending_slot: 120_000,
460 minimized_account_set: DashSet::new(),
461 };
462 assert!(
463 minimizer.minimized_account_set.is_empty(),
464 "rent collection accounts should be empty: len={}",
465 minimizer.minimized_account_set.len()
466 );
467 }
468 }
469
470 #[test]
471 fn test_minimization_get_vote_accounts() {
472 solana_logger::setup();
473
474 let bootstrap_validator_pubkey = solana_pubkey::new_rand();
475 let bootstrap_validator_stake_lamports = 30;
476 let genesis_config_info = create_genesis_config_with_leader(
477 10,
478 &bootstrap_validator_pubkey,
479 bootstrap_validator_stake_lamports,
480 );
481
482 let bank = Arc::new(Bank::new_for_tests(&genesis_config_info.genesis_config));
483
484 let minimizer = SnapshotMinimizer {
485 bank: &bank,
486 starting_slot: 0,
487 ending_slot: 0,
488 minimized_account_set: DashSet::new(),
489 };
490 minimizer.get_vote_accounts();
491
492 assert!(minimizer
493 .minimized_account_set
494 .contains(&genesis_config_info.voting_keypair.pubkey()));
495 assert!(minimizer
496 .minimized_account_set
497 .contains(&genesis_config_info.validator_pubkey));
498 }
499
500 #[test]
501 fn test_minimization_get_stake_accounts() {
502 solana_logger::setup();
503
504 let bootstrap_validator_pubkey = solana_pubkey::new_rand();
505 let bootstrap_validator_stake_lamports = 30;
506 let genesis_config_info = create_genesis_config_with_leader(
507 10,
508 &bootstrap_validator_pubkey,
509 bootstrap_validator_stake_lamports,
510 );
511
512 let bank = Arc::new(Bank::new_for_tests(&genesis_config_info.genesis_config));
513 let minimizer = SnapshotMinimizer {
514 bank: &bank,
515 starting_slot: 0,
516 ending_slot: 0,
517 minimized_account_set: DashSet::new(),
518 };
519 minimizer.get_stake_accounts();
520
521 let mut expected_stake_accounts: Vec<_> = genesis_config_info
522 .genesis_config
523 .accounts
524 .iter()
525 .filter_map(|(pubkey, account)| {
526 stake::program::check_id(account.owner()).then_some(*pubkey)
527 })
528 .collect();
529 expected_stake_accounts.push(bootstrap_validator_pubkey);
530
531 assert_eq!(
532 minimizer.minimized_account_set.len(),
533 expected_stake_accounts.len()
534 );
535 for stake_pubkey in expected_stake_accounts {
536 assert!(minimizer.minimized_account_set.contains(&stake_pubkey));
537 }
538 }
539
540 #[test]
541 fn test_minimization_get_owner_accounts() {
542 solana_logger::setup();
543
544 let (genesis_config, _) = create_genesis_config(1_000_000);
545 let bank = Arc::new(Bank::new_for_tests(&genesis_config));
546
547 let pubkey = solana_pubkey::new_rand();
548 let owner_pubkey = solana_pubkey::new_rand();
549 bank.store_account(&pubkey, &AccountSharedData::new(1, 0, &owner_pubkey));
550
551 let owner_accounts = DashSet::new();
552 owner_accounts.insert(pubkey);
553 let minimizer = SnapshotMinimizer {
554 bank: &bank,
555 starting_slot: 0,
556 ending_slot: 0,
557 minimized_account_set: owner_accounts,
558 };
559
560 minimizer.get_owner_accounts();
561 assert!(minimizer.minimized_account_set.contains(&pubkey));
562 assert!(minimizer.minimized_account_set.contains(&owner_pubkey));
563 }
564
565 #[test]
566 fn test_minimization_add_programdata_accounts() {
567 solana_logger::setup();
568
569 let (genesis_config, _) = create_genesis_config(1_000_000);
570 let bank = Arc::new(Bank::new_for_tests(&genesis_config));
571
572 let non_program_id = solana_pubkey::new_rand();
573 let program_id = solana_pubkey::new_rand();
574 let programdata_address = solana_pubkey::new_rand();
575
576 let program = UpgradeableLoaderState::Program {
577 programdata_address,
578 };
579
580 let non_program_acount = AccountSharedData::new(1, 0, &non_program_id);
581 let mut program_account =
582 AccountSharedData::new_data(40, &program, &bpf_loader_upgradeable::id()).unwrap();
583 program_account.set_executable(true);
584
585 bank.store_account(&non_program_id, &non_program_acount);
586 bank.store_account(&program_id, &program_account);
587
588 let programdata_accounts = DashSet::new();
590 programdata_accounts.insert(non_program_id);
591 let minimizer = SnapshotMinimizer {
592 bank: &bank,
593 starting_slot: 0,
594 ending_slot: 0,
595 minimized_account_set: programdata_accounts,
596 };
597 minimizer.get_programdata_accounts();
598 assert_eq!(minimizer.minimized_account_set.len(), 1);
599 assert!(minimizer.minimized_account_set.contains(&non_program_id));
600
601 minimizer.minimized_account_set.insert(program_id);
603 minimizer.get_programdata_accounts();
604 assert_eq!(minimizer.minimized_account_set.len(), 3);
605 assert!(minimizer.minimized_account_set.contains(&non_program_id));
606 assert!(minimizer.minimized_account_set.contains(&program_id));
607 assert!(minimizer
608 .minimized_account_set
609 .contains(&programdata_address));
610 }
611
612 #[test]
613 fn test_minimize_accounts_db() {
614 solana_logger::setup();
615
616 let (genesis_config, _) = create_genesis_config(1_000_000);
617 let bank = Arc::new(Bank::new_for_tests(&genesis_config));
618 let accounts = &bank.accounts().accounts_db;
619
620 let num_slots = 5;
621 let num_accounts_per_slot = 300;
622
623 let mut current_slot = 0;
624 let minimized_account_set = DashSet::new();
625 for _ in 0..num_slots {
626 let pubkeys: Vec<_> = (0..num_accounts_per_slot)
627 .map(|_| solana_pubkey::new_rand())
628 .collect();
629
630 let some_lamport = 223;
631 let no_data = 0;
632 let owner = *AccountSharedData::default().owner();
633 let account = AccountSharedData::new(some_lamport, no_data, &owner);
634
635 current_slot += 1;
636
637 for (index, pubkey) in pubkeys.iter().enumerate() {
638 accounts.store_for_tests(current_slot, &[(pubkey, &account)]);
639
640 if current_slot % 2 == 0 && index % 100 == 0 {
641 minimized_account_set.insert(*pubkey);
642 }
643 }
644 accounts.calculate_accounts_delta_hash(current_slot);
645 accounts.add_root_and_flush_write_cache(current_slot);
646 }
647
648 assert_eq!(minimized_account_set.len(), 6);
649 let minimizer = SnapshotMinimizer {
650 bank: &bank,
651 starting_slot: current_slot,
652 ending_slot: current_slot,
653 minimized_account_set,
654 };
655 minimizer.minimize_accounts_db();
656
657 let snapshot_storages = accounts.get_storages(..=current_slot).0;
658 assert_eq!(snapshot_storages.len(), 3);
659
660 let mut account_count = 0;
661 snapshot_storages.into_iter().for_each(|storage| {
662 storage.accounts.scan_pubkeys(|_| {
663 account_count += 1;
664 });
665 });
666
667 assert_eq!(
668 account_count,
669 minimizer.minimized_account_set.len() + num_accounts_per_slot
670 ); }
672}