atlas_runtime/bank/
accounts_lt_hash.rs

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