API to match account owner against a set of owners (#30154)
This commit is contained in:
parent
bfc29a6b2f
commit
d0623a38c8
|
@ -42,9 +42,9 @@ use {
|
||||||
get_ancient_append_vec_capacity, is_ancient, AccountsToStore, StorageSelector,
|
get_ancient_append_vec_capacity, is_ancient, AccountsToStore, StorageSelector,
|
||||||
},
|
},
|
||||||
append_vec::{
|
append_vec::{
|
||||||
aligned_stored_size, AppendVec, StorableAccountsWithHashesAndWriteVersions,
|
aligned_stored_size, AppendVec, MatchAccountOwnerError,
|
||||||
StoredAccountMeta, StoredMetaWriteVersion, APPEND_VEC_MMAPPED_FILES_OPEN,
|
StorableAccountsWithHashesAndWriteVersions, StoredAccountMeta, StoredMetaWriteVersion,
|
||||||
STORE_META_OVERHEAD,
|
APPEND_VEC_MMAPPED_FILES_OPEN, STORE_META_OVERHEAD,
|
||||||
},
|
},
|
||||||
cache_hash_data::{CacheHashData, CacheHashDataFile},
|
cache_hash_data::{CacheHashData, CacheHashDataFile},
|
||||||
contains::Contains,
|
contains::Contains,
|
||||||
|
@ -815,6 +815,32 @@ impl<'a> LoadedAccountAccessor<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn account_matches_owners(&self, owners: &[&Pubkey]) -> Result<(), MatchAccountOwnerError> {
|
||||||
|
match self {
|
||||||
|
LoadedAccountAccessor::Cached(cached_account) => cached_account
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|cached_account| {
|
||||||
|
(!cached_account.account.is_zero_lamport()
|
||||||
|
&& owners.contains(&cached_account.account.owner()))
|
||||||
|
.then_some(())
|
||||||
|
})
|
||||||
|
.ok_or(MatchAccountOwnerError::NoMatch),
|
||||||
|
LoadedAccountAccessor::Stored(maybe_storage_entry) => {
|
||||||
|
// storage entry may not be present if slot was cleaned up in
|
||||||
|
// between reading the accounts index and calling this function to
|
||||||
|
// get account meta from the storage entry here
|
||||||
|
maybe_storage_entry
|
||||||
|
.as_ref()
|
||||||
|
.map(|(storage_entry, offset)| {
|
||||||
|
storage_entry
|
||||||
|
.accounts
|
||||||
|
.account_matches_owners(*offset, owners)
|
||||||
|
})
|
||||||
|
.unwrap_or(Err(MatchAccountOwnerError::UnableToLoad))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum LoadedAccount<'a> {
|
pub enum LoadedAccount<'a> {
|
||||||
|
@ -4918,6 +4944,38 @@ impl AccountsDb {
|
||||||
self.do_load(ancestors, pubkey, None, load_hint, LoadZeroLamports::None)
|
self.do_load(ancestors, pubkey, None, load_hint, LoadZeroLamports::None)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn account_matches_owners(
|
||||||
|
&self,
|
||||||
|
ancestors: &Ancestors,
|
||||||
|
account: &Pubkey,
|
||||||
|
owners: &[&Pubkey],
|
||||||
|
) -> Result<(), MatchAccountOwnerError> {
|
||||||
|
let (slot, storage_location, _maybe_account_accesor) = self
|
||||||
|
.read_index_for_accessor_or_load_slow(ancestors, account, None, false)
|
||||||
|
.ok_or(MatchAccountOwnerError::UnableToLoad)?;
|
||||||
|
|
||||||
|
if !storage_location.is_cached() {
|
||||||
|
let result = self.read_only_accounts_cache.load(*account, slot);
|
||||||
|
if let Some(account) = result {
|
||||||
|
return (!account.is_zero_lamport() && owners.contains(&account.owner()))
|
||||||
|
.then_some(())
|
||||||
|
.ok_or(MatchAccountOwnerError::NoMatch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let (account_accessor, _slot) = self
|
||||||
|
.retry_to_get_account_accessor(
|
||||||
|
slot,
|
||||||
|
storage_location,
|
||||||
|
ancestors,
|
||||||
|
account,
|
||||||
|
None,
|
||||||
|
LoadHint::Unspecified,
|
||||||
|
)
|
||||||
|
.ok_or(MatchAccountOwnerError::UnableToLoad)?;
|
||||||
|
account_accessor.account_matches_owners(owners)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn load_account_into_read_cache(&self, ancestors: &Ancestors, pubkey: &Pubkey) {
|
pub fn load_account_into_read_cache(&self, ancestors: &Ancestors, pubkey: &Pubkey) {
|
||||||
self.do_load_with_populate_read_cache(
|
self.do_load_with_populate_read_cache(
|
||||||
ancestors,
|
ancestors,
|
||||||
|
@ -14068,6 +14126,101 @@ pub mod tests {
|
||||||
assert_eq!(db.read_only_accounts_cache.cache_len(), 1);
|
assert_eq!(db.read_only_accounts_cache.cache_len(), 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_account_matches_owners() {
|
||||||
|
let db = Arc::new(AccountsDb::new_with_config_for_tests(
|
||||||
|
Vec::new(),
|
||||||
|
&ClusterType::Development,
|
||||||
|
AccountSecondaryIndexes::default(),
|
||||||
|
AccountShrinkThreshold::default(),
|
||||||
|
));
|
||||||
|
|
||||||
|
let owners: Vec<Pubkey> = (0..2).map(|_| Pubkey::new_unique()).collect();
|
||||||
|
let owners_refs: Vec<&Pubkey> = owners.iter().collect();
|
||||||
|
|
||||||
|
let account1_key = Pubkey::new_unique();
|
||||||
|
let account1 = AccountSharedData::new(321, 10, &owners[0]);
|
||||||
|
|
||||||
|
let account2_key = Pubkey::new_unique();
|
||||||
|
let account2 = AccountSharedData::new(1, 1, &owners[1]);
|
||||||
|
|
||||||
|
let account3_key = Pubkey::new_unique();
|
||||||
|
let account3 = AccountSharedData::new(1, 1, &Pubkey::new_unique());
|
||||||
|
|
||||||
|
// Account with 0 lamports
|
||||||
|
let account4_key = Pubkey::new_unique();
|
||||||
|
let account4 = AccountSharedData::new(0, 1, &owners[1]);
|
||||||
|
|
||||||
|
db.store_cached((0, &[(&account1_key, &account1)][..]), None);
|
||||||
|
db.store_cached((1, &[(&account2_key, &account2)][..]), None);
|
||||||
|
db.store_cached((2, &[(&account3_key, &account3)][..]), None);
|
||||||
|
db.store_cached((3, &[(&account4_key, &account4)][..]), None);
|
||||||
|
|
||||||
|
db.add_root(0);
|
||||||
|
db.add_root(1);
|
||||||
|
db.add_root(2);
|
||||||
|
db.add_root(3);
|
||||||
|
|
||||||
|
// Flush the cache so that the account meta will be read from the storage
|
||||||
|
db.flush_accounts_cache(true, None);
|
||||||
|
db.clean_accounts_for_tests();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
db.account_matches_owners(&Ancestors::default(), &account1_key, &owners_refs),
|
||||||
|
Ok(())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
db.account_matches_owners(&Ancestors::default(), &account2_key, &owners_refs),
|
||||||
|
Ok(())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
db.account_matches_owners(&Ancestors::default(), &account3_key, &owners_refs),
|
||||||
|
Err(MatchAccountOwnerError::NoMatch)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
db.account_matches_owners(&Ancestors::default(), &account4_key, &owners_refs),
|
||||||
|
Err(MatchAccountOwnerError::NoMatch)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
db.account_matches_owners(&Ancestors::default(), &Pubkey::new_unique(), &owners_refs),
|
||||||
|
Err(MatchAccountOwnerError::UnableToLoad)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Flush the cache and load account1 (so that it's in the cache)
|
||||||
|
db.flush_accounts_cache(true, None);
|
||||||
|
db.clean_accounts_for_tests();
|
||||||
|
let _ = db
|
||||||
|
.do_load(
|
||||||
|
&Ancestors::default(),
|
||||||
|
&account1_key,
|
||||||
|
Some(0),
|
||||||
|
LoadHint::Unspecified,
|
||||||
|
LoadZeroLamports::SomeWithZeroLamportAccountForTests,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
db.account_matches_owners(&Ancestors::default(), &account1_key, &owners_refs),
|
||||||
|
Ok(())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
db.account_matches_owners(&Ancestors::default(), &account2_key, &owners_refs),
|
||||||
|
Ok(())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
db.account_matches_owners(&Ancestors::default(), &account3_key, &owners_refs),
|
||||||
|
Err(MatchAccountOwnerError::NoMatch)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
db.account_matches_owners(&Ancestors::default(), &account4_key, &owners_refs),
|
||||||
|
Err(MatchAccountOwnerError::NoMatch)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
db.account_matches_owners(&Ancestors::default(), &Pubkey::new_unique(), &owners_refs),
|
||||||
|
Err(MatchAccountOwnerError::UnableToLoad)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// a test that will accept either answer
|
/// a test that will accept either answer
|
||||||
const LOAD_ZERO_LAMPORTS_ANY_TESTS: LoadZeroLamports = LoadZeroLamports::None;
|
const LOAD_ZERO_LAMPORTS_ANY_TESTS: LoadZeroLamports = LoadZeroLamports::None;
|
||||||
|
|
||||||
|
|
|
@ -28,6 +28,7 @@ use {
|
||||||
Mutex,
|
Mutex,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
thiserror::Error,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub mod test_utils;
|
pub mod test_utils;
|
||||||
|
@ -270,6 +271,14 @@ impl<'a> Iterator for AppendVecAccountsIter<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Error, Debug, PartialEq, Eq)]
|
||||||
|
pub enum MatchAccountOwnerError {
|
||||||
|
#[error("The account owner does not match with the provided list")]
|
||||||
|
NoMatch,
|
||||||
|
#[error("Unable to load the account")]
|
||||||
|
UnableToLoad,
|
||||||
|
}
|
||||||
|
|
||||||
/// A thread-safe, file-backed block of memory used to store `Account` instances. Append operations
|
/// 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
|
/// 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
|
/// restrictions are placed on reading. That is, one may read items from one thread while another
|
||||||
|
@ -572,7 +581,7 @@ impl AppendVec {
|
||||||
Some((unsafe { &*ptr }, next))
|
Some((unsafe { &*ptr }, next))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return account metadata for the account at `offset` if its data doesn't overrun
|
/// Return stored account metadata for the account at `offset` if its data doesn't overrun
|
||||||
/// the internal buffer. Otherwise return None. Also return the offset of the first byte
|
/// the internal buffer. Otherwise return None. Also return the offset of the first byte
|
||||||
/// after the requested data that falls on a 64-byte boundary.
|
/// after the requested data that falls on a 64-byte boundary.
|
||||||
pub fn get_account<'a>(&'a self, offset: usize) -> Option<(StoredAccountMeta<'a>, usize)> {
|
pub fn get_account<'a>(&'a self, offset: usize) -> Option<(StoredAccountMeta<'a>, usize)> {
|
||||||
|
@ -594,6 +603,31 @@ impl AppendVec {
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_account_meta<'a>(&self, offset: usize) -> Option<&'a AccountMeta> {
|
||||||
|
// Skip over StoredMeta data in the account
|
||||||
|
let offset = offset.checked_add(mem::size_of::<StoredMeta>())?;
|
||||||
|
// u64_align! does an unchecked add for alignment. Check that it won't cause an overflow.
|
||||||
|
offset.checked_add(ALIGN_BOUNDARY_OFFSET - 1)?;
|
||||||
|
let (account_meta, _): (&AccountMeta, _) = self.get_type(u64_align!(offset))?;
|
||||||
|
Some(account_meta)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return Some(true) if the account owner at `offset` is one of the pubkeys in `owners`.
|
||||||
|
/// Return Some(false) if the account owner is not one of the pubkeys in `owners`.
|
||||||
|
/// It returns None if the `offset` value causes a data overrun.
|
||||||
|
pub fn account_matches_owners(
|
||||||
|
&self,
|
||||||
|
offset: usize,
|
||||||
|
owners: &[&Pubkey],
|
||||||
|
) -> Result<(), MatchAccountOwnerError> {
|
||||||
|
let account_meta = self
|
||||||
|
.get_account_meta(offset)
|
||||||
|
.ok_or(MatchAccountOwnerError::UnableToLoad)?;
|
||||||
|
(account_meta.lamports != 0 && owners.contains(&&account_meta.owner))
|
||||||
|
.then_some(())
|
||||||
|
.ok_or(MatchAccountOwnerError::NoMatch)
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub fn get_account_test(&self, offset: usize) -> Option<(StoredMeta, AccountSharedData)> {
|
pub fn get_account_test(&self, offset: usize) -> Option<(StoredMeta, AccountSharedData)> {
|
||||||
let (stored_account, _) = self.get_account(offset)?;
|
let (stored_account, _) = self.get_account(offset)?;
|
||||||
|
@ -1047,6 +1081,47 @@ pub mod tests {
|
||||||
assert_eq!(av.get_account_test(index1).unwrap(), account1);
|
assert_eq!(av.get_account_test(index1).unwrap(), account1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_account_matches_owners() {
|
||||||
|
let path = get_append_vec_path("test_append_data");
|
||||||
|
let av = AppendVec::new(&path.path, true, 1024 * 1024);
|
||||||
|
let owners: Vec<Pubkey> = (0..2).map(|_| Pubkey::new_unique()).collect();
|
||||||
|
let owners_refs: Vec<&Pubkey> = owners.iter().collect();
|
||||||
|
|
||||||
|
let mut account = create_test_account(5);
|
||||||
|
account.1.set_owner(owners[0]);
|
||||||
|
let index = av.append_account_test(&account).unwrap();
|
||||||
|
assert_eq!(av.account_matches_owners(index, &owners_refs), Ok(()));
|
||||||
|
|
||||||
|
let mut account1 = create_test_account(6);
|
||||||
|
account1.1.set_owner(owners[1]);
|
||||||
|
let index1 = av.append_account_test(&account1).unwrap();
|
||||||
|
assert_eq!(av.account_matches_owners(index1, &owners_refs), Ok(()));
|
||||||
|
assert_eq!(av.account_matches_owners(index, &owners_refs), Ok(()));
|
||||||
|
|
||||||
|
let mut account2 = create_test_account(6);
|
||||||
|
account2.1.set_owner(Pubkey::new_unique());
|
||||||
|
let index2 = av.append_account_test(&account2).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
av.account_matches_owners(index2, &owners_refs),
|
||||||
|
Err(MatchAccountOwnerError::NoMatch)
|
||||||
|
);
|
||||||
|
|
||||||
|
// tests for overflow
|
||||||
|
assert_eq!(
|
||||||
|
av.account_matches_owners(usize::MAX - mem::size_of::<StoredMeta>(), &owners_refs),
|
||||||
|
Err(MatchAccountOwnerError::UnableToLoad)
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
av.account_matches_owners(
|
||||||
|
usize::MAX - mem::size_of::<StoredMeta>() - mem::size_of::<AccountMeta>() + 1,
|
||||||
|
&owners_refs
|
||||||
|
),
|
||||||
|
Err(MatchAccountOwnerError::UnableToLoad)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_append_vec_append_many() {
|
fn test_append_vec_append_many() {
|
||||||
let path = get_append_vec_path("test_append_many");
|
let path = get_append_vec_path("test_append_many");
|
||||||
|
|
Loading…
Reference in New Issue