diff --git a/accounts-db/src/accounts_cache.rs b/accounts-db/src/accounts_cache.rs index dcb2c3fdd517cf..04d1ef9d736bcc 100644 --- a/accounts-db/src/accounts_cache.rs +++ b/accounts-db/src/accounts_cache.rs @@ -1,10 +1,12 @@ use { - crate::accounts_db::IncludeSlotInHash, + crate::{ + accounts_db::{AccountsDb, IncludeSlotInHash}, + accounts_hash::AccountHash, + }, dashmap::DashMap, solana_sdk::{ account::{AccountSharedData, ReadableAccount}, clock::Slot, - hash::Hash, pubkey::Pubkey, }, std::{ @@ -141,7 +143,7 @@ pub type CachedAccount = Arc; #[derive(Debug)] pub struct CachedAccountInner { pub account: AccountSharedData, - hash: RwLock>, + hash: RwLock>, slot: Slot, pubkey: Pubkey, /// temporarily here during feature activation @@ -150,13 +152,13 @@ pub struct CachedAccountInner { } impl CachedAccountInner { - pub fn hash(&self) -> Hash { + pub fn hash(&self) -> AccountHash { let hash = self.hash.read().unwrap(); match *hash { Some(hash) => hash, None => { drop(hash); - let hash = crate::accounts_db::AccountsDb::hash_account( + let hash = AccountsDb::hash_account( self.slot, &self.account, &self.pubkey, diff --git a/accounts-db/src/accounts_db.rs b/accounts-db/src/accounts_db.rs index 4291cdfe9a24ad..e82a2edf080526 100644 --- a/accounts-db/src/accounts_db.rs +++ b/accounts-db/src/accounts_db.rs @@ -33,7 +33,7 @@ use { accounts_cache::{AccountsCache, CachedAccount, SlotCache}, accounts_file::{AccountsFile, AccountsFileError}, accounts_hash::{ - AccountsDeltaHash, AccountsHash, AccountsHashKind, AccountsHasher, + AccountHash, AccountsDeltaHash, AccountsHash, AccountsHashKind, AccountsHasher, CalcAccountsHashConfig, CalculateHashIntermediate, HashStats, IncrementalAccountsHash, SerdeAccountsDeltaHash, SerdeAccountsHash, SerdeIncrementalAccountsHash, ZeroLamportAccounts, @@ -894,9 +894,9 @@ pub enum LoadedAccount<'a> { } impl<'a> LoadedAccount<'a> { - pub fn loaded_hash(&self) -> Hash { + pub fn loaded_hash(&self) -> AccountHash { match self { - LoadedAccount::Stored(stored_account_meta) => *stored_account_meta.hash(), + LoadedAccount::Stored(stored_account_meta) => AccountHash(*stored_account_meta.hash()), LoadedAccount::Cached(cached_account) => cached_account.hash(), } } @@ -913,7 +913,7 @@ impl<'a> LoadedAccount<'a> { slot: Slot, pubkey: &Pubkey, include_slot: IncludeSlotInHash, - ) -> Hash { + ) -> AccountHash { match self { LoadedAccount::Stored(stored_account_meta) => AccountsDb::hash_account( slot, @@ -2392,7 +2392,7 @@ impl<'a> AppendVecScan for ScanState<'a> { let balance = loaded_account.lamports(); let mut loaded_hash = loaded_account.loaded_hash(); - let hash_is_missing = loaded_hash == Hash::default(); + let hash_is_missing = loaded_hash == AccountHash(Hash::default()); if (self.config.check_hash || hash_is_missing) && !AccountsDb::is_filler_account_helper(pubkey, self.filler_account_suffix) { @@ -2406,13 +2406,13 @@ impl<'a> AppendVecScan for ScanState<'a> { } else if self.config.check_hash && computed_hash != loaded_hash { info!( "hash mismatch found: computed: {}, loaded: {}, pubkey: {}", - computed_hash, loaded_hash, pubkey + computed_hash.0, loaded_hash.0, pubkey ); self.mismatch_found.fetch_add(1, Ordering::Relaxed); } } let source_item = CalculateHashIntermediate { - hash: loaded_hash, + hash: loaded_hash.0, lamports: balance, pubkey: *pubkey, }; @@ -2429,7 +2429,7 @@ impl<'a> AppendVecScan for ScanState<'a> { #[derive(Clone, Debug, Eq, PartialEq)] pub struct PubkeyHashAccount { pub pubkey: Pubkey, - pub hash: Hash, + pub hash: AccountHash, pub account: AccountSharedData, } @@ -5567,7 +5567,7 @@ impl AccountsDb { pubkey: &Pubkey, max_root: Option, load_hint: LoadHint, - ) -> Option { + ) -> Option { let (slot, storage_location, _maybe_account_accesor) = self.read_index_for_accessor_or_load_slow(ancestors, pubkey, max_root, false)?; // Notice the subtle `?` at previous line, we bail out pretty early if missing. @@ -6185,7 +6185,7 @@ impl AccountsDb { account: &T, pubkey: &Pubkey, include_slot: IncludeSlotInHash, - ) -> Hash { + ) -> AccountHash { Self::hash_account_data( slot, account.lamports(), @@ -6207,9 +6207,9 @@ impl AccountsDb { data: &[u8], pubkey: &Pubkey, include_slot: IncludeSlotInHash, - ) -> Hash { + ) -> AccountHash { if lamports == 0 { - return Hash::default(); + return AccountHash(Hash::default()); } let mut hasher = blake3::Hasher::new(); @@ -6239,7 +6239,7 @@ impl AccountsDb { hasher.update(owner.as_ref()); hasher.update(pubkey.as_ref()); - Hash::new_from_array(hasher.finalize().into()) + AccountHash(Hash::new_from_array(hasher.finalize().into())) } fn bulk_assign_write_version(&self, count: usize) -> StoredMetaWriteVersion { @@ -6560,7 +6560,7 @@ impl AccountsDb { } } - let (accounts, hashes): (Vec<(&Pubkey, &AccountSharedData)>, Vec) = iter_items + let (accounts, hashes): (Vec<(&Pubkey, &AccountSharedData)>, Vec) = iter_items .iter() .filter_map(|iter_item| { let key = iter_item.key(); @@ -7025,7 +7025,7 @@ impl AccountsDb { |loaded_account| { let mut loaded_hash = loaded_account.loaded_hash(); let balance = loaded_account.lamports(); - let hash_is_missing = loaded_hash == Hash::default(); + let hash_is_missing = loaded_hash == AccountHash(Hash::default()); if (config.check_hash || hash_is_missing) && !self.is_filler_account(pubkey) { let computed_hash = loaded_account.compute_hash(*slot, pubkey, config.include_slot_in_hash); @@ -7033,7 +7033,7 @@ impl AccountsDb { loaded_hash = computed_hash; } else if config.check_hash && computed_hash != loaded_hash { - info!("hash mismatch found: computed: {}, loaded: {}, pubkey: {}", computed_hash, loaded_hash, pubkey); + info!("hash mismatch found: computed: {}, loaded: {}, pubkey: {}", computed_hash.0, loaded_hash.0, pubkey); mismatch_found .fetch_add(1, Ordering::Relaxed); return None; @@ -7041,7 +7041,7 @@ impl AccountsDb { } sum += balance as u128; - Some(loaded_hash) + Some(loaded_hash.0) }, ) } else { @@ -7916,17 +7916,20 @@ impl AccountsDb { /// 1. pubkey, hash pairs for the slot /// 2. us spent scanning /// 3. Measure started when we began accumulating - pub fn get_pubkey_hash_for_slot(&self, slot: Slot) -> (Vec<(Pubkey, Hash)>, u64, Measure) { + pub fn get_pubkey_hash_for_slot( + &self, + slot: Slot, + ) -> (Vec<(Pubkey, AccountHash)>, u64, Measure) { let mut scan = Measure::start("scan"); - let scan_result: ScanStorageResult<(Pubkey, Hash), DashMap> = self - .scan_account_storage( + let scan_result: ScanStorageResult<(Pubkey, AccountHash), DashMap> = + self.scan_account_storage( slot, |loaded_account: LoadedAccount| { // Cache only has one version per key, don't need to worry about versioning Some((*loaded_account.pubkey(), loaded_account.loaded_hash())) }, - |accum: &DashMap, loaded_account: LoadedAccount| { + |accum: &DashMap, loaded_account: LoadedAccount| { let loaded_hash = loaded_account.loaded_hash(); accum.insert(*loaded_account.pubkey(), loaded_hash); }, @@ -7944,7 +7947,7 @@ impl AccountsDb { /// Return all of the accounts for a given slot pub fn get_pubkey_hash_account_for_slot(&self, slot: Slot) -> Vec { type ScanResult = - ScanStorageResult>; + ScanStorageResult>; let scan_result: ScanResult = self.scan_account_storage( slot, |loaded_account: LoadedAccount| { @@ -7955,7 +7958,8 @@ impl AccountsDb { account: loaded_account.take_account(), }) }, - |accum: &DashMap, loaded_account: LoadedAccount| { + |accum: &DashMap, + loaded_account: LoadedAccount| { // Storage may have duplicates so only keep the latest version for each key accum.insert( *loaded_account.pubkey(), @@ -10482,10 +10486,10 @@ pub mod tests { ]; let expected_hashes = [ - Hash::from_str("5K3NW73xFHwgTWVe4LyCg4QfQda8f88uZj2ypDx2kmmH").unwrap(), - Hash::from_str("84ozw83MZ8oeSF4hRAg7SeW1Tqs9LMXagX1BrDRjtZEx").unwrap(), - Hash::from_str("5XqtnEJ41CG2JWNp7MAg9nxkRUAnyjLxfsKsdrLxQUbC").unwrap(), - Hash::from_str("DpvwJcznzwULYh19Zu5CuAA4AT6WTBe4H6n15prATmqj").unwrap(), + AccountHash(Hash::from_str("5K3NW73xFHwgTWVe4LyCg4QfQda8f88uZj2ypDx2kmmH").unwrap()), + AccountHash(Hash::from_str("84ozw83MZ8oeSF4hRAg7SeW1Tqs9LMXagX1BrDRjtZEx").unwrap()), + AccountHash(Hash::from_str("5XqtnEJ41CG2JWNp7MAg9nxkRUAnyjLxfsKsdrLxQUbC").unwrap()), + AccountHash(Hash::from_str("DpvwJcznzwULYh19Zu5CuAA4AT6WTBe4H6n15prATmqj").unwrap()), ]; let mut raw_accounts = Vec::default(); @@ -10505,7 +10509,7 @@ pub mod tests { if slot == 1 && matches!(include_slot_in_hash, IncludeSlotInHash::IncludeSlot) { assert_eq!(hash, expected_hashes[i]); } - raw_expected[i].hash = hash; + raw_expected[i].hash = hash.0; } let to_store = raw_accounts @@ -12696,7 +12700,7 @@ pub mod tests { let account = stored_account.to_account_shared_data(); let expected_account_hash = - Hash::from_str("6VeAL4x4PVkECKL1hD1avwPE1uMCRoWiZJzVMvVNYhTq").unwrap(); + AccountHash(Hash::from_str("6VeAL4x4PVkECKL1hD1avwPE1uMCRoWiZJzVMvVNYhTq").unwrap()); assert_eq!( AccountsDb::hash_account( @@ -18161,7 +18165,7 @@ pub mod tests { // Ensure the zero-lamport accounts are NOT included in the full accounts hash. let full_account_hashes = [(2, 0), (3, 0), (4, 1)].into_iter().map(|(index, slot)| { let (pubkey, account) = &accounts[index]; - AccountsDb::hash_account(slot, account, pubkey, INCLUDE_SLOT_IN_HASH_TESTS) + AccountsDb::hash_account(slot, account, pubkey, INCLUDE_SLOT_IN_HASH_TESTS).0 }); let expected_accounts_hash = AccountsHash(compute_merkle_root(full_account_hashes)); assert_eq!(full_accounts_hash.0, expected_accounts_hash); @@ -18243,6 +18247,7 @@ pub mod tests { Hash::new_from_array(hash.into()) } else { AccountsDb::hash_account(slot, account, pubkey, INCLUDE_SLOT_IN_HASH_TESTS) + .0 } }); let expected_accounts_hash = diff --git a/accounts-db/src/accounts_hash.rs b/accounts-db/src/accounts_hash.rs index 222f2b1a640984..cc6db20fbfbb00 100644 --- a/accounts-db/src/accounts_hash.rs +++ b/accounts-db/src/accounts_hash.rs @@ -740,14 +740,9 @@ impl<'a> AccountsHasher<'a> { ) } - pub fn accumulate_account_hashes(mut hashes: Vec<(Pubkey, Hash)>) -> Hash { - Self::sort_hashes_by_pubkey(&mut hashes); - - Self::compute_merkle_root_loop(hashes, MERKLE_FANOUT, |i| &i.1) - } - - pub fn sort_hashes_by_pubkey(hashes: &mut Vec<(Pubkey, Hash)>) { + pub fn accumulate_account_hashes(mut hashes: Vec<(Pubkey, AccountHash)>) -> Hash { hashes.par_sort_unstable_by(|a, b| a.0.cmp(&b.0)); + Self::compute_merkle_root_loop(hashes, MERKLE_FANOUT, |i| &i.1 .0) } pub fn compare_two_hash_entries( @@ -1206,6 +1201,21 @@ pub enum ZeroLamportAccounts { Included, } +/// Hash of an account +#[repr(transparent)] +#[derive(Debug, Copy, Clone, Eq, PartialEq, Pod, Zeroable)] +pub struct AccountHash(pub Hash); + +// Ensure the newtype wrapper never changes size from the underlying Hash +// This also ensures there are no padding bytes, which is requried to safely implement Pod +const _: () = assert!(std::mem::size_of::() == std::mem::size_of::()); + +impl Borrow for AccountHash { + fn borrow(&self) -> &Hash { + &self.0 + } +} + /// Hash of accounts #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub enum AccountsHashKind { @@ -2320,14 +2330,18 @@ mod tests { .collect(); let result = if pass == 0 { - test_hashing_larger(input.clone(), fanout) + test_hashing_larger(input, fanout) } else { // this sorts inside let early_result = AccountsHasher::accumulate_account_hashes( - input.iter().map(|i| (i.0, i.1)).collect::>(), + input + .iter() + .map(|i| (i.0, AccountHash(i.1))) + .collect::>(), ); - AccountsHasher::sort_hashes_by_pubkey(&mut input); - let result = AccountsHasher::compute_merkle_root(input.clone(), fanout); + + input.par_sort_unstable_by(|a, b| a.0.cmp(&b.0)); + let result = AccountsHasher::compute_merkle_root(input, fanout); assert_eq!(early_result, result); result }; diff --git a/programs/loader-v4/src/lib.rs b/programs/loader-v4/src/lib.rs index 4645b33c26a6e0..6f15096ecc6389 100644 --- a/programs/loader-v4/src/lib.rs +++ b/programs/loader-v4/src/lib.rs @@ -366,7 +366,11 @@ pub fn process_instruction_deploy( authority_address, )?; let current_slot = invoke_context.get_sysvar_cache().get_clock()?.slot; - if state.slot.saturating_add(DEPLOYMENT_COOLDOWN_IN_SLOTS) > current_slot { + + // Slot = 0 indicates that the program hasn't been deployed yet. So no need to check for the cooldown slots. + // (Without this check, the program deployment is failing in freshly started test validators. That's + // because at startup current_slot is 0, which is < DEPLOYMENT_COOLDOWN_IN_SLOTS). + if state.slot != 0 && state.slot.saturating_add(DEPLOYMENT_COOLDOWN_IN_SLOTS) > current_slot { ic_logger_msg!( log_collector, "Program was deployed recently, cooldown still in effect" diff --git a/runtime/src/bank/bank_hash_details.rs b/runtime/src/bank/bank_hash_details.rs index d4ea3f65651b5c..a1b4fa74f2ff73 100644 --- a/runtime/src/bank/bank_hash_details.rs +++ b/runtime/src/bank/bank_hash_details.rs @@ -8,7 +8,10 @@ use { de::{self, Deserialize, Deserializer}, ser::{Serialize, SerializeSeq, Serializer}, }, - solana_accounts_db::{accounts_db::PubkeyHashAccount, accounts_hash::AccountsDeltaHash}, + solana_accounts_db::{ + accounts_db::PubkeyHashAccount, + accounts_hash::{AccountHash, AccountsDeltaHash}, + }, solana_sdk::{ account::{Account, AccountSharedData, ReadableAccount}, clock::{Epoch, Slot}, @@ -124,7 +127,7 @@ impl From<&PubkeyHashAccount> for SerdeAccount { } = pubkey_hash_account; Self { pubkey: pubkey.to_string(), - hash: hash.to_string(), + hash: hash.0.to_string(), owner: account.owner().to_string(), lamports: account.lamports(), rent_epoch: account.rent_epoch(), @@ -139,7 +142,7 @@ impl TryFrom for PubkeyHashAccount { fn try_from(temp_account: SerdeAccount) -> Result { let pubkey = Pubkey::from_str(&temp_account.pubkey).map_err(|err| err.to_string())?; - let hash = Hash::from_str(&temp_account.hash).map_err(|err| err.to_string())?; + let hash = AccountHash(Hash::from_str(&temp_account.hash).map_err(|err| err.to_string())?); let account = AccountSharedData::from(Account { lamports: temp_account.lamports, @@ -244,7 +247,7 @@ pub mod tests { rent_epoch: 123, }); let account_pubkey = Pubkey::new_unique(); - let account_hash = hash("account".as_bytes()); + let account_hash = AccountHash(hash("account".as_bytes())); let accounts = BankHashAccounts { accounts: vec![PubkeyHashAccount { pubkey: account_pubkey,