1use {
2 super::Bank,
3 rayon::prelude::*,
4 solana_accounts_db::accounts_db::AccountsDb,
5 solana_lattice_hash::lt_hash::LtHash,
6 solana_measure::{meas_dur, measure::Measure},
7 solana_sdk::{
8 account::{accounts_equal, AccountSharedData},
9 pubkey::Pubkey,
10 },
11 solana_svm::transaction_processing_callback::AccountState,
12 std::{ops::AddAssign, time::Duration},
13};
14
15impl Bank {
16 pub fn is_accounts_lt_hash_enabled(&self) -> bool {
18 self.rc
19 .accounts
20 .accounts_db
21 .is_experimental_accumulator_hash_enabled()
22 }
23
24 pub fn update_accounts_lt_hash(&self) {
33 debug_assert!(self.is_accounts_lt_hash_enabled());
34 let delta_lt_hash = self.calculate_delta_lt_hash();
35 let mut accounts_lt_hash = self.accounts_lt_hash.lock().unwrap();
36 accounts_lt_hash.0.mix_in(&delta_lt_hash);
37 }
38
39 fn calculate_delta_lt_hash(&self) -> LtHash {
49 debug_assert!(self.is_accounts_lt_hash_enabled());
50 let measure_total = Measure::start("");
51 let slot = self.slot();
52
53 let strictly_ancestors = {
57 let mut ancestors = self.ancestors.clone();
58 ancestors.remove(&self.slot());
59 ancestors
60 };
61
62 if slot == 0 {
63 assert!(strictly_ancestors.is_empty());
75 self.cache_for_accounts_lt_hash.write().unwrap().clear();
76 }
77
78 let (accounts_curr, time_loading_accounts_curr) = meas_dur!({
82 self.rc
83 .accounts
84 .accounts_db
85 .get_pubkey_hash_account_for_slot(slot)
86 });
87 let num_accounts_total = accounts_curr.len();
88
89 #[derive(Debug, Default)]
90 struct Stats {
91 num_cache_misses: usize,
92 num_accounts_unmodified: usize,
93 time_loading_accounts_prev: Duration,
94 time_comparing_accounts: Duration,
95 time_computing_hashes: Duration,
96 time_mixing_hashes: Duration,
97 }
98 impl AddAssign for Stats {
99 fn add_assign(&mut self, other: Self) {
100 self.num_cache_misses += other.num_cache_misses;
101 self.num_accounts_unmodified += other.num_accounts_unmodified;
102 self.time_loading_accounts_prev += other.time_loading_accounts_prev;
103 self.time_comparing_accounts += other.time_comparing_accounts;
104 self.time_computing_hashes += other.time_computing_hashes;
105 self.time_mixing_hashes += other.time_mixing_hashes;
106 }
107 }
108
109 let do_calculate_delta_lt_hash = || {
110 const CHUNK_SIZE: usize = 128;
115 let cache_for_accounts_lt_hash = self.cache_for_accounts_lt_hash.read().unwrap();
116 accounts_curr
117 .par_iter()
118 .fold_chunks(
119 CHUNK_SIZE,
120 || (LtHash::identity(), Stats::default()),
121 |mut accum, elem| {
122 let pubkey = &elem.pubkey;
123 let curr_account = &elem.account;
124
125 let (initial_state_of_account, measure_load) = meas_dur!({
127 match cache_for_accounts_lt_hash.get(pubkey) {
128 Some(initial_state_of_account) => initial_state_of_account.clone(),
129 None => {
130 accum.1.num_cache_misses += 1;
131 let account_slot = self
137 .rc
138 .accounts
139 .load_with_fixed_root_do_not_populate_read_cache(
140 &strictly_ancestors,
141 pubkey,
142 );
143 match account_slot {
144 Some((account, _slot)) => {
145 InitialStateOfAccount::Alive(account)
146 }
147 None => InitialStateOfAccount::Dead,
148 }
149 }
150 }
151 });
152 accum.1.time_loading_accounts_prev += measure_load;
153
154 match initial_state_of_account {
156 InitialStateOfAccount::Dead => {
157 }
159 InitialStateOfAccount::Alive(prev_account) => {
160 let (are_accounts_equal, measure_is_equal) =
161 meas_dur!(accounts_equal(curr_account, &prev_account));
162 accum.1.time_comparing_accounts += measure_is_equal;
163 if are_accounts_equal {
164 accum.1.num_accounts_unmodified += 1;
166 return accum;
167 }
168 let (prev_lt_hash, measure_hashing) =
169 meas_dur!(AccountsDb::lt_hash_account(&prev_account, pubkey));
170 let (_, measure_mixing) =
171 meas_dur!(accum.0.mix_out(&prev_lt_hash.0));
172 accum.1.time_computing_hashes += measure_hashing;
173 accum.1.time_mixing_hashes += measure_mixing;
174 }
175 }
176
177 let (curr_lt_hash, measure_hashing) =
179 meas_dur!(AccountsDb::lt_hash_account(curr_account, pubkey));
180 let (_, measure_mixing) = meas_dur!(accum.0.mix_in(&curr_lt_hash.0));
181 accum.1.time_computing_hashes += measure_hashing;
182 accum.1.time_mixing_hashes += measure_mixing;
183
184 accum
185 },
186 )
187 .reduce(
188 || (LtHash::identity(), Stats::default()),
189 |mut accum, elem| {
190 accum.0.mix_in(&elem.0);
191 accum.1 += elem.1;
192 accum
193 },
194 )
195 };
196 let (delta_lt_hash, stats) = self
197 .rc
198 .accounts
199 .accounts_db
200 .thread_pool
201 .install(do_calculate_delta_lt_hash);
202
203 let total_time = measure_total.end_as_duration();
204 let num_accounts_modified =
205 num_accounts_total.saturating_sub(stats.num_accounts_unmodified);
206 datapoint_info!(
207 "bank-accounts_lt_hash",
208 ("slot", slot, i64),
209 ("num_accounts_total", num_accounts_total, i64),
210 ("num_accounts_modified", num_accounts_modified, i64),
211 (
212 "num_accounts_unmodified",
213 stats.num_accounts_unmodified,
214 i64
215 ),
216 ("num_cache_misses", stats.num_cache_misses, i64),
217 ("total_us", total_time.as_micros(), i64),
218 (
219 "loading_accounts_curr_us",
220 time_loading_accounts_curr.as_micros(),
221 i64
222 ),
223 (
224 "par_loading_accounts_prev_us",
225 stats.time_loading_accounts_prev.as_micros(),
226 i64
227 ),
228 (
229 "par_comparing_accounts_us",
230 stats.time_comparing_accounts.as_micros(),
231 i64
232 ),
233 (
234 "par_computing_hashes_us",
235 stats.time_computing_hashes.as_micros(),
236 i64
237 ),
238 (
239 "par_mixing_hashes_us",
240 stats.time_mixing_hashes.as_micros(),
241 i64
242 ),
243 );
244
245 delta_lt_hash
246 }
247
248 pub fn inspect_account_for_accounts_lt_hash(
254 &self,
255 address: &Pubkey,
256 account_state: &AccountState,
257 is_writable: bool,
258 ) {
259 debug_assert!(self.is_accounts_lt_hash_enabled());
260 if !is_writable {
261 return;
263 }
264
265 let is_in_cache = self
268 .cache_for_accounts_lt_hash
269 .read()
270 .unwrap()
271 .contains_key(address);
272 if !is_in_cache {
273 self.cache_for_accounts_lt_hash
274 .write()
275 .unwrap()
276 .entry(*address)
277 .or_insert_with(|| match account_state {
278 AccountState::Dead => InitialStateOfAccount::Dead,
279 AccountState::Alive(account) => {
280 InitialStateOfAccount::Alive((*account).clone())
281 }
282 });
283 }
284 }
285}
286
287#[derive(Debug, Clone)]
289pub enum InitialStateOfAccount {
290 Dead,
292 Alive(AccountSharedData),
294}
295
296#[cfg(test)]
297mod tests {
298 use {
299 super::*,
300 crate::{
301 bank::tests::new_bank_from_parent_with_bank_forks, genesis_utils,
302 runtime_config::RuntimeConfig, snapshot_bank_utils, snapshot_config::SnapshotConfig,
303 snapshot_utils,
304 },
305 solana_accounts_db::accounts_db::{
306 AccountsDbConfig, DuplicatesLtHash, ACCOUNTS_DB_CONFIG_FOR_TESTING,
307 },
308 solana_sdk::{
309 account::{ReadableAccount as _, WritableAccount as _},
310 fee_calculator::FeeRateGovernor,
311 genesis_config::{self, GenesisConfig},
312 native_token::LAMPORTS_PER_SOL,
313 pubkey::{self, Pubkey},
314 signature::Signer as _,
315 signer::keypair::Keypair,
316 },
317 std::{cmp, collections::HashMap, ops::RangeFull, str::FromStr as _, sync::Arc},
318 tempfile::TempDir,
319 test_case::test_case,
320 };
321
322 #[derive(Debug, Copy, Clone, Eq, PartialEq)]
324 enum Features {
325 None,
327 All,
329 }
330
331 fn genesis_config_with(features: Features) -> (GenesisConfig, Keypair) {
333 let mint_lamports = 123_456_789 * LAMPORTS_PER_SOL;
334 match features {
335 Features::None => genesis_config::create_genesis_config(mint_lamports),
336 Features::All => {
337 let info = genesis_utils::create_genesis_config(mint_lamports);
338 (info.genesis_config, info.mint_keypair)
339 }
340 }
341 }
342
343 #[test]
344 fn test_update_accounts_lt_hash() {
345 let keypair1 = Keypair::new();
356 let keypair2 = Keypair::new();
357 let keypair3 = Keypair::new();
358 let keypair4 = Keypair::new();
359 let keypair5 = Keypair::new();
360
361 let (mut genesis_config, mint_keypair) =
362 genesis_config::create_genesis_config(123_456_789 * LAMPORTS_PER_SOL);
363 genesis_config.fee_rate_governor = FeeRateGovernor::new(0, 0);
364 let (bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config);
365 bank.rc
366 .accounts
367 .accounts_db
368 .set_is_experimental_accumulator_hash_enabled(true);
369
370 assert!(bank.is_accounts_lt_hash_enabled());
372
373 let amount = cmp::max(
374 bank.get_minimum_balance_for_rent_exemption(0),
375 LAMPORTS_PER_SOL,
376 );
377
378 bank.register_unique_recent_blockhash_for_test();
381 bank.transfer(amount, &mint_keypair, &keypair1.pubkey())
382 .unwrap();
383 bank.transfer(amount, &mint_keypair, &keypair2.pubkey())
384 .unwrap();
385 bank.transfer(amount, &mint_keypair, &keypair5.pubkey())
386 .unwrap();
387
388 bank.freeze();
390 let prev_accounts_lt_hash = bank.accounts_lt_hash.lock().unwrap().clone();
391
392 let prev_mint = bank.get_account_with_fixed_root(&mint_keypair.pubkey());
394 let prev_account1 = bank.get_account_with_fixed_root(&keypair1.pubkey());
395 let prev_account2 = bank.get_account_with_fixed_root(&keypair2.pubkey());
396 let prev_account3 = bank.get_account_with_fixed_root(&keypair3.pubkey());
397 let prev_account4 = bank.get_account_with_fixed_root(&keypair4.pubkey());
398 let prev_account5 = bank.get_account_with_fixed_root(&keypair5.pubkey());
399
400 assert!(prev_mint.is_some());
401 assert!(prev_account1.is_some());
402 assert!(prev_account2.is_some());
403 assert!(prev_account3.is_none());
404 assert!(prev_account4.is_none());
405 assert!(prev_account5.is_some());
406
407 let sysvars = [
411 Pubkey::from_str("SysvarS1otHashes111111111111111111111111111").unwrap(),
412 Pubkey::from_str("SysvarC1ock11111111111111111111111111111111").unwrap(),
413 Pubkey::from_str("SysvarRecentB1ockHashes11111111111111111111").unwrap(),
414 Pubkey::from_str("SysvarS1otHistory11111111111111111111111111").unwrap(),
415 ];
416 let prev_sysvar_accounts: Vec<_> = sysvars
417 .iter()
418 .map(|address| bank.get_account_with_fixed_root(address))
419 .collect();
420
421 let bank = {
422 let slot = bank.slot() + 1;
423 new_bank_from_parent_with_bank_forks(&bank_forks, bank, &Pubkey::default(), slot)
424 };
425
426 bank.register_unique_recent_blockhash_for_test();
428 bank.transfer(amount, &keypair2, &keypair1.pubkey())
429 .unwrap();
430
431 bank.register_unique_recent_blockhash_for_test();
434 bank.transfer(amount, &mint_keypair, &keypair4.pubkey())
435 .unwrap();
436 bank.register_unique_recent_blockhash_for_test();
437 bank.transfer(amount, &keypair4, &keypair3.pubkey())
438 .unwrap();
439
440 bank.rc.accounts.store_cached(
442 (
443 bank.slot(),
444 [(&keypair5.pubkey(), &prev_account5.clone().unwrap())].as_slice(),
445 ),
446 None,
447 );
448
449 bank.freeze();
451
452 let actual_delta_lt_hash = bank.calculate_delta_lt_hash();
453 let post_accounts_lt_hash = bank.accounts_lt_hash.lock().unwrap().clone();
454 let post_mint = bank.get_account_with_fixed_root(&mint_keypair.pubkey());
455 let post_account1 = bank.get_account_with_fixed_root(&keypair1.pubkey());
456 let post_account2 = bank.get_account_with_fixed_root(&keypair2.pubkey());
457 let post_account3 = bank.get_account_with_fixed_root(&keypair3.pubkey());
458 let post_account4 = bank.get_account_with_fixed_root(&keypair4.pubkey());
459 let post_account5 = bank.get_account_with_fixed_root(&keypair5.pubkey());
460
461 assert!(post_mint.is_some());
462 assert!(post_account1.is_some());
463 assert!(post_account2.is_none());
464 assert!(post_account3.is_some());
465 assert!(post_account4.is_none());
466 assert!(post_account5.is_some());
467
468 let post_sysvar_accounts: Vec<_> = sysvars
469 .iter()
470 .map(|address| bank.get_account_with_fixed_root(address))
471 .collect();
472
473 let mut expected_delta_lt_hash = LtHash::identity();
474 let mut expected_accounts_lt_hash = prev_accounts_lt_hash.clone();
475 let mut updater =
476 |address: &Pubkey, prev: Option<AccountSharedData>, post: Option<AccountSharedData>| {
477 if let Some(prev) = prev {
479 let prev_lt_hash = AccountsDb::lt_hash_account(&prev, address);
480 expected_delta_lt_hash.mix_out(&prev_lt_hash.0);
481 expected_accounts_lt_hash.0.mix_out(&prev_lt_hash.0);
482 }
483
484 let post = post.unwrap_or_default();
486 let post_lt_hash = AccountsDb::lt_hash_account(&post, address);
487 expected_delta_lt_hash.mix_in(&post_lt_hash.0);
488 expected_accounts_lt_hash.0.mix_in(&post_lt_hash.0);
489 };
490 updater(&mint_keypair.pubkey(), prev_mint, post_mint);
491 updater(&keypair1.pubkey(), prev_account1, post_account1);
492 updater(&keypair2.pubkey(), prev_account2, post_account2);
493 updater(&keypair3.pubkey(), prev_account3, post_account3);
494 updater(&keypair4.pubkey(), prev_account4, post_account4);
495 updater(&keypair5.pubkey(), prev_account5, post_account5);
496 for (i, sysvar) in sysvars.iter().enumerate() {
497 updater(
498 sysvar,
499 prev_sysvar_accounts[i].clone(),
500 post_sysvar_accounts[i].clone(),
501 );
502 }
503
504 let expected = expected_delta_lt_hash.checksum();
506 let actual = actual_delta_lt_hash.checksum();
507 assert_eq!(
508 expected, actual,
509 "delta_lt_hash, expected: {expected}, actual: {actual}",
510 );
511
512 let expected = expected_accounts_lt_hash.0.checksum();
514 let actual = post_accounts_lt_hash.0.checksum();
515 assert_eq!(
516 expected, actual,
517 "accounts_lt_hash, expected: {expected}, actual: {actual}",
518 );
519 }
520
521 #[test_case(Features::None; "no features")]
527 #[test_case(Features::All; "all features")]
528 fn test_slot0_accounts_lt_hash(features: Features) {
529 let (genesis_config, mint_keypair) = genesis_config_with(features);
530 let (bank, _bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config);
531 bank.rc
532 .accounts
533 .accounts_db
534 .set_is_experimental_accumulator_hash_enabled(true);
535
536 assert!(bank.is_accounts_lt_hash_enabled());
538
539 assert_eq!(bank.slot(), 0);
541
542 bank.transfer(LAMPORTS_PER_SOL, &mint_keypair, &Pubkey::new_unique())
544 .unwrap();
545
546 bank.freeze();
548 let actual_accounts_lt_hash = bank.accounts_lt_hash.lock().unwrap().clone();
549
550 let calculated_accounts_lt_hash = bank
552 .rc
553 .accounts
554 .accounts_db
555 .calculate_accounts_lt_hash_at_startup_from_index(&bank.ancestors, bank.slot());
556 assert_eq!(actual_accounts_lt_hash, calculated_accounts_lt_hash);
557 }
558
559 #[test_case(Features::None; "no features")]
560 #[test_case(Features::All; "all features")]
561 fn test_inspect_account_for_accounts_lt_hash(features: Features) {
562 let (genesis_config, _mint_keypair) = genesis_config_with(features);
563 let (bank, _bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config);
564 bank.rc
565 .accounts
566 .accounts_db
567 .set_is_experimental_accumulator_hash_enabled(true);
568
569 assert!(bank.is_accounts_lt_hash_enabled());
571
572 assert_eq!(bank.cache_for_accounts_lt_hash.read().unwrap().len(), 0);
574
575 bank.inspect_account_for_accounts_lt_hash(
577 &Pubkey::new_unique(),
578 &AccountState::Dead,
579 false,
580 );
581 bank.inspect_account_for_accounts_lt_hash(
582 &Pubkey::new_unique(),
583 &AccountState::Alive(&AccountSharedData::default()),
584 false,
585 );
586 assert_eq!(bank.cache_for_accounts_lt_hash.read().unwrap().len(), 0);
587
588 let address = Pubkey::new_unique();
590 bank.inspect_account_for_accounts_lt_hash(&address, &AccountState::Dead, true);
591 assert_eq!(bank.cache_for_accounts_lt_hash.read().unwrap().len(), 1);
592 assert!(bank
593 .cache_for_accounts_lt_hash
594 .read()
595 .unwrap()
596 .contains_key(&address));
597
598 let address = Pubkey::new_unique();
600 let initial_lamports = 123;
601 let mut account = AccountSharedData::new(initial_lamports, 0, &Pubkey::default());
602 bank.inspect_account_for_accounts_lt_hash(&address, &AccountState::Alive(&account), true);
603 assert_eq!(bank.cache_for_accounts_lt_hash.read().unwrap().len(), 2);
604 if let InitialStateOfAccount::Alive(cached_account) = bank
605 .cache_for_accounts_lt_hash
606 .read()
607 .unwrap()
608 .get(&address)
609 .unwrap()
610 {
611 assert_eq!(*cached_account, account);
612 } else {
613 panic!("wrong initial state for account");
614 };
615
616 let updated_lamports = account.lamports() + 1;
618 account.set_lamports(updated_lamports);
619 bank.inspect_account_for_accounts_lt_hash(&address, &AccountState::Alive(&account), true);
620 assert_eq!(bank.cache_for_accounts_lt_hash.read().unwrap().len(), 2);
621 if let InitialStateOfAccount::Alive(cached_account) = bank
622 .cache_for_accounts_lt_hash
623 .read()
624 .unwrap()
625 .get(&address)
626 .unwrap()
627 {
628 assert_eq!(cached_account.lamports(), initial_lamports);
629 } else {
630 panic!("wrong initial state for account");
631 };
632
633 {
635 let address = Pubkey::new_unique();
636 bank.inspect_account_for_accounts_lt_hash(&address, &AccountState::Dead, true);
637 assert_eq!(bank.cache_for_accounts_lt_hash.read().unwrap().len(), 3);
638 match bank
639 .cache_for_accounts_lt_hash
640 .read()
641 .unwrap()
642 .get(&address)
643 .unwrap()
644 {
645 InitialStateOfAccount::Dead => { }
646 _ => panic!("wrong initial state for account"),
647 };
648
649 bank.inspect_account_for_accounts_lt_hash(
650 &address,
651 &AccountState::Alive(&AccountSharedData::default()),
652 true,
653 );
654 assert_eq!(bank.cache_for_accounts_lt_hash.read().unwrap().len(), 3);
655 match bank
656 .cache_for_accounts_lt_hash
657 .read()
658 .unwrap()
659 .get(&address)
660 .unwrap()
661 {
662 InitialStateOfAccount::Dead => { }
663 _ => panic!("wrong initial state for account"),
664 };
665 }
666 }
667
668 #[test_case(Features::None; "no features")]
669 #[test_case(Features::All; "all features")]
670 fn test_calculate_accounts_lt_hash_at_startup_from_index(features: Features) {
671 let (genesis_config, mint_keypair) = genesis_config_with(features);
672 let (mut bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config);
673 bank.rc
674 .accounts
675 .accounts_db
676 .set_is_experimental_accumulator_hash_enabled(true);
677
678 assert!(bank.is_accounts_lt_hash_enabled());
680
681 let amount = cmp::max(
682 bank.get_minimum_balance_for_rent_exemption(0),
683 LAMPORTS_PER_SOL,
684 );
685
686 for _ in 0..7 {
689 let slot = bank.slot() + 1;
690 bank =
691 new_bank_from_parent_with_bank_forks(&bank_forks, bank, &Pubkey::default(), slot);
692 for _ in 0..13 {
693 bank.register_unique_recent_blockhash_for_test();
694 bank.transfer(amount, &mint_keypair, &pubkey::new_rand())
697 .unwrap();
698 }
699 bank.freeze();
700 }
701 let expected_accounts_lt_hash = bank.accounts_lt_hash.lock().unwrap().clone();
702
703 bank.squash();
706 bank.force_flush_accounts_cache();
707
708 let calculated_accounts_lt_hash = bank
710 .rc
711 .accounts
712 .accounts_db
713 .calculate_accounts_lt_hash_at_startup_from_index(&bank.ancestors, bank.slot());
714 assert_eq!(expected_accounts_lt_hash, calculated_accounts_lt_hash);
715 }
716
717 #[test_case(Features::None; "no features")]
718 #[test_case(Features::All; "all features")]
719 fn test_calculate_accounts_lt_hash_at_startup_from_storages(features: Features) {
720 let (genesis_config, mint_keypair) = genesis_config_with(features);
721 let (mut bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config);
722 bank.rc
723 .accounts
724 .accounts_db
725 .set_is_experimental_accumulator_hash_enabled(true);
726
727 assert!(bank.is_accounts_lt_hash_enabled());
729
730 let amount = cmp::max(
731 bank.get_minimum_balance_for_rent_exemption(0),
732 LAMPORTS_PER_SOL,
733 );
734
735 let duplicate_pubkey = pubkey::new_rand();
737
738 for _ in 0..7 {
741 let slot = bank.slot() + 1;
742 bank =
743 new_bank_from_parent_with_bank_forks(&bank_forks, bank, &Pubkey::default(), slot);
744 for _ in 0..9 {
745 bank.register_unique_recent_blockhash_for_test();
746 bank.transfer(amount, &mint_keypair, &pubkey::new_rand())
750 .unwrap();
751
752 bank.register_unique_recent_blockhash_for_test();
753 bank.transfer(amount, &mint_keypair, &duplicate_pubkey)
754 .unwrap();
755 }
756
757 bank.squash();
759 bank.force_flush_accounts_cache();
760 }
761 let expected_accounts_lt_hash = bank.accounts_lt_hash.lock().unwrap().clone();
762
763 let (mut storages, _slots) = bank
765 .rc
766 .accounts
767 .accounts_db
768 .get_snapshot_storages(RangeFull);
769 storages.sort_unstable_by_key(|storage| cmp::Reverse(storage.slot()));
772 let storages = storages.into_boxed_slice();
773
774 let mut stored_accounts_map = HashMap::<_, Vec<_>>::new();
776 for storage in &storages {
777 storage.accounts.scan_accounts(|stored_account_meta| {
778 let pubkey = stored_account_meta.pubkey();
779 let account_lt_hash = AccountsDb::lt_hash_account(&stored_account_meta, pubkey);
780 stored_accounts_map
781 .entry(*pubkey)
782 .or_default()
783 .push(account_lt_hash)
784 });
785 }
786
787 let duplicates_lt_hash = stored_accounts_map
790 .values()
791 .map(|lt_hashes| {
792 <_hashes[1..]
794 })
795 .fold(LtHash::identity(), |mut accum, duplicate_lt_hashes| {
796 for duplicate_lt_hash in duplicate_lt_hashes {
797 accum.mix_in(&duplicate_lt_hash.0);
798 }
799 accum
800 });
801 let duplicates_lt_hash = DuplicatesLtHash(duplicates_lt_hash);
802
803 let calculated_accounts_lt_hash_from_storages = bank
805 .rc
806 .accounts
807 .accounts_db
808 .calculate_accounts_lt_hash_at_startup_from_storages(&storages, &duplicates_lt_hash);
809 assert_eq!(
810 expected_accounts_lt_hash,
811 calculated_accounts_lt_hash_from_storages
812 );
813 }
814
815 #[test_case(Features::None; "no features")]
816 #[test_case(Features::All; "all features")]
817 fn test_verify_accounts_lt_hash_at_startup(features: Features) {
818 let (genesis_config, mint_keypair) = genesis_config_with(features);
819 let (mut bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config);
820 bank.rc
821 .accounts
822 .accounts_db
823 .set_is_experimental_accumulator_hash_enabled(true);
824
825 assert!(bank.is_accounts_lt_hash_enabled());
827
828 let amount = cmp::max(
829 bank.get_minimum_balance_for_rent_exemption(0),
830 LAMPORTS_PER_SOL,
831 );
832
833 let duplicate_pubkey = pubkey::new_rand();
835
836 for _ in 0..9 {
839 let slot = bank.slot() + 1;
840 bank =
841 new_bank_from_parent_with_bank_forks(&bank_forks, bank, &Pubkey::default(), slot);
842 for _ in 0..3 {
843 bank.register_unique_recent_blockhash_for_test();
844 bank.transfer(amount, &mint_keypair, &pubkey::new_rand())
845 .unwrap();
846 bank.register_unique_recent_blockhash_for_test();
847 bank.transfer(amount, &mint_keypair, &duplicate_pubkey)
848 .unwrap();
849 }
850
851 bank.fill_bank_with_ticks_for_tests();
853 bank.squash();
854 bank.force_flush_accounts_cache();
855 }
856
857 let snapshot_config = SnapshotConfig::default();
859 let bank_snapshots_dir = TempDir::new().unwrap();
860 let snapshot_archives_dir = TempDir::new().unwrap();
861 let snapshot = snapshot_bank_utils::bank_to_full_snapshot_archive(
862 &bank_snapshots_dir,
863 &bank,
864 Some(snapshot_config.snapshot_version),
865 &snapshot_archives_dir,
866 &snapshot_archives_dir,
867 snapshot_config.archive_format,
868 )
869 .unwrap();
870 let (_accounts_tempdir, accounts_dir) = snapshot_utils::create_tmp_accounts_dir_for_tests();
871 let accounts_db_config = AccountsDbConfig {
872 enable_experimental_accumulator_hash: true,
873 ..ACCOUNTS_DB_CONFIG_FOR_TESTING
874 };
875 let (roundtrip_bank, _) = snapshot_bank_utils::bank_from_snapshot_archives(
876 &[accounts_dir],
877 &bank_snapshots_dir,
878 &snapshot,
879 None,
880 &genesis_config,
881 &RuntimeConfig::default(),
882 None,
883 None,
884 None,
885 false,
886 false,
887 false,
888 false,
889 Some(accounts_db_config),
890 None,
891 Arc::default(),
892 )
893 .unwrap();
894
895 roundtrip_bank.wait_for_initial_accounts_hash_verification_completed_for_tests();
897 assert_eq!(roundtrip_bank, *bank);
898 }
899}