Skip to main content

solana_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::{runtime_config::RuntimeConfig, snapshot_bank_utils, snapshot_utils},
393        agave_snapshots::snapshot_config::SnapshotConfig,
394        solana_account::{ReadableAccount as _, WritableAccount as _},
395        solana_accounts_db::{
396            accounts_db::{AccountsDbConfig, MarkObsoleteAccounts, ACCOUNTS_DB_CONFIG_FOR_TESTING},
397            accounts_index::{
398                AccountsIndexConfig, IndexLimitMb, ACCOUNTS_INDEX_CONFIG_FOR_TESTING,
399            },
400        },
401        solana_fee_calculator::FeeRateGovernor,
402        solana_genesis_config::{self, GenesisConfig},
403        solana_keypair::Keypair,
404        solana_native_token::LAMPORTS_PER_SOL,
405        solana_pubkey::{self as pubkey, Pubkey},
406        solana_signer::Signer as _,
407        std::{cmp, iter, str::FromStr as _, sync::Arc},
408        tempfile::TempDir,
409        test_case::{test_case, test_matrix},
410    };
411
412    /// What features should be enabled?
413    #[derive(Debug, Copy, Clone, Eq, PartialEq)]
414    enum Features {
415        /// Do not enable any features
416        None,
417        /// Enable all features
418        All,
419    }
420
421    /// Creates a genesis config with `features` enabled
422    fn genesis_config_with(features: Features) -> (GenesisConfig, Keypair) {
423        let mint_lamports = 123_456_789 * LAMPORTS_PER_SOL;
424        match features {
425            Features::None => solana_genesis_config::create_genesis_config(mint_lamports),
426            Features::All => {
427                let info = crate::genesis_utils::create_genesis_config(mint_lamports);
428                (info.genesis_config, info.mint_keypair)
429            }
430        }
431    }
432
433    #[test]
434    fn test_update_accounts_lt_hash() {
435        // Write to address 1, 2, and 5 in first bank, so that in second bank we have
436        // updates to these three accounts.  Make address 2 go to zero (dead).  Make address 1 and 3 stay
437        // alive.  Make address 5 unchanged.  Ensure the updates are expected.
438        //
439        // 1: alive -> alive
440        // 2: alive -> dead
441        // 3: dead -> alive
442        // 4. dead -> dead
443        // 5. alive -> alive *unchanged*
444
445        let keypair1 = Keypair::new();
446        let keypair2 = Keypair::new();
447        let keypair3 = Keypair::new();
448        let keypair4 = Keypair::new();
449        let keypair5 = Keypair::new();
450
451        let (mut genesis_config, mint_keypair) =
452            solana_genesis_config::create_genesis_config(123_456_789 * LAMPORTS_PER_SOL);
453        genesis_config.fee_rate_governor = FeeRateGovernor::new(0, 0);
454        let (bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config);
455
456        let amount = cmp::max(
457            bank.get_minimum_balance_for_rent_exemption(0),
458            LAMPORTS_PER_SOL,
459        );
460
461        // send lamports to accounts 1, 2, and 5 so they are alive,
462        // and so we'll have a delta in the next bank
463        bank.register_unique_recent_blockhash_for_test();
464        bank.transfer(amount, &mint_keypair, &keypair1.pubkey())
465            .unwrap();
466        bank.transfer(amount, &mint_keypair, &keypair2.pubkey())
467            .unwrap();
468        bank.transfer(amount, &mint_keypair, &keypair5.pubkey())
469            .unwrap();
470
471        // manually freeze the bank to trigger update_accounts_lt_hash() to run
472        bank.freeze();
473        let prev_accounts_lt_hash = bank.accounts_lt_hash.lock().unwrap().clone();
474
475        // save the initial values of the accounts to use for asserts later
476        let prev_mint = bank.get_account_with_fixed_root(&mint_keypair.pubkey());
477        let prev_account1 = bank.get_account_with_fixed_root(&keypair1.pubkey());
478        let prev_account2 = bank.get_account_with_fixed_root(&keypair2.pubkey());
479        let prev_account3 = bank.get_account_with_fixed_root(&keypair3.pubkey());
480        let prev_account4 = bank.get_account_with_fixed_root(&keypair4.pubkey());
481        let prev_account5 = bank.get_account_with_fixed_root(&keypair5.pubkey());
482
483        assert!(prev_mint.is_some());
484        assert!(prev_account1.is_some());
485        assert!(prev_account2.is_some());
486        assert!(prev_account3.is_none());
487        assert!(prev_account4.is_none());
488        assert!(prev_account5.is_some());
489
490        // These sysvars are also updated, but outside of transaction processing.  This means they
491        // will not be in the accounts lt hash cache, but *will* be in the list of modified
492        // accounts.  They must be included in the accounts lt hash.
493        let sysvars = [
494            Pubkey::from_str("SysvarS1otHashes111111111111111111111111111").unwrap(),
495            Pubkey::from_str("SysvarC1ock11111111111111111111111111111111").unwrap(),
496            Pubkey::from_str("SysvarRecentB1ockHashes11111111111111111111").unwrap(),
497            Pubkey::from_str("SysvarS1otHistory11111111111111111111111111").unwrap(),
498        ];
499        let prev_sysvar_accounts: Vec<_> = sysvars
500            .iter()
501            .map(|address| bank.get_account_with_fixed_root(address))
502            .collect();
503
504        let bank = {
505            let slot = bank.slot() + 1;
506            Bank::new_from_parent_with_bank_forks(&bank_forks, bank, &Pubkey::default(), slot)
507        };
508
509        // send from account 2 to account 1; account 1 stays alive, account 2 ends up dead
510        bank.register_unique_recent_blockhash_for_test();
511        bank.transfer(amount, &keypair2, &keypair1.pubkey())
512            .unwrap();
513
514        // send lamports to account 4, then turn around and send them to account 3
515        // account 3 will be alive, and account 4 will end dead
516        bank.register_unique_recent_blockhash_for_test();
517        bank.transfer(amount, &mint_keypair, &keypair4.pubkey())
518            .unwrap();
519        bank.register_unique_recent_blockhash_for_test();
520        bank.transfer(amount, &keypair4, &keypair3.pubkey())
521            .unwrap();
522
523        // store account 5 into this new bank, unchanged
524        bank.store_account(&keypair5.pubkey(), prev_account5.as_ref().unwrap());
525
526        // freeze the bank to trigger update_accounts_lt_hash() to run
527        bank.freeze();
528
529        let actual_delta_lt_hash = bank.calculate_delta_lt_hash();
530        let post_accounts_lt_hash = bank.accounts_lt_hash.lock().unwrap().clone();
531        let post_mint = bank.get_account_with_fixed_root(&mint_keypair.pubkey());
532        let post_account1 = bank.get_account_with_fixed_root(&keypair1.pubkey());
533        let post_account2 = bank.get_account_with_fixed_root(&keypair2.pubkey());
534        let post_account3 = bank.get_account_with_fixed_root(&keypair3.pubkey());
535        let post_account4 = bank.get_account_with_fixed_root(&keypair4.pubkey());
536        let post_account5 = bank.get_account_with_fixed_root(&keypair5.pubkey());
537
538        assert!(post_mint.is_some());
539        assert!(post_account1.is_some());
540        assert!(post_account2.is_none());
541        assert!(post_account3.is_some());
542        assert!(post_account4.is_none());
543        assert!(post_account5.is_some());
544
545        let post_sysvar_accounts: Vec<_> = sysvars
546            .iter()
547            .map(|address| bank.get_account_with_fixed_root(address))
548            .collect();
549
550        let mut expected_delta_lt_hash = LtHash::identity();
551        let mut expected_accounts_lt_hash = prev_accounts_lt_hash.clone();
552        let mut updater =
553            |address: &Pubkey, prev: Option<AccountSharedData>, post: Option<AccountSharedData>| {
554                // if there was an alive account, mix out
555                if let Some(prev) = prev {
556                    let prev_lt_hash = AccountsDb::lt_hash_account(&prev, address);
557                    expected_delta_lt_hash.mix_out(&prev_lt_hash.0);
558                    expected_accounts_lt_hash.0.mix_out(&prev_lt_hash.0);
559                }
560
561                // mix in the new one
562                let post = post.unwrap_or_default();
563                let post_lt_hash = AccountsDb::lt_hash_account(&post, address);
564                expected_delta_lt_hash.mix_in(&post_lt_hash.0);
565                expected_accounts_lt_hash.0.mix_in(&post_lt_hash.0);
566            };
567        updater(&mint_keypair.pubkey(), prev_mint, post_mint);
568        updater(&keypair1.pubkey(), prev_account1, post_account1);
569        updater(&keypair2.pubkey(), prev_account2, post_account2);
570        updater(&keypair3.pubkey(), prev_account3, post_account3);
571        updater(&keypair4.pubkey(), prev_account4, post_account4);
572        updater(&keypair5.pubkey(), prev_account5, post_account5);
573        for (i, sysvar) in sysvars.iter().enumerate() {
574            updater(
575                sysvar,
576                prev_sysvar_accounts[i].clone(),
577                post_sysvar_accounts[i].clone(),
578            );
579        }
580
581        // now make sure the delta lt hashes match
582        let expected = expected_delta_lt_hash.checksum();
583        let actual = actual_delta_lt_hash.checksum();
584        assert_eq!(
585            expected, actual,
586            "delta_lt_hash, expected: {expected}, actual: {actual}",
587        );
588
589        // ...and the accounts lt hashes match too
590        let expected = expected_accounts_lt_hash.0.checksum();
591        let actual = post_accounts_lt_hash.0.checksum();
592        assert_eq!(
593            expected, actual,
594            "accounts_lt_hash, expected: {expected}, actual: {actual}",
595        );
596    }
597
598    /// Ensure that the accounts lt hash is correct for slot 0
599    ///
600    /// This test does a simple transfer in slot 0 so that a primordial account is modified.
601    ///
602    /// See the comments in calculate_delta_lt_hash() for more information.
603    #[test_case(Features::None; "no features")]
604    #[test_case(Features::All; "all features")]
605    fn test_slot0_accounts_lt_hash(features: Features) {
606        let (genesis_config, mint_keypair) = genesis_config_with(features);
607        let (bank, _bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config);
608
609        // ensure this bank is for slot 0, otherwise this test doesn't actually do anything...
610        assert_eq!(bank.slot(), 0);
611
612        // process a transaction that modifies a primordial account
613        bank.transfer(LAMPORTS_PER_SOL, &mint_keypair, &Pubkey::new_unique())
614            .unwrap();
615
616        // manually freeze the bank to trigger update_accounts_lt_hash() to run
617        bank.freeze();
618        let actual_accounts_lt_hash = bank.accounts_lt_hash.lock().unwrap().clone();
619
620        // ensure the actual accounts lt hash matches the value calculated from the index
621        let calculated_accounts_lt_hash = bank
622            .rc
623            .accounts
624            .accounts_db
625            .calculate_accounts_lt_hash_at_startup_from_index(&bank.ancestors, bank.slot());
626        assert_eq!(actual_accounts_lt_hash, calculated_accounts_lt_hash);
627    }
628
629    #[test_case(Features::None; "no features")]
630    #[test_case(Features::All; "all features")]
631    fn test_inspect_account_for_accounts_lt_hash(features: Features) {
632        let (genesis_config, _mint_keypair) = genesis_config_with(features);
633        let (bank, _bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config);
634
635        // the cache should start off empty
636        assert_eq!(bank.cache_for_accounts_lt_hash.len(), 0);
637
638        // ensure non-writable accounts are *not* added to the cache
639        bank.inspect_account_for_accounts_lt_hash(
640            &Pubkey::new_unique(),
641            &AccountState::Dead,
642            false,
643        );
644        bank.inspect_account_for_accounts_lt_hash(
645            &Pubkey::new_unique(),
646            &AccountState::Alive(&AccountSharedData::default()),
647            false,
648        );
649        assert_eq!(bank.cache_for_accounts_lt_hash.len(), 0);
650
651        // ensure *new* accounts are added to the cache
652        let address = Pubkey::new_unique();
653        bank.inspect_account_for_accounts_lt_hash(&address, &AccountState::Dead, true);
654        assert_eq!(bank.cache_for_accounts_lt_hash.len(), 1);
655        assert!(bank.cache_for_accounts_lt_hash.contains_key(&address));
656
657        // ensure *existing* accounts are added to the cache
658        let address = Pubkey::new_unique();
659        let initial_lamports = 123;
660        let mut account = AccountSharedData::new(initial_lamports, 0, &Pubkey::default());
661        bank.inspect_account_for_accounts_lt_hash(&address, &AccountState::Alive(&account), true);
662        assert_eq!(bank.cache_for_accounts_lt_hash.len(), 2);
663        if let CacheValue::InspectAccount(InitialStateOfAccount::Alive(cached_account)) = bank
664            .cache_for_accounts_lt_hash
665            .get(&address)
666            .unwrap()
667            .value()
668        {
669            assert_eq!(*cached_account, account);
670        } else {
671            panic!("wrong initial state for account");
672        };
673
674        // ensure if an account is modified multiple times that we only cache the *first* one
675        let updated_lamports = account.lamports() + 1;
676        account.set_lamports(updated_lamports);
677        bank.inspect_account_for_accounts_lt_hash(&address, &AccountState::Alive(&account), true);
678        assert_eq!(bank.cache_for_accounts_lt_hash.len(), 2);
679        if let CacheValue::InspectAccount(InitialStateOfAccount::Alive(cached_account)) = bank
680            .cache_for_accounts_lt_hash
681            .get(&address)
682            .unwrap()
683            .value()
684        {
685            assert_eq!(cached_account.lamports(), initial_lamports);
686        } else {
687            panic!("wrong initial state for account");
688        };
689
690        // and ensure multiple updates are handled correctly when the account is initially dead
691        {
692            let address = Pubkey::new_unique();
693            bank.inspect_account_for_accounts_lt_hash(&address, &AccountState::Dead, true);
694            assert_eq!(bank.cache_for_accounts_lt_hash.len(), 3);
695            match bank
696                .cache_for_accounts_lt_hash
697                .get(&address)
698                .unwrap()
699                .value()
700            {
701                CacheValue::InspectAccount(InitialStateOfAccount::Dead) => {
702                    // this is expected, nothing to do here
703                }
704                _ => panic!("wrong initial state for account"),
705            };
706
707            bank.inspect_account_for_accounts_lt_hash(
708                &address,
709                &AccountState::Alive(&AccountSharedData::default()),
710                true,
711            );
712            assert_eq!(bank.cache_for_accounts_lt_hash.len(), 3);
713            match bank
714                .cache_for_accounts_lt_hash
715                .get(&address)
716                .unwrap()
717                .value()
718            {
719                CacheValue::InspectAccount(InitialStateOfAccount::Dead) => {
720                    // this is expected, nothing to do here
721                }
722                _ => panic!("wrong initial state for account"),
723            };
724        }
725
726        // ensure accounts are *not* added to the cache if the bank is frozen
727        // N.B. this test should remain *last*, as Bank::freeze() is not meant to be undone
728        bank.freeze();
729        let address = Pubkey::new_unique();
730        let num_cache_entries_prev = bank.cache_for_accounts_lt_hash.len();
731        bank.inspect_account_for_accounts_lt_hash(&address, &AccountState::Dead, true);
732        let num_cache_entries_curr = bank.cache_for_accounts_lt_hash.len();
733        assert_eq!(num_cache_entries_curr, num_cache_entries_prev);
734        assert!(!bank.cache_for_accounts_lt_hash.contains_key(&address));
735    }
736
737    #[test_case(Features::None; "no features")]
738    #[test_case(Features::All; "all features")]
739    fn test_calculate_accounts_lt_hash_at_startup_from_index(features: Features) {
740        let (genesis_config, mint_keypair) = genesis_config_with(features);
741        let (mut bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config);
742
743        let amount = cmp::max(
744            bank.get_minimum_balance_for_rent_exemption(0),
745            LAMPORTS_PER_SOL,
746        );
747
748        // create some banks with some modified accounts so that there are stored accounts
749        // (note: the number of banks and transfers are arbitrary)
750        for _ in 0..7 {
751            let slot = bank.slot() + 1;
752            bank =
753                Bank::new_from_parent_with_bank_forks(&bank_forks, bank, &Pubkey::default(), slot);
754            for _ in 0..13 {
755                bank.register_unique_recent_blockhash_for_test();
756                // note: use a random pubkey here to ensure accounts
757                // are spread across all the index bins
758                bank.transfer(amount, &mint_keypair, &pubkey::new_rand())
759                    .unwrap();
760            }
761            bank.freeze();
762        }
763        let expected_accounts_lt_hash = bank.accounts_lt_hash.lock().unwrap().clone();
764
765        // root the bank and flush the accounts write cache to disk
766        // (this more accurately simulates startup, where accounts are in storages on disk)
767        bank.squash();
768        bank.force_flush_accounts_cache();
769
770        // call the fn that calculates the accounts lt hash at startup, then ensure it matches
771        let calculated_accounts_lt_hash = bank
772            .rc
773            .accounts
774            .accounts_db
775            .calculate_accounts_lt_hash_at_startup_from_index(&bank.ancestors, bank.slot());
776        assert_eq!(expected_accounts_lt_hash, calculated_accounts_lt_hash);
777    }
778
779    #[test_matrix(
780        [Features::None, Features::All],
781        [IndexLimitMb::Minimal, IndexLimitMb::InMemOnly],
782        [MarkObsoleteAccounts::Disabled, MarkObsoleteAccounts::Enabled]
783    )]
784    fn test_verify_accounts_lt_hash_at_startup(
785        features: Features,
786        accounts_index_limit: IndexLimitMb,
787        mark_obsolete_accounts: MarkObsoleteAccounts,
788    ) {
789        let (mut genesis_config, mint_keypair) = genesis_config_with(features);
790        // This test requires zero fees so that we can easily transfer an account's entire balance.
791        genesis_config.fee_rate_governor = FeeRateGovernor::new(0, 0);
792        let (mut bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config);
793
794        let amount = cmp::max(
795            bank.get_minimum_balance_for_rent_exemption(0),
796            LAMPORTS_PER_SOL,
797        );
798
799        // Write to this pubkey multiple times, so there are guaranteed duplicates in the storages.
800        let duplicate_pubkey = pubkey::new_rand();
801
802        // create some banks with some modified accounts so that there are stored accounts
803        // (note: the number of banks and transfers are arbitrary)
804        for _ in 0..9 {
805            let slot = bank.slot() + 1;
806            bank =
807                Bank::new_from_parent_with_bank_forks(&bank_forks, bank, &Pubkey::default(), slot);
808            for _ in 0..3 {
809                bank.register_unique_recent_blockhash_for_test();
810                bank.transfer(amount, &mint_keypair, &pubkey::new_rand())
811                    .unwrap();
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 to disk to ensure there are duplicates across the storages
818            bank.fill_bank_with_ticks_for_tests();
819            bank.squash();
820            bank.force_flush_accounts_cache();
821        }
822
823        // Create a few more storages to exercise the zero lamport duplicates handling during
824        // generate_index(), which is used for the lattice-based accounts verification.
825        // There needs to be accounts that only have a single duplicate (i.e. there are only two
826        // versions of the accounts), and toggle between non-zero and zero lamports.
827        // One account will go zero -> non-zero, and the other will go non-zero -> zero.
828        let num_accounts = 2;
829        let accounts: Vec<_> = iter::repeat_with(Keypair::new).take(num_accounts).collect();
830        for i in 0..num_accounts {
831            let slot = bank.slot() + 1;
832            bank =
833                Bank::new_from_parent_with_bank_forks(&bank_forks, bank, &Pubkey::default(), slot);
834            bank.register_unique_recent_blockhash_for_test();
835
836            // transfer into the accounts so they start with a non-zero balance
837            for account in &accounts {
838                bank.transfer(amount, &mint_keypair, &account.pubkey())
839                    .unwrap();
840                assert_ne!(bank.get_balance(&account.pubkey()), 0);
841            }
842
843            // then transfer *out* all the lamports from one of 'em
844            bank.transfer(
845                bank.get_balance(&accounts[i].pubkey()),
846                &accounts[i],
847                &pubkey::new_rand(),
848            )
849            .unwrap();
850            assert_eq!(bank.get_balance(&accounts[i].pubkey()), 0);
851
852            // flush the write cache to disk to ensure the storages match the accounts written here
853            bank.fill_bank_with_ticks_for_tests();
854            bank.squash();
855            bank.force_flush_accounts_cache();
856        }
857
858        // verification happens at startup, so mimic the behavior by loading from a snapshot
859        let snapshot_config = SnapshotConfig::default();
860        let bank_snapshots_dir = TempDir::new().unwrap();
861        let snapshot_archives_dir = TempDir::new().unwrap();
862        let snapshot = snapshot_bank_utils::bank_to_full_snapshot_archive(
863            &bank_snapshots_dir,
864            &bank,
865            Some(snapshot_config.snapshot_version),
866            &snapshot_archives_dir,
867            &snapshot_archives_dir,
868            snapshot_config.archive_format,
869        )
870        .unwrap();
871        let (_accounts_tempdir, accounts_dir) = snapshot_utils::create_tmp_accounts_dir_for_tests();
872        let accounts_index_config = AccountsIndexConfig {
873            index_limit_mb: accounts_index_limit,
874            ..ACCOUNTS_INDEX_CONFIG_FOR_TESTING
875        };
876        let accounts_db_config = AccountsDbConfig {
877            index: Some(accounts_index_config),
878            mark_obsolete_accounts,
879            ..ACCOUNTS_DB_CONFIG_FOR_TESTING
880        };
881        let roundtrip_bank = snapshot_bank_utils::bank_from_snapshot_archives(
882            &[accounts_dir],
883            &bank_snapshots_dir,
884            &snapshot,
885            None,
886            &genesis_config,
887            &RuntimeConfig::default(),
888            None,
889            None,
890            false,
891            false,
892            false,
893            accounts_db_config,
894            None,
895            Arc::default(),
896        )
897        .unwrap();
898
899        // Correctly calculating the accounts lt hash in Bank::new_from_snapshot() depends on the
900        // bank being frozen.  This is so we don't call `update_accounts_lt_hash()` twice on the
901        // same bank!
902        assert!(roundtrip_bank.is_frozen());
903
904        assert_eq!(roundtrip_bank, *bank);
905    }
906
907    /// Ensure that accounts written in Bank::new() are added to the accounts lt hash cache.
908    #[test_case(Features::None; "no features")]
909    #[test_case(Features::All; "all features")]
910    fn test_accounts_lt_hash_cache_values_from_bank_new(features: Features) {
911        let (genesis_config, _mint_keypair) = genesis_config_with(features);
912        let (mut bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config);
913
914        let slot = bank.slot() + 1;
915        bank = Bank::new_from_parent_with_bank_forks(&bank_forks, bank, &Pubkey::default(), slot);
916
917        // These are the two accounts *currently* added to the bank during Bank::new().
918        // More accounts could be added later, so if the test fails, inspect the actual cache
919        // accounts and update the expected cache accounts as necessary.
920        let expected_cache = &[
921            (
922                Pubkey::from_str_const("SysvarC1ock11111111111111111111111111111111"),
923                CacheValue::BankNew,
924            ),
925            (
926                Pubkey::from_str_const("SysvarS1otHashes111111111111111111111111111"),
927                CacheValue::BankNew,
928            ),
929        ];
930        let mut actual_cache: Vec<_> = bank
931            .cache_for_accounts_lt_hash
932            .iter()
933            .map(|entry| (*entry.key(), entry.value().clone()))
934            .collect();
935        actual_cache.sort_unstable_by(|a, b| a.0.cmp(&b.0));
936        assert_eq!(expected_cache, actual_cache.as_slice());
937    }
938
939    /// Ensure that the snapshot hash is correct
940    #[test_case(Features::None; "no features")]
941    #[test_case(Features::All; "all features")]
942    fn test_snapshots(features: Features) {
943        let (genesis_config, mint_keypair) = genesis_config_with(features);
944        let (mut bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config);
945
946        let amount = cmp::max(
947            bank.get_minimum_balance_for_rent_exemption(0),
948            LAMPORTS_PER_SOL,
949        );
950
951        // create some banks with some modified accounts so that there are stored accounts
952        // (note: the number of banks is arbitrary)
953        for _ in 0..3 {
954            let slot = bank.slot() + 1;
955            bank =
956                Bank::new_from_parent_with_bank_forks(&bank_forks, bank, &Pubkey::default(), slot);
957            bank.register_unique_recent_blockhash_for_test();
958            bank.transfer(amount, &mint_keypair, &pubkey::new_rand())
959                .unwrap();
960            bank.fill_bank_with_ticks_for_tests();
961            bank.squash();
962            bank.force_flush_accounts_cache();
963        }
964
965        let snapshot_config = SnapshotConfig::default();
966        let bank_snapshots_dir = TempDir::new().unwrap();
967        let snapshot_archives_dir = TempDir::new().unwrap();
968        let snapshot = snapshot_bank_utils::bank_to_full_snapshot_archive(
969            &bank_snapshots_dir,
970            &bank,
971            Some(snapshot_config.snapshot_version),
972            &snapshot_archives_dir,
973            &snapshot_archives_dir,
974            snapshot_config.archive_format,
975        )
976        .unwrap();
977        let (_accounts_tempdir, accounts_dir) = snapshot_utils::create_tmp_accounts_dir_for_tests();
978        let roundtrip_bank = snapshot_bank_utils::bank_from_snapshot_archives(
979            &[accounts_dir],
980            &bank_snapshots_dir,
981            &snapshot,
982            None,
983            &genesis_config,
984            &RuntimeConfig::default(),
985            None,
986            None,
987            false,
988            false,
989            false,
990            ACCOUNTS_DB_CONFIG_FOR_TESTING,
991            None,
992            Arc::default(),
993        )
994        .unwrap();
995
996        assert_eq!(roundtrip_bank, *bank);
997    }
998}