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        pubkey::Pubkey,
10    },
11    solana_svm::transaction_processing_callback::AccountState,
12    std::{ops::AddAssign, time::Duration},
13};
14
15impl Bank {
16    /// Returns if the accounts lt hash is enabled
17    pub fn is_accounts_lt_hash_enabled(&self) -> bool {
18        self.rc
19            .accounts
20            .accounts_db
21            .is_experimental_accumulator_hash_enabled()
22    }
23
24    /// Updates the accounts lt hash
25    ///
26    /// When freezing a bank, we compute and update the accounts lt hash.
27    /// For each account modified in this bank, we:
28    /// - mix out its previous state, and
29    /// - mix in its current state
30    ///
31    /// Since this function is non-idempotent, it should only be called once per bank.
32    pub fn update_accounts_lt_hash(&self) {
33        debug_assert!(self.is_accounts_lt_hash_enabled());
34        let delta_lt_hash = self.calculate_delta_lt_hash();
35        let mut accounts_lt_hash = self.accounts_lt_hash.lock().unwrap();
36        accounts_lt_hash.0.mix_in(&delta_lt_hash);
37    }
38
39    /// Calculates the lt hash *of only this slot*
40    ///
41    /// This can be thought of as akin to the accounts delta hash.
42    ///
43    /// For each account modified in this bank, we:
44    /// - mix out its previous state, and
45    /// - mix in its current state
46    ///
47    /// This function is idempotent, and may be called more than once.
48    fn calculate_delta_lt_hash(&self) -> LtHash {
49        debug_assert!(self.is_accounts_lt_hash_enabled());
50        let measure_total = Measure::start("");
51        let slot = self.slot();
52
53        // If we don't find the account in the cache, we need to go load it.
54        // We want the version of the account *before* it was written in this slot.
55        // Bank::ancestors *includes* this slot, so we need to remove it before loading.
56        let strictly_ancestors = {
57            let mut ancestors = self.ancestors.clone();
58            ancestors.remove(&self.slot());
59            ancestors
60        };
61
62        if slot == 0 {
63            // Slot 0 is special when calculating the accounts lt hash.
64            // Primordial accounts (those in genesis) that are modified by transaction processing
65            // in slot 0 will have Alive entries in the accounts lt hash cache.
66            // When calculating the accounts lt hash, if an account was initially alive, we mix
67            // *out* its previous lt hash value.  In slot 0, we haven't stored any previous lt hash
68            // values (since it is in the first slot), yet we'd still mix out these accounts!
69            // This produces the incorrect accounts lt hash.
70            // From the perspective of the accounts lt hash, in slot 0 we cannot have any accounts
71            // as previously alive.  So to work around this issue, we clear the cache.
72            // And since `strictly_ancestors` is empty, loading the previous version of the account
73            // from accounts db will return `None` (aka Dead), which is the correct behavior.
74            assert!(strictly_ancestors.is_empty());
75            self.cache_for_accounts_lt_hash.write().unwrap().clear();
76        }
77
78        // Get all the accounts stored in this slot.
79        // Since this bank is in the middle of being frozen, it hasn't been rooted.
80        // That means the accounts should all be in the write cache, and loading will be fast.
81        let (accounts_curr, time_loading_accounts_curr) = meas_dur!({
82            self.rc
83                .accounts
84                .accounts_db
85                .get_pubkey_hash_account_for_slot(slot)
86        });
87        let num_accounts_total = accounts_curr.len();
88
89        #[derive(Debug, Default)]
90        struct Stats {
91            num_cache_misses: usize,
92            num_accounts_unmodified: usize,
93            time_loading_accounts_prev: Duration,
94            time_comparing_accounts: Duration,
95            time_computing_hashes: Duration,
96            time_mixing_hashes: Duration,
97        }
98        impl AddAssign for Stats {
99            fn add_assign(&mut self, other: Self) {
100                self.num_cache_misses += other.num_cache_misses;
101                self.num_accounts_unmodified += other.num_accounts_unmodified;
102                self.time_loading_accounts_prev += other.time_loading_accounts_prev;
103                self.time_comparing_accounts += other.time_comparing_accounts;
104                self.time_computing_hashes += other.time_computing_hashes;
105                self.time_mixing_hashes += other.time_mixing_hashes;
106            }
107        }
108
109        let do_calculate_delta_lt_hash = || {
110            // Work on chunks of 128 pubkeys, which is 4 KiB.
111            // And 4 KiB is likely the smallest a real page size will be.
112            // And a single page is likely the smallest size a disk read will actually read.
113            // This can be tuned larger, but likely not smaller.
114            const CHUNK_SIZE: usize = 128;
115            let cache_for_accounts_lt_hash = self.cache_for_accounts_lt_hash.read().unwrap();
116            accounts_curr
117                .par_iter()
118                .fold_chunks(
119                    CHUNK_SIZE,
120                    || (LtHash::identity(), Stats::default()),
121                    |mut accum, elem| {
122                        let pubkey = &elem.pubkey;
123                        let curr_account = &elem.account;
124
125                        // load the initial state of the account
126                        let (initial_state_of_account, measure_load) = meas_dur!({
127                            match cache_for_accounts_lt_hash.get(pubkey) {
128                                Some(initial_state_of_account) => initial_state_of_account.clone(),
129                                None => {
130                                    accum.1.num_cache_misses += 1;
131                                    // If the initial state of the account is not in the accounts
132                                    // lt hash cache, it is likely this account was stored
133                                    // *outside* of transaction processing (e.g. as part of rent
134                                    // collection).  Do not populate the read cache, as this
135                                    // account likely will not be accessed again soon.
136                                    let account_slot = self
137                                        .rc
138                                        .accounts
139                                        .load_with_fixed_root_do_not_populate_read_cache(
140                                            &strictly_ancestors,
141                                            pubkey,
142                                        );
143                                    match account_slot {
144                                        Some((account, _slot)) => {
145                                            InitialStateOfAccount::Alive(account)
146                                        }
147                                        None => InitialStateOfAccount::Dead,
148                                    }
149                                }
150                            }
151                        });
152                        accum.1.time_loading_accounts_prev += measure_load;
153
154                        // mix out the previous version of the account
155                        match initial_state_of_account {
156                            InitialStateOfAccount::Dead => {
157                                // nothing to do here
158                            }
159                            InitialStateOfAccount::Alive(prev_account) => {
160                                let (are_accounts_equal, measure_is_equal) =
161                                    meas_dur!(accounts_equal(curr_account, &prev_account));
162                                accum.1.time_comparing_accounts += measure_is_equal;
163                                if are_accounts_equal {
164                                    // this account didn't actually change, so skip it for lt hashing
165                                    accum.1.num_accounts_unmodified += 1;
166                                    return accum;
167                                }
168                                let (prev_lt_hash, measure_hashing) =
169                                    meas_dur!(AccountsDb::lt_hash_account(&prev_account, pubkey));
170                                let (_, measure_mixing) =
171                                    meas_dur!(accum.0.mix_out(&prev_lt_hash.0));
172                                accum.1.time_computing_hashes += measure_hashing;
173                                accum.1.time_mixing_hashes += measure_mixing;
174                            }
175                        }
176
177                        // mix in the new version of the account
178                        let (curr_lt_hash, measure_hashing) =
179                            meas_dur!(AccountsDb::lt_hash_account(curr_account, pubkey));
180                        let (_, measure_mixing) = meas_dur!(accum.0.mix_in(&curr_lt_hash.0));
181                        accum.1.time_computing_hashes += measure_hashing;
182                        accum.1.time_mixing_hashes += measure_mixing;
183
184                        accum
185                    },
186                )
187                .reduce(
188                    || (LtHash::identity(), Stats::default()),
189                    |mut accum, elem| {
190                        accum.0.mix_in(&elem.0);
191                        accum.1 += elem.1;
192                        accum
193                    },
194                )
195        };
196        let (delta_lt_hash, stats) = self
197            .rc
198            .accounts
199            .accounts_db
200            .thread_pool
201            .install(do_calculate_delta_lt_hash);
202
203        let total_time = measure_total.end_as_duration();
204        let num_accounts_modified =
205            num_accounts_total.saturating_sub(stats.num_accounts_unmodified);
206        datapoint_info!(
207            "bank-accounts_lt_hash",
208            ("slot", slot, i64),
209            ("num_accounts_total", num_accounts_total, i64),
210            ("num_accounts_modified", num_accounts_modified, i64),
211            (
212                "num_accounts_unmodified",
213                stats.num_accounts_unmodified,
214                i64
215            ),
216            ("num_cache_misses", stats.num_cache_misses, i64),
217            ("total_us", total_time.as_micros(), i64),
218            (
219                "loading_accounts_curr_us",
220                time_loading_accounts_curr.as_micros(),
221                i64
222            ),
223            (
224                "par_loading_accounts_prev_us",
225                stats.time_loading_accounts_prev.as_micros(),
226                i64
227            ),
228            (
229                "par_comparing_accounts_us",
230                stats.time_comparing_accounts.as_micros(),
231                i64
232            ),
233            (
234                "par_computing_hashes_us",
235                stats.time_computing_hashes.as_micros(),
236                i64
237            ),
238            (
239                "par_mixing_hashes_us",
240                stats.time_mixing_hashes.as_micros(),
241                i64
242            ),
243        );
244
245        delta_lt_hash
246    }
247
248    /// Caches initial state of writeable accounts
249    ///
250    /// If a transaction account is writeable, cache its initial account state.
251    /// The initial state is needed when computing the accounts lt hash for the slot, and caching
252    /// the initial state saves us from having to look it up on disk later.
253    pub fn inspect_account_for_accounts_lt_hash(
254        &self,
255        address: &Pubkey,
256        account_state: &AccountState,
257        is_writable: bool,
258    ) {
259        debug_assert!(self.is_accounts_lt_hash_enabled());
260        if !is_writable {
261            // if the account is not writable, then it cannot be modified; nothing to do here
262            return;
263        }
264
265        // Only insert the account the *first* time we see it.
266        // We want to capture the value of the account *before* any modifications during this slot.
267        let is_in_cache = self
268            .cache_for_accounts_lt_hash
269            .read()
270            .unwrap()
271            .contains_key(address);
272        if !is_in_cache {
273            self.cache_for_accounts_lt_hash
274                .write()
275                .unwrap()
276                .entry(*address)
277                .or_insert_with(|| match account_state {
278                    AccountState::Dead => InitialStateOfAccount::Dead,
279                    AccountState::Alive(account) => {
280                        InitialStateOfAccount::Alive((*account).clone())
281                    }
282                });
283        }
284    }
285}
286
287/// The initial state of an account prior to being modified in this slot/transaction
288#[derive(Debug, Clone)]
289pub enum InitialStateOfAccount {
290    /// The account was initiall dead
291    Dead,
292    /// The account was initially alive
293    Alive(AccountSharedData),
294}
295
296#[cfg(test)]
297mod tests {
298    use {
299        super::*,
300        crate::{
301            bank::tests::new_bank_from_parent_with_bank_forks, genesis_utils,
302            runtime_config::RuntimeConfig, snapshot_bank_utils, snapshot_config::SnapshotConfig,
303            snapshot_utils,
304        },
305        solana_accounts_db::accounts_db::{
306            AccountsDbConfig, DuplicatesLtHash, ACCOUNTS_DB_CONFIG_FOR_TESTING,
307        },
308        solana_sdk::{
309            account::{ReadableAccount as _, WritableAccount as _},
310            fee_calculator::FeeRateGovernor,
311            genesis_config::{self, GenesisConfig},
312            native_token::LAMPORTS_PER_SOL,
313            pubkey::{self, Pubkey},
314            signature::Signer as _,
315            signer::keypair::Keypair,
316        },
317        std::{cmp, collections::HashMap, ops::RangeFull, str::FromStr as _, sync::Arc},
318        tempfile::TempDir,
319        test_case::test_case,
320    };
321
322    /// What features should be enabled?
323    #[derive(Debug, Copy, Clone, Eq, PartialEq)]
324    enum Features {
325        /// Do not enable any features
326        None,
327        /// Enable all features
328        All,
329    }
330
331    /// Creates a genesis config with `features` enabled
332    fn genesis_config_with(features: Features) -> (GenesisConfig, Keypair) {
333        let mint_lamports = 123_456_789 * LAMPORTS_PER_SOL;
334        match features {
335            Features::None => genesis_config::create_genesis_config(mint_lamports),
336            Features::All => {
337                let info = genesis_utils::create_genesis_config(mint_lamports);
338                (info.genesis_config, info.mint_keypair)
339            }
340        }
341    }
342
343    #[test]
344    fn test_update_accounts_lt_hash() {
345        // Write to address 1, 2, and 5 in first bank, so that in second bank we have
346        // updates to these three accounts.  Make address 2 go to zero (dead).  Make address 1 and 3 stay
347        // alive.  Make address 5 unchanged.  Ensure the updates are expected.
348        //
349        // 1: alive -> alive
350        // 2: alive -> dead
351        // 3: dead -> alive
352        // 4. dead -> dead
353        // 5. alive -> alive *unchanged*
354
355        let keypair1 = Keypair::new();
356        let keypair2 = Keypair::new();
357        let keypair3 = Keypair::new();
358        let keypair4 = Keypair::new();
359        let keypair5 = Keypair::new();
360
361        let (mut genesis_config, mint_keypair) =
362            genesis_config::create_genesis_config(123_456_789 * LAMPORTS_PER_SOL);
363        genesis_config.fee_rate_governor = FeeRateGovernor::new(0, 0);
364        let (bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config);
365        bank.rc
366            .accounts
367            .accounts_db
368            .set_is_experimental_accumulator_hash_enabled(true);
369
370        // ensure the accounts lt hash is enabled, otherwise this test doesn't actually do anything...
371        assert!(bank.is_accounts_lt_hash_enabled());
372
373        let amount = cmp::max(
374            bank.get_minimum_balance_for_rent_exemption(0),
375            LAMPORTS_PER_SOL,
376        );
377
378        // send lamports to accounts 1, 2, and 5 so they are alive,
379        // and so we'll have a delta in the next bank
380        bank.register_unique_recent_blockhash_for_test();
381        bank.transfer(amount, &mint_keypair, &keypair1.pubkey())
382            .unwrap();
383        bank.transfer(amount, &mint_keypair, &keypair2.pubkey())
384            .unwrap();
385        bank.transfer(amount, &mint_keypair, &keypair5.pubkey())
386            .unwrap();
387
388        // manually freeze the bank to trigger update_accounts_lt_hash() to run
389        bank.freeze();
390        let prev_accounts_lt_hash = bank.accounts_lt_hash.lock().unwrap().clone();
391
392        // save the initial values of the accounts to use for asserts later
393        let prev_mint = bank.get_account_with_fixed_root(&mint_keypair.pubkey());
394        let prev_account1 = bank.get_account_with_fixed_root(&keypair1.pubkey());
395        let prev_account2 = bank.get_account_with_fixed_root(&keypair2.pubkey());
396        let prev_account3 = bank.get_account_with_fixed_root(&keypair3.pubkey());
397        let prev_account4 = bank.get_account_with_fixed_root(&keypair4.pubkey());
398        let prev_account5 = bank.get_account_with_fixed_root(&keypair5.pubkey());
399
400        assert!(prev_mint.is_some());
401        assert!(prev_account1.is_some());
402        assert!(prev_account2.is_some());
403        assert!(prev_account3.is_none());
404        assert!(prev_account4.is_none());
405        assert!(prev_account5.is_some());
406
407        // These sysvars are also updated, but outside of transaction processing.  This means they
408        // will not be in the accounts lt hash cache, but *will* be in the list of modified
409        // accounts.  They must be included in the accounts lt hash.
410        let sysvars = [
411            Pubkey::from_str("SysvarS1otHashes111111111111111111111111111").unwrap(),
412            Pubkey::from_str("SysvarC1ock11111111111111111111111111111111").unwrap(),
413            Pubkey::from_str("SysvarRecentB1ockHashes11111111111111111111").unwrap(),
414            Pubkey::from_str("SysvarS1otHistory11111111111111111111111111").unwrap(),
415        ];
416        let prev_sysvar_accounts: Vec<_> = sysvars
417            .iter()
418            .map(|address| bank.get_account_with_fixed_root(address))
419            .collect();
420
421        let bank = {
422            let slot = bank.slot() + 1;
423            new_bank_from_parent_with_bank_forks(&bank_forks, bank, &Pubkey::default(), slot)
424        };
425
426        // send from account 2 to account 1; account 1 stays alive, account 2 ends up dead
427        bank.register_unique_recent_blockhash_for_test();
428        bank.transfer(amount, &keypair2, &keypair1.pubkey())
429            .unwrap();
430
431        // send lamports to account 4, then turn around and send them to account 3
432        // account 3 will be alive, and account 4 will end dead
433        bank.register_unique_recent_blockhash_for_test();
434        bank.transfer(amount, &mint_keypair, &keypair4.pubkey())
435            .unwrap();
436        bank.register_unique_recent_blockhash_for_test();
437        bank.transfer(amount, &keypair4, &keypair3.pubkey())
438            .unwrap();
439
440        // store account 5 into this new bank, unchanged
441        bank.rc.accounts.store_cached(
442            (
443                bank.slot(),
444                [(&keypair5.pubkey(), &prev_account5.clone().unwrap())].as_slice(),
445            ),
446            None,
447        );
448
449        // freeze the bank to trigger update_accounts_lt_hash() to run
450        bank.freeze();
451
452        let actual_delta_lt_hash = bank.calculate_delta_lt_hash();
453        let post_accounts_lt_hash = bank.accounts_lt_hash.lock().unwrap().clone();
454        let post_mint = bank.get_account_with_fixed_root(&mint_keypair.pubkey());
455        let post_account1 = bank.get_account_with_fixed_root(&keypair1.pubkey());
456        let post_account2 = bank.get_account_with_fixed_root(&keypair2.pubkey());
457        let post_account3 = bank.get_account_with_fixed_root(&keypair3.pubkey());
458        let post_account4 = bank.get_account_with_fixed_root(&keypair4.pubkey());
459        let post_account5 = bank.get_account_with_fixed_root(&keypair5.pubkey());
460
461        assert!(post_mint.is_some());
462        assert!(post_account1.is_some());
463        assert!(post_account2.is_none());
464        assert!(post_account3.is_some());
465        assert!(post_account4.is_none());
466        assert!(post_account5.is_some());
467
468        let post_sysvar_accounts: Vec<_> = sysvars
469            .iter()
470            .map(|address| bank.get_account_with_fixed_root(address))
471            .collect();
472
473        let mut expected_delta_lt_hash = LtHash::identity();
474        let mut expected_accounts_lt_hash = prev_accounts_lt_hash.clone();
475        let mut updater =
476            |address: &Pubkey, prev: Option<AccountSharedData>, post: Option<AccountSharedData>| {
477                // if there was an alive account, mix out
478                if let Some(prev) = prev {
479                    let prev_lt_hash = AccountsDb::lt_hash_account(&prev, address);
480                    expected_delta_lt_hash.mix_out(&prev_lt_hash.0);
481                    expected_accounts_lt_hash.0.mix_out(&prev_lt_hash.0);
482                }
483
484                // mix in the new one
485                let post = post.unwrap_or_default();
486                let post_lt_hash = AccountsDb::lt_hash_account(&post, address);
487                expected_delta_lt_hash.mix_in(&post_lt_hash.0);
488                expected_accounts_lt_hash.0.mix_in(&post_lt_hash.0);
489            };
490        updater(&mint_keypair.pubkey(), prev_mint, post_mint);
491        updater(&keypair1.pubkey(), prev_account1, post_account1);
492        updater(&keypair2.pubkey(), prev_account2, post_account2);
493        updater(&keypair3.pubkey(), prev_account3, post_account3);
494        updater(&keypair4.pubkey(), prev_account4, post_account4);
495        updater(&keypair5.pubkey(), prev_account5, post_account5);
496        for (i, sysvar) in sysvars.iter().enumerate() {
497            updater(
498                sysvar,
499                prev_sysvar_accounts[i].clone(),
500                post_sysvar_accounts[i].clone(),
501            );
502        }
503
504        // now make sure the delta lt hashes match
505        let expected = expected_delta_lt_hash.checksum();
506        let actual = actual_delta_lt_hash.checksum();
507        assert_eq!(
508            expected, actual,
509            "delta_lt_hash, expected: {expected}, actual: {actual}",
510        );
511
512        // ...and the accounts lt hashes match too
513        let expected = expected_accounts_lt_hash.0.checksum();
514        let actual = post_accounts_lt_hash.0.checksum();
515        assert_eq!(
516            expected, actual,
517            "accounts_lt_hash, expected: {expected}, actual: {actual}",
518        );
519    }
520
521    /// Ensure that the accounts lt hash is correct for slot 0
522    ///
523    /// This test does a simple transfer in slot 0 so that a primordial account is modified.
524    ///
525    /// See the comments in calculate_delta_lt_hash() for more information.
526    #[test_case(Features::None; "no features")]
527    #[test_case(Features::All; "all features")]
528    fn test_slot0_accounts_lt_hash(features: Features) {
529        let (genesis_config, mint_keypair) = genesis_config_with(features);
530        let (bank, _bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config);
531        bank.rc
532            .accounts
533            .accounts_db
534            .set_is_experimental_accumulator_hash_enabled(true);
535
536        // ensure the accounts lt hash is enabled, otherwise this test doesn't actually do anything...
537        assert!(bank.is_accounts_lt_hash_enabled());
538
539        // ensure this bank is for slot 0, otherwise this test doesn't actually do anything...
540        assert_eq!(bank.slot(), 0);
541
542        // process a transaction that modifies a primordial account
543        bank.transfer(LAMPORTS_PER_SOL, &mint_keypair, &Pubkey::new_unique())
544            .unwrap();
545
546        // manually freeze the bank to trigger update_accounts_lt_hash() to run
547        bank.freeze();
548        let actual_accounts_lt_hash = bank.accounts_lt_hash.lock().unwrap().clone();
549
550        // ensure the actual accounts lt hash matches the value calculated from the index
551        let calculated_accounts_lt_hash = bank
552            .rc
553            .accounts
554            .accounts_db
555            .calculate_accounts_lt_hash_at_startup_from_index(&bank.ancestors, bank.slot());
556        assert_eq!(actual_accounts_lt_hash, calculated_accounts_lt_hash);
557    }
558
559    #[test_case(Features::None; "no features")]
560    #[test_case(Features::All; "all features")]
561    fn test_inspect_account_for_accounts_lt_hash(features: Features) {
562        let (genesis_config, _mint_keypair) = genesis_config_with(features);
563        let (bank, _bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config);
564        bank.rc
565            .accounts
566            .accounts_db
567            .set_is_experimental_accumulator_hash_enabled(true);
568
569        // ensure the accounts lt hash is enabled, otherwise this test doesn't actually do anything...
570        assert!(bank.is_accounts_lt_hash_enabled());
571
572        // the cache should start off empty
573        assert_eq!(bank.cache_for_accounts_lt_hash.read().unwrap().len(), 0);
574
575        // ensure non-writable accounts are *not* added to the cache
576        bank.inspect_account_for_accounts_lt_hash(
577            &Pubkey::new_unique(),
578            &AccountState::Dead,
579            false,
580        );
581        bank.inspect_account_for_accounts_lt_hash(
582            &Pubkey::new_unique(),
583            &AccountState::Alive(&AccountSharedData::default()),
584            false,
585        );
586        assert_eq!(bank.cache_for_accounts_lt_hash.read().unwrap().len(), 0);
587
588        // ensure *new* accounts are added to the cache
589        let address = Pubkey::new_unique();
590        bank.inspect_account_for_accounts_lt_hash(&address, &AccountState::Dead, true);
591        assert_eq!(bank.cache_for_accounts_lt_hash.read().unwrap().len(), 1);
592        assert!(bank
593            .cache_for_accounts_lt_hash
594            .read()
595            .unwrap()
596            .contains_key(&address));
597
598        // ensure *existing* accounts are added to the cache
599        let address = Pubkey::new_unique();
600        let initial_lamports = 123;
601        let mut account = AccountSharedData::new(initial_lamports, 0, &Pubkey::default());
602        bank.inspect_account_for_accounts_lt_hash(&address, &AccountState::Alive(&account), true);
603        assert_eq!(bank.cache_for_accounts_lt_hash.read().unwrap().len(), 2);
604        if let InitialStateOfAccount::Alive(cached_account) = bank
605            .cache_for_accounts_lt_hash
606            .read()
607            .unwrap()
608            .get(&address)
609            .unwrap()
610        {
611            assert_eq!(*cached_account, account);
612        } else {
613            panic!("wrong initial state for account");
614        };
615
616        // ensure if an account is modified multiple times that we only cache the *first* one
617        let updated_lamports = account.lamports() + 1;
618        account.set_lamports(updated_lamports);
619        bank.inspect_account_for_accounts_lt_hash(&address, &AccountState::Alive(&account), true);
620        assert_eq!(bank.cache_for_accounts_lt_hash.read().unwrap().len(), 2);
621        if let InitialStateOfAccount::Alive(cached_account) = bank
622            .cache_for_accounts_lt_hash
623            .read()
624            .unwrap()
625            .get(&address)
626            .unwrap()
627        {
628            assert_eq!(cached_account.lamports(), initial_lamports);
629        } else {
630            panic!("wrong initial state for account");
631        };
632
633        // and ensure multiple updates are handled correctly when the account is initially dead
634        {
635            let address = Pubkey::new_unique();
636            bank.inspect_account_for_accounts_lt_hash(&address, &AccountState::Dead, true);
637            assert_eq!(bank.cache_for_accounts_lt_hash.read().unwrap().len(), 3);
638            match bank
639                .cache_for_accounts_lt_hash
640                .read()
641                .unwrap()
642                .get(&address)
643                .unwrap()
644            {
645                InitialStateOfAccount::Dead => { /* this is expected, nothing to do here*/ }
646                _ => panic!("wrong initial state for account"),
647            };
648
649            bank.inspect_account_for_accounts_lt_hash(
650                &address,
651                &AccountState::Alive(&AccountSharedData::default()),
652                true,
653            );
654            assert_eq!(bank.cache_for_accounts_lt_hash.read().unwrap().len(), 3);
655            match bank
656                .cache_for_accounts_lt_hash
657                .read()
658                .unwrap()
659                .get(&address)
660                .unwrap()
661            {
662                InitialStateOfAccount::Dead => { /* this is expected, nothing to do here*/ }
663                _ => panic!("wrong initial state for account"),
664            };
665        }
666    }
667
668    #[test_case(Features::None; "no features")]
669    #[test_case(Features::All; "all features")]
670    fn test_calculate_accounts_lt_hash_at_startup_from_index(features: Features) {
671        let (genesis_config, mint_keypair) = genesis_config_with(features);
672        let (mut bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config);
673        bank.rc
674            .accounts
675            .accounts_db
676            .set_is_experimental_accumulator_hash_enabled(true);
677
678        // ensure the accounts lt hash is enabled, otherwise this test doesn't actually do anything...
679        assert!(bank.is_accounts_lt_hash_enabled());
680
681        let amount = cmp::max(
682            bank.get_minimum_balance_for_rent_exemption(0),
683            LAMPORTS_PER_SOL,
684        );
685
686        // create some banks with some modified accounts so that there are stored accounts
687        // (note: the number of banks and transfers are arbitrary)
688        for _ in 0..7 {
689            let slot = bank.slot() + 1;
690            bank =
691                new_bank_from_parent_with_bank_forks(&bank_forks, bank, &Pubkey::default(), slot);
692            for _ in 0..13 {
693                bank.register_unique_recent_blockhash_for_test();
694                // note: use a random pubkey here to ensure accounts
695                // are spread across all the index bins
696                bank.transfer(amount, &mint_keypair, &pubkey::new_rand())
697                    .unwrap();
698            }
699            bank.freeze();
700        }
701        let expected_accounts_lt_hash = bank.accounts_lt_hash.lock().unwrap().clone();
702
703        // root the bank and flush the accounts write cache to disk
704        // (this more accurately simulates startup, where accounts are in storages on disk)
705        bank.squash();
706        bank.force_flush_accounts_cache();
707
708        // call the fn that calculates the accounts lt hash at startup, then ensure it matches
709        let calculated_accounts_lt_hash = bank
710            .rc
711            .accounts
712            .accounts_db
713            .calculate_accounts_lt_hash_at_startup_from_index(&bank.ancestors, bank.slot());
714        assert_eq!(expected_accounts_lt_hash, calculated_accounts_lt_hash);
715    }
716
717    #[test_case(Features::None; "no features")]
718    #[test_case(Features::All; "all features")]
719    fn test_calculate_accounts_lt_hash_at_startup_from_storages(features: Features) {
720        let (genesis_config, mint_keypair) = genesis_config_with(features);
721        let (mut bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config);
722        bank.rc
723            .accounts
724            .accounts_db
725            .set_is_experimental_accumulator_hash_enabled(true);
726
727        // ensure the accounts lt hash is enabled, otherwise this test doesn't actually do anything...
728        assert!(bank.is_accounts_lt_hash_enabled());
729
730        let amount = cmp::max(
731            bank.get_minimum_balance_for_rent_exemption(0),
732            LAMPORTS_PER_SOL,
733        );
734
735        // Write to this pubkey multiple times, so there are guaranteed duplicates in the storages.
736        let duplicate_pubkey = pubkey::new_rand();
737
738        // create some banks with some modified accounts so that there are stored accounts
739        // (note: the number of banks and transfers are arbitrary)
740        for _ in 0..7 {
741            let slot = bank.slot() + 1;
742            bank =
743                new_bank_from_parent_with_bank_forks(&bank_forks, bank, &Pubkey::default(), slot);
744            for _ in 0..9 {
745                bank.register_unique_recent_blockhash_for_test();
746                // note: use a random pubkey here to ensure accounts
747                // are spread across all the index bins
748                // (and calculating the accounts lt hash from storages requires no duplicates)
749                bank.transfer(amount, &mint_keypair, &pubkey::new_rand())
750                    .unwrap();
751
752                bank.register_unique_recent_blockhash_for_test();
753                bank.transfer(amount, &mint_keypair, &duplicate_pubkey)
754                    .unwrap();
755            }
756
757            // flush the write cache each slot to ensure there are account duplicates in the storages
758            bank.squash();
759            bank.force_flush_accounts_cache();
760        }
761        let expected_accounts_lt_hash = bank.accounts_lt_hash.lock().unwrap().clone();
762
763        // go through the storages to find the duplicates
764        let (mut storages, _slots) = bank
765            .rc
766            .accounts
767            .accounts_db
768            .get_snapshot_storages(RangeFull);
769        // sort the storages in slot-descending order
770        // this makes skipping the latest easier
771        storages.sort_unstable_by_key(|storage| cmp::Reverse(storage.slot()));
772        let storages = storages.into_boxed_slice();
773
774        // get all the lt hashes for each version of all accounts
775        let mut stored_accounts_map = HashMap::<_, Vec<_>>::new();
776        for storage in &storages {
777            storage.accounts.scan_accounts(|stored_account_meta| {
778                let pubkey = stored_account_meta.pubkey();
779                let account_lt_hash = AccountsDb::lt_hash_account(&stored_account_meta, pubkey);
780                stored_accounts_map
781                    .entry(*pubkey)
782                    .or_default()
783                    .push(account_lt_hash)
784            });
785        }
786
787        // calculate the duplicates lt hash by skipping the first version (latest) of each account,
788        // and then mixing together all the rest
789        let duplicates_lt_hash = stored_accounts_map
790            .values()
791            .map(|lt_hashes| {
792                // the first element in the vec is the latest; all the rest are duplicates
793                &lt_hashes[1..]
794            })
795            .fold(LtHash::identity(), |mut accum, duplicate_lt_hashes| {
796                for duplicate_lt_hash in duplicate_lt_hashes {
797                    accum.mix_in(&duplicate_lt_hash.0);
798                }
799                accum
800            });
801        let duplicates_lt_hash = DuplicatesLtHash(duplicates_lt_hash);
802
803        // ensure that calculating the accounts lt hash from storages is correct
804        let calculated_accounts_lt_hash_from_storages = bank
805            .rc
806            .accounts
807            .accounts_db
808            .calculate_accounts_lt_hash_at_startup_from_storages(&storages, &duplicates_lt_hash);
809        assert_eq!(
810            expected_accounts_lt_hash,
811            calculated_accounts_lt_hash_from_storages
812        );
813    }
814
815    #[test_case(Features::None; "no features")]
816    #[test_case(Features::All; "all features")]
817    fn test_verify_accounts_lt_hash_at_startup(features: Features) {
818        let (genesis_config, mint_keypair) = genesis_config_with(features);
819        let (mut bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config);
820        bank.rc
821            .accounts
822            .accounts_db
823            .set_is_experimental_accumulator_hash_enabled(true);
824
825        // ensure the accounts lt hash is enabled, otherwise this test doesn't actually do anything...
826        assert!(bank.is_accounts_lt_hash_enabled());
827
828        let amount = cmp::max(
829            bank.get_minimum_balance_for_rent_exemption(0),
830            LAMPORTS_PER_SOL,
831        );
832
833        // Write to this pubkey multiple times, so there are guaranteed duplicates in the storages.
834        let duplicate_pubkey = pubkey::new_rand();
835
836        // create some banks with some modified accounts so that there are stored accounts
837        // (note: the number of banks and transfers are arbitrary)
838        for _ in 0..9 {
839            let slot = bank.slot() + 1;
840            bank =
841                new_bank_from_parent_with_bank_forks(&bank_forks, bank, &Pubkey::default(), slot);
842            for _ in 0..3 {
843                bank.register_unique_recent_blockhash_for_test();
844                bank.transfer(amount, &mint_keypair, &pubkey::new_rand())
845                    .unwrap();
846                bank.register_unique_recent_blockhash_for_test();
847                bank.transfer(amount, &mint_keypair, &duplicate_pubkey)
848                    .unwrap();
849            }
850
851            // flush the write cache to disk to ensure there are duplicates across the storages
852            bank.fill_bank_with_ticks_for_tests();
853            bank.squash();
854            bank.force_flush_accounts_cache();
855        }
856
857        // verification happens at startup, so mimic the behavior by loading from a snapshot
858        let snapshot_config = SnapshotConfig::default();
859        let bank_snapshots_dir = TempDir::new().unwrap();
860        let snapshot_archives_dir = TempDir::new().unwrap();
861        let snapshot = snapshot_bank_utils::bank_to_full_snapshot_archive(
862            &bank_snapshots_dir,
863            &bank,
864            Some(snapshot_config.snapshot_version),
865            &snapshot_archives_dir,
866            &snapshot_archives_dir,
867            snapshot_config.archive_format,
868        )
869        .unwrap();
870        let (_accounts_tempdir, accounts_dir) = snapshot_utils::create_tmp_accounts_dir_for_tests();
871        let accounts_db_config = AccountsDbConfig {
872            enable_experimental_accumulator_hash: true,
873            ..ACCOUNTS_DB_CONFIG_FOR_TESTING
874        };
875        let (roundtrip_bank, _) = snapshot_bank_utils::bank_from_snapshot_archives(
876            &[accounts_dir],
877            &bank_snapshots_dir,
878            &snapshot,
879            None,
880            &genesis_config,
881            &RuntimeConfig::default(),
882            None,
883            None,
884            None,
885            false,
886            false,
887            false,
888            false,
889            Some(accounts_db_config),
890            None,
891            Arc::default(),
892        )
893        .unwrap();
894
895        // Wait for the startup verification to complete.  If we don't panic, then we're good!
896        roundtrip_bank.wait_for_initial_accounts_hash_verification_completed_for_tests();
897        assert_eq!(roundtrip_bank, *bank);
898    }
899}