diff --git a/accounts-bench/src/main.rs b/accounts-bench/src/main.rs index 82dfbd325483e6..32ccd8f7485615 100644 --- a/accounts-bench/src/main.rs +++ b/accounts-bench/src/main.rs @@ -6,6 +6,7 @@ use solana_runtime::{ accounts_index::Ancestors, }; use solana_sdk::pubkey::Pubkey; +use std::collections::HashSet; use std::fs; use std::path::PathBuf; @@ -83,6 +84,7 @@ fn main() { ancestors.insert(i as u64, i - 1); accounts.add_root(i as u64); } + let cap_exempt = HashSet::new(); for x in 0..iterations { if clean { let mut time = Measure::start("clean"); @@ -96,7 +98,10 @@ fn main() { } else { let mut pubkeys: Vec = vec![]; let mut time = Measure::start("hash"); - let hash = accounts.accounts_db.update_accounts_hash(0, &ancestors); + let hash = accounts + .accounts_db + .update_accounts_hash(0, &ancestors, &cap_exempt) + .0; time.stop(); println!("hash: {} {}", hash, time); create_test_accounts(&accounts, &mut pubkeys, 1, 0); diff --git a/runtime/benches/accounts.rs b/runtime/benches/accounts.rs index 4b9e99bc5d7435..6c89661a78a6ee 100644 --- a/runtime/benches/accounts.rs +++ b/runtime/benches/accounts.rs @@ -7,6 +7,7 @@ use solana_runtime::{ bank::*, }; use solana_sdk::{account::Account, genesis_config::create_genesis_config, pubkey::Pubkey}; +use std::collections::HashSet; use std::{path::PathBuf, sync::Arc}; use test::Bencher; @@ -67,10 +68,17 @@ fn test_accounts_squash(bencher: &mut Bencher) { fn test_accounts_hash_bank_hash(bencher: &mut Bencher) { let accounts = Accounts::new(vec![PathBuf::from("bench_accounts_hash_internal")]); let mut pubkeys: Vec = vec![]; - create_test_accounts(&accounts, &mut pubkeys, 60000, 0); + let num_accounts = 60_000; + let slot = 0; + create_test_accounts(&accounts, &mut pubkeys, num_accounts, slot); let ancestors = vec![(0, 0)].into_iter().collect(); - accounts.accounts_db.update_accounts_hash(0, &ancestors); - bencher.iter(|| assert!(accounts.verify_bank_hash(0, &ancestors))); + let cap_exempt = HashSet::new(); + let (_, total_lamports) = accounts + .accounts_db + .update_accounts_hash(0, &ancestors, &cap_exempt); + bencher.iter(|| { + assert!(accounts.verify_bank_hash_and_lamports(0, &ancestors, total_lamports, &cap_exempt)) + }); } #[bench] @@ -80,8 +88,11 @@ fn test_update_accounts_hash(bencher: &mut Bencher) { let mut pubkeys: Vec = vec![]; create_test_accounts(&accounts, &mut pubkeys, 50_000, 0); let ancestors = vec![(0, 0)].into_iter().collect(); + let cap_exempt = HashSet::new(); bencher.iter(|| { - accounts.accounts_db.update_accounts_hash(0, &ancestors); + accounts + .accounts_db + .update_accounts_hash(0, &ancestors, &cap_exempt); }); } diff --git a/runtime/src/accounts.rs b/runtime/src/accounts.rs index 50dd2d12840841..85772dce7b297c 100644 --- a/runtime/src/accounts.rs +++ b/runtime/src/accounts.rs @@ -428,8 +428,19 @@ impl Accounts { } #[must_use] - pub fn verify_bank_hash(&self, slot: Slot, ancestors: &Ancestors) -> bool { - if let Err(err) = self.accounts_db.verify_bank_hash(slot, ancestors) { + pub fn verify_bank_hash_and_lamports( + &self, + slot: Slot, + ancestors: &Ancestors, + total_lamports: u64, + capitalization_exempt: &HashSet, + ) -> bool { + if let Err(err) = self.accounts_db.verify_bank_hash_and_lamports( + slot, + ancestors, + total_lamports, + capitalization_exempt, + ) { warn!("verify_bank_hash failed: {:?}", err); false } else { diff --git a/runtime/src/accounts_db.rs b/runtime/src/accounts_db.rs index 16521b84d511a3..c226df1eca0cfd 100644 --- a/runtime/src/accounts_db.rs +++ b/runtime/src/accounts_db.rs @@ -152,6 +152,7 @@ pub enum BankHashVerificationError { MismatchedAccountHash, MismatchedBankHash, MissingBankHash, + MismatchedTotalLamports, } /// Persistent storage structure holding the accounts @@ -1545,13 +1546,15 @@ impl AccountsDB { &self, ancestors: &Ancestors, check_hash: bool, - ) -> Result { + capitalization_exempt: &HashSet, + ) -> Result<(Hash, u64), BankHashVerificationError> { use BankHashVerificationError::*; let mut scan = Measure::start("scan"); let accounts_index = self.accounts_index.read().unwrap(); let storage = self.storage.read().unwrap(); let keys: Vec<_> = accounts_index.account_maps.keys().collect(); let mismatch_found = AtomicU64::new(0); + let total_lamports = AtomicU64::new(0); let hashes: Vec<_> = keys .par_iter() .filter_map(|pubkey| { @@ -1564,6 +1567,12 @@ impl AccountsDB { .and_then(|storage_map| storage_map.get(&account_info.store_id)) .and_then(|store| { let account = store.accounts.get_account(account_info.offset)?.0; + if !(solana_sdk::sysvar::check_id(&account.account_meta.owner) + || capitalization_exempt.contains(&pubkey)) + { + total_lamports + .fetch_add(account_info.lamports, Ordering::Relaxed); + } if check_hash { let hash = Self::hash_stored_account(*slot, &account); @@ -1603,7 +1612,7 @@ impl AccountsDB { ("hash_accumulate", accumulate.as_us(), i64), ("hash_total", hash_total, i64), ); - Ok(accumulated_hash) + Ok((accumulated_hash, total_lamports.load(Ordering::Relaxed))) } pub fn get_accounts_hash(&self, slot: Slot) -> Hash { @@ -1612,22 +1621,40 @@ impl AccountsDB { bank_hash_info.snapshot_hash } - pub fn update_accounts_hash(&self, slot: Slot, ancestors: &Ancestors) -> Hash { - let hash = self.calculate_accounts_hash(ancestors, false).unwrap(); + pub fn update_accounts_hash( + &self, + slot: Slot, + ancestors: &Ancestors, + capitalization_exempt: &HashSet, + ) -> (Hash, u64) { + let (hash, total_lamports) = self + .calculate_accounts_hash(ancestors, false, capitalization_exempt) + .unwrap(); let mut bank_hashes = self.bank_hashes.write().unwrap(); let mut bank_hash_info = bank_hashes.get_mut(&slot).unwrap(); bank_hash_info.snapshot_hash = hash; - hash + (hash, total_lamports) } - pub fn verify_bank_hash( + pub fn verify_bank_hash_and_lamports( &self, slot: Slot, ancestors: &Ancestors, + total_lamports: u64, + capitalization_exempt: &HashSet, ) -> Result<(), BankHashVerificationError> { use BankHashVerificationError::*; - let calculated_hash = self.calculate_accounts_hash(ancestors, true)?; + let (calculated_hash, calculated_lamports) = + self.calculate_accounts_hash(ancestors, true, capitalization_exempt)?; + + if calculated_lamports != total_lamports { + warn!( + "Mismatched total lamports: {} calculated: {}", + total_lamports, calculated_lamports + ); + return Err(MismatchedTotalLamports); + } let bank_hashes = self.bank_hashes.read().unwrap(); if let Some(found_hash_info) = bank_hashes.get(&slot) { @@ -2901,8 +2928,8 @@ pub mod tests { let ancestors = linear_ancestors(latest_slot); assert_eq!( - daccounts.update_accounts_hash(latest_slot, &ancestors), - accounts.update_accounts_hash(latest_slot, &ancestors) + daccounts.update_accounts_hash(latest_slot, &ancestors, &HashSet::new()), + accounts.update_accounts_hash(latest_slot, &ancestors, &HashSet::new()) ); } @@ -3035,13 +3062,13 @@ pub mod tests { let ancestors = linear_ancestors(current_slot); info!("ancestors: {:?}", ancestors); - let hash = accounts.update_accounts_hash(current_slot, &ancestors); + let hash = accounts.update_accounts_hash(current_slot, &ancestors, &HashSet::new()); accounts.clean_accounts(); accounts.process_dead_slots(None); assert_eq!( - accounts.update_accounts_hash(current_slot, &ancestors), + accounts.update_accounts_hash(current_slot, &ancestors, &HashSet::new()), hash ); @@ -3162,7 +3189,7 @@ pub mod tests { accounts.add_root(current_slot); accounts.print_accounts_stats("pre_f"); - accounts.update_accounts_hash(4, &HashMap::default()); + accounts.update_accounts_hash(4, &HashMap::default(), &HashSet::new()); let accounts = f(accounts, current_slot); @@ -3173,7 +3200,9 @@ pub mod tests { assert_load_account(&accounts, current_slot, purged_pubkey2, 0); assert_load_account(&accounts, current_slot, dummy_pubkey, dummy_lamport); - accounts.verify_bank_hash(4, &HashMap::default()).unwrap(); + accounts + .verify_bank_hash_and_lamports(4, &HashMap::default(), 1222, &HashSet::new()) + .unwrap(); } #[test] @@ -3552,12 +3581,12 @@ pub mod tests { } #[test] - fn test_verify_bank_hash() { + fn test_verify_bank_hash1() { use BankHashVerificationError::*; solana_logger::setup(); let db = AccountsDB::new(Vec::new()); - let key = Pubkey::default(); + let key = Pubkey::new_rand(); let some_data_len = 0; let some_slot: Slot = 0; let account = Account::new(1, some_data_len, &key); @@ -3565,12 +3594,20 @@ pub mod tests { db.store(some_slot, &[(&key, &account)]); db.add_root(some_slot); - db.update_accounts_hash(some_slot, &ancestors); - assert_matches!(db.verify_bank_hash(some_slot, &ancestors), Ok(_)); + db.update_accounts_hash(some_slot, &ancestors, &HashSet::new()); + assert_matches!( + db.verify_bank_hash_and_lamports(some_slot, &ancestors, 1, &HashSet::new()), + Ok(_) + ); + + assert_matches!( + db.verify_bank_hash_and_lamports(some_slot, &ancestors, 10, &HashSet::new()), + Err(MismatchedTotalLamports) + ); db.bank_hashes.write().unwrap().remove(&some_slot).unwrap(); assert_matches!( - db.verify_bank_hash(some_slot, &ancestors), + db.verify_bank_hash_and_lamports(some_slot, &ancestors, 1, &HashSet::new()), Err(MissingBankHash) ); @@ -3585,7 +3622,7 @@ pub mod tests { .unwrap() .insert(some_slot, bank_hash_info); assert_matches!( - db.verify_bank_hash(some_slot, &ancestors), + db.verify_bank_hash_and_lamports(some_slot, &ancestors, 1, &HashSet::new()), Err(MismatchedBankHash) ); } @@ -3603,8 +3640,11 @@ pub mod tests { .unwrap() .insert(some_slot, BankHashInfo::default()); db.add_root(some_slot); - db.update_accounts_hash(some_slot, &ancestors); - assert_matches!(db.verify_bank_hash(some_slot, &ancestors), Ok(_)); + db.update_accounts_hash(some_slot, &ancestors, &HashSet::new()); + assert_matches!( + db.verify_bank_hash_and_lamports(some_slot, &ancestors, 0, &HashSet::new()), + Ok(_) + ); } #[test] @@ -3627,7 +3667,7 @@ pub mod tests { db.store_with_hashes(some_slot, accounts, &[some_hash]); db.add_root(some_slot); assert_matches!( - db.verify_bank_hash(some_slot, &ancestors), + db.verify_bank_hash_and_lamports(some_slot, &ancestors, 1, &HashSet::new()), Err(MismatchedAccountHash) ); } @@ -4045,14 +4085,14 @@ pub mod tests { ); let no_ancestors = HashMap::default(); - accounts.update_accounts_hash(current_slot, &no_ancestors); + accounts.update_accounts_hash(current_slot, &no_ancestors, &HashSet::new()); accounts - .verify_bank_hash(current_slot, &no_ancestors) + .verify_bank_hash_and_lamports(current_slot, &no_ancestors, 22300, &HashSet::new()) .unwrap(); let accounts = reconstruct_accounts_db_via_serialization(&accounts, current_slot); accounts - .verify_bank_hash(current_slot, &no_ancestors) + .verify_bank_hash_and_lamports(current_slot, &no_ancestors, 22300, &HashSet::new()) .unwrap(); // repeating should be no-op diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index 6d80a75d489087..15cf748297b95f 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -429,6 +429,10 @@ pub struct Bank { pub operating_mode: Option, pub lazy_rent_collection: AtomicBool, + + pub builtin_programs: Arc>>, + + pub genesis_cap_exempt_ids: Arc>>, } impl Default for BlockhashQueue { @@ -550,6 +554,8 @@ impl Bank { lazy_rent_collection: AtomicBool::new( parent.lazy_rent_collection.load(Ordering::Relaxed), ), + builtin_programs: parent.builtin_programs.clone(), + genesis_cap_exempt_ids: parent.genesis_cap_exempt_ids.clone(), }; datapoint_info!( @@ -658,6 +664,8 @@ impl Bank { skip_drop: new(), operating_mode: Some(genesis_config.operating_mode), lazy_rent_collection: new(), + genesis_cap_exempt_ids: new(), + builtin_programs: new(), }; bank.finish_init(); @@ -1172,6 +1180,7 @@ impl Bank { panic!("{} repeated in genesis config", pubkey); } self.store_account(pubkey, account); + self.genesis_cap_exempt_ids.write().unwrap().insert(*pubkey); } // highest staked node is the first collector @@ -1216,6 +1225,10 @@ impl Bank { pub fn add_native_program(&self, name: &str, program_id: &Pubkey) { let account = native_loader::create_loadable_account(name); self.store_account(program_id, &account); + self.genesis_cap_exempt_ids + .write() + .unwrap() + .insert(*program_id); debug!("Added native program {} under {:?}", name, program_id); } @@ -2777,9 +2790,14 @@ impl Bank { /// snapshot. #[must_use] fn verify_bank_hash(&self) -> bool { - self.rc - .accounts - .verify_bank_hash(self.slot(), &self.ancestors) + let mut capitalization_exempt = self.builtin_programs.read().unwrap().clone(); + capitalization_exempt.extend(self.genesis_cap_exempt_ids.read().unwrap().iter()); + self.rc.accounts.verify_bank_hash_and_lamports( + self.slot(), + &self.ancestors, + self.capitalization(), + &capitalization_exempt, + ) } pub fn get_snapshot_storages(&self) -> SnapshotStorages { @@ -2841,10 +2859,15 @@ impl Bank { } pub fn update_accounts_hash(&self) -> Hash { - self.rc - .accounts - .accounts_db - .update_accounts_hash(self.slot(), &self.ancestors) + let mut capitalization_exempt = self.builtin_programs.read().unwrap().clone(); + capitalization_exempt.extend(self.genesis_cap_exempt_ids.read().unwrap().iter()); + let (hash, total_lamports) = self.rc.accounts.accounts_db.update_accounts_hash( + self.slot(), + &self.ancestors, + &capitalization_exempt, + ); + assert_eq!(total_lamports, self.capitalization()); + hash } /// A snapshot bank should be purged of 0 lamport accounts which are not part of the hash @@ -3096,6 +3119,7 @@ impl Bank { debug!("Added builtin loader {} under {:?}", name, program_id); } } + self.builtin_programs.write().unwrap().insert(program_id); } pub fn compare_bank(&self, dbank: &Bank) {