Abstract out StoredAccountMeta as an Enum (#30478)

#### Problem
The existing StoredAccountMeta requires the file and in-memory layout of
account metadata to match StoredMeta and AccountMeta.  This limits the
flexibility to implement different accounts storage file formats.

#### Summary of Changes
This PR abstracts out StoredAccountMeta as an Enum.
This commit is contained in:
Yueh-Hsuan Chiang 2023-03-09 15:52:51 -08:00 committed by GitHub
parent b0112a5f43
commit 71b6370426
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 211 additions and 85 deletions

View File

@ -1,7 +1,7 @@
use {
crate::storable_accounts::StorableAccounts,
crate::{append_vec::AppendVecStoredAccountMeta, storable_accounts::StorableAccounts},
solana_sdk::{
account::{Account, AccountSharedData, ReadableAccount},
account::{AccountSharedData, ReadableAccount},
hash::Hash,
pubkey::Pubkey,
stake_history::Epoch,
@ -103,95 +103,104 @@ impl<'a: 'b, 'b, T: ReadableAccount + Sync + 'b, U: StorableAccounts<'a, T>, V:
/// References to account data stored elsewhere. Getting an `Account` requires cloning
/// (see `StoredAccountMeta::clone_account()`).
#[derive(PartialEq, Eq, Debug)]
pub struct StoredAccountMeta<'a> {
pub(crate) meta: &'a StoredMeta,
/// account data
pub(crate) account_meta: &'a AccountMeta,
pub(crate) data: &'a [u8],
pub(crate) offset: usize,
pub(crate) stored_size: usize,
pub(crate) hash: &'a Hash,
pub enum StoredAccountMeta<'a> {
AppendVec(AppendVecStoredAccountMeta<'a>),
}
impl<'a> StoredAccountMeta<'a> {
/// Return a new Account by copying all the data referenced by the `StoredAccountMeta`.
pub fn clone_account(&self) -> AccountSharedData {
AccountSharedData::from(Account {
lamports: self.account_meta.lamports,
owner: self.account_meta.owner,
executable: self.account_meta.executable,
rent_epoch: self.account_meta.rent_epoch,
data: self.data.to_vec(),
})
match self {
Self::AppendVec(av) => av.clone_account(),
}
}
pub fn pubkey(&self) -> &'a Pubkey {
&self.meta.pubkey
match self {
Self::AppendVec(av) => av.pubkey(),
}
}
pub fn hash(&self) -> &'a Hash {
self.hash
match self {
Self::AppendVec(av) => av.hash(),
}
}
pub fn stored_size(&self) -> usize {
self.stored_size
match self {
Self::AppendVec(av) => av.stored_size(),
}
}
pub fn offset(&self) -> usize {
self.offset
match self {
Self::AppendVec(av) => av.offset(),
}
}
pub fn data(&self) -> &'a [u8] {
self.data
match self {
Self::AppendVec(av) => av.data(),
}
}
pub fn data_len(&self) -> u64 {
self.meta.data_len
match self {
Self::AppendVec(av) => av.data_len(),
}
}
pub fn write_version(&self) -> StoredMetaWriteVersion {
self.meta.write_version_obsolete
match self {
Self::AppendVec(av) => av.write_version(),
}
}
pub fn meta(&self) -> &StoredMeta {
match self {
Self::AppendVec(av) => av.meta(),
}
}
pub fn set_meta(&mut self, meta: &'a StoredMeta) {
match self {
Self::AppendVec(av) => av.set_meta(meta),
}
}
pub(crate) fn sanitize(&self) -> bool {
self.sanitize_executable() && self.sanitize_lamports()
}
pub(crate) fn sanitize_executable(&self) -> bool {
// Sanitize executable to ensure higher 7-bits are cleared correctly.
self.ref_executable_byte() & !1 == 0
}
pub(crate) fn sanitize_lamports(&self) -> bool {
// Sanitize 0 lamports to ensure to be same as AccountSharedData::default()
self.account_meta.lamports != 0 || self.clone_account() == AccountSharedData::default()
}
pub(crate) fn ref_executable_byte(&self) -> &'a u8 {
// Use extra references to avoid value silently clamped to 1 (=true) and 0 (=false)
// Yes, this really happens; see test_new_from_file_crafted_executable
let executable_bool: &bool = &self.account_meta.executable;
// UNSAFE: Force to interpret mmap-backed bool as u8 to really read the actual memory content
let executable_byte: &u8 = unsafe { &*(executable_bool as *const bool as *const u8) };
executable_byte
match self {
Self::AppendVec(av) => av.sanitize(),
}
}
}
impl<'a> ReadableAccount for StoredAccountMeta<'a> {
fn lamports(&self) -> u64 {
self.account_meta.lamports
match self {
Self::AppendVec(av) => av.lamports(),
}
}
fn data(&self) -> &[u8] {
self.data()
match self {
Self::AppendVec(av) => av.data(),
}
}
fn owner(&self) -> &Pubkey {
&self.account_meta.owner
match self {
Self::AppendVec(av) => av.owner(),
}
}
fn executable(&self) -> bool {
self.account_meta.executable
match self {
Self::AppendVec(av) => av.executable(),
}
}
fn rent_epoch(&self) -> Epoch {
self.account_meta.rent_epoch
match self {
Self::AppendVec(av) => av.rent_epoch(),
}
}
}

