1use {
2 super::Bank,
3 agave_feature_set as feature_set,
4 rayon::prelude::*,
5 solana_account::{accounts_equal, AccountSharedData},
6 solana_accounts_db::accounts_db::AccountsDb,
7 solana_hash::Hash,
8 solana_lattice_hash::lt_hash::LtHash,
9 solana_measure::{meas_dur, measure::Measure},
10 solana_pubkey::Pubkey,
11 solana_svm_callback::AccountState,
12 std::{
13 ops::AddAssign,
14 sync::atomic::{AtomicU64, Ordering},
15 time::Duration,
16 },
17};
18
19impl Bank {
20 pub fn is_accounts_lt_hash_enabled(&self) -> bool {
22 self.rc
23 .accounts
24 .accounts_db
25 .is_experimental_accumulator_hash_enabled()
26 || self
27 .feature_set
28 .is_active(&feature_set::accounts_lt_hash::id())
29 }
30
31 pub fn is_snapshots_lt_hash_enabled(&self) -> bool {
33 self.is_accounts_lt_hash_enabled()
34 && (self
35 .rc
36 .accounts
37 .accounts_db
38 .snapshots_use_experimental_accumulator_hash()
39 || self
40 .feature_set
41 .is_active(&feature_set::snapshots_lt_hash::id()))
42 }
43
44 pub fn update_accounts_lt_hash(&self) {
53 debug_assert!(self.is_accounts_lt_hash_enabled());
54 let delta_lt_hash = self.calculate_delta_lt_hash();
55 let mut accounts_lt_hash = self.accounts_lt_hash.lock().unwrap();
56 accounts_lt_hash.0.mix_in(&delta_lt_hash);
57
58 if !self
60 .feature_set
61 .is_active(&feature_set::accounts_lt_hash::id())
62 {
63 log::info!(
64 "updated accounts lattice hash for slot {}, delta_lt_hash checksum: {}, accounts_lt_hash checksum: {}",
65 self.slot(),
66 delta_lt_hash.checksum(),
67 accounts_lt_hash.0.checksum(),
68 );
69 }
70 }
71
72 fn calculate_delta_lt_hash(&self) -> LtHash {
82 debug_assert!(self.is_accounts_lt_hash_enabled());
83 let measure_total = Measure::start("");
84 let slot = self.slot();
85
86 let strictly_ancestors = {
90 let mut ancestors = self.ancestors.clone();
91 ancestors.remove(&self.slot());
92 ancestors
93 };
94
95 if slot == 0 {
96 assert!(strictly_ancestors.is_empty());
108 self.cache_for_accounts_lt_hash.clear();
109 }
110
111 let (accounts_curr, time_loading_accounts_curr) = meas_dur!({
115 self.rc
116 .accounts
117 .accounts_db
118 .get_pubkey_account_for_slot(slot)
119 });
120 let num_accounts_total = accounts_curr.len();
121
122 #[derive(Debug, Default)]
123 struct Stats {
124 num_cache_misses: usize,
125 num_accounts_unmodified: usize,
126 time_loading_accounts_prev: Duration,
127 time_comparing_accounts: Duration,
128 time_computing_hashes: Duration,
129 time_mixing_hashes: Duration,
130 }
131 impl AddAssign for Stats {
132 fn add_assign(&mut self, other: Self) {
133 self.num_cache_misses += other.num_cache_misses;
134 self.num_accounts_unmodified += other.num_accounts_unmodified;
135 self.time_loading_accounts_prev += other.time_loading_accounts_prev;
136 self.time_comparing_accounts += other.time_comparing_accounts;
137 self.time_computing_hashes += other.time_computing_hashes;
138 self.time_mixing_hashes += other.time_mixing_hashes;
139 }
140 }
141
142 let do_calculate_delta_lt_hash = || {
143 const CHUNK_SIZE: usize = 128;
148 accounts_curr
149 .par_iter()
150 .fold_chunks(
151 CHUNK_SIZE,
152 || (LtHash::identity(), Stats::default()),
153 |mut accum, (pubkey, curr_account)| {
154 let (initial_state_of_account, measure_load) = meas_dur!({
156 let cache_value = self
157 .cache_for_accounts_lt_hash
158 .get(pubkey)
159 .map(|entry| entry.value().clone());
160 match cache_value {
161 Some(CacheValue::InspectAccount(initial_state_of_account)) => {
162 initial_state_of_account
163 }
164 Some(CacheValue::BankNew) | None => {
165 accum.1.num_cache_misses += 1;
166 let account_slot = self
173 .rc
174 .accounts
175 .load_with_fixed_root_do_not_populate_read_cache(
176 &strictly_ancestors,
177 pubkey,
178 );
179 match account_slot {
180 Some((account, _slot)) => {
181 InitialStateOfAccount::Alive(account)
182 }
183 None => InitialStateOfAccount::Dead,
184 }
185 }
186 }
187 });
188 accum.1.time_loading_accounts_prev += measure_load;
189
190 match initial_state_of_account {
192 InitialStateOfAccount::Dead => {
193 }
195 InitialStateOfAccount::Alive(prev_account) => {
196 let (are_accounts_equal, measure_is_equal) =
197 meas_dur!(accounts_equal(curr_account, &prev_account));
198 accum.1.time_comparing_accounts += measure_is_equal;
199 if are_accounts_equal {
200 accum.1.num_accounts_unmodified += 1;
202 return accum;
203 }
204 let (prev_lt_hash, measure_hashing) =
205 meas_dur!(AccountsDb::lt_hash_account(&prev_account, pubkey));
206 let (_, measure_mixing) =
207 meas_dur!(accum.0.mix_out(&prev_lt_hash.0));
208 accum.1.time_computing_hashes += measure_hashing;
209 accum.1.time_mixing_hashes += measure_mixing;
210 }
211 }
212
213 let (curr_lt_hash, measure_hashing) =
215 meas_dur!(AccountsDb::lt_hash_account(curr_account, pubkey));
216 let (_, measure_mixing) = meas_dur!(accum.0.mix_in(&curr_lt_hash.0));
217 accum.1.time_computing_hashes += measure_hashing;
218 accum.1.time_mixing_hashes += measure_mixing;
219
220 accum
221 },
222 )
223 .reduce(
224 || (LtHash::identity(), Stats::default()),
225 |mut accum, elem| {
226 accum.0.mix_in(&elem.0);
227 accum.1 += elem.1;
228 accum
229 },
230 )
231 };
232 let (delta_lt_hash, stats) = self
233 .rc
234 .accounts
235 .accounts_db
236 .thread_pool
237 .install(do_calculate_delta_lt_hash);
238
239 let total_time = measure_total.end_as_duration();
240 let num_accounts_modified =
241 num_accounts_total.saturating_sub(stats.num_accounts_unmodified);
242 datapoint_info!(
243 "bank-accounts_lt_hash",
244 ("slot", slot, i64),
245 ("num_accounts_total", num_accounts_total, i64),
246 ("num_accounts_modified", num_accounts_modified, i64),
247 (
248 "num_accounts_unmodified",
249 stats.num_accounts_unmodified,
250 i64
251 ),
252 ("num_cache_misses", stats.num_cache_misses, i64),
253 ("total_us", total_time.as_micros(), i64),
254 (
255 "loading_accounts_curr_us",
256 time_loading_accounts_curr.as_micros(),
257 i64
258 ),
259 (
260 "par_loading_accounts_prev_us",
261 stats.time_loading_accounts_prev.as_micros(),
262 i64
263 ),
264 (
265 "par_comparing_accounts_us",
266 stats.time_comparing_accounts.as_micros(),
267 i64
268 ),
269 (
270 "par_computing_hashes_us",
271 stats.time_computing_hashes.as_micros(),
272 i64
273 ),
274 (
275 "par_mixing_hashes_us",
276 stats.time_mixing_hashes.as_micros(),
277 i64
278 ),
279 (
280 "num_inspect_account_hits",
281 self.stats_for_accounts_lt_hash
282 .num_inspect_account_hits
283 .load(Ordering::Relaxed),
284 i64
285 ),
286 (
287 "num_inspect_account_misses",
288 self.stats_for_accounts_lt_hash
289 .num_inspect_account_misses
290 .load(Ordering::Relaxed),
291 i64
292 ),
293 (
294 "num_inspect_account_after_frozen",
295 self.stats_for_accounts_lt_hash
296 .num_inspect_account_after_frozen
297 .load(Ordering::Relaxed),
298 i64
299 ),
300 (
301 "inspect_account_lookup_ns",
302 self.stats_for_accounts_lt_hash
303 .inspect_account_lookup_time_ns
304 .load(Ordering::Relaxed),
305 i64
306 ),
307 (
308 "inspect_account_insert_ns",
309 self.stats_for_accounts_lt_hash
310 .inspect_account_insert_time_ns
311 .load(Ordering::Relaxed),
312 i64
313 ),
314 );
315
316 delta_lt_hash
317 }
318
319 pub fn inspect_account_for_accounts_lt_hash(
325 &self,
326 address: &Pubkey,
327 account_state: &AccountState,
328 is_writable: bool,
329 ) {
330 debug_assert!(self.is_accounts_lt_hash_enabled());
331 if !is_writable {
332 return;
334 }
335
336 let (is_in_cache, lookup_time) =
339 meas_dur!(self.cache_for_accounts_lt_hash.contains_key(address));
340 if !is_in_cache {
341 let freeze_guard = self.freeze_lock();
344 let is_frozen = *freeze_guard != Hash::default();
345 if is_frozen {
346 self.stats_for_accounts_lt_hash
354 .num_inspect_account_after_frozen
355 .fetch_add(1, Ordering::Relaxed);
356 return;
357 }
358 let (_, insert_time) = meas_dur!({
359 self.cache_for_accounts_lt_hash
360 .entry(*address)
361 .or_insert_with(|| {
362 let initial_state_of_account = match account_state {
363 AccountState::Dead => InitialStateOfAccount::Dead,
364 AccountState::Alive(account) => {
365 InitialStateOfAccount::Alive((*account).clone())
366 }
367 };
368 CacheValue::InspectAccount(initial_state_of_account)
369 });
370 });
371 drop(freeze_guard);
372
373 self.stats_for_accounts_lt_hash
374 .num_inspect_account_misses
375 .fetch_add(1, Ordering::Relaxed);
376 self.stats_for_accounts_lt_hash
377 .inspect_account_insert_time_ns
378 .fetch_add(insert_time.as_nanos() as u64, Ordering::Relaxed);
380 } else {
381 self.stats_for_accounts_lt_hash
383 .num_inspect_account_hits
384 .fetch_add(1, Ordering::Relaxed);
385 }
386
387 self.stats_for_accounts_lt_hash
388 .inspect_account_lookup_time_ns
389 .fetch_add(lookup_time.as_nanos() as u64, Ordering::Relaxed);
391 }
392}
393
394#[derive(Debug, Default)]
396pub struct Stats {
397 num_inspect_account_hits: AtomicU64,
399 num_inspect_account_misses: AtomicU64,
401 num_inspect_account_after_frozen: AtomicU64,
403 inspect_account_lookup_time_ns: AtomicU64,
405 inspect_account_insert_time_ns: AtomicU64,
407}
408
409#[derive(Debug, Clone, PartialEq)]
411pub enum InitialStateOfAccount {
412 Dead,
414 Alive(AccountSharedData),
416}
417
418#[derive(Debug, Clone, PartialEq)]
420pub enum CacheValue {
421 InspectAccount(InitialStateOfAccount),
424 BankNew,
427}
428
429#[cfg(test)]
430mod tests {
431 use {
432 super::*,
433 crate::{
434 bank::tests::{new_bank_from_parent_with_bank_forks, new_from_parent_next_epoch},
435 runtime_config::RuntimeConfig,
436 snapshot_bank_utils,
437 snapshot_config::SnapshotConfig,
438 snapshot_utils,
439 },
440 solana_account::{ReadableAccount as _, WritableAccount as _},
441 solana_accounts_db::{
442 accounts_db::{AccountsDbConfig, DuplicatesLtHash, ACCOUNTS_DB_CONFIG_FOR_TESTING},
443 accounts_index::{
444 AccountsIndexConfig, IndexLimitMb, ACCOUNTS_INDEX_CONFIG_FOR_TESTING,
445 },
446 },
447 solana_feature_gate_interface::{self as feature, Feature},
448 solana_fee_calculator::FeeRateGovernor,
449 solana_genesis_config::{self, GenesisConfig},
450 solana_keypair::Keypair,
451 solana_native_token::LAMPORTS_PER_SOL,
452 solana_pubkey::{self as pubkey, Pubkey},
453 solana_signer::Signer as _,
454 std::{cmp, collections::HashMap, iter, ops::RangeFull, str::FromStr as _, sync::Arc},
455 tempfile::TempDir,
456 test_case::{test_case, test_matrix},
457 };
458
459 #[derive(Debug, Copy, Clone, Eq, PartialEq)]
461 enum Features {
462 None,
464 All,
466 }
467
468 #[derive(Debug, Copy, Clone, Eq, PartialEq)]
470 enum Cli {
471 Off,
473 On,
475 }
476
477 fn genesis_config_with(features: Features) -> (GenesisConfig, Keypair) {
479 let mint_lamports = 123_456_789 * LAMPORTS_PER_SOL;
480 match features {
481 Features::None => solana_genesis_config::create_genesis_config(mint_lamports),
482 Features::All => {
483 let info = crate::genesis_utils::create_genesis_config(mint_lamports);
484 (info.genesis_config, info.mint_keypair)
485 }
486 }
487 }
488
489 #[test]
490 fn test_update_accounts_lt_hash() {
491 let keypair1 = Keypair::new();
502 let keypair2 = Keypair::new();
503 let keypair3 = Keypair::new();
504 let keypair4 = Keypair::new();
505 let keypair5 = Keypair::new();
506
507 let (mut genesis_config, mint_keypair) =
508 solana_genesis_config::create_genesis_config(123_456_789 * LAMPORTS_PER_SOL);
509 genesis_config.fee_rate_governor = FeeRateGovernor::new(0, 0);
510 let (bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config);
511 bank.rc
512 .accounts
513 .accounts_db
514 .set_is_experimental_accumulator_hash_enabled(true);
515
516 assert!(bank.is_accounts_lt_hash_enabled());
518
519 let amount = cmp::max(
520 bank.get_minimum_balance_for_rent_exemption(0),
521 LAMPORTS_PER_SOL,
522 );
523
524 bank.register_unique_recent_blockhash_for_test();
527 bank.transfer(amount, &mint_keypair, &keypair1.pubkey())
528 .unwrap();
529 bank.transfer(amount, &mint_keypair, &keypair2.pubkey())
530 .unwrap();
531 bank.transfer(amount, &mint_keypair, &keypair5.pubkey())
532 .unwrap();
533
534 bank.freeze();
536 let prev_accounts_lt_hash = bank.accounts_lt_hash.lock().unwrap().clone();
537
538 let prev_mint = bank.get_account_with_fixed_root(&mint_keypair.pubkey());
540 let prev_account1 = bank.get_account_with_fixed_root(&keypair1.pubkey());
541 let prev_account2 = bank.get_account_with_fixed_root(&keypair2.pubkey());
542 let prev_account3 = bank.get_account_with_fixed_root(&keypair3.pubkey());
543 let prev_account4 = bank.get_account_with_fixed_root(&keypair4.pubkey());
544 let prev_account5 = bank.get_account_with_fixed_root(&keypair5.pubkey());
545
546 assert!(prev_mint.is_some());
547 assert!(prev_account1.is_some());
548 assert!(prev_account2.is_some());
549 assert!(prev_account3.is_none());
550 assert!(prev_account4.is_none());
551 assert!(prev_account5.is_some());
552
553 let sysvars = [
557 Pubkey::from_str("SysvarS1otHashes111111111111111111111111111").unwrap(),
558 Pubkey::from_str("SysvarC1ock11111111111111111111111111111111").unwrap(),
559 Pubkey::from_str("SysvarRecentB1ockHashes11111111111111111111").unwrap(),
560 Pubkey::from_str("SysvarS1otHistory11111111111111111111111111").unwrap(),
561 ];
562 let prev_sysvar_accounts: Vec<_> = sysvars
563 .iter()
564 .map(|address| bank.get_account_with_fixed_root(address))
565 .collect();
566
567 let bank = {
568 let slot = bank.slot() + 1;
569 new_bank_from_parent_with_bank_forks(&bank_forks, bank, &Pubkey::default(), slot)
570 };
571
572 bank.register_unique_recent_blockhash_for_test();
574 bank.transfer(amount, &keypair2, &keypair1.pubkey())
575 .unwrap();
576
577 bank.register_unique_recent_blockhash_for_test();
580 bank.transfer(amount, &mint_keypair, &keypair4.pubkey())
581 .unwrap();
582 bank.register_unique_recent_blockhash_for_test();
583 bank.transfer(amount, &keypair4, &keypair3.pubkey())
584 .unwrap();
585
586 bank.rc.accounts.store_cached(
588 (
589 bank.slot(),
590 [(&keypair5.pubkey(), &prev_account5.clone().unwrap())].as_slice(),
591 ),
592 None,
593 );
594
595 bank.freeze();
597
598 let actual_delta_lt_hash = bank.calculate_delta_lt_hash();
599 let post_accounts_lt_hash = bank.accounts_lt_hash.lock().unwrap().clone();
600 let post_mint = bank.get_account_with_fixed_root(&mint_keypair.pubkey());
601 let post_account1 = bank.get_account_with_fixed_root(&keypair1.pubkey());
602 let post_account2 = bank.get_account_with_fixed_root(&keypair2.pubkey());
603 let post_account3 = bank.get_account_with_fixed_root(&keypair3.pubkey());
604 let post_account4 = bank.get_account_with_fixed_root(&keypair4.pubkey());
605 let post_account5 = bank.get_account_with_fixed_root(&keypair5.pubkey());
606
607 assert!(post_mint.is_some());
608 assert!(post_account1.is_some());
609 assert!(post_account2.is_none());
610 assert!(post_account3.is_some());
611 assert!(post_account4.is_none());
612 assert!(post_account5.is_some());
613
614 let post_sysvar_accounts: Vec<_> = sysvars
615 .iter()
616 .map(|address| bank.get_account_with_fixed_root(address))
617 .collect();
618
619 let mut expected_delta_lt_hash = LtHash::identity();
620 let mut expected_accounts_lt_hash = prev_accounts_lt_hash.clone();
621 let mut updater =
622 |address: &Pubkey, prev: Option<AccountSharedData>, post: Option<AccountSharedData>| {
623 if let Some(prev) = prev {
625 let prev_lt_hash = AccountsDb::lt_hash_account(&prev, address);
626 expected_delta_lt_hash.mix_out(&prev_lt_hash.0);
627 expected_accounts_lt_hash.0.mix_out(&prev_lt_hash.0);
628 }
629
630 let post = post.unwrap_or_default();
632 let post_lt_hash = AccountsDb::lt_hash_account(&post, address);
633 expected_delta_lt_hash.mix_in(&post_lt_hash.0);
634 expected_accounts_lt_hash.0.mix_in(&post_lt_hash.0);
635 };
636 updater(&mint_keypair.pubkey(), prev_mint, post_mint);
637 updater(&keypair1.pubkey(), prev_account1, post_account1);
638 updater(&keypair2.pubkey(), prev_account2, post_account2);
639 updater(&keypair3.pubkey(), prev_account3, post_account3);
640 updater(&keypair4.pubkey(), prev_account4, post_account4);
641 updater(&keypair5.pubkey(), prev_account5, post_account5);
642 for (i, sysvar) in sysvars.iter().enumerate() {
643 updater(
644 sysvar,
645 prev_sysvar_accounts[i].clone(),
646 post_sysvar_accounts[i].clone(),
647 );
648 }
649
650 let expected = expected_delta_lt_hash.checksum();
652 let actual = actual_delta_lt_hash.checksum();
653 assert_eq!(
654 expected, actual,
655 "delta_lt_hash, expected: {expected}, actual: {actual}",
656 );
657
658 let expected = expected_accounts_lt_hash.0.checksum();
660 let actual = post_accounts_lt_hash.0.checksum();
661 assert_eq!(
662 expected, actual,
663 "accounts_lt_hash, expected: {expected}, actual: {actual}",
664 );
665 }
666
667 #[test_case(Features::None; "no features")]
673 #[test_case(Features::All; "all features")]
674 fn test_slot0_accounts_lt_hash(features: Features) {
675 let (genesis_config, mint_keypair) = genesis_config_with(features);
676 let (bank, _bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config);
677 bank.rc
678 .accounts
679 .accounts_db
680 .set_is_experimental_accumulator_hash_enabled(features == Features::None);
681
682 assert!(bank.is_accounts_lt_hash_enabled());
684
685 assert_eq!(bank.slot(), 0);
687
688 bank.transfer(LAMPORTS_PER_SOL, &mint_keypair, &Pubkey::new_unique())
690 .unwrap();
691
692 bank.freeze();
694 let actual_accounts_lt_hash = bank.accounts_lt_hash.lock().unwrap().clone();
695
696 let calculated_accounts_lt_hash = bank
698 .rc
699 .accounts
700 .accounts_db
701 .calculate_accounts_lt_hash_at_startup_from_index(&bank.ancestors, bank.slot());
702 assert_eq!(actual_accounts_lt_hash, calculated_accounts_lt_hash);
703 }
704
705 #[test_case(Features::None; "no features")]
706 #[test_case(Features::All; "all features")]
707 fn test_inspect_account_for_accounts_lt_hash(features: Features) {
708 let (genesis_config, _mint_keypair) = genesis_config_with(features);
709 let (bank, _bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config);
710 bank.rc
711 .accounts
712 .accounts_db
713 .set_is_experimental_accumulator_hash_enabled(features == Features::None);
714
715 assert!(bank.is_accounts_lt_hash_enabled());
717
718 assert_eq!(bank.cache_for_accounts_lt_hash.len(), 0);
720
721 bank.inspect_account_for_accounts_lt_hash(
723 &Pubkey::new_unique(),
724 &AccountState::Dead,
725 false,
726 );
727 bank.inspect_account_for_accounts_lt_hash(
728 &Pubkey::new_unique(),
729 &AccountState::Alive(&AccountSharedData::default()),
730 false,
731 );
732 assert_eq!(bank.cache_for_accounts_lt_hash.len(), 0);
733
734 let address = Pubkey::new_unique();
736 bank.inspect_account_for_accounts_lt_hash(&address, &AccountState::Dead, true);
737 assert_eq!(bank.cache_for_accounts_lt_hash.len(), 1);
738 assert!(bank.cache_for_accounts_lt_hash.contains_key(&address));
739
740 let address = Pubkey::new_unique();
742 let initial_lamports = 123;
743 let mut account = AccountSharedData::new(initial_lamports, 0, &Pubkey::default());
744 bank.inspect_account_for_accounts_lt_hash(&address, &AccountState::Alive(&account), true);
745 assert_eq!(bank.cache_for_accounts_lt_hash.len(), 2);
746 if let CacheValue::InspectAccount(InitialStateOfAccount::Alive(cached_account)) = bank
747 .cache_for_accounts_lt_hash
748 .get(&address)
749 .unwrap()
750 .value()
751 {
752 assert_eq!(*cached_account, account);
753 } else {
754 panic!("wrong initial state for account");
755 };
756
757 let updated_lamports = account.lamports() + 1;
759 account.set_lamports(updated_lamports);
760 bank.inspect_account_for_accounts_lt_hash(&address, &AccountState::Alive(&account), true);
761 assert_eq!(bank.cache_for_accounts_lt_hash.len(), 2);
762 if let CacheValue::InspectAccount(InitialStateOfAccount::Alive(cached_account)) = bank
763 .cache_for_accounts_lt_hash
764 .get(&address)
765 .unwrap()
766 .value()
767 {
768 assert_eq!(cached_account.lamports(), initial_lamports);
769 } else {
770 panic!("wrong initial state for account");
771 };
772
773 {
775 let address = Pubkey::new_unique();
776 bank.inspect_account_for_accounts_lt_hash(&address, &AccountState::Dead, true);
777 assert_eq!(bank.cache_for_accounts_lt_hash.len(), 3);
778 match bank
779 .cache_for_accounts_lt_hash
780 .get(&address)
781 .unwrap()
782 .value()
783 {
784 CacheValue::InspectAccount(InitialStateOfAccount::Dead) => {
785 }
787 _ => panic!("wrong initial state for account"),
788 };
789
790 bank.inspect_account_for_accounts_lt_hash(
791 &address,
792 &AccountState::Alive(&AccountSharedData::default()),
793 true,
794 );
795 assert_eq!(bank.cache_for_accounts_lt_hash.len(), 3);
796 match bank
797 .cache_for_accounts_lt_hash
798 .get(&address)
799 .unwrap()
800 .value()
801 {
802 CacheValue::InspectAccount(InitialStateOfAccount::Dead) => {
803 }
805 _ => panic!("wrong initial state for account"),
806 };
807 }
808
809 bank.freeze();
812 let address = Pubkey::new_unique();
813 let num_cache_entries_prev = bank.cache_for_accounts_lt_hash.len();
814 bank.inspect_account_for_accounts_lt_hash(&address, &AccountState::Dead, true);
815 let num_cache_entries_curr = bank.cache_for_accounts_lt_hash.len();
816 assert_eq!(num_cache_entries_curr, num_cache_entries_prev);
817 assert!(!bank.cache_for_accounts_lt_hash.contains_key(&address));
818 }
819
820 #[test_case(Features::None; "no features")]
821 #[test_case(Features::All; "all features")]
822 fn test_calculate_accounts_lt_hash_at_startup_from_index(features: Features) {
823 let (genesis_config, mint_keypair) = genesis_config_with(features);
824 let (mut bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config);
825 bank.rc
826 .accounts
827 .accounts_db
828 .set_is_experimental_accumulator_hash_enabled(features == Features::None);
829
830 assert!(bank.is_accounts_lt_hash_enabled());
832
833 let amount = cmp::max(
834 bank.get_minimum_balance_for_rent_exemption(0),
835 LAMPORTS_PER_SOL,
836 );
837
838 for _ in 0..7 {
841 let slot = bank.slot() + 1;
842 bank =
843 new_bank_from_parent_with_bank_forks(&bank_forks, bank, &Pubkey::default(), slot);
844 for _ in 0..13 {
845 bank.register_unique_recent_blockhash_for_test();
846 bank.transfer(amount, &mint_keypair, &pubkey::new_rand())
849 .unwrap();
850 }
851 bank.freeze();
852 }
853 let expected_accounts_lt_hash = bank.accounts_lt_hash.lock().unwrap().clone();
854
855 bank.squash();
858 bank.force_flush_accounts_cache();
859
860 let calculated_accounts_lt_hash = bank
862 .rc
863 .accounts
864 .accounts_db
865 .calculate_accounts_lt_hash_at_startup_from_index(&bank.ancestors, bank.slot());
866 assert_eq!(expected_accounts_lt_hash, calculated_accounts_lt_hash);
867 }
868
869 #[test_case(Features::None; "no features")]
870 #[test_case(Features::All; "all features")]
871 fn test_calculate_accounts_lt_hash_at_startup_from_storages(features: Features) {
872 let (genesis_config, mint_keypair) = genesis_config_with(features);
873 let (mut bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config);
874 bank.rc
875 .accounts
876 .accounts_db
877 .set_is_experimental_accumulator_hash_enabled(features == Features::None);
878
879 assert!(bank.is_accounts_lt_hash_enabled());
881
882 let amount = cmp::max(
883 bank.get_minimum_balance_for_rent_exemption(0),
884 LAMPORTS_PER_SOL,
885 );
886
887 let duplicate_pubkey = pubkey::new_rand();
889
890 for _ in 0..7 {
893 let slot = bank.slot() + 1;
894 bank =
895 new_bank_from_parent_with_bank_forks(&bank_forks, bank, &Pubkey::default(), slot);
896 for _ in 0..9 {
897 bank.register_unique_recent_blockhash_for_test();
898 bank.transfer(amount, &mint_keypair, &pubkey::new_rand())
902 .unwrap();
903
904 bank.register_unique_recent_blockhash_for_test();
905 bank.transfer(amount, &mint_keypair, &duplicate_pubkey)
906 .unwrap();
907 }
908
909 bank.squash();
911 bank.force_flush_accounts_cache();
912 }
913 let expected_accounts_lt_hash = bank.accounts_lt_hash.lock().unwrap().clone();
914
915 let (mut storages, _slots) = bank.rc.accounts.accounts_db.get_storages(RangeFull);
917 storages.sort_unstable_by_key(|storage| cmp::Reverse(storage.slot()));
920 let storages = storages.into_boxed_slice();
921
922 let mut stored_accounts_map = HashMap::<_, Vec<_>>::new();
924 for storage in &storages {
925 storage.accounts.scan_accounts(|_offset, account| {
926 let pubkey = account.pubkey();
927 let account_lt_hash = AccountsDb::lt_hash_account(&account, pubkey);
928 stored_accounts_map
929 .entry(*pubkey)
930 .or_default()
931 .push(account_lt_hash)
932 });
933 }
934
935 let duplicates_lt_hash = stored_accounts_map
938 .values()
939 .map(|lt_hashes| {
940 <_hashes[1..]
942 })
943 .fold(LtHash::identity(), |mut accum, duplicate_lt_hashes| {
944 for duplicate_lt_hash in duplicate_lt_hashes {
945 accum.mix_in(&duplicate_lt_hash.0);
946 }
947 accum
948 });
949 let duplicates_lt_hash = DuplicatesLtHash(duplicates_lt_hash);
950
951 let calculated_accounts_lt_hash_from_storages = bank
953 .rc
954 .accounts
955 .accounts_db
956 .calculate_accounts_lt_hash_at_startup_from_storages(&storages, &duplicates_lt_hash);
957 assert_eq!(
958 expected_accounts_lt_hash,
959 calculated_accounts_lt_hash_from_storages
960 );
961 }
962
963 #[test_matrix(
964 [Features::None, Features::All],
965 [Cli::Off, Cli::On],
966 [IndexLimitMb::Minimal, IndexLimitMb::InMemOnly]
967 )]
968 fn test_verify_accounts_lt_hash_at_startup(
969 features: Features,
970 verify_cli: Cli,
971 accounts_index_limit: IndexLimitMb,
972 ) {
973 let (mut genesis_config, mint_keypair) = genesis_config_with(features);
974 genesis_config.fee_rate_governor = FeeRateGovernor::new(0, 0);
976 let (mut bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config);
977 bank.rc
978 .accounts
979 .accounts_db
980 .set_is_experimental_accumulator_hash_enabled(features == Features::None);
981
982 assert!(bank.is_accounts_lt_hash_enabled());
984
985 let amount = cmp::max(
986 bank.get_minimum_balance_for_rent_exemption(0),
987 LAMPORTS_PER_SOL,
988 );
989
990 let duplicate_pubkey = pubkey::new_rand();
992
993 for _ in 0..9 {
996 let slot = bank.slot() + 1;
997 bank =
998 new_bank_from_parent_with_bank_forks(&bank_forks, bank, &Pubkey::default(), slot);
999 for _ in 0..3 {
1000 bank.register_unique_recent_blockhash_for_test();
1001 bank.transfer(amount, &mint_keypair, &pubkey::new_rand())
1002 .unwrap();
1003 bank.register_unique_recent_blockhash_for_test();
1004 bank.transfer(amount, &mint_keypair, &duplicate_pubkey)
1005 .unwrap();
1006 }
1007
1008 bank.fill_bank_with_ticks_for_tests();
1010 bank.squash();
1011 bank.force_flush_accounts_cache();
1012 }
1013
1014 let num_accounts = 2;
1020 let accounts: Vec<_> = iter::repeat_with(Keypair::new).take(num_accounts).collect();
1021 for i in 0..num_accounts {
1022 let slot = bank.slot() + 1;
1023 bank =
1024 new_bank_from_parent_with_bank_forks(&bank_forks, bank, &Pubkey::default(), slot);
1025 bank.register_unique_recent_blockhash_for_test();
1026
1027 for account in &accounts {
1029 bank.transfer(amount, &mint_keypair, &account.pubkey())
1030 .unwrap();
1031 assert_ne!(bank.get_balance(&account.pubkey()), 0);
1032 }
1033
1034 bank.transfer(
1036 bank.get_balance(&accounts[i].pubkey()),
1037 &accounts[i],
1038 &pubkey::new_rand(),
1039 )
1040 .unwrap();
1041 assert_eq!(bank.get_balance(&accounts[i].pubkey()), 0);
1042
1043 bank.fill_bank_with_ticks_for_tests();
1045 bank.squash();
1046 bank.force_flush_accounts_cache();
1047 }
1048
1049 let snapshot_config = SnapshotConfig::default();
1051 let bank_snapshots_dir = TempDir::new().unwrap();
1052 let snapshot_archives_dir = TempDir::new().unwrap();
1053 let snapshot = snapshot_bank_utils::bank_to_full_snapshot_archive(
1054 &bank_snapshots_dir,
1055 &bank,
1056 Some(snapshot_config.snapshot_version),
1057 &snapshot_archives_dir,
1058 &snapshot_archives_dir,
1059 snapshot_config.archive_format,
1060 )
1061 .unwrap();
1062 let (_accounts_tempdir, accounts_dir) = snapshot_utils::create_tmp_accounts_dir_for_tests();
1063 let accounts_index_config = AccountsIndexConfig {
1064 index_limit_mb: accounts_index_limit,
1065 ..ACCOUNTS_INDEX_CONFIG_FOR_TESTING
1066 };
1067 let accounts_db_config = AccountsDbConfig {
1068 enable_experimental_accumulator_hash: match verify_cli {
1069 Cli::Off => false,
1070 Cli::On => true,
1071 },
1072 index: Some(accounts_index_config),
1073 ..ACCOUNTS_DB_CONFIG_FOR_TESTING
1074 };
1075 let (roundtrip_bank, _) = snapshot_bank_utils::bank_from_snapshot_archives(
1076 &[accounts_dir],
1077 &bank_snapshots_dir,
1078 &snapshot,
1079 None,
1080 &genesis_config,
1081 &RuntimeConfig::default(),
1082 None,
1083 None,
1084 None,
1085 false,
1086 false,
1087 false,
1088 false,
1089 Some(accounts_db_config),
1090 None,
1091 Arc::default(),
1092 )
1093 .unwrap();
1094
1095 assert!(roundtrip_bank.is_frozen());
1099
1100 roundtrip_bank.wait_for_initial_accounts_hash_verification_completed_for_tests();
1102 assert_eq!(roundtrip_bank, *bank);
1103 }
1104
1105 #[test_case(Features::None; "no features")]
1107 #[test_case(Features::All; "all features")]
1108 fn test_accounts_lt_hash_cache_values_from_bank_new(features: Features) {
1109 let (genesis_config, _mint_keypair) = genesis_config_with(features);
1110 let (mut bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config);
1111 bank.rc
1112 .accounts
1113 .accounts_db
1114 .set_is_experimental_accumulator_hash_enabled(features == Features::None);
1115
1116 assert!(bank.is_accounts_lt_hash_enabled());
1118
1119 let slot = bank.slot() + 1;
1120 bank = new_bank_from_parent_with_bank_forks(&bank_forks, bank, &Pubkey::default(), slot);
1121
1122 let expected_cache = &[
1126 (
1127 Pubkey::from_str_const("SysvarC1ock11111111111111111111111111111111"),
1128 CacheValue::BankNew,
1129 ),
1130 (
1131 Pubkey::from_str_const("SysvarS1otHashes111111111111111111111111111"),
1132 CacheValue::BankNew,
1133 ),
1134 ];
1135 let mut actual_cache: Vec<_> = bank
1136 .cache_for_accounts_lt_hash
1137 .iter()
1138 .map(|entry| (*entry.key(), entry.value().clone()))
1139 .collect();
1140 actual_cache.sort_unstable_by(|a, b| a.0.cmp(&b.0));
1141 assert_eq!(expected_cache, actual_cache.as_slice());
1142 }
1143
1144 #[test_matrix(
1146 [Features::None, Features::All],
1147 [Cli::Off, Cli::On],
1148 [Cli::Off, Cli::On]
1149 )]
1150 fn test_accounts_lt_hash_feature_activation(features: Features, cli: Cli, verify_cli: Cli) {
1151 let (mut genesis_config, mint_keypair) = genesis_config_with(features);
1152 _ = genesis_config
1154 .accounts
1155 .remove(&feature_set::accounts_lt_hash::id());
1156 let (mut bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config);
1157 bank.rc
1158 .accounts
1159 .accounts_db
1160 .set_is_experimental_accumulator_hash_enabled(match cli {
1161 Cli::Off => false,
1162 Cli::On => true,
1163 });
1164
1165 let amount = cmp::max(
1166 bank.get_minimum_balance_for_rent_exemption(Feature::size_of()),
1167 1,
1168 );
1169
1170 for _ in 0..9 {
1173 let slot = bank.slot() + 1;
1174 bank =
1175 new_bank_from_parent_with_bank_forks(&bank_forks, bank, &Pubkey::default(), slot);
1176 bank.register_unique_recent_blockhash_for_test();
1177 bank.transfer(amount, &mint_keypair, &pubkey::new_rand())
1178 .unwrap();
1179 bank.fill_bank_with_ticks_for_tests();
1180 bank.squash();
1181 bank.force_flush_accounts_cache();
1182 }
1183
1184 let slot = bank.slot() + 1;
1188 bank = new_bank_from_parent_with_bank_forks(&bank_forks, bank, &Pubkey::default(), slot);
1189 bank.store_account_and_update_capitalization(
1190 &feature_set::accounts_lt_hash::id(),
1191 &feature::create_account(&Feature { activated_at: None }, amount),
1192 );
1193 assert!(!bank
1194 .feature_set
1195 .is_active(&feature_set::accounts_lt_hash::id()));
1196 bank = new_from_parent_next_epoch(bank, &bank_forks, 1);
1197 assert!(bank
1198 .feature_set
1199 .is_active(&feature_set::accounts_lt_hash::id()));
1200
1201 for _ in 0..5 {
1203 let slot = bank.slot() + 1;
1204 bank =
1205 new_bank_from_parent_with_bank_forks(&bank_forks, bank, &Pubkey::default(), slot);
1206 bank.register_unique_recent_blockhash_for_test();
1207 bank.transfer(amount, &mint_keypair, &pubkey::new_rand())
1208 .unwrap();
1209 bank.fill_bank_with_ticks_for_tests();
1210 }
1211
1212 bank.squash();
1217 bank.force_flush_accounts_cache();
1218 let calculated_accounts_lt_hash_from_index = bank
1219 .rc
1220 .accounts
1221 .accounts_db
1222 .calculate_accounts_lt_hash_at_startup_from_index(&bank.ancestors, bank.slot);
1223 assert_eq!(
1224 calculated_accounts_lt_hash_from_index,
1225 *bank.accounts_lt_hash.lock().unwrap(),
1226 );
1227
1228 let snapshot_config = SnapshotConfig::default();
1231 let bank_snapshots_dir = TempDir::new().unwrap();
1232 let snapshot_archives_dir = TempDir::new().unwrap();
1233 let snapshot = snapshot_bank_utils::bank_to_full_snapshot_archive(
1234 &bank_snapshots_dir,
1235 &bank,
1236 Some(snapshot_config.snapshot_version),
1237 &snapshot_archives_dir,
1238 &snapshot_archives_dir,
1239 snapshot_config.archive_format,
1240 )
1241 .unwrap();
1242 let (_accounts_tempdir, accounts_dir) = snapshot_utils::create_tmp_accounts_dir_for_tests();
1243 let accounts_db_config = AccountsDbConfig {
1244 enable_experimental_accumulator_hash: match verify_cli {
1245 Cli::Off => false,
1246 Cli::On => true,
1247 },
1248 ..ACCOUNTS_DB_CONFIG_FOR_TESTING
1249 };
1250 let (roundtrip_bank, _) = snapshot_bank_utils::bank_from_snapshot_archives(
1251 &[accounts_dir],
1252 &bank_snapshots_dir,
1253 &snapshot,
1254 None,
1255 &genesis_config,
1256 &RuntimeConfig::default(),
1257 None,
1258 None,
1259 None,
1260 false,
1261 false,
1262 false,
1263 false,
1264 Some(accounts_db_config),
1265 None,
1266 Arc::default(),
1267 )
1268 .unwrap();
1269
1270 roundtrip_bank.wait_for_initial_accounts_hash_verification_completed_for_tests();
1272 assert_eq!(roundtrip_bank, *bank);
1273 }
1274
1275 #[test_matrix(
1277 [Features::None, Features::All],
1278 [Cli::Off, Cli::On],
1279 [Cli::Off, Cli::On]
1280 )]
1281 fn test_snapshots_lt_hash(features: Features, cli: Cli, verify_cli: Cli) {
1282 let (genesis_config, mint_keypair) = genesis_config_with(features);
1283 let (mut bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config);
1284 bank.rc
1285 .accounts
1286 .accounts_db
1287 .set_is_experimental_accumulator_hash_enabled(features == Features::None);
1288 assert!(bank.is_accounts_lt_hash_enabled());
1290
1291 bank.rc
1292 .accounts
1293 .accounts_db
1294 .set_snapshots_use_experimental_accumulator_hash(match cli {
1295 Cli::Off => false,
1296 Cli::On => true,
1297 });
1298
1299 let amount = cmp::max(
1300 bank.get_minimum_balance_for_rent_exemption(0),
1301 LAMPORTS_PER_SOL,
1302 );
1303
1304 for _ in 0..3 {
1307 let slot = bank.slot() + 1;
1308 bank =
1309 new_bank_from_parent_with_bank_forks(&bank_forks, bank, &Pubkey::default(), slot);
1310 bank.register_unique_recent_blockhash_for_test();
1311 bank.transfer(amount, &mint_keypair, &pubkey::new_rand())
1312 .unwrap();
1313 bank.fill_bank_with_ticks_for_tests();
1314 bank.squash();
1315 bank.force_flush_accounts_cache();
1316 }
1317
1318 let snapshot_config = SnapshotConfig::default();
1319 let bank_snapshots_dir = TempDir::new().unwrap();
1320 let snapshot_archives_dir = TempDir::new().unwrap();
1321 let snapshot = snapshot_bank_utils::bank_to_full_snapshot_archive(
1322 &bank_snapshots_dir,
1323 &bank,
1324 Some(snapshot_config.snapshot_version),
1325 &snapshot_archives_dir,
1326 &snapshot_archives_dir,
1327 snapshot_config.archive_format,
1328 )
1329 .unwrap();
1330 let (_accounts_tempdir, accounts_dir) = snapshot_utils::create_tmp_accounts_dir_for_tests();
1331 let accounts_db_config = AccountsDbConfig {
1332 enable_experimental_accumulator_hash: features == Features::None,
1333 snapshots_use_experimental_accumulator_hash: match verify_cli {
1334 Cli::Off => false,
1335 Cli::On => true,
1336 },
1337 ..ACCOUNTS_DB_CONFIG_FOR_TESTING
1338 };
1339 let (roundtrip_bank, _) = snapshot_bank_utils::bank_from_snapshot_archives(
1340 &[accounts_dir],
1341 &bank_snapshots_dir,
1342 &snapshot,
1343 None,
1344 &genesis_config,
1345 &RuntimeConfig::default(),
1346 None,
1347 None,
1348 None,
1349 false,
1350 false,
1351 false,
1352 false,
1353 Some(accounts_db_config),
1354 None,
1355 Arc::default(),
1356 )
1357 .unwrap();
1358
1359 roundtrip_bank.wait_for_initial_accounts_hash_verification_completed_for_tests();
1361 assert_eq!(roundtrip_bank, *bank);
1362 }
1363}