solana_runtime/bank/
accounts_lt_hash.rs

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    /// Returns if the accounts lt hash is enabled
21    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    /// Returns if snapshots use the accounts lt hash
32    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    /// Updates the accounts lt hash
45    ///
46    /// When freezing a bank, we compute and update the accounts lt hash.
47    /// For each account modified in this bank, we:
48    /// - mix out its previous state, and
49    /// - mix in its current state
50    ///
51    /// Since this function is non-idempotent, it should only be called once per bank.
52    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 the feature gate is not yet active, log the lt hash checksums for debug/testing
59        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    /// Calculates the lt hash *of only this slot*
73    ///
74    /// This can be thought of as akin to the accounts delta hash.
75    ///
76    /// For each account modified in this bank, we:
77    /// - mix out its previous state, and
78    /// - mix in its current state
79    ///
80    /// This function is idempotent, and may be called more than once.
81    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        // If we don't find the account in the cache, we need to go load it.
87        // We want the version of the account *before* it was written in this slot.
88        // Bank::ancestors *includes* this slot, so we need to remove it before loading.
89        let strictly_ancestors = {
90            let mut ancestors = self.ancestors.clone();
91            ancestors.remove(&self.slot());
92            ancestors
93        };
94
95        if slot == 0 {
96            // Slot 0 is special when calculating the accounts lt hash.
97            // Primordial accounts (those in genesis) that are modified by transaction processing
98            // in slot 0 will have Alive entries in the accounts lt hash cache.
99            // When calculating the accounts lt hash, if an account was initially alive, we mix
100            // *out* its previous lt hash value.  In slot 0, we haven't stored any previous lt hash
101            // values (since it is in the first slot), yet we'd still mix out these accounts!
102            // This produces the incorrect accounts lt hash.
103            // From the perspective of the accounts lt hash, in slot 0 we cannot have any accounts
104            // as previously alive.  So to work around this issue, we clear the cache.
105            // And since `strictly_ancestors` is empty, loading the previous version of the account
106            // from accounts db will return `None` (aka Dead), which is the correct behavior.
107            assert!(strictly_ancestors.is_empty());
108            self.cache_for_accounts_lt_hash.clear();
109        }
110
111        // Get all the accounts stored in this slot.
112        // Since this bank is in the middle of being frozen, it hasn't been rooted.
113        // That means the accounts should all be in the write cache, and loading will be fast.
114        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            // Work on chunks of 128 pubkeys, which is 4 KiB.
144            // And 4 KiB is likely the smallest a real page size will be.
145            // And a single page is likely the smallest size a disk read will actually read.
146            // This can be tuned larger, but likely not smaller.
147            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                        // load the initial state of the account
155                        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                                    // If the initial state of the account is not in the accounts
167                                    // lt hash cache, or is explicitly unknown, then it is likely
168                                    // this account was stored *outside* of transaction processing
169                                    // (e.g. as part of rent collection, or creating a new bank).
170                                    // Do not populate the read cache, as this account likely will
171                                    // not be accessed again soon.
172                                    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                        // mix out the previous version of the account
191                        match initial_state_of_account {
192                            InitialStateOfAccount::Dead => {
193                                // nothing to do here
194                            }
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                                    // this account didn't actually change, so skip it for lt hashing
201                                    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                        // mix in the new version of the account
214                        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    /// Caches initial state of writeable accounts
320    ///
321    /// If a transaction account is writeable, cache its initial account state.
322    /// The initial state is needed when computing the accounts lt hash for the slot, and caching
323    /// the initial state saves us from having to look it up on disk later.
324    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            // if the account is not writable, then it cannot be modified; nothing to do here
333            return;
334        }
335
336        // Only insert the account the *first* time we see it.
337        // We want to capture the value of the account *before* any modifications during this slot.
338        let (is_in_cache, lookup_time) =
339            meas_dur!(self.cache_for_accounts_lt_hash.contains_key(address));
340        if !is_in_cache {
341            // We need to check if the bank is frozen.  In order to do that safely, we
342            // must hold a read lock on Bank::hash to read the frozen state.
343            let freeze_guard = self.freeze_lock();
344            let is_frozen = *freeze_guard != Hash::default();
345            if is_frozen {
346                // If the bank is frozen, do not add this account to the cache.
347                // It is possible for the leader to be executing transactions after freeze has
348                // started, i.e. while any deferred changes to account state is finishing up.
349                // This means the transaction could load an account *after* it was modified by the
350                // deferred changes, which would be the wrong initial state of the account.
351                // Inserting the wrong initial state of an account into the cache will end up
352                // producing the wrong accounts lt hash.
353                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                // N.B. this needs to be nanoseconds because it can be so fast
379                .fetch_add(insert_time.as_nanos() as u64, Ordering::Relaxed);
380        } else {
381            // The account is already in the cache, so nothing to do here other than update stats.
382            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            // N.B. this needs to be nanoseconds because it can be so fast
390            .fetch_add(lookup_time.as_nanos() as u64, Ordering::Relaxed);
391    }
392}
393
394/// Stats related to accounts lt hash
395#[derive(Debug, Default)]
396pub struct Stats {
397    /// the number of times the cache already contained the account being inspected
398    num_inspect_account_hits: AtomicU64,
399    /// the number of times the cache *did not* already contain the account being inspected
400    num_inspect_account_misses: AtomicU64,
401    /// the number of times an account was inspected after the bank was frozen
402    num_inspect_account_after_frozen: AtomicU64,
403    /// time spent checking if accounts are in the cache
404    inspect_account_lookup_time_ns: AtomicU64,
405    /// time spent inserting accounts into the cache
406    inspect_account_insert_time_ns: AtomicU64,
407}
408
409/// The initial state of an account prior to being modified in this slot/transaction
410#[derive(Debug, Clone, PartialEq)]
411pub enum InitialStateOfAccount {
412    /// The account was initiall dead
413    Dead,
414    /// The account was initially alive
415    Alive(AccountSharedData),
416}
417
418/// The value type for the accounts lt hash cache
419#[derive(Debug, Clone, PartialEq)]
420pub enum CacheValue {
421    /// The value was inserted by `inspect_account()`.
422    /// This means we will have the initial state of the account.
423    InspectAccount(InitialStateOfAccount),
424    /// The value was inserted by `Bank::new()`.
425    /// This means we will *not* have the initial state of the account.
426    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    /// What features should be enabled?
460    #[derive(Debug, Copy, Clone, Eq, PartialEq)]
461    enum Features {
462        /// Do not enable any features
463        None,
464        /// Enable all features
465        All,
466    }
467
468    /// Should the experimental accumulator hash cli arg be enabled?
469    #[derive(Debug, Copy, Clone, Eq, PartialEq)]
470    enum Cli {
471        /// Do not enable the cli arg
472        Off,
473        /// Enable the cli arg
474        On,
475    }
476
477    /// Creates a genesis config with `features` enabled
478    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        // Write to address 1, 2, and 5 in first bank, so that in second bank we have
492        // updates to these three accounts.  Make address 2 go to zero (dead).  Make address 1 and 3 stay
493        // alive.  Make address 5 unchanged.  Ensure the updates are expected.
494        //
495        // 1: alive -> alive
496        // 2: alive -> dead
497        // 3: dead -> alive
498        // 4. dead -> dead
499        // 5. alive -> alive *unchanged*
500
501        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        // ensure the accounts lt hash is enabled, otherwise this test doesn't actually do anything...
517        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        // send lamports to accounts 1, 2, and 5 so they are alive,
525        // and so we'll have a delta in the next bank
526        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        // manually freeze the bank to trigger update_accounts_lt_hash() to run
535        bank.freeze();
536        let prev_accounts_lt_hash = bank.accounts_lt_hash.lock().unwrap().clone();
537
538        // save the initial values of the accounts to use for asserts later
539        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        // These sysvars are also updated, but outside of transaction processing.  This means they
554        // will not be in the accounts lt hash cache, but *will* be in the list of modified
555        // accounts.  They must be included in the accounts lt hash.
556        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        // send from account 2 to account 1; account 1 stays alive, account 2 ends up dead
573        bank.register_unique_recent_blockhash_for_test();
574        bank.transfer(amount, &keypair2, &keypair1.pubkey())
575            .unwrap();
576
577        // send lamports to account 4, then turn around and send them to account 3
578        // account 3 will be alive, and account 4 will end dead
579        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        // store account 5 into this new bank, unchanged
587        bank.rc.accounts.store_cached(
588            (
589                bank.slot(),
590                [(&keypair5.pubkey(), &prev_account5.clone().unwrap())].as_slice(),
591            ),
592            None,
593        );
594
595        // freeze the bank to trigger update_accounts_lt_hash() to run
596        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 there was an alive account, mix out
624                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                // mix in the new one
631                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        // now make sure the delta lt hashes match
651        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        // ...and the accounts lt hashes match too
659        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    /// Ensure that the accounts lt hash is correct for slot 0
668    ///
669    /// This test does a simple transfer in slot 0 so that a primordial account is modified.
670    ///
671    /// See the comments in calculate_delta_lt_hash() for more information.
672    #[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        // ensure the accounts lt hash is enabled, otherwise this test doesn't actually do anything...
683        assert!(bank.is_accounts_lt_hash_enabled());
684
685        // ensure this bank is for slot 0, otherwise this test doesn't actually do anything...
686        assert_eq!(bank.slot(), 0);
687
688        // process a transaction that modifies a primordial account
689        bank.transfer(LAMPORTS_PER_SOL, &mint_keypair, &Pubkey::new_unique())
690            .unwrap();
691
692        // manually freeze the bank to trigger update_accounts_lt_hash() to run
693        bank.freeze();
694        let actual_accounts_lt_hash = bank.accounts_lt_hash.lock().unwrap().clone();
695
696        // ensure the actual accounts lt hash matches the value calculated from the index
697        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        // ensure the accounts lt hash is enabled, otherwise this test doesn't actually do anything...
716        assert!(bank.is_accounts_lt_hash_enabled());
717
718        // the cache should start off empty
719        assert_eq!(bank.cache_for_accounts_lt_hash.len(), 0);
720
721        // ensure non-writable accounts are *not* added to the cache
722        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        // ensure *new* accounts are added to the cache
735        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        // ensure *existing* accounts are added to the cache
741        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        // ensure if an account is modified multiple times that we only cache the *first* one
758        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        // and ensure multiple updates are handled correctly when the account is initially dead
774        {
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                    // this is expected, nothing to do here
786                }
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                    // this is expected, nothing to do here
804                }
805                _ => panic!("wrong initial state for account"),
806            };
807        }
808
809        // ensure accounts are *not* added to the cache if the bank is frozen
810        // N.B. this test should remain *last*, as Bank::freeze() is not meant to be undone
811        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        // ensure the accounts lt hash is enabled, otherwise this test doesn't actually do anything...
831        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        // create some banks with some modified accounts so that there are stored accounts
839        // (note: the number of banks and transfers are arbitrary)
840        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                // note: use a random pubkey here to ensure accounts
847                // are spread across all the index bins
848                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        // root the bank and flush the accounts write cache to disk
856        // (this more accurately simulates startup, where accounts are in storages on disk)
857        bank.squash();
858        bank.force_flush_accounts_cache();
859
860        // call the fn that calculates the accounts lt hash at startup, then ensure it matches
861        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        // ensure the accounts lt hash is enabled, otherwise this test doesn't actually do anything...
880        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        // Write to this pubkey multiple times, so there are guaranteed duplicates in the storages.
888        let duplicate_pubkey = pubkey::new_rand();
889
890        // create some banks with some modified accounts so that there are stored accounts
891        // (note: the number of banks and transfers are arbitrary)
892        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                // note: use a random pubkey here to ensure accounts
899                // are spread across all the index bins
900                // (and calculating the accounts lt hash from storages requires no duplicates)
901                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            // flush the write cache each slot to ensure there are account duplicates in the storages
910            bank.squash();
911            bank.force_flush_accounts_cache();
912        }
913        let expected_accounts_lt_hash = bank.accounts_lt_hash.lock().unwrap().clone();
914
915        // go through the storages to find the duplicates
916        let (mut storages, _slots) = bank.rc.accounts.accounts_db.get_storages(RangeFull);
917        // sort the storages in slot-descending order
918        // this makes skipping the latest easier
919        storages.sort_unstable_by_key(|storage| cmp::Reverse(storage.slot()));
920        let storages = storages.into_boxed_slice();
921
922        // get all the lt hashes for each version of all accounts
923        let mut stored_accounts_map = HashMap::<_, Vec<_>>::new();
924        for storage in &storages {
925            storage.accounts.scan_accounts(|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        // calculate the duplicates lt hash by skipping the first version (latest) of each account,
936        // and then mixing together all the rest
937        let duplicates_lt_hash = stored_accounts_map
938            .values()
939            .map(|lt_hashes| {
940                // the first element in the vec is the latest; all the rest are duplicates
941                &lt_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        // ensure that calculating the accounts lt hash from storages is correct
952        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        // This test requires zero fees so that we can easily transfer an account's entire balance.
975        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        // ensure the accounts lt hash is enabled, otherwise this test doesn't actually do anything...
983        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        // Write to this pubkey multiple times, so there are guaranteed duplicates in the storages.
991        let duplicate_pubkey = pubkey::new_rand();
992
993        // create some banks with some modified accounts so that there are stored accounts
994        // (note: the number of banks and transfers are arbitrary)
995        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            // flush the write cache to disk to ensure there are duplicates across the storages
1009            bank.fill_bank_with_ticks_for_tests();
1010            bank.squash();
1011            bank.force_flush_accounts_cache();
1012        }
1013
1014        // Create a few more storages to exercise the zero lamport duplicates handling during
1015        // generate_index(), which is used for the lattice-based accounts verification.
1016        // There needs to be accounts that only have a single duplicate (i.e. there are only two
1017        // versions of the accounts), and toggle between non-zero and zero lamports.
1018        // One account will go zero -> non-zero, and the other will go non-zero -> zero.
1019        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            // transfer into the accounts so they start with a non-zero balance
1028            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            // then transfer *out* all the lamports from one of 'em
1035            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            // flush the write cache to disk to ensure the storages match the accounts written here
1044            bank.fill_bank_with_ticks_for_tests();
1045            bank.squash();
1046            bank.force_flush_accounts_cache();
1047        }
1048
1049        // verification happens at startup, so mimic the behavior by loading from a snapshot
1050        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        // Correctly calculating the accounts lt hash in Bank::new_from_fields() depends on the
1096        // bank being frozen.  This is so we don't call `update_accounts_lt_hash()` twice on the
1097        // same bank!
1098        assert!(roundtrip_bank.is_frozen());
1099
1100        // Wait for the startup verification to complete.  If we don't panic, then we're good!
1101        roundtrip_bank.wait_for_initial_accounts_hash_verification_completed_for_tests();
1102        assert_eq!(roundtrip_bank, *bank);
1103    }
1104
1105    /// Ensure that accounts written in Bank::new() are added to the accounts lt hash cache.
1106    #[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        // ensure the accounts lt hash is enabled, otherwise this test doesn't actually do anything...
1117        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        // These are the two accounts *currently* added to the bank during Bank::new().
1123        // More accounts could be added later, so if the test fails, inspect the actual cache
1124        // accounts and update the expected cache accounts as necessary.
1125        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    /// Ensure that feature activation plays nicely with the cli arg
1145    #[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        // since we're testing feature activation, it must start deactivated (i.e. not present)
1153        _ = 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        // create some banks with some modified accounts so that there are stored accounts
1171        // (note: the number of banks and transfers are arbitrary)
1172        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        // Create a new bank so that we can store the feature gate account;
1185        // this triggers feature activation at the next epoch boundary.
1186        // Then create another bank in the next epoch to activate the feature.
1187        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        // create some more banks with some more modified accounts
1202        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        // Now verify the accounts lt hash from feature activation is the same as if we calculated
1213        // it at startup.  We root the bank and flush the accounts write cache for snapshots,
1214        // yet also do it here explicitly.  This allows us to verify the accounts lt hash with both
1215        // the index-based and the storages-based calculation in similar startup-like states.
1216        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        // Verification using storages happens at startup.
1229        // Mimic the behavior by taking, then loading from, a snapshot.
1230        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        // Wait for the startup verification to complete.  If we don't panic, then we're good!
1271        roundtrip_bank.wait_for_initial_accounts_hash_verification_completed_for_tests();
1272        assert_eq!(roundtrip_bank, *bank);
1273    }
1274
1275    /// Ensure that the snapshot hash is correct when snapshots_lt_hash is enabled
1276    #[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        // ensure the accounts lt hash is enabled, otherwise the snapshot lt hash is disabled
1289        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        // create some banks with some modified accounts so that there are stored accounts
1305        // (note: the number of banks is arbitrary)
1306        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        // Wait for the startup verification to complete.  If we don't panic, then we're good!
1360        roundtrip_bank.wait_for_initial_accounts_hash_verification_completed_for_tests();
1361        assert_eq!(roundtrip_bank, *bank);
1362    }
1363}