1use {
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
35pub 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 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 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!(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_builtins(&self) {
116 builtins::get_pubkeys().iter().for_each(|pubkey| {
117 self.minimized_account_set.insert(*pubkey);
118 });
119 }
120
121 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 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 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 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 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 fn get_stake_accounts(&self) {
179 self.bank.get_stake_accounts(&self.minimized_account_set);
180 }
181
182 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 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 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 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 self.accounts_db()
250 .log_dead_slots
251 .store(true, Ordering::Relaxed);
252 }
253
254 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 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 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 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 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 {
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 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 {
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 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 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 ); }
692}