1use {
2 super::Bank,
3 crossbeam_utils::CachePadded,
4 rayon::{
5 ThreadPool, ThreadPoolBuilder,
6 iter::{IntoParallelIterator, ParallelIterator},
7 },
8 solana_account::{AccountSharedData, ReadableAccount},
9 solana_accounts_db::{accounts_db::AccountsDb, storable_accounts::StorableAccounts},
10 solana_lattice_hash::lt_hash::LtHash,
11 solana_pubkey::Pubkey,
12 std::{
13 array, hint,
14 mem::size_of,
15 sync::{
16 Arc, LazyLock, Mutex,
17 atomic::{AtomicU64, AtomicUsize, Ordering},
18 },
19 time::Instant,
20 },
21};
22
23const NUM_ACCOUNTS_HASHER_THREADS: usize = 4;
25
26const MAX_BYTES_SEEN_ACCOUNTS_FREELIST: usize = 10_000_000;
28
29impl Bank {
30 pub fn enqueue_on_chain_accounts_lt_hash_updates<'a>(
39 &self,
40 accounts: &impl StorableAccounts<'a>,
41 ) {
42 if accounts.is_empty() {
43 return;
44 }
45
46 let seen_accounts_freelist = seen_accounts_freelist();
47 let mut seen_accounts = seen_accounts_freelist.try_pop().unwrap_or_default();
48 let async_progress = &self.accounts_lt_hash_async_progress;
49 let thread_pool = accounts_hasher_thread_pool();
50
51 for index in (0..accounts.len()).rev() {
53 let address = accounts.pubkey(index);
54 if !seen_accounts.insert(*address) {
55 continue;
57 }
58 let prev_account = self
59 .rc
60 .accounts
61 .load_with_fixed_root_do_not_populate_read_cache(&self.ancestors, address)
62 .map(|(account, _slot)| account);
63 let curr_account = accounts.account(index, |account| {
64 (account.lamports() != 0).then(|| account.take_account())
65 });
66 if prev_account.is_none() && curr_account.is_none() {
67 } else {
69 async_progress.spawn(
71 thread_pool,
72 AccountsLtHashUpdate {
73 address: *address,
74 prev_account,
75 curr_account,
76 },
77 );
78 }
79 }
80
81 seen_accounts_freelist.try_push(seen_accounts);
83 }
84
85 pub fn enqueue_off_chain_accounts_lt_hash_updates<'a>(
96 &self,
97 accounts: &impl StorableAccounts<'a>,
98 thread_pool_for_loading_accounts: Option<&ThreadPool>,
99 ) {
100 if cfg!(debug_assertions) {
101 use ahash::HashSetExt as _;
103 let mut seen_accounts = ahash::HashSet::with_capacity(accounts.len());
104 let mut duplicate_pubkeys = ahash::HashSet::with_capacity(0); for index in 0..accounts.len() {
106 let pubkey = accounts.pubkey(index);
107 if !seen_accounts.insert(pubkey) {
108 duplicate_pubkeys.insert(pubkey);
110 }
111 }
112 if !duplicate_pubkeys.is_empty() {
113 let mut duplicate_accounts = ahash::HashMap::<_, Vec<_>>::default();
114 for duplicate_pubkey in duplicate_pubkeys {
115 for index in 0..accounts.len() {
116 let pubkey = accounts.pubkey(index);
117 if pubkey == duplicate_pubkey {
118 duplicate_accounts
119 .entry(pubkey)
120 .or_default()
121 .push(accounts.account(index, |account| account.take_account()));
122 }
123 }
124 }
125 panic!("duplicate accounts were enqueued for hashing: {duplicate_accounts:?}");
126 }
127 }
128
129 let async_progress = &self.accounts_lt_hash_async_progress;
130 let thread_pool_for_hashing_accounts = accounts_hasher_thread_pool();
131
132 let load_then_enqueue = |index| {
135 let address = accounts.pubkey(index);
136 let prev_account = self
137 .rc
138 .accounts
139 .load_with_fixed_root_do_not_populate_read_cache(&self.ancestors, address)
140 .map(|(account, _slot)| account);
141 let curr_account = accounts.account(index, |account| {
142 (account.lamports() != 0).then(|| account.take_account())
143 });
144 if prev_account.is_none() && curr_account.is_none() {
145 } else {
147 async_progress.spawn(
149 thread_pool_for_hashing_accounts,
150 AccountsLtHashUpdate {
151 address: *address,
152 prev_account,
153 curr_account,
154 },
155 );
156 }
157 };
158
159 if let Some(thread_pool_for_loading_accounts) = thread_pool_for_loading_accounts {
160 thread_pool_for_loading_accounts.install(|| {
163 (0..accounts.len())
164 .into_par_iter()
165 .for_each(load_then_enqueue);
166 });
167 } else {
168 (0..accounts.len()).for_each(load_then_enqueue);
169 }
170 }
171
172 pub fn finish_accounts_lt_hash_updates(&self) {
182 let timer = Instant::now();
183 let num_jobs_total = {
184 let mut accounts_lt_hash = self.accounts_lt_hash.lock().unwrap();
185 self.accounts_lt_hash_async_progress
186 .finish(&mut accounts_lt_hash.0)
187 };
188 let finish_time = timer.elapsed();
189
190 let seen_accounts_freelist_stats = seen_accounts_freelist().stats();
191 datapoint_info!(
192 "bank-accounts_lt_hash",
193 ("slot", self.slot(), i64),
194 ("num_jobs", num_jobs_total, i64),
195 ("finish_us", finish_time.as_micros(), i64),
196 (
197 "seen_accounts_freelist_num_containers",
198 seen_accounts_freelist_stats.num_containers,
199 i64
200 ),
201 (
202 "seen_accounts_freelist_capacity_elems",
203 seen_accounts_freelist_stats.capacity_elems,
204 i64
205 ),
206 (
207 "seen_accounts_freelist_capacity_bytes",
208 seen_accounts_freelist_stats.capacity_bytes,
209 i64
210 ),
211 );
212 }
213}
214
215pub struct AccountsLtHashAsyncProgress {
217 accumulators: Arc<[Mutex<CachePadded<LtHash>>; NUM_ACCOUNTS_HASHER_THREADS]>,
244 num_jobs_pending: Arc<AtomicUsize>,
245 num_jobs_total: AtomicU64,
246}
247
248impl AccountsLtHashAsyncProgress {
249 pub fn new() -> Self {
251 Self {
252 accumulators: Arc::new(array::from_fn(|_| {
253 Mutex::new(CachePadded::new(LtHash::identity()))
254 })),
255 num_jobs_pending: Arc::new(AtomicUsize::new(0)),
256 num_jobs_total: AtomicU64::new(0),
257 }
258 }
259
260 fn spawn(&self, thread_pool: &'static ThreadPool, update: AccountsLtHashUpdate) {
262 self.num_jobs_pending.fetch_add(1, Ordering::Relaxed);
263 self.num_jobs_total.fetch_add(1, Ordering::Relaxed);
264 thread_pool.spawn({
265 let accumulators = Arc::clone(&self.accumulators);
266 let num_jobs_pending = Arc::clone(&self.num_jobs_pending);
267 move || {
268 let worker_index = thread_pool.current_thread_index().unwrap();
270
271 debug_assert!(worker_index < accumulators.len());
274 let accumulator = unsafe { accumulators.get_unchecked(worker_index) };
275
276 Self::process(&mut accumulator.lock().unwrap(), update);
277
278 num_jobs_pending.fetch_sub(1, Ordering::Relaxed);
282 }
283 });
284 }
285
286 fn finish(&self, lt_hash: &mut LtHash) -> u64 {
293 while self.num_jobs_pending.load(Ordering::Relaxed) > 0 {
294 hint::spin_loop();
296 }
297
298 for thread_accumulator in self.accumulators.iter() {
299 lt_hash.mix_in(&thread_accumulator.lock().unwrap());
300 }
301 self.num_jobs_total.load(Ordering::Relaxed)
302 }
303
304 fn process(accum_lt_hash: &mut LtHash, update: AccountsLtHashUpdate) {
309 let AccountsLtHashUpdate {
310 address,
311 prev_account,
312 curr_account,
313 } = update;
314 if let Some(prev_account) = prev_account {
315 let prev_lt_hash = AccountsDb::lt_hash_account(&prev_account, &address);
316 accum_lt_hash.mix_out(&prev_lt_hash.0);
317 }
318 if let Some(curr_account) = curr_account {
319 let curr_lt_hash = AccountsDb::lt_hash_account(&curr_account, &address);
320 accum_lt_hash.mix_in(&curr_lt_hash.0);
321 }
322 }
323}
324
325#[derive(Debug)]
327struct AccountsLtHashUpdate {
328 address: Pubkey,
329 prev_account: Option<AccountSharedData>,
330 curr_account: Option<AccountSharedData>,
331}
332
333fn seen_accounts_freelist() -> &'static HashSetFreelist<Pubkey> {
335 const MAX_CONTAINERS: usize = 50;
338 static FREELIST: LazyLock<HashSetFreelist<Pubkey>> = LazyLock::new(|| {
339 HashSetFreelist::new(MAX_CONTAINERS, Some(MAX_BYTES_SEEN_ACCOUNTS_FREELIST))
340 });
341 &FREELIST
342}
343
344#[derive(Debug)]
346struct HashSetFreelist<T> {
347 max_containers: usize,
349
350 max_capacity: Option<usize>,
352
353 inner: Mutex<HashSetFreelistInner<T>>,
354}
355
356impl<T> HashSetFreelist<T> {
357 fn new(max_containers: usize, max_bytes: Option<usize>) -> Self {
367 let max_capacity = max_bytes.map(|max_bytes| max_bytes / size_of::<T>());
368 Self {
369 max_containers,
370 max_capacity,
371 inner: Mutex::new(HashSetFreelistInner {
372 list: Vec::with_capacity(max_containers),
373 total_capacity: 0,
374 }),
375 }
376 }
377
378 fn try_push(&self, mut container: ahash::HashSet<T>) {
380 let capacity = container.capacity();
387 if capacity == 0 {
388 return;
389 }
390
391 container.clear();
393
394 let mut inner = self.inner.lock().unwrap();
395
396 if inner.list.len() >= self.max_containers {
397 return;
399 }
400
401 let Some(new_total_capacity) = inner.total_capacity.checked_add(capacity) else {
402 return;
404 };
405
406 let max_capacity = self.max_capacity.unwrap_or(usize::MAX);
407 if new_total_capacity > max_capacity {
408 return;
410 }
411
412 inner.list.push(container);
413 inner.total_capacity = new_total_capacity;
414 }
415
416 fn try_pop(&self) -> Option<ahash::HashSet<T>> {
420 let mut inner = self.inner.lock().unwrap();
421 let container = inner.list.pop()?;
422 assert!(container.is_empty());
423 inner.total_capacity -= container.capacity();
424 Some(container)
425 }
426
427 fn stats(&self) -> FreelistStats {
429 let inner = self.inner.lock().unwrap();
430 let num_containers = inner.list.len();
431 let capacity_elems = inner.total_capacity;
432 drop(inner);
433 FreelistStats {
434 num_containers,
435 capacity_elems,
436 capacity_bytes: capacity_elems.saturating_mul(size_of::<T>()),
437 }
438 }
439}
440
441#[derive(Debug)]
443struct HashSetFreelistInner<T> {
444 list: Vec<ahash::HashSet<T>>,
446 total_capacity: usize,
448}
449
450#[derive(Debug, Eq, PartialEq)]
452struct FreelistStats {
453 num_containers: usize,
455 capacity_elems: usize,
457 capacity_bytes: usize,
459}
460
461fn accounts_hasher_thread_pool() -> &'static ThreadPool {
465 static THREAD_POOL: LazyLock<ThreadPool> = LazyLock::new(|| {
466 ThreadPoolBuilder::new()
467 .num_threads(NUM_ACCOUNTS_HASHER_THREADS)
468 .stack_size(8 * 1024 * 1024)
469 .thread_name(|i| format!("solAcctsHashr{i:02}"))
470 .build()
471 .expect("new accounts hasher rayon threadpool")
472 });
473 &THREAD_POOL
474}
475
476#[cfg(test)]
477mod tests {
478 use {
479 super::*,
480 crate::{
481 genesis_utils::create_genesis_config_with_leader_ex, runtime_config::RuntimeConfig,
482 snapshot_bank_utils, snapshot_utils,
483 },
484 agave_feature_set::FeatureSet,
485 agave_snapshots::snapshot_config::SnapshotConfig,
486 ahash::HashSetExt as _,
487 solana_accounts_db::{
488 accounts_db::{ACCOUNTS_DB_CONFIG_FOR_TESTING, AccountsDbConfig},
489 accounts_index::{ACCOUNTS_INDEX_CONFIG_FOR_TESTING, AccountsIndexConfig, IndexLimit},
490 },
491 solana_cluster_type::ClusterType,
492 solana_fee_calculator::FeeRateGovernor,
493 solana_genesis_config::{self, GenesisConfig},
494 solana_hash::Hash,
495 solana_keypair::Keypair,
496 solana_leader_schedule::SlotLeader,
497 solana_native_token::LAMPORTS_PER_SOL,
498 solana_pubkey::{self as pubkey, Pubkey},
499 solana_rent::Rent,
500 solana_signer::Signer as _,
501 std::{
502 cmp, iter,
503 str::FromStr as _,
504 sync::{Arc, Barrier},
505 thread,
506 },
507 tempfile::TempDir,
508 test_case::{test_case, test_matrix},
509 };
510
511 #[derive(Debug, Copy, Clone, Eq, PartialEq)]
513 enum Features {
514 None,
516 All,
518 }
519
520 fn genesis_config_with(features: Features) -> (GenesisConfig, Keypair) {
522 let mint_keypair = Keypair::new();
523 let mint_lamports = 123_456_789 * LAMPORTS_PER_SOL;
524 let validator_lamports = 100 * LAMPORTS_PER_SOL;
525 let validator_stake_lamports = 10 * LAMPORTS_PER_SOL;
526 let validator_pubkey = Pubkey::new_unique();
527 let vote_account_pubkey = Pubkey::new_unique();
528 let stake_account_pubkey = Pubkey::new_unique();
529 let feature_set = match features {
530 Features::None => FeatureSet::default(),
531 Features::All => FeatureSet::all_enabled(),
532 };
533
534 let config = create_genesis_config_with_leader_ex(
535 mint_lamports,
536 &mint_keypair.pubkey(),
537 &validator_pubkey,
538 &vote_account_pubkey,
539 &stake_account_pubkey,
540 None,
541 validator_stake_lamports,
542 validator_lamports,
543 FeeRateGovernor::default(),
544 Rent::default(),
545 ClusterType::Development,
546 &feature_set,
547 vec![],
548 );
549
550 (config, mint_keypair)
551 }
552
553 #[test]
554 fn test_update_accounts_lt_hash() {
555 let keypair1 = Keypair::new();
566 let keypair2 = Keypair::new();
567 let keypair3 = Keypair::new();
568 let keypair4 = Keypair::new();
569 let keypair5 = Keypair::new();
570
571 let (mut genesis_config, mint_keypair) =
572 solana_genesis_config::create_genesis_config(123_456_789 * LAMPORTS_PER_SOL);
573 genesis_config.fee_rate_governor = FeeRateGovernor::new(0, 0);
575 let (bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config);
576
577 let amount = cmp::max(
578 bank.get_minimum_balance_for_rent_exemption(0),
579 LAMPORTS_PER_SOL,
580 );
581
582 bank.register_unique_recent_blockhash_for_test();
585 bank.transfer(amount, &mint_keypair, &keypair1.pubkey())
586 .unwrap();
587 bank.transfer(amount, &mint_keypair, &keypair2.pubkey())
588 .unwrap();
589 bank.transfer(amount, &mint_keypair, &keypair5.pubkey())
590 .unwrap();
591
592 bank.freeze();
594 let prev_accounts_lt_hash = bank.accounts_lt_hash.lock().unwrap().clone();
595
596 let prev_mint = bank.get_account_with_fixed_root(&mint_keypair.pubkey());
598 let prev_account1 = bank.get_account_with_fixed_root(&keypair1.pubkey());
599 let prev_account2 = bank.get_account_with_fixed_root(&keypair2.pubkey());
600 let prev_account3 = bank.get_account_with_fixed_root(&keypair3.pubkey());
601 let prev_account4 = bank.get_account_with_fixed_root(&keypair4.pubkey());
602 let prev_account5 = bank.get_account_with_fixed_root(&keypair5.pubkey());
603
604 assert!(prev_mint.is_some());
605 assert!(prev_account1.is_some());
606 assert!(prev_account2.is_some());
607 assert!(prev_account3.is_none());
608 assert!(prev_account4.is_none());
609 assert!(prev_account5.is_some());
610
611 let sysvars = [
615 Pubkey::from_str("SysvarS1otHashes111111111111111111111111111").unwrap(),
616 Pubkey::from_str("SysvarC1ock11111111111111111111111111111111").unwrap(),
617 Pubkey::from_str("SysvarRecentB1ockHashes11111111111111111111").unwrap(),
618 Pubkey::from_str("SysvarS1otHistory11111111111111111111111111").unwrap(),
619 ];
620 let prev_sysvar_accounts: Vec<_> = sysvars
621 .iter()
622 .map(|address| bank.get_account_with_fixed_root(address))
623 .collect();
624
625 let bank = {
626 let slot = bank.slot() + 1;
627 Bank::new_from_parent_with_bank_forks(&bank_forks, bank, SlotLeader::default(), slot)
628 };
629
630 bank.register_unique_recent_blockhash_for_test();
632 bank.transfer(amount, &keypair2, &keypair1.pubkey())
633 .unwrap();
634
635 bank.register_unique_recent_blockhash_for_test();
638 bank.transfer(amount, &mint_keypair, &keypair4.pubkey())
639 .unwrap();
640 bank.register_unique_recent_blockhash_for_test();
641 bank.transfer(amount, &keypair4, &keypair3.pubkey())
642 .unwrap();
643
644 bank.store_account(&keypair5.pubkey(), prev_account5.as_ref().unwrap());
646
647 bank.freeze();
649
650 let post_accounts_lt_hash = bank.accounts_lt_hash.lock().unwrap().clone();
651 let post_mint = bank.get_account_with_fixed_root(&mint_keypair.pubkey());
652 let post_account1 = bank.get_account_with_fixed_root(&keypair1.pubkey());
653 let post_account2 = bank.get_account_with_fixed_root(&keypair2.pubkey());
654 let post_account3 = bank.get_account_with_fixed_root(&keypair3.pubkey());
655 let post_account4 = bank.get_account_with_fixed_root(&keypair4.pubkey());
656 let post_account5 = bank.get_account_with_fixed_root(&keypair5.pubkey());
657
658 assert!(post_mint.is_some());
659 assert!(post_account1.is_some());
660 assert!(post_account2.is_none());
661 assert!(post_account3.is_some());
662 assert!(post_account4.is_none());
663 assert!(post_account5.is_some());
664
665 let post_sysvar_accounts: Vec<_> = sysvars
666 .iter()
667 .map(|address| bank.get_account_with_fixed_root(address))
668 .collect();
669
670 let mut expected_accounts_lt_hash = prev_accounts_lt_hash;
671 let mut updater =
672 |address: &Pubkey, prev: Option<AccountSharedData>, post: Option<AccountSharedData>| {
673 if let Some(prev) = prev {
675 let prev_lt_hash = AccountsDb::lt_hash_account(&prev, address);
676 expected_accounts_lt_hash.0.mix_out(&prev_lt_hash.0);
677 }
678
679 let post = post.unwrap_or_default();
681 let post_lt_hash = AccountsDb::lt_hash_account(&post, address);
682 expected_accounts_lt_hash.0.mix_in(&post_lt_hash.0);
683 };
684 updater(&mint_keypair.pubkey(), prev_mint, post_mint);
685 updater(&keypair1.pubkey(), prev_account1, post_account1);
686 updater(&keypair2.pubkey(), prev_account2, post_account2);
687 updater(&keypair3.pubkey(), prev_account3, post_account3);
688 updater(&keypair4.pubkey(), prev_account4, post_account4);
689 updater(&keypair5.pubkey(), prev_account5, post_account5);
690 for (i, sysvar) in sysvars.iter().enumerate() {
691 updater(
692 sysvar,
693 prev_sysvar_accounts[i].clone(),
694 post_sysvar_accounts[i].clone(),
695 );
696 }
697
698 let expected = expected_accounts_lt_hash.0.checksum();
700 let actual = post_accounts_lt_hash.0.checksum();
701 assert_eq!(
702 expected, actual,
703 "accounts_lt_hash, expected: {expected}, actual: {actual}",
704 );
705 }
706
707 #[test_case(Features::None; "no features")]
713 #[test_case(Features::All; "all features")]
714 fn test_slot0_accounts_lt_hash(features: Features) {
715 let (genesis_config, mint_keypair) = genesis_config_with(features);
716 let (bank, _bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config);
717
718 assert_eq!(bank.slot(), 0);
720
721 bank.transfer(LAMPORTS_PER_SOL, &mint_keypair, &Pubkey::new_unique())
723 .unwrap();
724
725 bank.freeze();
727 let actual_accounts_lt_hash = bank.accounts_lt_hash.lock().unwrap().clone();
728
729 let calculated_accounts_lt_hash = bank
731 .rc
732 .accounts
733 .accounts_db
734 .calculate_accounts_lt_hash_at_startup_from_index(&bank.ancestors);
735 assert_eq!(actual_accounts_lt_hash, calculated_accounts_lt_hash);
736 }
737
738 #[test_case(Features::None; "no features")]
739 #[test_case(Features::All; "all features")]
740 fn test_calculate_accounts_lt_hash_at_startup_from_index(features: Features) {
741 let (genesis_config, mint_keypair) = genesis_config_with(features);
742 let (mut bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config);
743
744 let amount = cmp::max(
745 bank.get_minimum_balance_for_rent_exemption(0),
746 LAMPORTS_PER_SOL,
747 );
748
749 for _ in 0..7 {
752 let slot = bank.slot() + 1;
753 bank = Bank::new_from_parent_with_bank_forks(
754 &bank_forks,
755 bank,
756 SlotLeader::default(),
757 slot,
758 );
759 for _ in 0..13 {
760 bank.register_unique_recent_blockhash_for_test();
761 bank.transfer(amount, &mint_keypair, &pubkey::new_rand())
764 .unwrap();
765 }
766 bank.freeze();
767 }
768 let expected_accounts_lt_hash = bank.accounts_lt_hash.lock().unwrap().clone();
769
770 bank.squash();
773 bank.force_flush_accounts_cache();
774
775 let calculated_accounts_lt_hash = bank
777 .rc
778 .accounts
779 .accounts_db
780 .calculate_accounts_lt_hash_at_startup_from_index(&bank.ancestors);
781 assert_eq!(expected_accounts_lt_hash, calculated_accounts_lt_hash);
782 }
783
784 #[test_matrix(
785 [Features::None, Features::All],
786 [IndexLimit::Minimal, IndexLimit::InMemOnly]
787 )]
788 fn test_verify_accounts_lt_hash_at_startup(
789 features: Features,
790 accounts_index_limit: IndexLimit,
791 ) {
792 let (mut genesis_config, mint_keypair) = genesis_config_with(features);
793 genesis_config.fee_rate_governor = FeeRateGovernor::new(0, 0);
795 let (mut bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config);
796
797 let amount = cmp::max(
798 bank.get_minimum_balance_for_rent_exemption(0),
799 LAMPORTS_PER_SOL,
800 );
801
802 let duplicate_pubkey = pubkey::new_rand();
804
805 for _ in 0..9 {
808 let slot = bank.slot() + 1;
809 let leader = *bank.leader();
810 bank = Bank::new_from_parent_with_bank_forks(&bank_forks, bank, leader, slot);
811 for _ in 0..3 {
812 bank.register_unique_recent_blockhash_for_test();
813 bank.transfer(amount, &mint_keypair, &pubkey::new_rand())
814 .unwrap();
815 bank.register_unique_recent_blockhash_for_test();
816 bank.transfer(amount, &mint_keypair, &duplicate_pubkey)
817 .unwrap();
818 }
819
820 bank.fill_bank_with_ticks_for_tests();
822 bank.squash();
823 bank.force_flush_accounts_cache();
824 }
825
826 let num_accounts = 2;
832 let accounts: Vec<_> = iter::repeat_with(Keypair::new).take(num_accounts).collect();
833 for i in 0..num_accounts {
834 let slot = bank.slot() + 1;
835 let leader = *bank.leader();
836 bank = Bank::new_from_parent_with_bank_forks(&bank_forks, bank, leader, slot);
837 bank.register_unique_recent_blockhash_for_test();
838
839 for account in &accounts {
841 bank.transfer(amount, &mint_keypair, &account.pubkey())
842 .unwrap();
843 assert_ne!(bank.get_balance(&account.pubkey()), 0);
844 }
845
846 bank.transfer(
848 bank.get_balance(&accounts[i].pubkey()),
849 &accounts[i],
850 &pubkey::new_rand(),
851 )
852 .unwrap();
853 assert_eq!(bank.get_balance(&accounts[i].pubkey()), 0);
854
855 bank.fill_bank_with_ticks_for_tests();
857 bank.squash();
858 bank.force_flush_accounts_cache();
859 }
860 bank.set_block_id(Some(Hash::default()));
861
862 let bank_snapshots_dir = TempDir::new().unwrap();
864 let snapshot_archives_dir = TempDir::new().unwrap();
865 let snapshot_config = SnapshotConfig {
866 full_snapshot_archives_dir: snapshot_archives_dir.path().to_path_buf(),
867 incremental_snapshot_archives_dir: snapshot_archives_dir.path().to_path_buf(),
868 bank_snapshots_dir: bank_snapshots_dir.path().to_path_buf(),
869 ..SnapshotConfig::default()
870 };
871 let snapshot =
872 snapshot_bank_utils::bank_to_full_snapshot_archive(&snapshot_config, &bank).unwrap();
873 let (_accounts_tempdir, accounts_dir) = snapshot_utils::create_tmp_accounts_dir_for_tests();
874 let accounts_index_config = AccountsIndexConfig {
875 index_limit: accounts_index_limit,
876 ..ACCOUNTS_INDEX_CONFIG_FOR_TESTING
877 };
878 let accounts_db_config = AccountsDbConfig {
879 index: Some(accounts_index_config),
880 ..ACCOUNTS_DB_CONFIG_FOR_TESTING
881 };
882 let roundtrip_bank = snapshot_bank_utils::bank_from_snapshot_archives(
883 &[accounts_dir],
884 &snapshot,
885 None,
886 &snapshot_config,
887 &genesis_config,
888 &RuntimeConfig::default(),
889 None,
890 None, None,
892 false,
893 false,
894 false,
895 accounts_db_config,
896 None,
897 Arc::default(),
898 )
899 .unwrap();
900
901 assert!(roundtrip_bank.is_frozen());
905
906 assert_eq!(roundtrip_bank, *bank);
907 }
908
909 #[test_case(Features::None; "no features")]
911 #[test_case(Features::All; "all features")]
912 fn test_snapshots(features: Features) {
913 let (genesis_config, mint_keypair) = genesis_config_with(features);
914 let (mut bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config);
915
916 let amount = cmp::max(
917 bank.get_minimum_balance_for_rent_exemption(0),
918 LAMPORTS_PER_SOL,
919 );
920
921 for _ in 0..3 {
924 let slot = bank.slot() + 1;
925 let leader = *bank.leader();
926 bank = Bank::new_from_parent_with_bank_forks(&bank_forks, bank, leader, slot);
927 bank.register_unique_recent_blockhash_for_test();
928 bank.transfer(amount, &mint_keypair, &pubkey::new_rand())
929 .unwrap();
930 bank.fill_bank_with_ticks_for_tests();
931 bank.squash();
932 bank.force_flush_accounts_cache();
933 }
934 bank.set_block_id(Some(Hash::default()));
935
936 let bank_snapshots_dir = TempDir::new().unwrap();
937 let snapshot_archives_dir = TempDir::new().unwrap();
938 let snapshot_config = SnapshotConfig {
939 full_snapshot_archives_dir: snapshot_archives_dir.path().to_path_buf(),
940 incremental_snapshot_archives_dir: snapshot_archives_dir.path().to_path_buf(),
941 bank_snapshots_dir: bank_snapshots_dir.path().to_path_buf(),
942 ..SnapshotConfig::default()
943 };
944 let snapshot =
945 snapshot_bank_utils::bank_to_full_snapshot_archive(&snapshot_config, &bank).unwrap();
946 let (_accounts_tempdir, accounts_dir) = snapshot_utils::create_tmp_accounts_dir_for_tests();
947 let roundtrip_bank = snapshot_bank_utils::bank_from_snapshot_archives(
948 &[accounts_dir],
949 &snapshot,
950 None,
951 &snapshot_config,
952 &genesis_config,
953 &RuntimeConfig::default(),
954 None,
955 None, None,
957 false,
958 false,
959 false,
960 ACCOUNTS_DB_CONFIG_FOR_TESTING,
961 None,
962 Arc::default(),
963 )
964 .unwrap();
965
966 assert_eq!(roundtrip_bank, *bank);
967 }
968
969 #[should_panic(expected = "duplicate accounts were enqueued for hashing")]
971 #[test_case(Features::None; "no features")]
972 #[test_case(Features::All; "all features")]
973 fn test_enqueue_off_chain_accounts_lt_hash_updates_catches_duplicates(features: Features) {
974 use rand::seq::SliceRandom as _;
975 let (genesis_config, _) = genesis_config_with(features);
976 let bank = Bank::new_for_tests(&genesis_config);
977
978 let pubkey1 = pubkey::new_rand();
979 let pubkey2 = pubkey::new_rand();
980 let pubkey3 = pubkey::new_rand();
981
982 let mut accounts = [
983 (&pubkey1, &AccountSharedData::new(11, 0, &Pubkey::default())),
985 (&pubkey2, &AccountSharedData::new(21, 0, &Pubkey::default())),
987 (&pubkey2, &AccountSharedData::new(22, 0, &Pubkey::default())),
988 (&pubkey3, &AccountSharedData::new(31, 0, &Pubkey::default())),
990 (&pubkey3, &AccountSharedData::new(32, 0, &Pubkey::default())),
991 (&pubkey3, &AccountSharedData::new(33, 0, &Pubkey::default())),
992 ];
993 accounts.shuffle(&mut rand::rng());
994
995 bank.store_accounts((bank.slot(), accounts.as_slice()), None);
996 }
997
998 #[test]
1000 fn test_freelist_max_capacity() {
1001 type Container = ahash::HashSet<u64>;
1002
1003 let container = Container::with_capacity(77);
1006
1007 let max_capacity = container.capacity();
1008 let max_bytes = max_capacity * size_of::<u64>();
1009 let mut freelist = HashSetFreelist::new(10, Some(max_bytes));
1010
1011 freelist.try_push(Container::with_capacity(max_capacity + 1));
1013 let stats0 = freelist.stats();
1014 assert_eq!(stats0.num_containers, 0);
1015 assert_eq!(stats0.capacity_elems, 0);
1016 assert_eq!(stats0.capacity_bytes, 0);
1017
1018 freelist.try_push(container);
1020 let stats1 = freelist.stats();
1021 assert_eq!(stats1.num_containers, 1);
1022 assert_eq!(stats1.capacity_elems, max_capacity);
1023 assert_eq!(stats1.capacity_bytes, max_bytes);
1024
1025 freelist.try_push(Container::with_capacity(1));
1027 assert_eq!(freelist.stats(), stats1);
1028
1029 freelist.max_capacity = None;
1031 let container = Container::with_capacity(1);
1032 let container_capacity = container.capacity();
1033 freelist.try_push(container);
1034 let stats2 = freelist.stats();
1035 assert_eq!(stats2.num_containers, 2);
1036 assert_eq!(stats2.capacity_elems, max_capacity + container_capacity);
1037 assert_eq!(
1038 stats2.capacity_bytes,
1039 max_bytes + container_capacity * size_of::<u64>(),
1040 );
1041 }
1042
1043 #[test]
1045 fn test_freelist_concurrent_push() {
1046 let container = ahash::HashSet::<u64>::with_capacity(77);
1049
1050 let num_threads = 16;
1051 let barrier = Arc::new(Barrier::new(num_threads));
1052 let max_capacity = container.capacity();
1053 let max_bytes = max_capacity * size_of::<u64>();
1054 let freelist = Arc::new(HashSetFreelist::new(num_threads, Some(max_bytes)));
1055
1056 let threads: Vec<_> = iter::repeat_with(|| {
1057 let container = container.clone();
1058 let freelist = Arc::clone(&freelist);
1059 let barrier = Arc::clone(&barrier);
1060 thread::spawn(move || {
1061 barrier.wait();
1062 freelist.try_push(container);
1063 })
1064 })
1065 .take(num_threads)
1066 .collect();
1067
1068 for thread in threads {
1069 thread.join().unwrap();
1070 }
1071
1072 let stats = freelist.stats();
1073 assert_eq!(stats.num_containers, 1);
1074 assert_eq!(stats.capacity_elems, max_capacity);
1075 assert_eq!(stats.capacity_bytes, max_bytes);
1076 }
1077}