solana_runtime/bank/
accounts_lt_hash.rs

1use {
2    super::Bank,
3    rayon::prelude::*,
4    solana_accounts_db::accounts_db::AccountsDb,
5    solana_lattice_hash::lt_hash::LtHash,
6    solana_measure::{meas_dur, measure::Measure},
7    solana_sdk::{
8        account::{accounts_equal, AccountSharedData},
9        feature_set,
10        pubkey::Pubkey,
11    },
12    solana_svm::transaction_processing_callback::AccountState,
13    std::{
14        ops::AddAssign,
15        sync::atomic::{AtomicU64, Ordering},
16        time::Duration,
17    },
18};
19
20impl Bank {
21    /// Returns if the accounts lt hash is enabled
22    pub fn is_accounts_lt_hash_enabled(&self) -> bool {
23        self.rc
24            .accounts
25            .accounts_db
26            .is_experimental_accumulator_hash_enabled()
27            || self
28                .feature_set
29                .is_active(&feature_set::accounts_lt_hash::id())
30    }
31
32    /// Returns if snapshots use the accounts lt hash
33    pub fn is_snapshots_lt_hash_enabled(&self) -> bool {
34        self.is_accounts_lt_hash_enabled()
35            && (self
36                .rc
37                .accounts
38                .accounts_db
39                .snapshots_use_experimental_accumulator_hash()
40                || self
41                    .feature_set
42                    .is_active(&feature_set::snapshots_lt_hash::id()))
43    }
44
45    /// Updates the accounts lt hash
46    ///
47    /// When freezing a bank, we compute and update the accounts lt hash.
48    /// For each account modified in this bank, we:
49    /// - mix out its previous state, and
50    /// - mix in its current state
51    ///
52    /// Since this function is non-idempotent, it should only be called once per bank.
53    pub fn update_accounts_lt_hash(&self) {
54        debug_assert!(self.is_accounts_lt_hash_enabled());
55        let delta_lt_hash = self.calculate_delta_lt_hash();
56        let mut accounts_lt_hash = self.accounts_lt_hash.lock().unwrap();
57        accounts_lt_hash.0.mix_in(&delta_lt_hash);
58
59        // If the feature gate is not yet active, log the lt hash checksums for debug/testing
60        if !self
61            .feature_set
62            .is_active(&feature_set::accounts_lt_hash::id())
63        {
64            log::info!(
65                "updated accounts lattice hash for slot {}, delta_lt_hash checksum: {}, accounts_lt_hash checksum: {}",
66                self.slot(),
67                delta_lt_hash.checksum(),
68                accounts_lt_hash.0.checksum(),
69            );
70        }
71    }
72
73    /// Calculates the lt hash *of only this slot*
74    ///
75    /// This can be thought of as akin to the accounts delta hash.
76    ///
77    /// For each account modified in this bank, we:
78    /// - mix out its previous state, and
79    /// - mix in its current state
80    ///
81    /// This function is idempotent, and may be called more than once.
82    fn calculate_delta_lt_hash(&self) -> LtHash {
83        debug_assert!(self.is_accounts_lt_hash_enabled());
84        let measure_total = Measure::start("");
85        let slot = self.slot();
86
87        // If we don't find the account in the cache, we need to go load it.
88        // We want the version of the account *before* it was written in this slot.
89        // Bank::ancestors *includes* this slot, so we need to remove it before loading.
90        let strictly_ancestors = {
91            let mut ancestors = self.ancestors.clone();
92            ancestors.remove(&self.slot());
93            ancestors
94        };
95
96        if slot == 0 {
97            // Slot 0 is special when calculating the accounts lt hash.
98            // Primordial accounts (those in genesis) that are modified by transaction processing
99            // in slot 0 will have Alive entries in the accounts lt hash cache.
100            // When calculating the accounts lt hash, if an account was initially alive, we mix
101            // *out* its previous lt hash value.  In slot 0, we haven't stored any previous lt hash
102            // values (since it is in the first slot), yet we'd still mix out these accounts!
103            // This produces the incorrect accounts lt hash.
104            // From the perspective of the accounts lt hash, in slot 0 we cannot have any accounts
105            // as previously alive.  So to work around this issue, we clear the cache.
106            // And since `strictly_ancestors` is empty, loading the previous version of the account
107            // from accounts db will return `None` (aka Dead), which is the correct behavior.
108            assert!(strictly_ancestors.is_empty());
109            self.cache_for_accounts_lt_hash.clear();
110        }
111
112        // Get all the accounts stored in this slot.
113        // Since this bank is in the middle of being frozen, it hasn't been rooted.
114        // That means the accounts should all be in the write cache, and loading will be fast.
115        let (accounts_curr, time_loading_accounts_curr) = meas_dur!({
116            self.rc
117                .accounts
118                .accounts_db
119                .get_pubkey_account_for_slot(slot)
120        });
121        let num_accounts_total = accounts_curr.len();
122
123        #[derive(Debug, Default)]
124        struct Stats {
125            num_cache_misses: usize,
126            num_accounts_unmodified: usize,
127            time_loading_accounts_prev: Duration,
128            time_comparing_accounts: Duration,
129            time_computing_hashes: Duration,
130            time_mixing_hashes: Duration,
131        }
132        impl AddAssign for Stats {
133            fn add_assign(&mut self, other: Self) {
134                self.num_cache_misses += other.num_cache_misses;
135                self.num_accounts_unmodified += other.num_accounts_unmodified;
136                self.time_loading_accounts_prev += other.time_loading_accounts_prev;
137                self.time_comparing_accounts += other.time_comparing_accounts;
138                self.time_computing_hashes += other.time_computing_hashes;
139                self.time_mixing_hashes += other.time_mixing_hashes;
140            }
141        }
142
143        let do_calculate_delta_lt_hash = || {
144            // Work on chunks of 128 pubkeys, which is 4 KiB.
145            // And 4 KiB is likely the smallest a real page size will be.
146            // And a single page is likely the smallest size a disk read will actually read.
147            // This can be tuned larger, but likely not smaller.
148            const CHUNK_SIZE: usize = 128;
149            accounts_curr
150                .par_iter()
151                .fold_chunks(
152                    CHUNK_SIZE,
153                    || (LtHash::identity(), Stats::default()),
154                    |mut accum, (pubkey, curr_account)| {
155                        // load the initial state of the account
156                        let (initial_state_of_account, measure_load) = meas_dur!({
157                            let cache_value = self
158                                .cache_for_accounts_lt_hash
159                                .get(pubkey)
160                                .map(|entry| entry.value().clone());
161                            match cache_value {
162                                Some(CacheValue::InspectAccount(initial_state_of_account)) => {
163                                    initial_state_of_account
164                                }
165                                Some(CacheValue::BankNew) | None => {
166                                    accum.1.num_cache_misses += 1;
167                                    // If the initial state of the account is not in the accounts
168                                    // lt hash cache, or is explicitly unknown, then it is likely
169                                    // this account was stored *outside* of transaction processing
170                                    // (e.g. as part of rent collection, or creating a new bank).
171                                    // Do not populate the read cache, as this account likely will
172                                    // not be accessed again soon.
173                                    let account_slot = self
174                                        .rc
175                                        .accounts
176                                        .load_with_fixed_root_do_not_populate_read_cache(
177                                            &strictly_ancestors,
178                                            pubkey,
179                                        );
180                                    match account_slot {
181                                        Some((account, _slot)) => {
182                                            InitialStateOfAccount::Alive(account)
183                                        }
184                                        None => InitialStateOfAccount::Dead,
185                                    }
186                                }
187                            }
188                        });
189                        accum.1.time_loading_accounts_prev += measure_load;
190
191                        // mix out the previous version of the account
192                        match initial_state_of_account {
193                            InitialStateOfAccount::Dead => {
194                                // nothing to do here
195                            }
196                            InitialStateOfAccount::Alive(prev_account) => {
197                                let (are_accounts_equal, measure_is_equal) =
198                                    meas_dur!(accounts_equal(curr_account, &prev_account));
199                                accum.1.time_comparing_accounts += measure_is_equal;
200                                if are_accounts_equal {
201                                    // this account didn't actually change, so skip it for lt hashing
202                                    accum.1.num_accounts_unmodified += 1;
203                                    return accum;
204                                }
205                                let (prev_lt_hash, measure_hashing) =
206                                    meas_dur!(AccountsDb::lt_hash_account(&prev_account, pubkey));
207                                let (_, measure_mixing) =
208                                    meas_dur!(accum.0.mix_out(&prev_lt_hash.0));
209                                accum.1.time_computing_hashes += measure_hashing;
210                                accum.1.time_mixing_hashes += measure_mixing;
211                            }
212                        }
213
214                        // mix in the new version of the account
215                        let (curr_lt_hash, measure_hashing) =
216                            meas_dur!(AccountsDb::lt_hash_account(curr_account, pubkey));
217                        let (_, measure_mixing) = meas_dur!(accum.0.mix_in(&curr_lt_hash.0));
218                        accum.1.time_computing_hashes += measure_hashing;
219                        accum.1.time_mixing_hashes += measure_mixing;
220
221                        accum
222                    },
223                )
224                .reduce(
225                    || (LtHash::identity(), Stats::default()),
226                    |mut accum, elem| {
227                        accum.0.mix_in(&elem.0);
228                        accum.1 += elem.1;
229                        accum
230                    },
231                )
232        };
233        let (delta_lt_hash, stats) = self
234            .rc
235            .accounts
236            .accounts_db
237            .thread_pool
238            .install(do_calculate_delta_lt_hash);
239
240        let total_time = measure_total.end_as_duration();
241        let num_accounts_modified =
242            num_accounts_total.saturating_sub(stats.num_accounts_unmodified);
243        datapoint_info!(
244            "bank-accounts_lt_hash",
245            ("slot", slot, i64),
246            ("num_accounts_total", num_accounts_total, i64),
247            ("num_accounts_modified", num_accounts_modified, i64),
248            (
249                "num_accounts_unmodified",
250                stats.num_accounts_unmodified,
251                i64
252            ),
253            ("num_cache_misses", stats.num_cache_misses, i64),
254            ("total_us", total_time.as_micros(), i64),
255            (
256                "loading_accounts_curr_us",
257                time_loading_accounts_curr.as_micros(),
258                i64
259            ),
260            (
261                "par_loading_accounts_prev_us",
262                stats.time_loading_accounts_prev.as_micros(),
263                i64
264            ),
265            (
266                "par_comparing_accounts_us",
267                stats.time_comparing_accounts.as_micros(),
268                i64
269            ),
270            (
271                "par_computing_hashes_us",
272                stats.time_computing_hashes.as_micros(),
273                i64
274            ),
275            (
276                "par_mixing_hashes_us",
277                stats.time_mixing_hashes.as_micros(),
278                i64
279            ),
280            (
281                "num_inspect_account_hits",
282                self.stats_for_accounts_lt_hash
283                    .num_inspect_account_hits
284                    .load(Ordering::Relaxed),
285                i64
286            ),
287            (
288                "num_inspect_account_misses",
289                self.stats_for_accounts_lt_hash
290                    .num_inspect_account_misses
291                    .load(Ordering::Relaxed),
292                i64
293            ),
294            (
295                "inspect_account_lookup_ns",
296                self.stats_for_accounts_lt_hash
297                    .inspect_account_lookup_time_ns
298                    .load(Ordering::Relaxed),
299                i64
300            ),
301            (
302                "inspect_account_insert_ns",
303                self.stats_for_accounts_lt_hash
304                    .inspect_account_insert_time_ns
305                    .load(Ordering::Relaxed),
306                i64
307            ),
308        );
309
310        delta_lt_hash
311    }
312
313    /// Caches initial state of writeable accounts
314    ///
315    /// If a transaction account is writeable, cache its initial account state.
316    /// The initial state is needed when computing the accounts lt hash for the slot, and caching
317    /// the initial state saves us from having to look it up on disk later.
318    pub fn inspect_account_for_accounts_lt_hash(
319        &self,
320        address: &Pubkey,
321        account_state: &AccountState,
322        is_writable: bool,
323    ) {
324        debug_assert!(self.is_accounts_lt_hash_enabled());
325        if !is_writable {
326            // if the account is not writable, then it cannot be modified; nothing to do here
327            return;
328        }
329
330        // Only insert the account the *first* time we see it.
331        // We want to capture the value of the account *before* any modifications during this slot.
332        let (is_in_cache, lookup_time) =
333            meas_dur!(self.cache_for_accounts_lt_hash.contains_key(address));
334        if !is_in_cache {
335            let (_, insert_time) = meas_dur!({
336                self.cache_for_accounts_lt_hash
337                    .entry(*address)
338                    .or_insert_with(|| {
339                        let initial_state_of_account = match account_state {
340                            AccountState::Dead => InitialStateOfAccount::Dead,
341                            AccountState::Alive(account) => {
342                                InitialStateOfAccount::Alive((*account).clone())
343                            }
344                        };
345                        CacheValue::InspectAccount(initial_state_of_account)
346                    });
347            });
348
349            self.stats_for_accounts_lt_hash
350                .num_inspect_account_misses
351                .fetch_add(1, Ordering::Relaxed);
352            self.stats_for_accounts_lt_hash
353                .inspect_account_insert_time_ns
354                // N.B. this needs to be nanoseconds because it can be so fast
355                .fetch_add(insert_time.as_nanos() as u64, Ordering::Relaxed);
356        } else {
357            // The account is already in the cache, so nothing to do here other than update stats.
358            self.stats_for_accounts_lt_hash
359                .num_inspect_account_hits
360                .fetch_add(1, Ordering::Relaxed);
361        }
362
363        self.stats_for_accounts_lt_hash
364            .inspect_account_lookup_time_ns
365            // N.B. this needs to be nanoseconds because it can be so fast
366            .fetch_add(lookup_time.as_nanos() as u64, Ordering::Relaxed);
367    }
368}
369
370/// Stats related to accounts lt hash
371#[derive(Debug, Default)]
372pub struct Stats {
373    /// the number of times the cache already contained the account being inspected
374    num_inspect_account_hits: AtomicU64,
375    /// the number of times the cache *did not* already contain the account being inspected
376    num_inspect_account_misses: AtomicU64,
377    /// time spent checking if accounts are in the cache
378    inspect_account_lookup_time_ns: AtomicU64,
379    /// time spent inserting accounts into the cache
380    inspect_account_insert_time_ns: AtomicU64,
381}
382
383/// The initial state of an account prior to being modified in this slot/transaction
384#[derive(Debug, Clone, PartialEq)]
385pub enum InitialStateOfAccount {
386    /// The account was initiall dead
387    Dead,
388    /// The account was initially alive
389    Alive(AccountSharedData),
390}
391
392/// The value type for the accounts lt hash cache
393#[derive(Debug, Clone, PartialEq)]
394pub enum CacheValue {
395    /// The value was inserted by `inspect_account()`.
396    /// This means we will have the initial state of the account.
397    InspectAccount(InitialStateOfAccount),
398    /// The value was inserted by `Bank::new()`.
399    /// This means we will *not* have the initial state of the account.
400    BankNew,
401}
402
403#[cfg(test)]
404mod tests {
405    use {
406        super::*,
407        crate::{
408            bank::tests::{new_bank_from_parent_with_bank_forks, new_from_parent_next_epoch},
409            genesis_utils,
410            runtime_config::RuntimeConfig,
411            snapshot_bank_utils,
412            snapshot_config::SnapshotConfig,
413            snapshot_utils,
414        },
415        solana_accounts_db::accounts_db::{
416            AccountsDbConfig, DuplicatesLtHash, ACCOUNTS_DB_CONFIG_FOR_TESTING,
417        },
418        solana_sdk::{
419            account::{ReadableAccount as _, WritableAccount as _},
420            feature::{self, Feature},
421            fee_calculator::FeeRateGovernor,
422            genesis_config::{self, GenesisConfig},
423            native_token::LAMPORTS_PER_SOL,
424            pubkey::{self, Pubkey},
425            signature::Signer as _,
426            signer::keypair::Keypair,
427        },
428        std::{cmp, collections::HashMap, ops::RangeFull, str::FromStr as _, sync::Arc},
429        tempfile::TempDir,
430        test_case::{test_case, test_matrix},
431    };
432
433    /// What features should be enabled?
434    #[derive(Debug, Copy, Clone, Eq, PartialEq)]
435    enum Features {
436        /// Do not enable any features
437        None,
438        /// Enable all features
439        All,
440    }
441
442    /// Should the experimental accumulator hash cli arg be enabled?
443    #[derive(Debug, Copy, Clone, Eq, PartialEq)]
444    enum Cli {
445        /// Do not enable the cli arg
446        Off,
447        /// Enable the cli arg
448        On,
449    }
450
451    /// Creates a genesis config with `features` enabled
452    fn genesis_config_with(features: Features) -> (GenesisConfig, Keypair) {
453        let mint_lamports = 123_456_789 * LAMPORTS_PER_SOL;
454        match features {
455            Features::None => genesis_config::create_genesis_config(mint_lamports),
456            Features::All => {
457                let info = genesis_utils::create_genesis_config(mint_lamports);
458                (info.genesis_config, info.mint_keypair)
459            }
460        }
461    }
462
463    #[test]
464    fn test_update_accounts_lt_hash() {
465        // Write to address 1, 2, and 5 in first bank, so that in second bank we have
466        // updates to these three accounts.  Make address 2 go to zero (dead).  Make address 1 and 3 stay
467        // alive.  Make address 5 unchanged.  Ensure the updates are expected.
468        //
469        // 1: alive -> alive
470        // 2: alive -> dead
471        // 3: dead -> alive
472        // 4. dead -> dead
473        // 5. alive -> alive *unchanged*
474
475        let keypair1 = Keypair::new();
476        let keypair2 = Keypair::new();
477        let keypair3 = Keypair::new();
478        let keypair4 = Keypair::new();
479        let keypair5 = Keypair::new();
480
481        let (mut genesis_config, mint_keypair) =
482            genesis_config::create_genesis_config(123_456_789 * LAMPORTS_PER_SOL);
483        genesis_config.fee_rate_governor = FeeRateGovernor::new(0, 0);
484        let (bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config);
485        bank.rc
486            .accounts
487            .accounts_db
488            .set_is_experimental_accumulator_hash_enabled(true);
489
490        // ensure the accounts lt hash is enabled, otherwise this test doesn't actually do anything...
491        assert!(bank.is_accounts_lt_hash_enabled());
492
493        let amount = cmp::max(
494            bank.get_minimum_balance_for_rent_exemption(0),
495            LAMPORTS_PER_SOL,
496        );
497
498        // send lamports to accounts 1, 2, and 5 so they are alive,
499        // and so we'll have a delta in the next bank
500        bank.register_unique_recent_blockhash_for_test();
501        bank.transfer(amount, &mint_keypair, &keypair1.pubkey())
502            .unwrap();
503        bank.transfer(amount, &mint_keypair, &keypair2.pubkey())
504            .unwrap();
505        bank.transfer(amount, &mint_keypair, &keypair5.pubkey())
506            .unwrap();
507
508        // manually freeze the bank to trigger update_accounts_lt_hash() to run
509        bank.freeze();
510        let prev_accounts_lt_hash = bank.accounts_lt_hash.lock().unwrap().clone();
511
512        // save the initial values of the accounts to use for asserts later
513        let prev_mint = bank.get_account_with_fixed_root(&mint_keypair.pubkey());
514        let prev_account1 = bank.get_account_with_fixed_root(&keypair1.pubkey());
515        let prev_account2 = bank.get_account_with_fixed_root(&keypair2.pubkey());
516        let prev_account3 = bank.get_account_with_fixed_root(&keypair3.pubkey());
517        let prev_account4 = bank.get_account_with_fixed_root(&keypair4.pubkey());
518        let prev_account5 = bank.get_account_with_fixed_root(&keypair5.pubkey());
519
520        assert!(prev_mint.is_some());
521        assert!(prev_account1.is_some());
522        assert!(prev_account2.is_some());
523        assert!(prev_account3.is_none());
524        assert!(prev_account4.is_none());
525        assert!(prev_account5.is_some());
526
527        // These sysvars are also updated, but outside of transaction processing.  This means they
528        // will not be in the accounts lt hash cache, but *will* be in the list of modified
529        // accounts.  They must be included in the accounts lt hash.
530        let sysvars = [
531            Pubkey::from_str("SysvarS1otHashes111111111111111111111111111").unwrap(),
532            Pubkey::from_str("SysvarC1ock11111111111111111111111111111111").unwrap(),
533            Pubkey::from_str("SysvarRecentB1ockHashes11111111111111111111").unwrap(),
534            Pubkey::from_str("SysvarS1otHistory11111111111111111111111111").unwrap(),
535        ];
536        let prev_sysvar_accounts: Vec<_> = sysvars
537            .iter()
538            .map(|address| bank.get_account_with_fixed_root(address))
539            .collect();
540
541        let bank = {
542            let slot = bank.slot() + 1;
543            new_bank_from_parent_with_bank_forks(&bank_forks, bank, &Pubkey::default(), slot)
544        };
545
546        // send from account 2 to account 1; account 1 stays alive, account 2 ends up dead
547        bank.register_unique_recent_blockhash_for_test();
548        bank.transfer(amount, &keypair2, &keypair1.pubkey())
549            .unwrap();
550
551        // send lamports to account 4, then turn around and send them to account 3
552        // account 3 will be alive, and account 4 will end dead
553        bank.register_unique_recent_blockhash_for_test();
554        bank.transfer(amount, &mint_keypair, &keypair4.pubkey())
555            .unwrap();
556        bank.register_unique_recent_blockhash_for_test();
557        bank.transfer(amount, &keypair4, &keypair3.pubkey())
558            .unwrap();
559
560        // store account 5 into this new bank, unchanged
561        bank.rc.accounts.store_cached(
562            (
563                bank.slot(),
564                [(&keypair5.pubkey(), &prev_account5.clone().unwrap())].as_slice(),
565            ),
566            None,
567        );
568
569        // freeze the bank to trigger update_accounts_lt_hash() to run
570        bank.freeze();
571
572        let actual_delta_lt_hash = bank.calculate_delta_lt_hash();
573        let post_accounts_lt_hash = bank.accounts_lt_hash.lock().unwrap().clone();
574        let post_mint = bank.get_account_with_fixed_root(&mint_keypair.pubkey());
575        let post_account1 = bank.get_account_with_fixed_root(&keypair1.pubkey());
576        let post_account2 = bank.get_account_with_fixed_root(&keypair2.pubkey());
577        let post_account3 = bank.get_account_with_fixed_root(&keypair3.pubkey());
578        let post_account4 = bank.get_account_with_fixed_root(&keypair4.pubkey());
579        let post_account5 = bank.get_account_with_fixed_root(&keypair5.pubkey());
580
581        assert!(post_mint.is_some());
582        assert!(post_account1.is_some());
583        assert!(post_account2.is_none());
584        assert!(post_account3.is_some());
585        assert!(post_account4.is_none());
586        assert!(post_account5.is_some());
587
588        let post_sysvar_accounts: Vec<_> = sysvars
589            .iter()
590            .map(|address| bank.get_account_with_fixed_root(address))
591            .collect();
592
593        let mut expected_delta_lt_hash = LtHash::identity();
594        let mut expected_accounts_lt_hash = prev_accounts_lt_hash.clone();
595        let mut updater =
596            |address: &Pubkey, prev: Option<AccountSharedData>, post: Option<AccountSharedData>| {
597                // if there was an alive account, mix out
598                if let Some(prev) = prev {
599                    let prev_lt_hash = AccountsDb::lt_hash_account(&prev, address);
600                    expected_delta_lt_hash.mix_out(&prev_lt_hash.0);
601                    expected_accounts_lt_hash.0.mix_out(&prev_lt_hash.0);
602                }
603
604                // mix in the new one
605                let post = post.unwrap_or_default();
606                let post_lt_hash = AccountsDb::lt_hash_account(&post, address);
607                expected_delta_lt_hash.mix_in(&post_lt_hash.0);
608                expected_accounts_lt_hash.0.mix_in(&post_lt_hash.0);
609            };
610        updater(&mint_keypair.pubkey(), prev_mint, post_mint);
611        updater(&keypair1.pubkey(), prev_account1, post_account1);
612        updater(&keypair2.pubkey(), prev_account2, post_account2);
613        updater(&keypair3.pubkey(), prev_account3, post_account3);
614        updater(&keypair4.pubkey(), prev_account4, post_account4);
615        updater(&keypair5.pubkey(), prev_account5, post_account5);
616        for (i, sysvar) in sysvars.iter().enumerate() {
617            updater(
618                sysvar,
619                prev_sysvar_accounts[i].clone(),
620                post_sysvar_accounts[i].clone(),
621            );
622        }
623
624        // now make sure the delta lt hashes match
625        let expected = expected_delta_lt_hash.checksum();
626        let actual = actual_delta_lt_hash.checksum();
627        assert_eq!(
628            expected, actual,
629            "delta_lt_hash, expected: {expected}, actual: {actual}",
630        );
631
632        // ...and the accounts lt hashes match too
633        let expected = expected_accounts_lt_hash.0.checksum();
634        let actual = post_accounts_lt_hash.0.checksum();
635        assert_eq!(
636            expected, actual,
637            "accounts_lt_hash, expected: {expected}, actual: {actual}",
638        );
639    }
640
641    /// Ensure that the accounts lt hash is correct for slot 0
642    ///
643    /// This test does a simple transfer in slot 0 so that a primordial account is modified.
644    ///
645    /// See the comments in calculate_delta_lt_hash() for more information.
646    #[test_case(Features::None; "no features")]
647    #[test_case(Features::All; "all features")]
648    fn test_slot0_accounts_lt_hash(features: Features) {
649        let (genesis_config, mint_keypair) = genesis_config_with(features);
650        let (bank, _bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config);
651        bank.rc
652            .accounts
653            .accounts_db
654            .set_is_experimental_accumulator_hash_enabled(features == Features::None);
655
656        // ensure the accounts lt hash is enabled, otherwise this test doesn't actually do anything...
657        assert!(bank.is_accounts_lt_hash_enabled());
658
659        // ensure this bank is for slot 0, otherwise this test doesn't actually do anything...
660        assert_eq!(bank.slot(), 0);
661
662        // process a transaction that modifies a primordial account
663        bank.transfer(LAMPORTS_PER_SOL, &mint_keypair, &Pubkey::new_unique())
664            .unwrap();
665
666        // manually freeze the bank to trigger update_accounts_lt_hash() to run
667        bank.freeze();
668        let actual_accounts_lt_hash = bank.accounts_lt_hash.lock().unwrap().clone();
669
670        // ensure the actual accounts lt hash matches the value calculated from the index
671        let calculated_accounts_lt_hash = bank
672            .rc
673            .accounts
674            .accounts_db
675            .calculate_accounts_lt_hash_at_startup_from_index(&bank.ancestors, bank.slot());
676        assert_eq!(actual_accounts_lt_hash, calculated_accounts_lt_hash);
677    }
678
679    #[test_case(Features::None; "no features")]
680    #[test_case(Features::All; "all features")]
681    fn test_inspect_account_for_accounts_lt_hash(features: Features) {
682        let (genesis_config, _mint_keypair) = genesis_config_with(features);
683        let (bank, _bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config);
684        bank.rc
685            .accounts
686            .accounts_db
687            .set_is_experimental_accumulator_hash_enabled(features == Features::None);
688
689        // ensure the accounts lt hash is enabled, otherwise this test doesn't actually do anything...
690        assert!(bank.is_accounts_lt_hash_enabled());
691
692        // the cache should start off empty
693        assert_eq!(bank.cache_for_accounts_lt_hash.len(), 0);
694
695        // ensure non-writable accounts are *not* added to the cache
696        bank.inspect_account_for_accounts_lt_hash(
697            &Pubkey::new_unique(),
698            &AccountState::Dead,
699            false,
700        );
701        bank.inspect_account_for_accounts_lt_hash(
702            &Pubkey::new_unique(),
703            &AccountState::Alive(&AccountSharedData::default()),
704            false,
705        );
706        assert_eq!(bank.cache_for_accounts_lt_hash.len(), 0);
707
708        // ensure *new* accounts are added to the cache
709        let address = Pubkey::new_unique();
710        bank.inspect_account_for_accounts_lt_hash(&address, &AccountState::Dead, true);
711        assert_eq!(bank.cache_for_accounts_lt_hash.len(), 1);
712        assert!(bank.cache_for_accounts_lt_hash.contains_key(&address));
713
714        // ensure *existing* accounts are added to the cache
715        let address = Pubkey::new_unique();
716        let initial_lamports = 123;
717        let mut account = AccountSharedData::new(initial_lamports, 0, &Pubkey::default());
718        bank.inspect_account_for_accounts_lt_hash(&address, &AccountState::Alive(&account), true);
719        assert_eq!(bank.cache_for_accounts_lt_hash.len(), 2);
720        if let CacheValue::InspectAccount(InitialStateOfAccount::Alive(cached_account)) = bank
721            .cache_for_accounts_lt_hash
722            .get(&address)
723            .unwrap()
724            .value()
725        {
726            assert_eq!(*cached_account, account);
727        } else {
728            panic!("wrong initial state for account");
729        };
730
731        // ensure if an account is modified multiple times that we only cache the *first* one
732        let updated_lamports = account.lamports() + 1;
733        account.set_lamports(updated_lamports);
734        bank.inspect_account_for_accounts_lt_hash(&address, &AccountState::Alive(&account), true);
735        assert_eq!(bank.cache_for_accounts_lt_hash.len(), 2);
736        if let CacheValue::InspectAccount(InitialStateOfAccount::Alive(cached_account)) = bank
737            .cache_for_accounts_lt_hash
738            .get(&address)
739            .unwrap()
740            .value()
741        {
742            assert_eq!(cached_account.lamports(), initial_lamports);
743        } else {
744            panic!("wrong initial state for account");
745        };
746
747        // and ensure multiple updates are handled correctly when the account is initially dead
748        {
749            let address = Pubkey::new_unique();
750            bank.inspect_account_for_accounts_lt_hash(&address, &AccountState::Dead, true);
751            assert_eq!(bank.cache_for_accounts_lt_hash.len(), 3);
752            match bank
753                .cache_for_accounts_lt_hash
754                .get(&address)
755                .unwrap()
756                .value()
757            {
758                CacheValue::InspectAccount(InitialStateOfAccount::Dead) => {
759                    // this is expected, nothing to do here
760                }
761                _ => panic!("wrong initial state for account"),
762            };
763
764            bank.inspect_account_for_accounts_lt_hash(
765                &address,
766                &AccountState::Alive(&AccountSharedData::default()),
767                true,
768            );
769            assert_eq!(bank.cache_for_accounts_lt_hash.len(), 3);
770            match bank
771                .cache_for_accounts_lt_hash
772                .get(&address)
773                .unwrap()
774                .value()
775            {
776                CacheValue::InspectAccount(InitialStateOfAccount::Dead) => {
777                    // this is expected, nothing to do here
778                }
779                _ => panic!("wrong initial state for account"),
780            };
781        }
782    }
783
784    #[test_case(Features::None; "no features")]
785    #[test_case(Features::All; "all features")]
786    fn test_calculate_accounts_lt_hash_at_startup_from_index(features: Features) {
787        let (genesis_config, mint_keypair) = genesis_config_with(features);
788        let (mut bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config);
789        bank.rc
790            .accounts
791            .accounts_db
792            .set_is_experimental_accumulator_hash_enabled(features == Features::None);
793
794        // ensure the accounts lt hash is enabled, otherwise this test doesn't actually do anything...
795        assert!(bank.is_accounts_lt_hash_enabled());
796
797        let amount = cmp::max(
798            bank.get_minimum_balance_for_rent_exemption(0),
799            LAMPORTS_PER_SOL,
800        );
801
802        // create some banks with some modified accounts so that there are stored accounts
803        // (note: the number of banks and transfers are arbitrary)
804        for _ in 0..7 {
805            let slot = bank.slot() + 1;
806            bank =
807                new_bank_from_parent_with_bank_forks(&bank_forks, bank, &Pubkey::default(), slot);
808            for _ in 0..13 {
809                bank.register_unique_recent_blockhash_for_test();
810                // note: use a random pubkey here to ensure accounts
811                // are spread across all the index bins
812                bank.transfer(amount, &mint_keypair, &pubkey::new_rand())
813                    .unwrap();
814            }
815            bank.freeze();
816        }
817        let expected_accounts_lt_hash = bank.accounts_lt_hash.lock().unwrap().clone();
818
819        // root the bank and flush the accounts write cache to disk
820        // (this more accurately simulates startup, where accounts are in storages on disk)
821        bank.squash();
822        bank.force_flush_accounts_cache();
823
824        // call the fn that calculates the accounts lt hash at startup, then ensure it matches
825        let calculated_accounts_lt_hash = bank
826            .rc
827            .accounts
828            .accounts_db
829            .calculate_accounts_lt_hash_at_startup_from_index(&bank.ancestors, bank.slot());
830        assert_eq!(expected_accounts_lt_hash, calculated_accounts_lt_hash);
831    }
832
833    #[test_case(Features::None; "no features")]
834    #[test_case(Features::All; "all features")]
835    fn test_calculate_accounts_lt_hash_at_startup_from_storages(features: Features) {
836        let (genesis_config, mint_keypair) = genesis_config_with(features);
837        let (mut bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config);
838        bank.rc
839            .accounts
840            .accounts_db
841            .set_is_experimental_accumulator_hash_enabled(features == Features::None);
842
843        // ensure the accounts lt hash is enabled, otherwise this test doesn't actually do anything...
844        assert!(bank.is_accounts_lt_hash_enabled());
845
846        let amount = cmp::max(
847            bank.get_minimum_balance_for_rent_exemption(0),
848            LAMPORTS_PER_SOL,
849        );
850
851        // Write to this pubkey multiple times, so there are guaranteed duplicates in the storages.
852        let duplicate_pubkey = pubkey::new_rand();
853
854        // create some banks with some modified accounts so that there are stored accounts
855        // (note: the number of banks and transfers are arbitrary)
856        for _ in 0..7 {
857            let slot = bank.slot() + 1;
858            bank =
859                new_bank_from_parent_with_bank_forks(&bank_forks, bank, &Pubkey::default(), slot);
860            for _ in 0..9 {
861                bank.register_unique_recent_blockhash_for_test();
862                // note: use a random pubkey here to ensure accounts
863                // are spread across all the index bins
864                // (and calculating the accounts lt hash from storages requires no duplicates)
865                bank.transfer(amount, &mint_keypair, &pubkey::new_rand())
866                    .unwrap();
867
868                bank.register_unique_recent_blockhash_for_test();
869                bank.transfer(amount, &mint_keypair, &duplicate_pubkey)
870                    .unwrap();
871            }
872
873            // flush the write cache each slot to ensure there are account duplicates in the storages
874            bank.squash();
875            bank.force_flush_accounts_cache();
876        }
877        let expected_accounts_lt_hash = bank.accounts_lt_hash.lock().unwrap().clone();
878
879        // go through the storages to find the duplicates
880        let (mut storages, _slots) = bank.rc.accounts.accounts_db.get_storages(RangeFull);
881        // sort the storages in slot-descending order
882        // this makes skipping the latest easier
883        storages.sort_unstable_by_key(|storage| cmp::Reverse(storage.slot()));
884        let storages = storages.into_boxed_slice();
885
886        // get all the lt hashes for each version of all accounts
887        let mut stored_accounts_map = HashMap::<_, Vec<_>>::new();
888        for storage in &storages {
889            storage.accounts.scan_accounts(|stored_account_meta| {
890                let pubkey = stored_account_meta.pubkey();
891                let account_lt_hash = AccountsDb::lt_hash_account(&stored_account_meta, pubkey);
892                stored_accounts_map
893                    .entry(*pubkey)
894                    .or_default()
895                    .push(account_lt_hash)
896            });
897        }
898
899        // calculate the duplicates lt hash by skipping the first version (latest) of each account,
900        // and then mixing together all the rest
901        let duplicates_lt_hash = stored_accounts_map
902            .values()
903            .map(|lt_hashes| {
904                // the first element in the vec is the latest; all the rest are duplicates
905                &lt_hashes[1..]
906            })
907            .fold(LtHash::identity(), |mut accum, duplicate_lt_hashes| {
908                for duplicate_lt_hash in duplicate_lt_hashes {
909                    accum.mix_in(&duplicate_lt_hash.0);
910                }
911                accum
912            });
913        let duplicates_lt_hash = DuplicatesLtHash(duplicates_lt_hash);
914
915        // ensure that calculating the accounts lt hash from storages is correct
916        let calculated_accounts_lt_hash_from_storages = bank
917            .rc
918            .accounts
919            .accounts_db
920            .calculate_accounts_lt_hash_at_startup_from_storages(&storages, &duplicates_lt_hash);
921        assert_eq!(
922            expected_accounts_lt_hash,
923            calculated_accounts_lt_hash_from_storages
924        );
925    }
926
927    #[test_matrix(
928        [Features::None, Features::All],
929        [Cli::Off, Cli::On]
930    )]
931    fn test_verify_accounts_lt_hash_at_startup(features: Features, verify_cli: Cli) {
932        let (genesis_config, mint_keypair) = genesis_config_with(features);
933        let (mut bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config);
934        bank.rc
935            .accounts
936            .accounts_db
937            .set_is_experimental_accumulator_hash_enabled(features == Features::None);
938
939        // ensure the accounts lt hash is enabled, otherwise this test doesn't actually do anything...
940        assert!(bank.is_accounts_lt_hash_enabled());
941
942        let amount = cmp::max(
943            bank.get_minimum_balance_for_rent_exemption(0),
944            LAMPORTS_PER_SOL,
945        );
946
947        // Write to this pubkey multiple times, so there are guaranteed duplicates in the storages.
948        let duplicate_pubkey = pubkey::new_rand();
949
950        // create some banks with some modified accounts so that there are stored accounts
951        // (note: the number of banks and transfers are arbitrary)
952        for _ in 0..9 {
953            let slot = bank.slot() + 1;
954            bank =
955                new_bank_from_parent_with_bank_forks(&bank_forks, bank, &Pubkey::default(), slot);
956            for _ in 0..3 {
957                bank.register_unique_recent_blockhash_for_test();
958                bank.transfer(amount, &mint_keypair, &pubkey::new_rand())
959                    .unwrap();
960                bank.register_unique_recent_blockhash_for_test();
961                bank.transfer(amount, &mint_keypair, &duplicate_pubkey)
962                    .unwrap();
963            }
964
965            // flush the write cache to disk to ensure there are duplicates across the storages
966            bank.fill_bank_with_ticks_for_tests();
967            bank.squash();
968            bank.force_flush_accounts_cache();
969        }
970
971        // verification happens at startup, so mimic the behavior by loading from a snapshot
972        let snapshot_config = SnapshotConfig::default();
973        let bank_snapshots_dir = TempDir::new().unwrap();
974        let snapshot_archives_dir = TempDir::new().unwrap();
975        let snapshot = snapshot_bank_utils::bank_to_full_snapshot_archive(
976            &bank_snapshots_dir,
977            &bank,
978            Some(snapshot_config.snapshot_version),
979            &snapshot_archives_dir,
980            &snapshot_archives_dir,
981            snapshot_config.archive_format,
982        )
983        .unwrap();
984        let (_accounts_tempdir, accounts_dir) = snapshot_utils::create_tmp_accounts_dir_for_tests();
985        let accounts_db_config = AccountsDbConfig {
986            enable_experimental_accumulator_hash: match verify_cli {
987                Cli::Off => false,
988                Cli::On => true,
989            },
990            ..ACCOUNTS_DB_CONFIG_FOR_TESTING
991        };
992        let (roundtrip_bank, _) = snapshot_bank_utils::bank_from_snapshot_archives(
993            &[accounts_dir],
994            &bank_snapshots_dir,
995            &snapshot,
996            None,
997            &genesis_config,
998            &RuntimeConfig::default(),
999            None,
1000            None,
1001            None,
1002            false,
1003            false,
1004            false,
1005            false,
1006            Some(accounts_db_config),
1007            None,
1008            Arc::default(),
1009        )
1010        .unwrap();
1011
1012        // Correctly calculating the accounts lt hash in Bank::new_from_fields() depends on the
1013        // bank being frozen.  This is so we don't call `update_accounts_lt_hash()` twice on the
1014        // same bank!
1015        assert!(roundtrip_bank.is_frozen());
1016
1017        // Wait for the startup verification to complete.  If we don't panic, then we're good!
1018        roundtrip_bank.wait_for_initial_accounts_hash_verification_completed_for_tests();
1019        assert_eq!(roundtrip_bank, *bank);
1020    }
1021
1022    /// Ensure that accounts written in Bank::new() are added to the accounts lt hash cache.
1023    #[test_case(Features::None; "no features")]
1024    #[test_case(Features::All; "all features")]
1025    fn test_accounts_lt_hash_cache_values_from_bank_new(features: Features) {
1026        let (genesis_config, _mint_keypair) = genesis_config_with(features);
1027        let (mut bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config);
1028        bank.rc
1029            .accounts
1030            .accounts_db
1031            .set_is_experimental_accumulator_hash_enabled(features == Features::None);
1032
1033        // ensure the accounts lt hash is enabled, otherwise this test doesn't actually do anything...
1034        assert!(bank.is_accounts_lt_hash_enabled());
1035
1036        let slot = bank.slot() + 1;
1037        bank = new_bank_from_parent_with_bank_forks(&bank_forks, bank, &Pubkey::default(), slot);
1038
1039        // These are the two accounts *currently* added to the bank during Bank::new().
1040        // More accounts could be added later, so if the test fails, inspect the actual cache
1041        // accounts and update the expected cache accounts as necessary.
1042        let expected_cache = &[
1043            (
1044                Pubkey::from_str_const("SysvarC1ock11111111111111111111111111111111"),
1045                CacheValue::BankNew,
1046            ),
1047            (
1048                Pubkey::from_str_const("SysvarS1otHashes111111111111111111111111111"),
1049                CacheValue::BankNew,
1050            ),
1051        ];
1052        let mut actual_cache: Vec<_> = bank
1053            .cache_for_accounts_lt_hash
1054            .iter()
1055            .map(|entry| (*entry.key(), entry.value().clone()))
1056            .collect();
1057        actual_cache.sort_unstable_by(|a, b| a.0.cmp(&b.0));
1058        assert_eq!(expected_cache, actual_cache.as_slice());
1059    }
1060
1061    /// Ensure that feature activation plays nicely with the cli arg
1062    #[test_matrix(
1063        [Features::None, Features::All],
1064        [Cli::Off, Cli::On],
1065        [Cli::Off, Cli::On]
1066    )]
1067    fn test_accounts_lt_hash_feature_activation(features: Features, cli: Cli, verify_cli: Cli) {
1068        let (mut genesis_config, mint_keypair) = genesis_config_with(features);
1069        // since we're testing feature activation, it must start deactivated (i.e. not present)
1070        _ = genesis_config
1071            .accounts
1072            .remove(&feature_set::accounts_lt_hash::id());
1073        let (mut bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config);
1074        bank.rc
1075            .accounts
1076            .accounts_db
1077            .set_is_experimental_accumulator_hash_enabled(match cli {
1078                Cli::Off => false,
1079                Cli::On => true,
1080            });
1081
1082        let amount = cmp::max(
1083            bank.get_minimum_balance_for_rent_exemption(Feature::size_of()),
1084            1,
1085        );
1086
1087        // create some banks with some modified accounts so that there are stored accounts
1088        // (note: the number of banks and transfers are arbitrary)
1089        for _ in 0..9 {
1090            let slot = bank.slot() + 1;
1091            bank =
1092                new_bank_from_parent_with_bank_forks(&bank_forks, bank, &Pubkey::default(), slot);
1093            bank.register_unique_recent_blockhash_for_test();
1094            bank.transfer(amount, &mint_keypair, &pubkey::new_rand())
1095                .unwrap();
1096            bank.fill_bank_with_ticks_for_tests();
1097            bank.squash();
1098            bank.force_flush_accounts_cache();
1099        }
1100
1101        // Create a new bank so that we can store the feature gate account;
1102        // this triggers feature activation at the next epoch boundary.
1103        // Then create another bank in the next epoch to activate the feature.
1104        let slot = bank.slot() + 1;
1105        bank = new_bank_from_parent_with_bank_forks(&bank_forks, bank, &Pubkey::default(), slot);
1106        bank.store_account_and_update_capitalization(
1107            &feature_set::accounts_lt_hash::id(),
1108            &feature::create_account(&Feature { activated_at: None }, amount),
1109        );
1110        assert!(!bank
1111            .feature_set
1112            .is_active(&feature_set::accounts_lt_hash::id()));
1113        bank = new_from_parent_next_epoch(bank, &bank_forks, 1);
1114        assert!(bank
1115            .feature_set
1116            .is_active(&feature_set::accounts_lt_hash::id()));
1117
1118        // create some more banks with some more modified accounts
1119        for _ in 0..5 {
1120            let slot = bank.slot() + 1;
1121            bank =
1122                new_bank_from_parent_with_bank_forks(&bank_forks, bank, &Pubkey::default(), slot);
1123            bank.register_unique_recent_blockhash_for_test();
1124            bank.transfer(amount, &mint_keypair, &pubkey::new_rand())
1125                .unwrap();
1126            bank.fill_bank_with_ticks_for_tests();
1127        }
1128
1129        // Now verify the accounts lt hash from feature activation is the same as if we calculated
1130        // it at startup.  We root the bank and flush the accounts write cache for snapshots,
1131        // yet also do it here explicitly.  This allows us to verify the accounts lt hash with both
1132        // the index-based and the storages-based calculation in similar startup-like states.
1133        bank.squash();
1134        bank.force_flush_accounts_cache();
1135        let calculated_accounts_lt_hash_from_index = bank
1136            .rc
1137            .accounts
1138            .accounts_db
1139            .calculate_accounts_lt_hash_at_startup_from_index(&bank.ancestors, bank.slot);
1140        assert_eq!(
1141            calculated_accounts_lt_hash_from_index,
1142            *bank.accounts_lt_hash.lock().unwrap(),
1143        );
1144
1145        // Verification using storages happens at startup.
1146        // Mimic the behavior by taking, then loading from, a snapshot.
1147        let snapshot_config = SnapshotConfig::default();
1148        let bank_snapshots_dir = TempDir::new().unwrap();
1149        let snapshot_archives_dir = TempDir::new().unwrap();
1150        let snapshot = snapshot_bank_utils::bank_to_full_snapshot_archive(
1151            &bank_snapshots_dir,
1152            &bank,
1153            Some(snapshot_config.snapshot_version),
1154            &snapshot_archives_dir,
1155            &snapshot_archives_dir,
1156            snapshot_config.archive_format,
1157        )
1158        .unwrap();
1159        let (_accounts_tempdir, accounts_dir) = snapshot_utils::create_tmp_accounts_dir_for_tests();
1160        let accounts_db_config = AccountsDbConfig {
1161            enable_experimental_accumulator_hash: match verify_cli {
1162                Cli::Off => false,
1163                Cli::On => true,
1164            },
1165            ..ACCOUNTS_DB_CONFIG_FOR_TESTING
1166        };
1167        let (roundtrip_bank, _) = snapshot_bank_utils::bank_from_snapshot_archives(
1168            &[accounts_dir],
1169            &bank_snapshots_dir,
1170            &snapshot,
1171            None,
1172            &genesis_config,
1173            &RuntimeConfig::default(),
1174            None,
1175            None,
1176            None,
1177            false,
1178            false,
1179            false,
1180            false,
1181            Some(accounts_db_config),
1182            None,
1183            Arc::default(),
1184        )
1185        .unwrap();
1186
1187        // Wait for the startup verification to complete.  If we don't panic, then we're good!
1188        roundtrip_bank.wait_for_initial_accounts_hash_verification_completed_for_tests();
1189        assert_eq!(roundtrip_bank, *bank);
1190    }
1191
1192    /// Ensure that the snapshot hash is correct when snapshots_lt_hash is enabled
1193    #[test_matrix(
1194        [Features::None, Features::All],
1195        [Cli::Off, Cli::On],
1196        [Cli::Off, Cli::On]
1197    )]
1198    fn test_snapshots_lt_hash(features: Features, cli: Cli, verify_cli: Cli) {
1199        let (genesis_config, mint_keypair) = genesis_config_with(features);
1200        let (mut bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config);
1201        bank.rc
1202            .accounts
1203            .accounts_db
1204            .set_is_experimental_accumulator_hash_enabled(features == Features::None);
1205        // ensure the accounts lt hash is enabled, otherwise the snapshot lt hash is disabled
1206        assert!(bank.is_accounts_lt_hash_enabled());
1207
1208        bank.rc
1209            .accounts
1210            .accounts_db
1211            .set_snapshots_use_experimental_accumulator_hash(match cli {
1212                Cli::Off => false,
1213                Cli::On => true,
1214            });
1215
1216        let amount = cmp::max(
1217            bank.get_minimum_balance_for_rent_exemption(0),
1218            LAMPORTS_PER_SOL,
1219        );
1220
1221        // create some banks with some modified accounts so that there are stored accounts
1222        // (note: the number of banks is arbitrary)
1223        for _ in 0..3 {
1224            let slot = bank.slot() + 1;
1225            bank =
1226                new_bank_from_parent_with_bank_forks(&bank_forks, bank, &Pubkey::default(), slot);
1227            bank.register_unique_recent_blockhash_for_test();
1228            bank.transfer(amount, &mint_keypair, &pubkey::new_rand())
1229                .unwrap();
1230            bank.fill_bank_with_ticks_for_tests();
1231            bank.squash();
1232            bank.force_flush_accounts_cache();
1233        }
1234
1235        let snapshot_config = SnapshotConfig::default();
1236        let bank_snapshots_dir = TempDir::new().unwrap();
1237        let snapshot_archives_dir = TempDir::new().unwrap();
1238        let snapshot = snapshot_bank_utils::bank_to_full_snapshot_archive(
1239            &bank_snapshots_dir,
1240            &bank,
1241            Some(snapshot_config.snapshot_version),
1242            &snapshot_archives_dir,
1243            &snapshot_archives_dir,
1244            snapshot_config.archive_format,
1245        )
1246        .unwrap();
1247        let (_accounts_tempdir, accounts_dir) = snapshot_utils::create_tmp_accounts_dir_for_tests();
1248        let accounts_db_config = AccountsDbConfig {
1249            enable_experimental_accumulator_hash: features == Features::None,
1250            snapshots_use_experimental_accumulator_hash: match verify_cli {
1251                Cli::Off => false,
1252                Cli::On => true,
1253            },
1254            ..ACCOUNTS_DB_CONFIG_FOR_TESTING
1255        };
1256        let (roundtrip_bank, _) = snapshot_bank_utils::bank_from_snapshot_archives(
1257            &[accounts_dir],
1258            &bank_snapshots_dir,
1259            &snapshot,
1260            None,
1261            &genesis_config,
1262            &RuntimeConfig::default(),
1263            None,
1264            None,
1265            None,
1266            false,
1267            false,
1268            false,
1269            false,
1270            Some(accounts_db_config),
1271            None,
1272            Arc::default(),
1273        )
1274        .unwrap();
1275
1276        // Wait for the startup verification to complete.  If we don't panic, then we're good!
1277        roundtrip_bank.wait_for_initial_accounts_hash_verification_completed_for_tests();
1278        assert_eq!(roundtrip_bank, *bank);
1279    }
1280}