View File

@ -7976,7 +7976,7 @@ impl AccountsDb {
);
let offset = account_info.offset();
let account = store.accounts.get_account(offset).unwrap();
let stored_size = account.0.stored_size;
let stored_size = account.0.stored_size();
let count = store.remove_account(stored_size, reset_accounts);
if count == 0 {
self.dirty_stores.insert(*slot, store.clone());
@ -9454,7 +9454,7 @@ pub mod tests {
tests::*, AccountIndex, AccountSecondaryIndexes,
AccountSecondaryIndexesIncludeExclude, ReadAccountMapEntry, RefCount,
},
append_vec::test_utils::TempFile,
append_vec::{test_utils::TempFile, AppendVecStoredAccountMeta},
cache_hash_data_stats::CacheHashDataStats,
inline_spl_token,
secondary_index::MAX_NUM_LARGEST_INDEX_KEYS_RETURNED,
@ -9664,7 +9664,7 @@ pub mod tests {
pubkey,
data_len: 43,
};
let account = StoredAccountMeta {
let account = StoredAccountMeta::AppendVec(AppendVecStoredAccountMeta {
meta: &stored_meta,
/// account data
account_meta: &account_meta,
@ -9672,7 +9672,7 @@ pub mod tests {
offset,
stored_size: account_size,
hash: &hash,
};
});
let map = vec![&account];
let alive_total_bytes = account.stored_size();
let to_store = AccountsToStore::new(available_bytes, &map, alive_total_bytes, slot0);
@ -9760,38 +9760,38 @@ pub mod tests {
let offset = 99;
let stored_size = 101;
let hash = Hash::new_unique();
let stored_account = StoredAccountMeta {
let stored_account = StoredAccountMeta::AppendVec(AppendVecStoredAccountMeta {
meta: &meta,
account_meta: &account_meta,
data: &data,
offset,
stored_size,
hash: &hash,
};
let stored_account2 = StoredAccountMeta {
});
let stored_account2 = StoredAccountMeta::AppendVec(AppendVecStoredAccountMeta {
meta: &meta2,
account_meta: &account_meta,
data: &data,
offset,
stored_size,
hash: &hash,
};
let stored_account3 = StoredAccountMeta {
});
let stored_account3 = StoredAccountMeta::AppendVec(AppendVecStoredAccountMeta {
meta: &meta3,
account_meta: &account_meta,
data: &data,
offset,
stored_size,
hash: &hash,
};
let stored_account4 = StoredAccountMeta {
});
let stored_account4 = StoredAccountMeta::AppendVec(AppendVecStoredAccountMeta {
meta: &meta4,
account_meta: &account_meta,
data: &data,
offset,
stored_size,
hash: &hash,
};
});
let mut existing_ancient_pubkeys = HashSet::default();
let accounts = [&stored_account];
// pubkey NOT in existing_ancient_pubkeys, so do NOT unref, but add to existing_ancient_pubkeys
@ -12382,14 +12382,14 @@ pub mod tests {
let offset = 99;
let stored_size = 101;
let hash = Hash::new_unique();
let stored_account = StoredAccountMeta {
let stored_account = StoredAccountMeta::AppendVec(AppendVecStoredAccountMeta {
meta: &meta,
account_meta: &account_meta,
data: &data,
offset,
stored_size,
hash: &hash,
};
});
assert!(accounts_equal(&account, &stored_account));
}
@ -12426,14 +12426,14 @@ pub mod tests {
let (slot, meta, account_meta, data, offset, hash): InputTuple =
unsafe { std::mem::transmute::<InputBlob, InputTuple>(blob) };
let stored_account = StoredAccountMeta {
let stored_account = StoredAccountMeta::AppendVec(AppendVecStoredAccountMeta {
meta: &meta,
account_meta: &account_meta,
data: &data,
offset,
stored_size: CACHE_VIRTUAL_STORED_SIZE as usize,
hash: &hash,
};
});
let account = stored_account.clone_account();
let expected_account_hash = if cfg!(debug_assertions) {

View File

@ -138,9 +138,9 @@ impl AccountsDb {
// Passing 0 for everyone's write_version is sufficiently correct.
let meta = StoredMeta {
write_version_obsolete: local_write_version,
..*account.meta
..*account.meta()
};
account.meta = &meta;
account.set_meta(&meta);
let mut measure_pure_notify = Measure::start("accountsdb-plugin-notifying-accounts");
notifier.notify_account_restore_from_snapshot(slot, &account);
measure_pure_notify.stop();

View File

@ -770,7 +770,7 @@ pub mod tests {
},
INCLUDE_SLOT_IN_HASH_TESTS,
},
append_vec::{aligned_stored_size, AppendVec},
append_vec::{aligned_stored_size, AppendVec, AppendVecStoredAccountMeta},
storable_accounts::StorableAccountsBySlot,
},
solana_sdk::{
@ -1618,7 +1618,7 @@ pub mod tests {
pubkey,
data_len: 43,
};
let account = StoredAccountMeta {
let account = StoredAccountMeta::AppendVec(AppendVecStoredAccountMeta {
meta: &stored_meta,
/// account data
account_meta: &account_meta,
@ -1626,7 +1626,7 @@ pub mod tests {
offset,
stored_size: account_size,
hash: &hash,
};
});
let map = vec![&account];
for (selector, available_bytes) in [
(StorageSelector::Primary, account_size),

View File

@ -8,13 +8,19 @@ use {
crate::{
account_storage::meta::{
AccountMeta, StorableAccountsWithHashesAndWriteVersions, StoredAccountInfo,
StoredAccountMeta, StoredMeta,
StoredAccountMeta, StoredMeta, StoredMetaWriteVersion,
},
storable_accounts::StorableAccounts,
},
log::*,
memmap2::MmapMut,
solana_sdk::{account::ReadableAccount, clock::Slot, hash::Hash, pubkey::Pubkey},
solana_sdk::{
account::{Account, AccountSharedData, ReadableAccount},
clock::Slot,
hash::Hash,
pubkey::Pubkey,
stake_history::Epoch,
},
std::{
borrow::Borrow,
convert::TryFrom,
@ -89,6 +95,108 @@ pub enum MatchAccountOwnerError {
UnableToLoad,
}
/// References to account data stored elsewhere. Getting an `Account` requires cloning
/// (see `StoredAccountMeta::clone_account()`).
#[derive(PartialEq, Eq, Debug)]
pub struct AppendVecStoredAccountMeta<'a> {
pub meta: &'a StoredMeta,
/// account data
pub account_meta: &'a AccountMeta,
pub(crate) data: &'a [u8],
pub(crate) offset: usize,
pub(crate) stored_size: usize,
pub(crate) hash: &'a Hash,
}
impl<'a> AppendVecStoredAccountMeta<'a> {
pub fn clone_account(&self) -> AccountSharedData {
AccountSharedData::from(Account {
lamports: self.account_meta.lamports,
owner: self.account_meta.owner,
executable: self.account_meta.executable,
rent_epoch: self.account_meta.rent_epoch,
data: self.data.to_vec(),
})
}
pub fn pubkey(&self) -> &'a Pubkey {
&self.meta.pubkey
}
pub fn hash(&self) -> &'a Hash {
self.hash
}
pub fn stored_size(&self) -> usize {
self.stored_size
}
pub fn offset(&self) -> usize {
self.offset
}
pub fn data(&self) -> &'a [u8] {
self.data
}
pub fn data_len(&self) -> u64 {
self.meta.data_len
}
pub fn write_version(&self) -> StoredMetaWriteVersion {
self.meta.write_version_obsolete
}
pub fn meta(&self) -> &StoredMeta {
self.meta
}
pub fn set_meta(&mut self, meta: &'a StoredMeta) {
self.meta = meta;
}
pub(crate) fn sanitize(&self) -> bool {
self.sanitize_executable() && self.sanitize_lamports()
}
fn sanitize_executable(&self) -> bool {
// Sanitize executable to ensure higher 7-bits are cleared correctly.
self.ref_executable_byte() & !1 == 0
}
fn sanitize_lamports(&self) -> bool {
// Sanitize 0 lamports to ensure to be same as AccountSharedData::default()
self.account_meta.lamports != 0 || self.clone_account() == AccountSharedData::default()
}
fn ref_executable_byte(&self) -> &u8 {
// Use extra references to avoid value silently clamped to 1 (=true) and 0 (=false)
// Yes, this really happens; see test_new_from_file_crafted_executable
let executable_bool: &bool = &self.account_meta.executable;
// UNSAFE: Force to interpret mmap-backed bool as u8 to really read the actual memory content
let executable_byte: &u8 = unsafe { &*(executable_bool as *const bool as *const u8) };
executable_byte
}
}
impl<'a> ReadableAccount for AppendVecStoredAccountMeta<'a> {
fn lamports(&self) -> u64 {
self.account_meta.lamports
}
fn data(&self) -> &[u8] {
self.data()
}
fn owner(&self) -> &Pubkey {
&self.account_meta.owner
}
fn executable(&self) -> bool {
self.account_meta.executable
}
fn rent_epoch(&self) -> Epoch {
self.account_meta.rent_epoch
}
}
/// A thread-safe, file-backed block of memory used to store `Account` instances. Append operations
/// are serialized such that only one thread updates the internal `append_lock` at a time. No
/// restrictions are placed on reading. That is, one may read items from one thread while another
@ -401,14 +509,14 @@ impl AppendVec {
let (data, next) = self.get_slice(next, meta.data_len as usize)?;
let stored_size = next - offset;
Some((
StoredAccountMeta {
StoredAccountMeta::AppendVec(AppendVecStoredAccountMeta {
meta,
account_meta,
data,
offset,
stored_size,
hash,
},
}),
next,
))
}
@ -450,7 +558,7 @@ impl AppendVec {
offset: usize,
) -> Option<(StoredMeta, solana_sdk::account::AccountSharedData)> {
let (stored_account, _) = self.get_account(offset)?;
let meta = stored_account.meta.clone();
let meta = stored_account.meta().clone();
Some((meta, stored_account.clone_account()))
}
@ -593,6 +701,14 @@ pub mod tests {
}
impl<'a> StoredAccountMeta<'a> {
pub(crate) fn ref_executable_byte(&self) -> &u8 {
match self {
Self::AppendVec(av) => av.ref_executable_byte(),
}
}
}
impl<'a> AppendVecStoredAccountMeta<'a> {
#[allow(clippy::cast_ref_to_mut)]
fn set_data_len_unsafe(&self, new_data_len: u64) {
// UNSAFE: cast away & (= const ref) to &mut to force to mutate append-only (=read-only) AppendVec
@ -1071,7 +1187,7 @@ pub mod tests {
av.append_account_test(&create_test_account(10)).unwrap();
let accounts = av.accounts(0);
let account = accounts.first().unwrap();
let StoredAccountMeta::AppendVec(account) = accounts.first().unwrap();
account.set_data_len_unsafe(crafted_data_len);
assert_eq!(account.data_len(), crafted_data_len);
@ -1098,7 +1214,7 @@ pub mod tests {
av.append_account_test(&create_test_account(10)).unwrap();
let accounts = av.accounts(0);
let account = accounts.first().unwrap();
let StoredAccountMeta::AppendVec(account) = accounts.first().unwrap();
account.set_data_len_unsafe(too_large_data_len);
assert_eq!(account.data_len(), too_large_data_len);
@ -1133,14 +1249,14 @@ pub mod tests {
assert_eq!(*accounts[0].ref_executable_byte(), 0);
assert_eq!(*accounts[1].ref_executable_byte(), 1);
let account = &accounts[0];
let StoredAccountMeta::AppendVec(account) = &accounts[0];
let crafted_executable = u8::max_value() - 1;
account.set_executable_as_byte(crafted_executable);
// reload crafted accounts
let accounts = av.accounts(0);
let account = accounts.first().unwrap();
let StoredAccountMeta::AppendVec(account) = accounts.first().unwrap();
// upper 7-bits are not 0, so sanitization should fail
assert!(!account.sanitize_executable());

View File

@ -306,6 +306,7 @@ pub mod tests {
crate::{
account_storage::meta::{AccountMeta, StoredAccountMeta, StoredMeta},
accounts_db::INCLUDE_SLOT_IN_HASH_TESTS,
append_vec::AppendVecStoredAccountMeta,
},
solana_sdk::{
account::{accounts_equal, AccountSharedData, WritableAccount},
@ -353,14 +354,14 @@ pub mod tests {
let offset = 99;
let stored_size = 101;
let hash = Hash::new_unique();
let stored_account = StoredAccountMeta {
let stored_account = StoredAccountMeta::AppendVec(AppendVecStoredAccountMeta {
meta: &meta,
account_meta: &account_meta,
data: &data,
offset,
stored_size,
hash: &hash,
};
});
let test3 = (
slot,
@ -411,14 +412,14 @@ pub mod tests {
for entry in 0..entries {
let offset = 99;
let stored_size = 101;
raw2.push(StoredAccountMeta {
raw2.push(StoredAccountMeta::AppendVec(AppendVecStoredAccountMeta {
meta: &raw[entry as usize].3,
account_meta: &raw[entry as usize].4,
data: &data,
offset,
stored_size,
hash: &hash,
});
}));
}
let mut two = Vec::new();
@ -508,14 +509,14 @@ pub mod tests {
for entry in 0..entries {
let offset = 99;
let stored_size = 101;
raw2.push(StoredAccountMeta {
raw2.push(StoredAccountMeta::AppendVec(AppendVecStoredAccountMeta {
meta: &raw[entry as usize].2,
account_meta: &raw[entry as usize].3,
data: &data,
offset,
stored_size,
hash: &hashes[entry as usize],
});
}));
}
let raw2_refs = raw2.iter().collect::<Vec<_>>();