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