Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

API to match account owner against a set of owners #30154

Merged
merged 6 commits into from
Feb 8, 2023

Conversation

pgarg66
Copy link
Contributor

@pgarg66 pgarg66 commented Feb 6, 2023

Problem

The runtime V2 needs an API to filter executable/program accounts in a transaction, without reading the whole account in the memory. We don't currently have such an API.

An API that can match account's owner with a set of pubkeys (loader pubkeys) will be ideal.

Summary of Changes

Add API to account_matches_owners in append_vec.rs and accounts_db.rs. Also, add unit tests.

Fixes #

@jeffwashington
Copy link
Contributor

looks reasonable to me. Not ideal to be copying these fields out (like owner). Depends on usage I guess. I'm not sure what happens in the case where the account is in the read cache.

@pgarg66
Copy link
Contributor Author

pgarg66 commented Feb 6, 2023

looks reasonable to me. Not ideal to be copying these fields out (like owner). Depends on usage I guess. I'm not sure what happens in the case where the account is in the read cache.

Actually owner is the specific field we need for runtime V2. If the owner is rBPF/SBF loader, we'll treat it as an executable. (The executable flag by itself will be deprecated in the future).

@jeffwashington
Copy link
Contributor

Actually owner is the specific field we need for runtime

Depends then on how many times this is called then. Is it just on suspected exes or is it for every account in every tx?
You may want an api that asks, "Does the owner match &Pubkey or any of &[&Pubkey]. That would allow the internals to check the owner as a ref without copying it out so you can check it and usually decide owner is not what you need.

@pgarg66
Copy link
Contributor Author

pgarg66 commented Feb 6, 2023

Actually owner is the specific field we need for runtime

Depends then on how many times this is called then. Is it just on suspected exes or is it for every account in every tx? You may want an api that asks, "Does the owner match &Pubkey or any of &[&Pubkey]. That would allow the internals to check the owner as a ref without copying it out so you can check it and usually decide owner is not what you need.

Yes that's a good point. I'll update the PR.
At a high level, it looked nice to have an accessor for meta. But it can easily become an overhead.

Thanks for reviewing it!

@pgarg66 pgarg66 changed the title API to read account meta data from storage API to match account owner against a set of owners Feb 7, 2023
@pgarg66
Copy link
Contributor Author

pgarg66 commented Feb 7, 2023

Actually owner is the specific field we need for runtime

Depends then on how many times this is called then. Is it just on suspected exes or is it for every account in every tx? You may want an api that asks, "Does the owner match &Pubkey or any of &[&Pubkey]. That would allow the internals to check the owner as a ref without copying it out so you can check it and usually decide owner is not what you need.

Updated the PR as per your suggestion.

pub fn get_account_meta(&self, offset: usize) -> Option<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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this a danger in the current callers of u64_align right now like

let next = u64_align!(next);

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On a side note it might be good to extract all of these "align and offset" calculation functions into a common utility that can be reused in places like this:

let (next, overflow) = offset.overflowing_add(size);
if overflow || next > self.len() {
return None;
}
let data = &self.map[offset..next];
let next = u64_align!(next);
into a common utility so that we can reuse them across these functions

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this a danger in the current callers of u64_align right now like

let next = u64_align!(next);

Yes. In my test, if next is usize::MAX, it'll panic.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On a side note it might be good to extract all of these "align and offset" calculation functions into a common utility that can be reused in places like this:

let (next, overflow) = offset.overflowing_add(size);
if overflow || next > self.len() {
return None;
}
let data = &self.map[offset..next];
let next = u64_align!(next);

into a common utility so that we can reuse them across these functions

Yes, that will be ideal. Separate PR though?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd vote for separate pr. @alessandrod is good at getting this right. And @brooksprumo

return Some(owners.contains(&account.owner()));
}
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like this section of code was repurposed from here:

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.
let in_write_cache = storage_location.is_cached();
if !load_into_read_cache_only {
if !in_write_cache {
let result = self.read_only_accounts_cache.load(*pubkey, slot);
if let Some(account) = result {
if matches!(load_zero_lamports, LoadZeroLamports::None)
&& account.is_zero_lamport()
{
return None;
}
return Some((account, slot));
}
}
.

@jeffwashington is it important to also make the zero lamports check https://github.com/solana-labs/solana/blob/c7fb4b10401a10c21f27a2ccf47176ccb659b646/runtime/src/accounts_db.rs#L5350C5-L5351 in order to be consistent? I.e. right now when we look up accounts with zero lamports it returns None, what should be done here in those cases?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tested with a 0 lamport account, and the owner gets set to SystemProgram. I don't think the check is useful for our usecase (filtering executables/programs). If some other usecase tries to filter SystemProgram accounts, 0 lamport account will show up there. Not sure if that's a problem though.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the answer will be wrong if the account is zero lamport and you did look for system program owner. The result should be 'false' for zero lamport account to be consistent.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, missed this comment. I'll push another update.

@Lichtso Lichtso mentioned this pull request Feb 7, 2023
@pgarg66 pgarg66 marked this pull request as ready for review February 7, 2023 18:54
@@ -809,6 +809,26 @@ impl<'a> LoadedAccountAccessor<'a> {
}
}
}

fn account_matches_owners(&self, owners: &[&Pubkey]) -> Option<bool> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the Option return value is more confusing than it could be. I suggest an enum:
matches, does not match, unable to determine (for overflow case). Or, maybe even a result since unable to determine is an error (which should ideally never happen!?!?)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would you like a similar change for the append_vec.rs API?
I had modeled this mostly on load() API. And, a lot of internal APIs currently return an Option. I do agree that a Result with specific errors will be cleaner approach.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed all the new APIs to return Result

#[derive(Error, Debug, PartialEq, Eq)]
pub enum MatchAccountOwnerError {
#[error("The account owner does not match with the provided list")]
NoMatch,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm ok with this.
I was thinking though Result<bool, MatchAccountOwnerError>
Where bool was matched true/false
And error was only for the frustrating UnableToLoad case. Either way is ok with me and could be tweaked later. Making whatever you can pub(crate) would be great.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but to be clear, either way is ok. This is like an enum, which is the most clear, imho.

Copy link
Contributor

@jeffwashington jeffwashington left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm
Actually,
#30154 (comment)
is outstanding.

@pgarg66
Copy link
Contributor Author

pgarg66 commented Feb 7, 2023

lgtm Actually, #30154 (comment) is outstanding.

The latest commit should handle account with 0 lamports.

}

let (account_accessor, _slot) = self
.retry_to_get_account_accessor(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it doesn't look like zero lamport is handled for the case where we load from the append vec. I think you need this same type of code here:

!account.is_zero_lamport() && owners.contains(&account.owner()))

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The append_vec.rs makes the check and returns the error for 0 lamports.
The append_vec API is not returning the account meta, otherwise we could've consolidated this check in accounts_db.rs.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

my apologies. I was looking at the CURRENT master code to see that we weren't doing anything special on zero lamport accounts...

Copy link
Contributor

@jeffwashington jeffwashington left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

@pgarg66 pgarg66 merged commit d0623a3 into solana-labs:master Feb 8, 2023
@pgarg66 pgarg66 deleted the account-meta branch February 8, 2023 01:48
nickfrosty pushed a commit to nickfrosty/solana that referenced this pull request Mar 12, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Development

Successfully merging this pull request may close these issues.

None yet

3 participants