1use {
2 super::Bank,
3 crossbeam_queue::SegQueue,
4 crossbeam_utils::{
5 CachePadded,
6 sync::{Parker, Unparker},
7 },
8 rayon::{
9 ThreadPool, ThreadPoolBuilder,
10 iter::{IntoParallelIterator, ParallelIterator},
11 },
12 solana_account::{AccountSharedData, ReadableAccount},
13 solana_accounts_db::{accounts_db::AccountsDb, storable_accounts::StorableAccounts},
14 solana_lattice_hash::lt_hash::LtHash,
15 solana_pubkey::Pubkey,
16 std::{
17 array,
18 collections::hash_map::Entry,
19 hint,
20 mem::size_of,
21 sync::{
22 Arc, LazyLock, Mutex,
23 atomic::{AtomicBool, AtomicU64, AtomicUsize, Ordering},
24 },
25 thread,
26 time::{Duration, Instant},
27 },
28};
29
30const NUM_ACCOUNTS_HASHER_THREADS: usize = 4;
32
33const MAX_BYTES_SEEN_ACCOUNTS_FREELIST: usize = 10_000_000;
35
36impl Bank {
37 pub fn enqueue_on_chain_accounts_lt_hash_updates<'a>(
46 &self,
47 accounts: &impl StorableAccounts<'a>,
48 ) {
49 if accounts.is_empty() {
50 return;
51 }
52
53 let manager = accounts_lt_hash_manager();
54 let seen_accounts_freelist = seen_accounts_freelist();
55 let mut seen_accounts = seen_accounts_freelist.try_pop().unwrap_or_default();
56
57 self.accounts_lt_hash_async_progress
60 .num_jobs_pending
61 .fetch_add(accounts.len(), Ordering::Relaxed);
62
63 let mut num_enqueued = 0;
65 let mut num_skipped = 0;
66 for index in (0..accounts.len()).rev() {
67 let address = accounts.pubkey(index);
68 if !seen_accounts.insert(*address) {
69 num_skipped += 1;
71 continue;
72 }
73 let prev_account = self
74 .rc
75 .accounts
76 .load_with_fixed_root_do_not_populate_read_cache(&self.ancestors, address)
77 .map(|(account, _slot)| account);
78 let curr_account = accounts.account(index, |account| {
79 (account.lamports() != 0).then(|| account.take_account())
80 });
81 if prev_account.is_none() && curr_account.is_none() {
82 num_skipped += 1;
84 } else {
85 let async_progress = Arc::clone(&self.accounts_lt_hash_async_progress);
87 manager.queue.push(QueuedAccountsLtHashUpdate {
88 async_progress,
89 num_updates: 1,
90 inner: AccountsLtHashUpdate {
91 address: *address,
92 prev_account,
93 curr_account,
94 },
95 });
96 num_enqueued += 1;
97 }
98 }
99 debug_assert_eq!(num_enqueued + num_skipped, accounts.len());
100
101 if num_skipped > 0 {
103 self.accounts_lt_hash_async_progress
104 .num_jobs_pending
105 .fetch_sub(num_skipped, Ordering::Relaxed);
106 }
107
108 if num_enqueued > 0 && manager.num_banks_waiting.load(Ordering::Relaxed) > 0 {
110 manager.unparker.unpark();
111 }
112
113 seen_accounts_freelist.try_push(seen_accounts);
115 }
116
117 pub fn enqueue_off_chain_accounts_lt_hash_updates<'a>(
128 &self,
129 accounts: &impl StorableAccounts<'a>,
130 thread_pool_for_loading_accounts: Option<&ThreadPool>,
131 ) {
132 if cfg!(debug_assertions) {
133 use ahash::HashSetExt as _;
135 let mut seen_accounts = ahash::HashSet::with_capacity(accounts.len());
136 let mut duplicate_pubkeys = ahash::HashSet::with_capacity(0); for index in 0..accounts.len() {
138 let pubkey = accounts.pubkey(index);
139 if !seen_accounts.insert(pubkey) {
140 duplicate_pubkeys.insert(pubkey);
142 }
143 }
144 if !duplicate_pubkeys.is_empty() {
145 let mut duplicate_accounts = ahash::HashMap::<_, Vec<_>>::default();
146 for duplicate_pubkey in duplicate_pubkeys {
147 for index in 0..accounts.len() {
148 let pubkey = accounts.pubkey(index);
149 if pubkey == duplicate_pubkey {
150 duplicate_accounts
151 .entry(pubkey)
152 .or_default()
153 .push(accounts.account(index, |account| account.take_account()));
154 }
155 }
156 }
157 panic!("duplicate accounts were enqueued for hashing: {duplicate_accounts:?}");
158 }
159 }
160
161 let thread_pool_for_hashing_accounts = accounts_hasher_thread_pool();
162
163 let load_then_spawn = |index| {
166 let address = accounts.pubkey(index);
167 let prev_account = self
168 .rc
169 .accounts
170 .load_with_fixed_root_do_not_populate_read_cache(&self.ancestors, address)
171 .map(|(account, _slot)| account);
172 let curr_account = accounts.account(index, |account| {
173 (account.lamports() != 0).then(|| account.take_account())
174 });
175 let async_progress = Arc::clone(&self.accounts_lt_hash_async_progress);
176 async_progress.spawn(
177 thread_pool_for_hashing_accounts,
178 AccountsLtHashUpdate {
179 address: *address,
180 prev_account,
181 curr_account,
182 },
183 1,
184 );
185 };
186
187 self.accounts_lt_hash_async_progress
190 .num_jobs_pending
191 .fetch_add(accounts.len(), Ordering::Relaxed);
192
193 if let Some(thread_pool_for_loading_accounts) = thread_pool_for_loading_accounts {
194 thread_pool_for_loading_accounts.install(|| {
197 (0..accounts.len())
198 .into_par_iter()
199 .for_each(load_then_spawn);
200 });
201 } else {
202 (0..accounts.len()).for_each(load_then_spawn);
203 }
204 }
205
206 pub fn finish_accounts_lt_hash_updates(&self) {
216 let timer = Instant::now();
217 self.accounts_lt_hash_async_progress.set_is_at_end_of_slot();
218 let num_jobs_total = {
219 let mut accounts_lt_hash = self.accounts_lt_hash.lock().unwrap();
220 self.accounts_lt_hash_async_progress
221 .finish(&mut accounts_lt_hash.0)
222 };
223 self.accounts_lt_hash_async_progress
224 .clear_is_at_end_of_slot();
225 let finish_time = timer.elapsed();
226
227 let seen_accounts_freelist_stats = seen_accounts_freelist().stats();
228 datapoint_info!(
229 "bank-accounts_lt_hash",
230 ("slot", self.slot(), i64),
231 ("num_jobs", num_jobs_total, i64),
232 ("finish_us", finish_time.as_micros(), i64),
233 (
234 "seen_accounts_freelist_num_containers",
235 seen_accounts_freelist_stats.num_containers,
236 i64
237 ),
238 (
239 "seen_accounts_freelist_capacity_elems",
240 seen_accounts_freelist_stats.capacity_elems,
241 i64
242 ),
243 (
244 "seen_accounts_freelist_capacity_bytes",
245 seen_accounts_freelist_stats.capacity_bytes,
246 i64
247 ),
248 (
249 "num_banks_waiting",
250 accounts_lt_hash_manager()
251 .num_banks_waiting
252 .load(Ordering::Relaxed),
253 i64
254 ),
255 );
256 }
257}
258
259pub struct AccountsLtHashAsyncProgress {
261 accumulators: [Mutex<CachePadded<LtHash>>; NUM_ACCOUNTS_HASHER_THREADS],
288 num_jobs_pending: AtomicUsize,
289 num_jobs_total: AtomicU64,
290 is_at_end_of_slot: AtomicBool,
295}
296
297impl AccountsLtHashAsyncProgress {
298 pub fn new() -> Self {
300 Self {
301 accumulators: array::from_fn(|_| Mutex::new(CachePadded::new(LtHash::identity()))),
302 num_jobs_pending: AtomicUsize::new(0),
303 num_jobs_total: AtomicU64::new(0),
304 is_at_end_of_slot: AtomicBool::new(false),
305 }
306 }
307
308 fn spawn(
310 self: Arc<Self>,
311 thread_pool: &'static ThreadPool,
312 update: AccountsLtHashUpdate,
313 num_updates: usize,
314 ) {
315 self.num_jobs_total.fetch_add(1, Ordering::Relaxed);
316 thread_pool.spawn({
317 move || {
318 let worker_index = thread_pool.current_thread_index().unwrap();
320
321 debug_assert!(worker_index < self.accumulators.len());
324 let accumulator = unsafe { self.accumulators.get_unchecked(worker_index) };
325
326 Self::process(&mut accumulator.lock().unwrap(), update);
327
328 self.num_jobs_pending
332 .fetch_sub(num_updates, Ordering::Relaxed);
333 }
334 });
335 }
336
337 fn finish(&self, lt_hash: &mut LtHash) -> u64 {
344 while self.num_jobs_pending.load(Ordering::Relaxed) > 0 {
345 hint::spin_loop();
347 }
348
349 for thread_accumulator in self.accumulators.iter() {
350 lt_hash.mix_in(&thread_accumulator.lock().unwrap());
351 }
352 self.num_jobs_total.load(Ordering::Relaxed)
353 }
354
355 fn process(accum_lt_hash: &mut LtHash, update: AccountsLtHashUpdate) {
360 let AccountsLtHashUpdate {
361 address,
362 prev_account,
363 curr_account,
364 } = update;
365 if let Some(prev_account) = prev_account {
366 let prev_lt_hash = AccountsDb::lt_hash_account(&prev_account, &address);
367 accum_lt_hash.mix_out(&prev_lt_hash.0);
368 }
369 if let Some(curr_account) = curr_account {
370 let curr_lt_hash = AccountsDb::lt_hash_account(&curr_account, &address);
371 accum_lt_hash.mix_in(&curr_lt_hash.0);
372 }
373 }
374
375 pub fn set_is_at_end_of_slot(&self) {
378 if !self.is_at_end_of_slot.swap(true, Ordering::Relaxed) {
379 let manager = accounts_lt_hash_manager();
380 manager.num_banks_waiting.fetch_add(1, Ordering::Relaxed);
381 manager.unparker.unpark();
382 }
383 }
384
385 pub fn clear_is_at_end_of_slot(&self) {
389 if self.is_at_end_of_slot.swap(false, Ordering::Relaxed) {
390 accounts_lt_hash_manager()
391 .num_banks_waiting
392 .fetch_sub(1, Ordering::Relaxed);
393 }
394 }
395}
396
397fn accounts_lt_hash_manager() -> &'static AccountsLtHashManager {
401 static MANAGER: LazyLock<AccountsLtHashManager> = LazyLock::new(AccountsLtHashManager::new);
402 &MANAGER
403}
404
405struct AccountsLtHashManager {
412 queue: Arc<SegQueue<QueuedAccountsLtHashUpdate>>,
414 unparker: Unparker,
416 num_banks_waiting: Arc<AtomicUsize>,
419}
420
421impl AccountsLtHashManager {
422 const DEDUP_INTERVAL: Duration = Duration::from_millis(2);
424 const MAX_UPDATES_TO_POP: usize = 10_000;
427
428 fn new() -> Self {
429 let queue = Arc::new(SegQueue::<QueuedAccountsLtHashUpdate>::new());
430 let num_banks_waiting = Arc::new(AtomicUsize::new(0));
431 let parker = Parker::new();
432 let unparker = parker.unparker().clone();
433 thread::Builder::new()
434 .name("solAcctsHashMgr".into())
435 .spawn({
436 let queue = Arc::clone(&queue);
437 let num_banks_waiting = Arc::clone(&num_banks_waiting);
438 move || {
439 let thread_pool = accounts_hasher_thread_pool();
440 let mut deduplicated_updates = ahash::HashMap::default();
441 loop {
442 let start_time = Instant::now();
444 loop {
445 let mut queue_was_empty = false;
451 for _ in 0..Self::MAX_UPDATES_TO_POP {
452 let Some(queued_update) = queue.pop() else {
453 queue_was_empty = true;
454 break;
455 };
456 Self::deduplicate_update(&mut deduplicated_updates, queued_update);
457 }
458
459 if !deduplicated_updates.is_empty()
465 && num_banks_waiting.load(Ordering::Relaxed) > 0
466 {
467 break;
468 }
469
470 let remaining_time =
473 Self::DEDUP_INTERVAL.saturating_sub(start_time.elapsed());
474 if remaining_time == Duration::ZERO {
475 break;
476 }
477
478 if queue_was_empty {
482 parker.park_timeout(remaining_time);
483 }
484 }
485
486 for (_, queued_update) in deduplicated_updates.drain() {
488 let QueuedAccountsLtHashUpdate {
489 async_progress,
490 num_updates,
491 inner: account_update,
492 } = queued_update;
493 async_progress.spawn(thread_pool, account_update, num_updates);
494 }
495 }
496 }
497 })
498 .expect("new accounts hasher manager thread");
499 Self {
500 queue,
501 unparker,
502 num_banks_waiting,
503 }
504 }
505
506 fn deduplicate_update(
514 deduplicated_updates: &mut ahash::HashMap<(usize, Pubkey), QueuedAccountsLtHashUpdate>,
515 queued_update: QueuedAccountsLtHashUpdate,
516 ) {
517 let key = (
520 Arc::as_ptr(&queued_update.async_progress) as usize,
521 queued_update.inner.address,
522 );
523 match deduplicated_updates.entry(key) {
524 Entry::Vacant(entry) => {
525 entry.insert(queued_update);
526 }
527 Entry::Occupied(mut entry) => {
528 let value = entry.get_mut();
529 value.num_updates += queued_update.num_updates;
530 value.inner.curr_account = queued_update.inner.curr_account;
531 }
532 }
533 }
534}
535
536struct QueuedAccountsLtHashUpdate {
538 async_progress: Arc<AccountsLtHashAsyncProgress>,
540 num_updates: usize,
543 inner: AccountsLtHashUpdate,
545}
546
547#[derive(Debug)]
549struct AccountsLtHashUpdate {
550 address: Pubkey,
551 prev_account: Option<AccountSharedData>,
552 curr_account: Option<AccountSharedData>,
553}
554
555fn seen_accounts_freelist() -> &'static HashSetFreelist<Pubkey> {
557 const MAX_CONTAINERS: usize = 50;
560 static FREELIST: LazyLock<HashSetFreelist<Pubkey>> = LazyLock::new(|| {
561 HashSetFreelist::new(MAX_CONTAINERS, Some(MAX_BYTES_SEEN_ACCOUNTS_FREELIST))
562 });
563 &FREELIST
564}
565
566#[derive(Debug)]
568struct HashSetFreelist<T> {
569 max_containers: usize,
571
572 max_capacity: Option<usize>,
574
575 inner: Mutex<HashSetFreelistInner<T>>,
576}
577
578impl<T> HashSetFreelist<T> {
579 fn new(max_containers: usize, max_bytes: Option<usize>) -> Self {
589 let max_capacity = max_bytes.map(|max_bytes| max_bytes / size_of::<T>());
590 Self {
591 max_containers,
592 max_capacity,
593 inner: Mutex::new(HashSetFreelistInner {
594 list: Vec::with_capacity(max_containers),
595 total_capacity: 0,
596 }),
597 }
598 }
599
600 fn try_push(&self, mut container: ahash::HashSet<T>) {
602 let capacity = container.capacity();
609 if capacity == 0 {
610 return;
611 }
612
613 container.clear();
615
616 let mut inner = self.inner.lock().unwrap();
617
618 if inner.list.len() >= self.max_containers {
619 return;
621 }
622
623 let Some(new_total_capacity) = inner.total_capacity.checked_add(capacity) else {
624 return;
626 };
627
628 let max_capacity = self.max_capacity.unwrap_or(usize::MAX);
629 if new_total_capacity > max_capacity {
630 return;
632 }
633
634 inner.list.push(container);
635 inner.total_capacity = new_total_capacity;
636 }
637
638 fn try_pop(&self) -> Option<ahash::HashSet<T>> {
642 let mut inner = self.inner.lock().unwrap();
643 let container = inner.list.pop()?;
644 assert!(container.is_empty());
645 inner.total_capacity -= container.capacity();
646 Some(container)
647 }
648
649 fn stats(&self) -> FreelistStats {
651 let inner = self.inner.lock().unwrap();
652 let num_containers = inner.list.len();
653 let capacity_elems = inner.total_capacity;
654 drop(inner);
655 FreelistStats {
656 num_containers,
657 capacity_elems,
658 capacity_bytes: capacity_elems.saturating_mul(size_of::<T>()),
659 }
660 }
661}
662
663#[derive(Debug)]
665struct HashSetFreelistInner<T> {
666 list: Vec<ahash::HashSet<T>>,
668 total_capacity: usize,
670}
671
672#[derive(Debug, Eq, PartialEq)]
674struct FreelistStats {
675 num_containers: usize,
677 capacity_elems: usize,
679 capacity_bytes: usize,
681}
682
683fn accounts_hasher_thread_pool() -> &'static ThreadPool {
687 static THREAD_POOL: LazyLock<ThreadPool> = LazyLock::new(|| {
688 ThreadPoolBuilder::new()
689 .num_threads(NUM_ACCOUNTS_HASHER_THREADS)
690 .stack_size(8 * 1024 * 1024)
691 .thread_name(|i| format!("solAcctsHashr{i:02}"))
692 .build()
693 .expect("new accounts hasher rayon threadpool")
694 });
695 &THREAD_POOL
696}
697
698#[cfg(test)]
699mod tests {
700 use {
701 super::*,
702 crate::{
703 genesis_utils::create_genesis_config_with_leader_ex, runtime_config::RuntimeConfig,
704 snapshot_bank_utils, snapshot_utils,
705 },
706 agave_feature_set::FeatureSet,
707 agave_snapshots::snapshot_config::SnapshotConfig,
708 ahash::HashSetExt as _,
709 solana_accounts_db::{
710 accounts_db::{ACCOUNTS_DB_CONFIG_FOR_TESTING, AccountsDbConfig},
711 accounts_index::{
712 ACCOUNTS_INDEX_CONFIG_FOR_TESTING, AccountsIndexConfig,
713 DEFAULT_NUM_ENTRIES_OVERHEAD, DEFAULT_NUM_ENTRIES_TO_EVICT, IndexLimit,
714 IndexLimitThreshold,
715 },
716 },
717 solana_cluster_type::ClusterType,
718 solana_fee_calculator::FeeRateGovernor,
719 solana_genesis_config::{self, GenesisConfig},
720 solana_hash::Hash,
721 solana_keypair::Keypair,
722 solana_leader_schedule::SlotLeader,
723 solana_native_token::LAMPORTS_PER_SOL,
724 solana_pubkey::{self as pubkey, Pubkey},
725 solana_rent::Rent,
726 solana_signer::Signer as _,
727 std::{
728 cmp, iter,
729 str::FromStr as _,
730 sync::{Arc, Barrier},
731 thread,
732 },
733 tempfile::TempDir,
734 test_case::{test_case, test_matrix},
735 };
736
737 #[derive(Debug, Copy, Clone, Eq, PartialEq)]
739 enum Features {
740 None,
742 All,
744 }
745
746 fn genesis_config_with(features: Features) -> (GenesisConfig, Keypair) {
748 let mint_keypair = Keypair::new();
749 let mint_lamports = 123_456_789 * LAMPORTS_PER_SOL;
750 let validator_lamports = 100 * LAMPORTS_PER_SOL;
751 let validator_stake_lamports = 10 * LAMPORTS_PER_SOL;
752 let validator_pubkey = Pubkey::new_unique();
753 let vote_account_pubkey = Pubkey::new_unique();
754 let stake_account_pubkey = Pubkey::new_unique();
755 let feature_set = match features {
756 Features::None => FeatureSet::default(),
757 Features::All => FeatureSet::all_enabled(),
758 };
759
760 let config = create_genesis_config_with_leader_ex(
761 mint_lamports,
762 &mint_keypair.pubkey(),
763 &validator_pubkey,
764 &vote_account_pubkey,
765 &stake_account_pubkey,
766 None,
767 validator_stake_lamports,
768 validator_lamports,
769 FeeRateGovernor::default(),
770 Rent::default(),
771 ClusterType::Development,
772 &feature_set,
773 vec![],
774 );
775
776 (config, mint_keypair)
777 }
778
779 #[test]
780 fn test_update_accounts_lt_hash() {
781 let keypair1 = Keypair::new();
792 let keypair2 = Keypair::new();
793 let keypair3 = Keypair::new();
794 let keypair4 = Keypair::new();
795 let keypair5 = Keypair::new();
796
797 let (mut genesis_config, mint_keypair) =
798 solana_genesis_config::create_genesis_config(123_456_789 * LAMPORTS_PER_SOL);
799 genesis_config.fee_rate_governor = FeeRateGovernor::new(0, 0);
801 let (bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config);
802
803 let amount = cmp::max(
804 bank.get_minimum_balance_for_rent_exemption(0),
805 LAMPORTS_PER_SOL,
806 );
807
808 bank.register_unique_recent_blockhash_for_test();
811 bank.transfer(amount, &mint_keypair, &keypair1.pubkey())
812 .unwrap();
813 bank.transfer(amount, &mint_keypair, &keypair2.pubkey())
814 .unwrap();
815 bank.transfer(amount, &mint_keypair, &keypair5.pubkey())
816 .unwrap();
817
818 bank.freeze();
820 let prev_accounts_lt_hash = bank.accounts_lt_hash.lock().unwrap().clone();
821
822 let prev_mint = bank.get_account_with_fixed_root(&mint_keypair.pubkey());
824 let prev_account1 = bank.get_account_with_fixed_root(&keypair1.pubkey());
825 let prev_account2 = bank.get_account_with_fixed_root(&keypair2.pubkey());
826 let prev_account3 = bank.get_account_with_fixed_root(&keypair3.pubkey());
827 let prev_account4 = bank.get_account_with_fixed_root(&keypair4.pubkey());
828 let prev_account5 = bank.get_account_with_fixed_root(&keypair5.pubkey());
829
830 assert!(prev_mint.is_some());
831 assert!(prev_account1.is_some());
832 assert!(prev_account2.is_some());
833 assert!(prev_account3.is_none());
834 assert!(prev_account4.is_none());
835 assert!(prev_account5.is_some());
836
837 let sysvars = [
841 Pubkey::from_str("SysvarS1otHashes111111111111111111111111111").unwrap(),
842 Pubkey::from_str("SysvarC1ock11111111111111111111111111111111").unwrap(),
843 Pubkey::from_str("SysvarRecentB1ockHashes11111111111111111111").unwrap(),
844 Pubkey::from_str("SysvarS1otHistory11111111111111111111111111").unwrap(),
845 ];
846 let prev_sysvar_accounts: Vec<_> = sysvars
847 .iter()
848 .map(|address| bank.get_account_with_fixed_root(address))
849 .collect();
850
851 let bank = {
852 let slot = bank.slot() + 1;
853 Bank::new_from_parent_with_bank_forks(&bank_forks, bank, SlotLeader::default(), slot)
854 };
855
856 bank.register_unique_recent_blockhash_for_test();
858 bank.transfer(amount, &keypair2, &keypair1.pubkey())
859 .unwrap();
860
861 bank.register_unique_recent_blockhash_for_test();
864 bank.transfer(amount, &mint_keypair, &keypair4.pubkey())
865 .unwrap();
866 bank.register_unique_recent_blockhash_for_test();
867 bank.transfer(amount, &keypair4, &keypair3.pubkey())
868 .unwrap();
869
870 bank.store_account(&keypair5.pubkey(), prev_account5.as_ref().unwrap());
872
873 bank.freeze();
875
876 let post_accounts_lt_hash = bank.accounts_lt_hash.lock().unwrap().clone();
877 let post_mint = bank.get_account_with_fixed_root(&mint_keypair.pubkey());
878 let post_account1 = bank.get_account_with_fixed_root(&keypair1.pubkey());
879 let post_account2 = bank.get_account_with_fixed_root(&keypair2.pubkey());
880 let post_account3 = bank.get_account_with_fixed_root(&keypair3.pubkey());
881 let post_account4 = bank.get_account_with_fixed_root(&keypair4.pubkey());
882 let post_account5 = bank.get_account_with_fixed_root(&keypair5.pubkey());
883
884 assert!(post_mint.is_some());
885 assert!(post_account1.is_some());
886 assert!(post_account2.is_none());
887 assert!(post_account3.is_some());
888 assert!(post_account4.is_none());
889 assert!(post_account5.is_some());
890
891 let post_sysvar_accounts: Vec<_> = sysvars
892 .iter()
893 .map(|address| bank.get_account_with_fixed_root(address))
894 .collect();
895
896 let mut expected_accounts_lt_hash = prev_accounts_lt_hash;
897 let mut updater =
898 |address: &Pubkey, prev: Option<AccountSharedData>, post: Option<AccountSharedData>| {
899 if let Some(prev) = prev {
901 let prev_lt_hash = AccountsDb::lt_hash_account(&prev, address);
902 expected_accounts_lt_hash.0.mix_out(&prev_lt_hash.0);
903 }
904
905 let post = post.unwrap_or_default();
907 let post_lt_hash = AccountsDb::lt_hash_account(&post, address);
908 expected_accounts_lt_hash.0.mix_in(&post_lt_hash.0);
909 };
910 updater(&mint_keypair.pubkey(), prev_mint, post_mint);
911 updater(&keypair1.pubkey(), prev_account1, post_account1);
912 updater(&keypair2.pubkey(), prev_account2, post_account2);
913 updater(&keypair3.pubkey(), prev_account3, post_account3);
914 updater(&keypair4.pubkey(), prev_account4, post_account4);
915 updater(&keypair5.pubkey(), prev_account5, post_account5);
916 for (i, sysvar) in sysvars.iter().enumerate() {
917 updater(
918 sysvar,
919 prev_sysvar_accounts[i].clone(),
920 post_sysvar_accounts[i].clone(),
921 );
922 }
923
924 let expected = expected_accounts_lt_hash.0.checksum();
926 let actual = post_accounts_lt_hash.0.checksum();
927 assert_eq!(
928 expected, actual,
929 "accounts_lt_hash, expected: {expected}, actual: {actual}",
930 );
931 }
932
933 #[test_case(Features::None; "no features")]
939 #[test_case(Features::All; "all features")]
940 fn test_slot0_accounts_lt_hash(features: Features) {
941 let (genesis_config, mint_keypair) = genesis_config_with(features);
942 let (bank, _bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config);
943
944 assert_eq!(bank.slot(), 0);
946
947 bank.transfer(LAMPORTS_PER_SOL, &mint_keypair, &Pubkey::new_unique())
949 .unwrap();
950
951 bank.freeze();
953 let actual_accounts_lt_hash = bank.accounts_lt_hash.lock().unwrap().clone();
954
955 let calculated_accounts_lt_hash = bank
957 .rc
958 .accounts
959 .accounts_db
960 .calculate_accounts_lt_hash_at_startup_from_index(&bank.ancestors);
961 assert_eq!(actual_accounts_lt_hash, calculated_accounts_lt_hash);
962 }
963
964 #[test_case(Features::None; "no features")]
965 #[test_case(Features::All; "all features")]
966 fn test_calculate_accounts_lt_hash_at_startup_from_index(features: Features) {
967 let (genesis_config, mint_keypair) = genesis_config_with(features);
968 let (mut bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config);
969
970 let amount = cmp::max(
971 bank.get_minimum_balance_for_rent_exemption(0),
972 LAMPORTS_PER_SOL,
973 );
974
975 for _ in 0..7 {
978 let slot = bank.slot() + 1;
979 bank = Bank::new_from_parent_with_bank_forks(
980 &bank_forks,
981 bank,
982 SlotLeader::default(),
983 slot,
984 );
985 for _ in 0..13 {
986 bank.register_unique_recent_blockhash_for_test();
987 bank.transfer(amount, &mint_keypair, &pubkey::new_rand())
990 .unwrap();
991 }
992 bank.freeze();
993 }
994 let expected_accounts_lt_hash = bank.accounts_lt_hash.lock().unwrap().clone();
995
996 bank.squash();
999 bank.force_flush_accounts_cache();
1000
1001 let calculated_accounts_lt_hash = bank
1003 .rc
1004 .accounts
1005 .accounts_db
1006 .calculate_accounts_lt_hash_at_startup_from_index(&bank.ancestors);
1007 assert_eq!(expected_accounts_lt_hash, calculated_accounts_lt_hash);
1008 }
1009
1010 const INDEX_LIMIT_THRESHOLD: IndexLimit = IndexLimit::Threshold(IndexLimitThreshold {
1011 num_bytes: 25_000_000_000,
1012 num_entries_overhead: DEFAULT_NUM_ENTRIES_OVERHEAD,
1013 num_entries_to_evict: DEFAULT_NUM_ENTRIES_TO_EVICT,
1014 });
1015
1016 #[test_matrix(
1017 [Features::None, Features::All],
1018 [INDEX_LIMIT_THRESHOLD, IndexLimit::InMemOnly]
1019 )]
1020 fn test_verify_accounts_lt_hash_at_startup(
1021 features: Features,
1022 accounts_index_limit: IndexLimit,
1023 ) {
1024 let (mut genesis_config, mint_keypair) = genesis_config_with(features);
1025 genesis_config.fee_rate_governor = FeeRateGovernor::new(0, 0);
1027 let (mut bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config);
1028
1029 let amount = cmp::max(
1030 bank.get_minimum_balance_for_rent_exemption(0),
1031 LAMPORTS_PER_SOL,
1032 );
1033
1034 let duplicate_pubkey = pubkey::new_rand();
1036
1037 for _ in 0..9 {
1040 let slot = bank.slot() + 1;
1041 let leader = *bank.leader();
1042 bank = Bank::new_from_parent_with_bank_forks(&bank_forks, bank, leader, slot);
1043 for _ in 0..3 {
1044 bank.register_unique_recent_blockhash_for_test();
1045 bank.transfer(amount, &mint_keypair, &pubkey::new_rand())
1046 .unwrap();
1047 bank.register_unique_recent_blockhash_for_test();
1048 bank.transfer(amount, &mint_keypair, &duplicate_pubkey)
1049 .unwrap();
1050 }
1051
1052 bank.fill_bank_with_ticks_for_tests();
1054 bank.squash();
1055 bank.force_flush_accounts_cache();
1056 }
1057
1058 let num_accounts = 2;
1064 let accounts: Vec<_> = iter::repeat_with(Keypair::new).take(num_accounts).collect();
1065 for i in 0..num_accounts {
1066 let slot = bank.slot() + 1;
1067 let leader = *bank.leader();
1068 bank = Bank::new_from_parent_with_bank_forks(&bank_forks, bank, leader, slot);
1069 bank.register_unique_recent_blockhash_for_test();
1070
1071 for account in &accounts {
1073 bank.transfer(amount, &mint_keypair, &account.pubkey())
1074 .unwrap();
1075 assert_ne!(bank.get_balance(&account.pubkey()), 0);
1076 }
1077
1078 bank.transfer(
1080 bank.get_balance(&accounts[i].pubkey()),
1081 &accounts[i],
1082 &pubkey::new_rand(),
1083 )
1084 .unwrap();
1085 assert_eq!(bank.get_balance(&accounts[i].pubkey()), 0);
1086
1087 bank.fill_bank_with_ticks_for_tests();
1089 bank.squash();
1090 bank.force_flush_accounts_cache();
1091 }
1092 bank.set_block_id(Some(Hash::default()));
1093
1094 let bank_snapshots_dir = TempDir::new().unwrap();
1096 let snapshot_archives_dir = TempDir::new().unwrap();
1097 let snapshot_config = SnapshotConfig {
1098 full_snapshot_archives_dir: snapshot_archives_dir.path().to_path_buf(),
1099 incremental_snapshot_archives_dir: snapshot_archives_dir.path().to_path_buf(),
1100 bank_snapshots_dir: bank_snapshots_dir.path().to_path_buf(),
1101 ..SnapshotConfig::default()
1102 };
1103 let snapshot =
1104 snapshot_bank_utils::bank_to_full_snapshot_archive(&snapshot_config, &bank).unwrap();
1105 let (_accounts_tempdir, accounts_dir) = snapshot_utils::create_tmp_accounts_dir_for_tests();
1106 let accounts_index_config = AccountsIndexConfig {
1107 index_limit: accounts_index_limit,
1108 ..ACCOUNTS_INDEX_CONFIG_FOR_TESTING
1109 };
1110 let accounts_db_config = AccountsDbConfig {
1111 index: Some(accounts_index_config),
1112 ..ACCOUNTS_DB_CONFIG_FOR_TESTING
1113 };
1114 let roundtrip_bank = snapshot_bank_utils::bank_from_snapshot_archives(
1115 &[accounts_dir],
1116 &snapshot,
1117 None,
1118 &snapshot_config,
1119 &genesis_config,
1120 &RuntimeConfig::default(),
1121 None,
1122 None, None,
1124 false,
1125 false,
1126 false,
1127 accounts_db_config,
1128 None,
1129 Arc::default(),
1130 )
1131 .unwrap();
1132
1133 assert!(roundtrip_bank.is_frozen());
1137
1138 assert_eq!(roundtrip_bank, *bank);
1139 }
1140
1141 #[test_case(Features::None; "no features")]
1143 #[test_case(Features::All; "all features")]
1144 fn test_snapshots(features: Features) {
1145 let (genesis_config, mint_keypair) = genesis_config_with(features);
1146 let (mut bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config);
1147
1148 let amount = cmp::max(
1149 bank.get_minimum_balance_for_rent_exemption(0),
1150 LAMPORTS_PER_SOL,
1151 );
1152
1153 for _ in 0..3 {
1156 let slot = bank.slot() + 1;
1157 let leader = *bank.leader();
1158 bank = Bank::new_from_parent_with_bank_forks(&bank_forks, bank, leader, slot);
1159 bank.register_unique_recent_blockhash_for_test();
1160 bank.transfer(amount, &mint_keypair, &pubkey::new_rand())
1161 .unwrap();
1162 bank.fill_bank_with_ticks_for_tests();
1163 bank.squash();
1164 bank.force_flush_accounts_cache();
1165 }
1166 bank.set_block_id(Some(Hash::default()));
1167
1168 let bank_snapshots_dir = TempDir::new().unwrap();
1169 let snapshot_archives_dir = TempDir::new().unwrap();
1170 let snapshot_config = SnapshotConfig {
1171 full_snapshot_archives_dir: snapshot_archives_dir.path().to_path_buf(),
1172 incremental_snapshot_archives_dir: snapshot_archives_dir.path().to_path_buf(),
1173 bank_snapshots_dir: bank_snapshots_dir.path().to_path_buf(),
1174 ..SnapshotConfig::default()
1175 };
1176 let snapshot =
1177 snapshot_bank_utils::bank_to_full_snapshot_archive(&snapshot_config, &bank).unwrap();
1178 let (_accounts_tempdir, accounts_dir) = snapshot_utils::create_tmp_accounts_dir_for_tests();
1179 let roundtrip_bank = snapshot_bank_utils::bank_from_snapshot_archives(
1180 &[accounts_dir],
1181 &snapshot,
1182 None,
1183 &snapshot_config,
1184 &genesis_config,
1185 &RuntimeConfig::default(),
1186 None,
1187 None, None,
1189 false,
1190 false,
1191 false,
1192 ACCOUNTS_DB_CONFIG_FOR_TESTING,
1193 None,
1194 Arc::default(),
1195 )
1196 .unwrap();
1197
1198 assert_eq!(roundtrip_bank, *bank);
1199 }
1200
1201 #[should_panic(expected = "duplicate accounts were enqueued for hashing")]
1203 #[test_case(Features::None; "no features")]
1204 #[test_case(Features::All; "all features")]
1205 fn test_enqueue_off_chain_accounts_lt_hash_updates_catches_duplicates(features: Features) {
1206 use rand::seq::SliceRandom as _;
1207 let (genesis_config, _) = genesis_config_with(features);
1208 let bank = Bank::new_for_tests(&genesis_config);
1209
1210 let pubkey1 = pubkey::new_rand();
1211 let pubkey2 = pubkey::new_rand();
1212 let pubkey3 = pubkey::new_rand();
1213
1214 let mut accounts = [
1215 (&pubkey1, &AccountSharedData::new(11, 0, &Pubkey::default())),
1217 (&pubkey2, &AccountSharedData::new(21, 0, &Pubkey::default())),
1219 (&pubkey2, &AccountSharedData::new(22, 0, &Pubkey::default())),
1220 (&pubkey3, &AccountSharedData::new(31, 0, &Pubkey::default())),
1222 (&pubkey3, &AccountSharedData::new(32, 0, &Pubkey::default())),
1223 (&pubkey3, &AccountSharedData::new(33, 0, &Pubkey::default())),
1224 ];
1225 accounts.shuffle(&mut rand::rng());
1226
1227 bank.store_accounts((bank.slot(), accounts.as_slice()), None);
1228 }
1229
1230 #[test]
1231 fn test_deduplicate_account_updates() {
1232 let address = Pubkey::new_unique();
1233 let async_progress1 = Arc::new(AccountsLtHashAsyncProgress::new());
1234 let async_progress2 = Arc::new(AccountsLtHashAsyncProgress::new());
1235 let mut deduplicated_updates = ahash::HashMap::default();
1236
1237 AccountsLtHashManager::deduplicate_update(
1238 &mut deduplicated_updates,
1239 QueuedAccountsLtHashUpdate {
1240 async_progress: Arc::clone(&async_progress1),
1241 num_updates: 1,
1242 inner: AccountsLtHashUpdate {
1243 address,
1244 prev_account: Some(AccountSharedData::new(11, 0, &Pubkey::default())),
1245 curr_account: Some(AccountSharedData::new(12, 0, &Pubkey::default())),
1246 },
1247 },
1248 );
1249 AccountsLtHashManager::deduplicate_update(
1251 &mut deduplicated_updates,
1252 QueuedAccountsLtHashUpdate {
1253 async_progress: Arc::clone(&async_progress1),
1254 num_updates: 1,
1255 inner: AccountsLtHashUpdate {
1256 address,
1257 prev_account: Some(AccountSharedData::new(12, 0, &Pubkey::default())),
1258 curr_account: Some(AccountSharedData::new(13, 0, &Pubkey::default())),
1259 },
1260 },
1261 );
1262 AccountsLtHashManager::deduplicate_update(
1265 &mut deduplicated_updates,
1266 QueuedAccountsLtHashUpdate {
1267 async_progress: Arc::clone(&async_progress2),
1268 num_updates: 1,
1269 inner: AccountsLtHashUpdate {
1270 address,
1271 prev_account: Some(AccountSharedData::new(21, 0, &Pubkey::default())),
1272 curr_account: Some(AccountSharedData::new(22, 0, &Pubkey::default())),
1273 },
1274 },
1275 );
1276
1277 assert_eq!(deduplicated_updates.len(), 2);
1278 let key = (Arc::as_ptr(&async_progress1) as usize, address);
1279 let update = deduplicated_updates.get(&key).unwrap();
1280 assert_eq!(update.num_updates, 2);
1281 assert_eq!(update.inner.prev_account.as_ref().unwrap().lamports(), 11);
1282 assert_eq!(update.inner.curr_account.as_ref().unwrap().lamports(), 13);
1283 }
1284
1285 #[test]
1287 fn test_freelist_max_capacity() {
1288 type Container = ahash::HashSet<u64>;
1289
1290 let container = Container::with_capacity(77);
1293
1294 let max_capacity = container.capacity();
1295 let max_bytes = max_capacity * size_of::<u64>();
1296 let mut freelist = HashSetFreelist::new(10, Some(max_bytes));
1297
1298 freelist.try_push(Container::with_capacity(max_capacity + 1));
1300 let stats0 = freelist.stats();
1301 assert_eq!(stats0.num_containers, 0);
1302 assert_eq!(stats0.capacity_elems, 0);
1303 assert_eq!(stats0.capacity_bytes, 0);
1304
1305 freelist.try_push(container);
1307 let stats1 = freelist.stats();
1308 assert_eq!(stats1.num_containers, 1);
1309 assert_eq!(stats1.capacity_elems, max_capacity);
1310 assert_eq!(stats1.capacity_bytes, max_bytes);
1311
1312 freelist.try_push(Container::with_capacity(1));
1314 assert_eq!(freelist.stats(), stats1);
1315
1316 freelist.max_capacity = None;
1318 let container = Container::with_capacity(1);
1319 let container_capacity = container.capacity();
1320 freelist.try_push(container);
1321 let stats2 = freelist.stats();
1322 assert_eq!(stats2.num_containers, 2);
1323 assert_eq!(stats2.capacity_elems, max_capacity + container_capacity);
1324 assert_eq!(
1325 stats2.capacity_bytes,
1326 max_bytes + container_capacity * size_of::<u64>(),
1327 );
1328 }
1329
1330 #[test]
1332 fn test_freelist_concurrent_push() {
1333 let container = ahash::HashSet::<u64>::with_capacity(77);
1336
1337 let num_threads = 16;
1338 let barrier = Arc::new(Barrier::new(num_threads));
1339 let max_capacity = container.capacity();
1340 let max_bytes = max_capacity * size_of::<u64>();
1341 let freelist = Arc::new(HashSetFreelist::new(num_threads, Some(max_bytes)));
1342
1343 let threads: Vec<_> = iter::repeat_with(|| {
1344 let container = container.clone();
1345 let freelist = Arc::clone(&freelist);
1346 let barrier = Arc::clone(&barrier);
1347 thread::spawn(move || {
1348 barrier.wait();
1349 freelist.try_push(container);
1350 })
1351 })
1352 .take(num_threads)
1353 .collect();
1354
1355 for thread in threads {
1356 thread.join().unwrap();
1357 }
1358
1359 let stats = freelist.stats();
1360 assert_eq!(stats.num_containers, 1);
1361 assert_eq!(stats.capacity_elems, max_capacity);
1362 assert_eq!(stats.capacity_bytes, max_bytes);
1363 }
1364}