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 feature_set,
10 pubkey::Pubkey,
11 },
12 solana_svm::transaction_processing_callback::AccountState,
13 std::{
14 ops::AddAssign,
15 sync::atomic::{AtomicU64, Ordering},
16 time::Duration,
17 },
18};
19
20impl Bank {
21 pub fn is_accounts_lt_hash_enabled(&self) -> bool {
23 self.rc
24 .accounts
25 .accounts_db
26 .is_experimental_accumulator_hash_enabled()
27 || self
28 .feature_set
29 .is_active(&feature_set::accounts_lt_hash::id())
30 }
31
32 pub fn is_snapshots_lt_hash_enabled(&self) -> bool {
34 self.is_accounts_lt_hash_enabled()
35 && (self
36 .rc
37 .accounts
38 .accounts_db
39 .snapshots_use_experimental_accumulator_hash()
40 || self
41 .feature_set
42 .is_active(&feature_set::snapshots_lt_hash::id()))
43 }
44
45 pub fn update_accounts_lt_hash(&self) {
54 debug_assert!(self.is_accounts_lt_hash_enabled());
55 let delta_lt_hash = self.calculate_delta_lt_hash();
56 let mut accounts_lt_hash = self.accounts_lt_hash.lock().unwrap();
57 accounts_lt_hash.0.mix_in(&delta_lt_hash);
58
59 if !self
61 .feature_set
62 .is_active(&feature_set::accounts_lt_hash::id())
63 {
64 log::info!(
65 "updated accounts lattice hash for slot {}, delta_lt_hash checksum: {}, accounts_lt_hash checksum: {}",
66 self.slot(),
67 delta_lt_hash.checksum(),
68 accounts_lt_hash.0.checksum(),
69 );
70 }
71 }
72
73 fn calculate_delta_lt_hash(&self) -> LtHash {
83 debug_assert!(self.is_accounts_lt_hash_enabled());
84 let measure_total = Measure::start("");
85 let slot = self.slot();
86
87 let strictly_ancestors = {
91 let mut ancestors = self.ancestors.clone();
92 ancestors.remove(&self.slot());
93 ancestors
94 };
95
96 if slot == 0 {
97 assert!(strictly_ancestors.is_empty());
109 self.cache_for_accounts_lt_hash.clear();
110 }
111
112 let (accounts_curr, time_loading_accounts_curr) = meas_dur!({
116 self.rc
117 .accounts
118 .accounts_db
119 .get_pubkey_account_for_slot(slot)
120 });
121 let num_accounts_total = accounts_curr.len();
122
123 #[derive(Debug, Default)]
124 struct Stats {
125 num_cache_misses: usize,
126 num_accounts_unmodified: usize,
127 time_loading_accounts_prev: Duration,
128 time_comparing_accounts: Duration,
129 time_computing_hashes: Duration,
130 time_mixing_hashes: Duration,
131 }
132 impl AddAssign for Stats {
133 fn add_assign(&mut self, other: Self) {
134 self.num_cache_misses += other.num_cache_misses;
135 self.num_accounts_unmodified += other.num_accounts_unmodified;
136 self.time_loading_accounts_prev += other.time_loading_accounts_prev;
137 self.time_comparing_accounts += other.time_comparing_accounts;
138 self.time_computing_hashes += other.time_computing_hashes;
139 self.time_mixing_hashes += other.time_mixing_hashes;
140 }
141 }
142
143 let do_calculate_delta_lt_hash = || {
144 const CHUNK_SIZE: usize = 128;
149 accounts_curr
150 .par_iter()
151 .fold_chunks(
152 CHUNK_SIZE,
153 || (LtHash::identity(), Stats::default()),
154 |mut accum, (pubkey, curr_account)| {
155 let (initial_state_of_account, measure_load) = meas_dur!({
157 let cache_value = self
158 .cache_for_accounts_lt_hash
159 .get(pubkey)
160 .map(|entry| entry.value().clone());
161 match cache_value {
162 Some(CacheValue::InspectAccount(initial_state_of_account)) => {
163 initial_state_of_account
164 }
165 Some(CacheValue::BankNew) | None => {
166 accum.1.num_cache_misses += 1;
167 let account_slot = self
174 .rc
175 .accounts
176 .load_with_fixed_root_do_not_populate_read_cache(
177 &strictly_ancestors,
178 pubkey,
179 );
180 match account_slot {
181 Some((account, _slot)) => {
182 InitialStateOfAccount::Alive(account)
183 }
184 None => InitialStateOfAccount::Dead,
185 }
186 }
187 }
188 });
189 accum.1.time_loading_accounts_prev += measure_load;
190
191 match initial_state_of_account {
193 InitialStateOfAccount::Dead => {
194 }
196 InitialStateOfAccount::Alive(prev_account) => {
197 let (are_accounts_equal, measure_is_equal) =
198 meas_dur!(accounts_equal(curr_account, &prev_account));
199 accum.1.time_comparing_accounts += measure_is_equal;
200 if are_accounts_equal {
201 accum.1.num_accounts_unmodified += 1;
203 return accum;
204 }
205 let (prev_lt_hash, measure_hashing) =
206 meas_dur!(AccountsDb::lt_hash_account(&prev_account, pubkey));
207 let (_, measure_mixing) =
208 meas_dur!(accum.0.mix_out(&prev_lt_hash.0));
209 accum.1.time_computing_hashes += measure_hashing;
210 accum.1.time_mixing_hashes += measure_mixing;
211 }
212 }
213
214 let (curr_lt_hash, measure_hashing) =
216 meas_dur!(AccountsDb::lt_hash_account(curr_account, pubkey));
217 let (_, measure_mixing) = meas_dur!(accum.0.mix_in(&curr_lt_hash.0));
218 accum.1.time_computing_hashes += measure_hashing;
219 accum.1.time_mixing_hashes += measure_mixing;
220
221 accum
222 },
223 )
224 .reduce(
225 || (LtHash::identity(), Stats::default()),
226 |mut accum, elem| {
227 accum.0.mix_in(&elem.0);
228 accum.1 += elem.1;
229 accum
230 },
231 )
232 };
233 let (delta_lt_hash, stats) = self
234 .rc
235 .accounts
236 .accounts_db
237 .thread_pool
238 .install(do_calculate_delta_lt_hash);
239
240 let total_time = measure_total.end_as_duration();
241 let num_accounts_modified =
242 num_accounts_total.saturating_sub(stats.num_accounts_unmodified);
243 datapoint_info!(
244 "bank-accounts_lt_hash",
245 ("slot", slot, i64),
246 ("num_accounts_total", num_accounts_total, i64),
247 ("num_accounts_modified", num_accounts_modified, i64),
248 (
249 "num_accounts_unmodified",
250 stats.num_accounts_unmodified,
251 i64
252 ),
253 ("num_cache_misses", stats.num_cache_misses, i64),
254 ("total_us", total_time.as_micros(), i64),
255 (
256 "loading_accounts_curr_us",
257 time_loading_accounts_curr.as_micros(),
258 i64
259 ),
260 (
261 "par_loading_accounts_prev_us",
262 stats.time_loading_accounts_prev.as_micros(),
263 i64
264 ),
265 (
266 "par_comparing_accounts_us",
267 stats.time_comparing_accounts.as_micros(),
268 i64
269 ),
270 (
271 "par_computing_hashes_us",
272 stats.time_computing_hashes.as_micros(),
273 i64
274 ),
275 (
276 "par_mixing_hashes_us",
277 stats.time_mixing_hashes.as_micros(),
278 i64
279 ),
280 (
281 "num_inspect_account_hits",
282 self.stats_for_accounts_lt_hash
283 .num_inspect_account_hits
284 .load(Ordering::Relaxed),
285 i64
286 ),
287 (
288 "num_inspect_account_misses",
289 self.stats_for_accounts_lt_hash
290 .num_inspect_account_misses
291 .load(Ordering::Relaxed),
292 i64
293 ),
294 (
295 "inspect_account_lookup_ns",
296 self.stats_for_accounts_lt_hash
297 .inspect_account_lookup_time_ns
298 .load(Ordering::Relaxed),
299 i64
300 ),
301 (
302 "inspect_account_insert_ns",
303 self.stats_for_accounts_lt_hash
304 .inspect_account_insert_time_ns
305 .load(Ordering::Relaxed),
306 i64
307 ),
308 );
309
310 delta_lt_hash
311 }
312
313 pub fn inspect_account_for_accounts_lt_hash(
319 &self,
320 address: &Pubkey,
321 account_state: &AccountState,
322 is_writable: bool,
323 ) {
324 debug_assert!(self.is_accounts_lt_hash_enabled());
325 if !is_writable {
326 return;
328 }
329
330 let (is_in_cache, lookup_time) =
333 meas_dur!(self.cache_for_accounts_lt_hash.contains_key(address));
334 if !is_in_cache {
335 let (_, insert_time) = meas_dur!({
336 self.cache_for_accounts_lt_hash
337 .entry(*address)
338 .or_insert_with(|| {
339 let initial_state_of_account = match account_state {
340 AccountState::Dead => InitialStateOfAccount::Dead,
341 AccountState::Alive(account) => {
342 InitialStateOfAccount::Alive((*account).clone())
343 }
344 };
345 CacheValue::InspectAccount(initial_state_of_account)
346 });
347 });
348
349 self.stats_for_accounts_lt_hash
350 .num_inspect_account_misses
351 .fetch_add(1, Ordering::Relaxed);
352 self.stats_for_accounts_lt_hash
353 .inspect_account_insert_time_ns
354 .fetch_add(insert_time.as_nanos() as u64, Ordering::Relaxed);
356 } else {
357 self.stats_for_accounts_lt_hash
359 .num_inspect_account_hits
360 .fetch_add(1, Ordering::Relaxed);
361 }
362
363 self.stats_for_accounts_lt_hash
364 .inspect_account_lookup_time_ns
365 .fetch_add(lookup_time.as_nanos() as u64, Ordering::Relaxed);
367 }
368}
369
370#[derive(Debug, Default)]
372pub struct Stats {
373 num_inspect_account_hits: AtomicU64,
375 num_inspect_account_misses: AtomicU64,
377 inspect_account_lookup_time_ns: AtomicU64,
379 inspect_account_insert_time_ns: AtomicU64,
381}
382
383#[derive(Debug, Clone, PartialEq)]
385pub enum InitialStateOfAccount {
386 Dead,
388 Alive(AccountSharedData),
390}
391
392#[derive(Debug, Clone, PartialEq)]
394pub enum CacheValue {
395 InspectAccount(InitialStateOfAccount),
398 BankNew,
401}
402
403#[cfg(test)]
404mod tests {
405 use {
406 super::*,
407 crate::{
408 bank::tests::{new_bank_from_parent_with_bank_forks, new_from_parent_next_epoch},
409 genesis_utils,
410 runtime_config::RuntimeConfig,
411 snapshot_bank_utils,
412 snapshot_config::SnapshotConfig,
413 snapshot_utils,
414 },
415 solana_accounts_db::accounts_db::{
416 AccountsDbConfig, DuplicatesLtHash, ACCOUNTS_DB_CONFIG_FOR_TESTING,
417 },
418 solana_sdk::{
419 account::{ReadableAccount as _, WritableAccount as _},
420 feature::{self, Feature},
421 fee_calculator::FeeRateGovernor,
422 genesis_config::{self, GenesisConfig},
423 native_token::LAMPORTS_PER_SOL,
424 pubkey::{self, Pubkey},
425 signature::Signer as _,
426 signer::keypair::Keypair,
427 },
428 std::{cmp, collections::HashMap, ops::RangeFull, str::FromStr as _, sync::Arc},
429 tempfile::TempDir,
430 test_case::{test_case, test_matrix},
431 };
432
433 #[derive(Debug, Copy, Clone, Eq, PartialEq)]
435 enum Features {
436 None,
438 All,
440 }
441
442 #[derive(Debug, Copy, Clone, Eq, PartialEq)]
444 enum Cli {
445 Off,
447 On,
449 }
450
451 fn genesis_config_with(features: Features) -> (GenesisConfig, Keypair) {
453 let mint_lamports = 123_456_789 * LAMPORTS_PER_SOL;
454 match features {
455 Features::None => genesis_config::create_genesis_config(mint_lamports),
456 Features::All => {
457 let info = genesis_utils::create_genesis_config(mint_lamports);
458 (info.genesis_config, info.mint_keypair)
459 }
460 }
461 }
462
463 #[test]
464 fn test_update_accounts_lt_hash() {
465 let keypair1 = Keypair::new();
476 let keypair2 = Keypair::new();
477 let keypair3 = Keypair::new();
478 let keypair4 = Keypair::new();
479 let keypair5 = Keypair::new();
480
481 let (mut genesis_config, mint_keypair) =
482 genesis_config::create_genesis_config(123_456_789 * LAMPORTS_PER_SOL);
483 genesis_config.fee_rate_governor = FeeRateGovernor::new(0, 0);
484 let (bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config);
485 bank.rc
486 .accounts
487 .accounts_db
488 .set_is_experimental_accumulator_hash_enabled(true);
489
490 assert!(bank.is_accounts_lt_hash_enabled());
492
493 let amount = cmp::max(
494 bank.get_minimum_balance_for_rent_exemption(0),
495 LAMPORTS_PER_SOL,
496 );
497
498 bank.register_unique_recent_blockhash_for_test();
501 bank.transfer(amount, &mint_keypair, &keypair1.pubkey())
502 .unwrap();
503 bank.transfer(amount, &mint_keypair, &keypair2.pubkey())
504 .unwrap();
505 bank.transfer(amount, &mint_keypair, &keypair5.pubkey())
506 .unwrap();
507
508 bank.freeze();
510 let prev_accounts_lt_hash = bank.accounts_lt_hash.lock().unwrap().clone();
511
512 let prev_mint = bank.get_account_with_fixed_root(&mint_keypair.pubkey());
514 let prev_account1 = bank.get_account_with_fixed_root(&keypair1.pubkey());
515 let prev_account2 = bank.get_account_with_fixed_root(&keypair2.pubkey());
516 let prev_account3 = bank.get_account_with_fixed_root(&keypair3.pubkey());
517 let prev_account4 = bank.get_account_with_fixed_root(&keypair4.pubkey());
518 let prev_account5 = bank.get_account_with_fixed_root(&keypair5.pubkey());
519
520 assert!(prev_mint.is_some());
521 assert!(prev_account1.is_some());
522 assert!(prev_account2.is_some());
523 assert!(prev_account3.is_none());
524 assert!(prev_account4.is_none());
525 assert!(prev_account5.is_some());
526
527 let sysvars = [
531 Pubkey::from_str("SysvarS1otHashes111111111111111111111111111").unwrap(),
532 Pubkey::from_str("SysvarC1ock11111111111111111111111111111111").unwrap(),
533 Pubkey::from_str("SysvarRecentB1ockHashes11111111111111111111").unwrap(),
534 Pubkey::from_str("SysvarS1otHistory11111111111111111111111111").unwrap(),
535 ];
536 let prev_sysvar_accounts: Vec<_> = sysvars
537 .iter()
538 .map(|address| bank.get_account_with_fixed_root(address))
539 .collect();
540
541 let bank = {
542 let slot = bank.slot() + 1;
543 new_bank_from_parent_with_bank_forks(&bank_forks, bank, &Pubkey::default(), slot)
544 };
545
546 bank.register_unique_recent_blockhash_for_test();
548 bank.transfer(amount, &keypair2, &keypair1.pubkey())
549 .unwrap();
550
551 bank.register_unique_recent_blockhash_for_test();
554 bank.transfer(amount, &mint_keypair, &keypair4.pubkey())
555 .unwrap();
556 bank.register_unique_recent_blockhash_for_test();
557 bank.transfer(amount, &keypair4, &keypair3.pubkey())
558 .unwrap();
559
560 bank.rc.accounts.store_cached(
562 (
563 bank.slot(),
564 [(&keypair5.pubkey(), &prev_account5.clone().unwrap())].as_slice(),
565 ),
566 None,
567 );
568
569 bank.freeze();
571
572 let actual_delta_lt_hash = bank.calculate_delta_lt_hash();
573 let post_accounts_lt_hash = bank.accounts_lt_hash.lock().unwrap().clone();
574 let post_mint = bank.get_account_with_fixed_root(&mint_keypair.pubkey());
575 let post_account1 = bank.get_account_with_fixed_root(&keypair1.pubkey());
576 let post_account2 = bank.get_account_with_fixed_root(&keypair2.pubkey());
577 let post_account3 = bank.get_account_with_fixed_root(&keypair3.pubkey());
578 let post_account4 = bank.get_account_with_fixed_root(&keypair4.pubkey());
579 let post_account5 = bank.get_account_with_fixed_root(&keypair5.pubkey());
580
581 assert!(post_mint.is_some());
582 assert!(post_account1.is_some());
583 assert!(post_account2.is_none());
584 assert!(post_account3.is_some());
585 assert!(post_account4.is_none());
586 assert!(post_account5.is_some());
587
588 let post_sysvar_accounts: Vec<_> = sysvars
589 .iter()
590 .map(|address| bank.get_account_with_fixed_root(address))
591 .collect();
592
593 let mut expected_delta_lt_hash = LtHash::identity();
594 let mut expected_accounts_lt_hash = prev_accounts_lt_hash.clone();
595 let mut updater =
596 |address: &Pubkey, prev: Option<AccountSharedData>, post: Option<AccountSharedData>| {
597 if let Some(prev) = prev {
599 let prev_lt_hash = AccountsDb::lt_hash_account(&prev, address);
600 expected_delta_lt_hash.mix_out(&prev_lt_hash.0);
601 expected_accounts_lt_hash.0.mix_out(&prev_lt_hash.0);
602 }
603
604 let post = post.unwrap_or_default();
606 let post_lt_hash = AccountsDb::lt_hash_account(&post, address);
607 expected_delta_lt_hash.mix_in(&post_lt_hash.0);
608 expected_accounts_lt_hash.0.mix_in(&post_lt_hash.0);
609 };
610 updater(&mint_keypair.pubkey(), prev_mint, post_mint);
611 updater(&keypair1.pubkey(), prev_account1, post_account1);
612 updater(&keypair2.pubkey(), prev_account2, post_account2);
613 updater(&keypair3.pubkey(), prev_account3, post_account3);
614 updater(&keypair4.pubkey(), prev_account4, post_account4);
615 updater(&keypair5.pubkey(), prev_account5, post_account5);
616 for (i, sysvar) in sysvars.iter().enumerate() {
617 updater(
618 sysvar,
619 prev_sysvar_accounts[i].clone(),
620 post_sysvar_accounts[i].clone(),
621 );
622 }
623
624 let expected = expected_delta_lt_hash.checksum();
626 let actual = actual_delta_lt_hash.checksum();
627 assert_eq!(
628 expected, actual,
629 "delta_lt_hash, expected: {expected}, actual: {actual}",
630 );
631
632 let expected = expected_accounts_lt_hash.0.checksum();
634 let actual = post_accounts_lt_hash.0.checksum();
635 assert_eq!(
636 expected, actual,
637 "accounts_lt_hash, expected: {expected}, actual: {actual}",
638 );
639 }
640
641 #[test_case(Features::None; "no features")]
647 #[test_case(Features::All; "all features")]
648 fn test_slot0_accounts_lt_hash(features: Features) {
649 let (genesis_config, mint_keypair) = genesis_config_with(features);
650 let (bank, _bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config);
651 bank.rc
652 .accounts
653 .accounts_db
654 .set_is_experimental_accumulator_hash_enabled(features == Features::None);
655
656 assert!(bank.is_accounts_lt_hash_enabled());
658
659 assert_eq!(bank.slot(), 0);
661
662 bank.transfer(LAMPORTS_PER_SOL, &mint_keypair, &Pubkey::new_unique())
664 .unwrap();
665
666 bank.freeze();
668 let actual_accounts_lt_hash = bank.accounts_lt_hash.lock().unwrap().clone();
669
670 let calculated_accounts_lt_hash = bank
672 .rc
673 .accounts
674 .accounts_db
675 .calculate_accounts_lt_hash_at_startup_from_index(&bank.ancestors, bank.slot());
676 assert_eq!(actual_accounts_lt_hash, calculated_accounts_lt_hash);
677 }
678
679 #[test_case(Features::None; "no features")]
680 #[test_case(Features::All; "all features")]
681 fn test_inspect_account_for_accounts_lt_hash(features: Features) {
682 let (genesis_config, _mint_keypair) = genesis_config_with(features);
683 let (bank, _bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config);
684 bank.rc
685 .accounts
686 .accounts_db
687 .set_is_experimental_accumulator_hash_enabled(features == Features::None);
688
689 assert!(bank.is_accounts_lt_hash_enabled());
691
692 assert_eq!(bank.cache_for_accounts_lt_hash.len(), 0);
694
695 bank.inspect_account_for_accounts_lt_hash(
697 &Pubkey::new_unique(),
698 &AccountState::Dead,
699 false,
700 );
701 bank.inspect_account_for_accounts_lt_hash(
702 &Pubkey::new_unique(),
703 &AccountState::Alive(&AccountSharedData::default()),
704 false,
705 );
706 assert_eq!(bank.cache_for_accounts_lt_hash.len(), 0);
707
708 let address = Pubkey::new_unique();
710 bank.inspect_account_for_accounts_lt_hash(&address, &AccountState::Dead, true);
711 assert_eq!(bank.cache_for_accounts_lt_hash.len(), 1);
712 assert!(bank.cache_for_accounts_lt_hash.contains_key(&address));
713
714 let address = Pubkey::new_unique();
716 let initial_lamports = 123;
717 let mut account = AccountSharedData::new(initial_lamports, 0, &Pubkey::default());
718 bank.inspect_account_for_accounts_lt_hash(&address, &AccountState::Alive(&account), true);
719 assert_eq!(bank.cache_for_accounts_lt_hash.len(), 2);
720 if let CacheValue::InspectAccount(InitialStateOfAccount::Alive(cached_account)) = bank
721 .cache_for_accounts_lt_hash
722 .get(&address)
723 .unwrap()
724 .value()
725 {
726 assert_eq!(*cached_account, account);
727 } else {
728 panic!("wrong initial state for account");
729 };
730
731 let updated_lamports = account.lamports() + 1;
733 account.set_lamports(updated_lamports);
734 bank.inspect_account_for_accounts_lt_hash(&address, &AccountState::Alive(&account), true);
735 assert_eq!(bank.cache_for_accounts_lt_hash.len(), 2);
736 if let CacheValue::InspectAccount(InitialStateOfAccount::Alive(cached_account)) = bank
737 .cache_for_accounts_lt_hash
738 .get(&address)
739 .unwrap()
740 .value()
741 {
742 assert_eq!(cached_account.lamports(), initial_lamports);
743 } else {
744 panic!("wrong initial state for account");
745 };
746
747 {
749 let address = Pubkey::new_unique();
750 bank.inspect_account_for_accounts_lt_hash(&address, &AccountState::Dead, true);
751 assert_eq!(bank.cache_for_accounts_lt_hash.len(), 3);
752 match bank
753 .cache_for_accounts_lt_hash
754 .get(&address)
755 .unwrap()
756 .value()
757 {
758 CacheValue::InspectAccount(InitialStateOfAccount::Dead) => {
759 }
761 _ => panic!("wrong initial state for account"),
762 };
763
764 bank.inspect_account_for_accounts_lt_hash(
765 &address,
766 &AccountState::Alive(&AccountSharedData::default()),
767 true,
768 );
769 assert_eq!(bank.cache_for_accounts_lt_hash.len(), 3);
770 match bank
771 .cache_for_accounts_lt_hash
772 .get(&address)
773 .unwrap()
774 .value()
775 {
776 CacheValue::InspectAccount(InitialStateOfAccount::Dead) => {
777 }
779 _ => panic!("wrong initial state for account"),
780 };
781 }
782 }
783
784 #[test_case(Features::None; "no features")]
785 #[test_case(Features::All; "all features")]
786 fn test_calculate_accounts_lt_hash_at_startup_from_index(features: Features) {
787 let (genesis_config, mint_keypair) = genesis_config_with(features);
788 let (mut bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config);
789 bank.rc
790 .accounts
791 .accounts_db
792 .set_is_experimental_accumulator_hash_enabled(features == Features::None);
793
794 assert!(bank.is_accounts_lt_hash_enabled());
796
797 let amount = cmp::max(
798 bank.get_minimum_balance_for_rent_exemption(0),
799 LAMPORTS_PER_SOL,
800 );
801
802 for _ in 0..7 {
805 let slot = bank.slot() + 1;
806 bank =
807 new_bank_from_parent_with_bank_forks(&bank_forks, bank, &Pubkey::default(), slot);
808 for _ in 0..13 {
809 bank.register_unique_recent_blockhash_for_test();
810 bank.transfer(amount, &mint_keypair, &pubkey::new_rand())
813 .unwrap();
814 }
815 bank.freeze();
816 }
817 let expected_accounts_lt_hash = bank.accounts_lt_hash.lock().unwrap().clone();
818
819 bank.squash();
822 bank.force_flush_accounts_cache();
823
824 let calculated_accounts_lt_hash = bank
826 .rc
827 .accounts
828 .accounts_db
829 .calculate_accounts_lt_hash_at_startup_from_index(&bank.ancestors, bank.slot());
830 assert_eq!(expected_accounts_lt_hash, calculated_accounts_lt_hash);
831 }
832
833 #[test_case(Features::None; "no features")]
834 #[test_case(Features::All; "all features")]
835 fn test_calculate_accounts_lt_hash_at_startup_from_storages(features: Features) {
836 let (genesis_config, mint_keypair) = genesis_config_with(features);
837 let (mut bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config);
838 bank.rc
839 .accounts
840 .accounts_db
841 .set_is_experimental_accumulator_hash_enabled(features == Features::None);
842
843 assert!(bank.is_accounts_lt_hash_enabled());
845
846 let amount = cmp::max(
847 bank.get_minimum_balance_for_rent_exemption(0),
848 LAMPORTS_PER_SOL,
849 );
850
851 let duplicate_pubkey = pubkey::new_rand();
853
854 for _ in 0..7 {
857 let slot = bank.slot() + 1;
858 bank =
859 new_bank_from_parent_with_bank_forks(&bank_forks, bank, &Pubkey::default(), slot);
860 for _ in 0..9 {
861 bank.register_unique_recent_blockhash_for_test();
862 bank.transfer(amount, &mint_keypair, &pubkey::new_rand())
866 .unwrap();
867
868 bank.register_unique_recent_blockhash_for_test();
869 bank.transfer(amount, &mint_keypair, &duplicate_pubkey)
870 .unwrap();
871 }
872
873 bank.squash();
875 bank.force_flush_accounts_cache();
876 }
877 let expected_accounts_lt_hash = bank.accounts_lt_hash.lock().unwrap().clone();
878
879 let (mut storages, _slots) = bank.rc.accounts.accounts_db.get_storages(RangeFull);
881 storages.sort_unstable_by_key(|storage| cmp::Reverse(storage.slot()));
884 let storages = storages.into_boxed_slice();
885
886 let mut stored_accounts_map = HashMap::<_, Vec<_>>::new();
888 for storage in &storages {
889 storage.accounts.scan_accounts(|stored_account_meta| {
890 let pubkey = stored_account_meta.pubkey();
891 let account_lt_hash = AccountsDb::lt_hash_account(&stored_account_meta, pubkey);
892 stored_accounts_map
893 .entry(*pubkey)
894 .or_default()
895 .push(account_lt_hash)
896 });
897 }
898
899 let duplicates_lt_hash = stored_accounts_map
902 .values()
903 .map(|lt_hashes| {
904 <_hashes[1..]
906 })
907 .fold(LtHash::identity(), |mut accum, duplicate_lt_hashes| {
908 for duplicate_lt_hash in duplicate_lt_hashes {
909 accum.mix_in(&duplicate_lt_hash.0);
910 }
911 accum
912 });
913 let duplicates_lt_hash = DuplicatesLtHash(duplicates_lt_hash);
914
915 let calculated_accounts_lt_hash_from_storages = bank
917 .rc
918 .accounts
919 .accounts_db
920 .calculate_accounts_lt_hash_at_startup_from_storages(&storages, &duplicates_lt_hash);
921 assert_eq!(
922 expected_accounts_lt_hash,
923 calculated_accounts_lt_hash_from_storages
924 );
925 }
926
927 #[test_matrix(
928 [Features::None, Features::All],
929 [Cli::Off, Cli::On]
930 )]
931 fn test_verify_accounts_lt_hash_at_startup(features: Features, verify_cli: Cli) {
932 let (genesis_config, mint_keypair) = genesis_config_with(features);
933 let (mut bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config);
934 bank.rc
935 .accounts
936 .accounts_db
937 .set_is_experimental_accumulator_hash_enabled(features == Features::None);
938
939 assert!(bank.is_accounts_lt_hash_enabled());
941
942 let amount = cmp::max(
943 bank.get_minimum_balance_for_rent_exemption(0),
944 LAMPORTS_PER_SOL,
945 );
946
947 let duplicate_pubkey = pubkey::new_rand();
949
950 for _ in 0..9 {
953 let slot = bank.slot() + 1;
954 bank =
955 new_bank_from_parent_with_bank_forks(&bank_forks, bank, &Pubkey::default(), slot);
956 for _ in 0..3 {
957 bank.register_unique_recent_blockhash_for_test();
958 bank.transfer(amount, &mint_keypair, &pubkey::new_rand())
959 .unwrap();
960 bank.register_unique_recent_blockhash_for_test();
961 bank.transfer(amount, &mint_keypair, &duplicate_pubkey)
962 .unwrap();
963 }
964
965 bank.fill_bank_with_ticks_for_tests();
967 bank.squash();
968 bank.force_flush_accounts_cache();
969 }
970
971 let snapshot_config = SnapshotConfig::default();
973 let bank_snapshots_dir = TempDir::new().unwrap();
974 let snapshot_archives_dir = TempDir::new().unwrap();
975 let snapshot = snapshot_bank_utils::bank_to_full_snapshot_archive(
976 &bank_snapshots_dir,
977 &bank,
978 Some(snapshot_config.snapshot_version),
979 &snapshot_archives_dir,
980 &snapshot_archives_dir,
981 snapshot_config.archive_format,
982 )
983 .unwrap();
984 let (_accounts_tempdir, accounts_dir) = snapshot_utils::create_tmp_accounts_dir_for_tests();
985 let accounts_db_config = AccountsDbConfig {
986 enable_experimental_accumulator_hash: match verify_cli {
987 Cli::Off => false,
988 Cli::On => true,
989 },
990 ..ACCOUNTS_DB_CONFIG_FOR_TESTING
991 };
992 let (roundtrip_bank, _) = snapshot_bank_utils::bank_from_snapshot_archives(
993 &[accounts_dir],
994 &bank_snapshots_dir,
995 &snapshot,
996 None,
997 &genesis_config,
998 &RuntimeConfig::default(),
999 None,
1000 None,
1001 None,
1002 false,
1003 false,
1004 false,
1005 false,
1006 Some(accounts_db_config),
1007 None,
1008 Arc::default(),
1009 )
1010 .unwrap();
1011
1012 assert!(roundtrip_bank.is_frozen());
1016
1017 roundtrip_bank.wait_for_initial_accounts_hash_verification_completed_for_tests();
1019 assert_eq!(roundtrip_bank, *bank);
1020 }
1021
1022 #[test_case(Features::None; "no features")]
1024 #[test_case(Features::All; "all features")]
1025 fn test_accounts_lt_hash_cache_values_from_bank_new(features: Features) {
1026 let (genesis_config, _mint_keypair) = genesis_config_with(features);
1027 let (mut bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config);
1028 bank.rc
1029 .accounts
1030 .accounts_db
1031 .set_is_experimental_accumulator_hash_enabled(features == Features::None);
1032
1033 assert!(bank.is_accounts_lt_hash_enabled());
1035
1036 let slot = bank.slot() + 1;
1037 bank = new_bank_from_parent_with_bank_forks(&bank_forks, bank, &Pubkey::default(), slot);
1038
1039 let expected_cache = &[
1043 (
1044 Pubkey::from_str_const("SysvarC1ock11111111111111111111111111111111"),
1045 CacheValue::BankNew,
1046 ),
1047 (
1048 Pubkey::from_str_const("SysvarS1otHashes111111111111111111111111111"),
1049 CacheValue::BankNew,
1050 ),
1051 ];
1052 let mut actual_cache: Vec<_> = bank
1053 .cache_for_accounts_lt_hash
1054 .iter()
1055 .map(|entry| (*entry.key(), entry.value().clone()))
1056 .collect();
1057 actual_cache.sort_unstable_by(|a, b| a.0.cmp(&b.0));
1058 assert_eq!(expected_cache, actual_cache.as_slice());
1059 }
1060
1061 #[test_matrix(
1063 [Features::None, Features::All],
1064 [Cli::Off, Cli::On],
1065 [Cli::Off, Cli::On]
1066 )]
1067 fn test_accounts_lt_hash_feature_activation(features: Features, cli: Cli, verify_cli: Cli) {
1068 let (mut genesis_config, mint_keypair) = genesis_config_with(features);
1069 _ = genesis_config
1071 .accounts
1072 .remove(&feature_set::accounts_lt_hash::id());
1073 let (mut bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config);
1074 bank.rc
1075 .accounts
1076 .accounts_db
1077 .set_is_experimental_accumulator_hash_enabled(match cli {
1078 Cli::Off => false,
1079 Cli::On => true,
1080 });
1081
1082 let amount = cmp::max(
1083 bank.get_minimum_balance_for_rent_exemption(Feature::size_of()),
1084 1,
1085 );
1086
1087 for _ in 0..9 {
1090 let slot = bank.slot() + 1;
1091 bank =
1092 new_bank_from_parent_with_bank_forks(&bank_forks, bank, &Pubkey::default(), slot);
1093 bank.register_unique_recent_blockhash_for_test();
1094 bank.transfer(amount, &mint_keypair, &pubkey::new_rand())
1095 .unwrap();
1096 bank.fill_bank_with_ticks_for_tests();
1097 bank.squash();
1098 bank.force_flush_accounts_cache();
1099 }
1100
1101 let slot = bank.slot() + 1;
1105 bank = new_bank_from_parent_with_bank_forks(&bank_forks, bank, &Pubkey::default(), slot);
1106 bank.store_account_and_update_capitalization(
1107 &feature_set::accounts_lt_hash::id(),
1108 &feature::create_account(&Feature { activated_at: None }, amount),
1109 );
1110 assert!(!bank
1111 .feature_set
1112 .is_active(&feature_set::accounts_lt_hash::id()));
1113 bank = new_from_parent_next_epoch(bank, &bank_forks, 1);
1114 assert!(bank
1115 .feature_set
1116 .is_active(&feature_set::accounts_lt_hash::id()));
1117
1118 for _ in 0..5 {
1120 let slot = bank.slot() + 1;
1121 bank =
1122 new_bank_from_parent_with_bank_forks(&bank_forks, bank, &Pubkey::default(), slot);
1123 bank.register_unique_recent_blockhash_for_test();
1124 bank.transfer(amount, &mint_keypair, &pubkey::new_rand())
1125 .unwrap();
1126 bank.fill_bank_with_ticks_for_tests();
1127 }
1128
1129 bank.squash();
1134 bank.force_flush_accounts_cache();
1135 let calculated_accounts_lt_hash_from_index = bank
1136 .rc
1137 .accounts
1138 .accounts_db
1139 .calculate_accounts_lt_hash_at_startup_from_index(&bank.ancestors, bank.slot);
1140 assert_eq!(
1141 calculated_accounts_lt_hash_from_index,
1142 *bank.accounts_lt_hash.lock().unwrap(),
1143 );
1144
1145 let snapshot_config = SnapshotConfig::default();
1148 let bank_snapshots_dir = TempDir::new().unwrap();
1149 let snapshot_archives_dir = TempDir::new().unwrap();
1150 let snapshot = snapshot_bank_utils::bank_to_full_snapshot_archive(
1151 &bank_snapshots_dir,
1152 &bank,
1153 Some(snapshot_config.snapshot_version),
1154 &snapshot_archives_dir,
1155 &snapshot_archives_dir,
1156 snapshot_config.archive_format,
1157 )
1158 .unwrap();
1159 let (_accounts_tempdir, accounts_dir) = snapshot_utils::create_tmp_accounts_dir_for_tests();
1160 let accounts_db_config = AccountsDbConfig {
1161 enable_experimental_accumulator_hash: match verify_cli {
1162 Cli::Off => false,
1163 Cli::On => true,
1164 },
1165 ..ACCOUNTS_DB_CONFIG_FOR_TESTING
1166 };
1167 let (roundtrip_bank, _) = snapshot_bank_utils::bank_from_snapshot_archives(
1168 &[accounts_dir],
1169 &bank_snapshots_dir,
1170 &snapshot,
1171 None,
1172 &genesis_config,
1173 &RuntimeConfig::default(),
1174 None,
1175 None,
1176 None,
1177 false,
1178 false,
1179 false,
1180 false,
1181 Some(accounts_db_config),
1182 None,
1183 Arc::default(),
1184 )
1185 .unwrap();
1186
1187 roundtrip_bank.wait_for_initial_accounts_hash_verification_completed_for_tests();
1189 assert_eq!(roundtrip_bank, *bank);
1190 }
1191
1192 #[test_matrix(
1194 [Features::None, Features::All],
1195 [Cli::Off, Cli::On],
1196 [Cli::Off, Cli::On]
1197 )]
1198 fn test_snapshots_lt_hash(features: Features, cli: Cli, verify_cli: Cli) {
1199 let (genesis_config, mint_keypair) = genesis_config_with(features);
1200 let (mut bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config);
1201 bank.rc
1202 .accounts
1203 .accounts_db
1204 .set_is_experimental_accumulator_hash_enabled(features == Features::None);
1205 assert!(bank.is_accounts_lt_hash_enabled());
1207
1208 bank.rc
1209 .accounts
1210 .accounts_db
1211 .set_snapshots_use_experimental_accumulator_hash(match cli {
1212 Cli::Off => false,
1213 Cli::On => true,
1214 });
1215
1216 let amount = cmp::max(
1217 bank.get_minimum_balance_for_rent_exemption(0),
1218 LAMPORTS_PER_SOL,
1219 );
1220
1221 for _ in 0..3 {
1224 let slot = bank.slot() + 1;
1225 bank =
1226 new_bank_from_parent_with_bank_forks(&bank_forks, bank, &Pubkey::default(), slot);
1227 bank.register_unique_recent_blockhash_for_test();
1228 bank.transfer(amount, &mint_keypair, &pubkey::new_rand())
1229 .unwrap();
1230 bank.fill_bank_with_ticks_for_tests();
1231 bank.squash();
1232 bank.force_flush_accounts_cache();
1233 }
1234
1235 let snapshot_config = SnapshotConfig::default();
1236 let bank_snapshots_dir = TempDir::new().unwrap();
1237 let snapshot_archives_dir = TempDir::new().unwrap();
1238 let snapshot = snapshot_bank_utils::bank_to_full_snapshot_archive(
1239 &bank_snapshots_dir,
1240 &bank,
1241 Some(snapshot_config.snapshot_version),
1242 &snapshot_archives_dir,
1243 &snapshot_archives_dir,
1244 snapshot_config.archive_format,
1245 )
1246 .unwrap();
1247 let (_accounts_tempdir, accounts_dir) = snapshot_utils::create_tmp_accounts_dir_for_tests();
1248 let accounts_db_config = AccountsDbConfig {
1249 enable_experimental_accumulator_hash: features == Features::None,
1250 snapshots_use_experimental_accumulator_hash: match verify_cli {
1251 Cli::Off => false,
1252 Cli::On => true,
1253 },
1254 ..ACCOUNTS_DB_CONFIG_FOR_TESTING
1255 };
1256 let (roundtrip_bank, _) = snapshot_bank_utils::bank_from_snapshot_archives(
1257 &[accounts_dir],
1258 &bank_snapshots_dir,
1259 &snapshot,
1260 None,
1261 &genesis_config,
1262 &RuntimeConfig::default(),
1263 None,
1264 None,
1265 None,
1266 false,
1267 false,
1268 false,
1269 false,
1270 Some(accounts_db_config),
1271 None,
1272 Arc::default(),
1273 )
1274 .unwrap();
1275
1276 roundtrip_bank.wait_for_initial_accounts_hash_verification_completed_for_tests();
1278 assert_eq!(roundtrip_bank, *bank);
1279 }
1280}