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

account_saver: Remove nested options #2724

Merged
merged 6 commits into from
Aug 28, 2024

Conversation

apfitzge
Copy link

Problem

  • Nested options here is confusing in the accounts_db code.
  • In reality, they are either all Some or the outer is None

Summary of Changes

  • Remove nested options
  • Get rid of unneccessary Boxes

Fixes #

let (account_infos, cached_accounts) = txn_iter
.enumerate()
.map(|(i, txn)| {
let (account_infos, cached_accounts) = (0..accounts_and_meta_to_store.len())
Copy link
Author

Choose a reason for hiding this comment

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

if the txn_iterator is empty, we still want to iterate over the accounts to store.

Might make sense to just have some sort of iteration over accounts_and_meta_to_store?

Copy link

Choose a reason for hiding this comment

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

That sounds nice to me

Copy link
Author

Choose a reason for hiding this comment

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

Hm sounded good in theory, but when I just took a stab it I ran into a problem immediately.
We implement StorableAccounts on various tuple types. Rust does not allow us to implement Iterator for arbitrary types that we didn't define.

So afaict we'd need to wrap these in some type or have an iter fn - which doesn't seem much better imo.

} else {
Box::new(std::iter::empty())
};
let mut write_version_producer = if self.accounts_update_notifier.is_some() {
Copy link
Author

Choose a reason for hiding this comment

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

we either iterate over a range or have an empty. 0..0 should be equivalent, but match the Range type so we can avoid doing a Box allocation

Copy link

Choose a reason for hiding this comment

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

How about just using a u64 to track the latest write version and increment it whenever we notify?

Copy link
Author

Choose a reason for hiding this comment

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

assert_eq!(transactions.len(), accounts.len());
transactions.iter().copied()
}
None => [].iter().copied(),
Copy link
Author

Choose a reason for hiding this comment

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

behavior change here - instead of creating a box for different types of iterators, we either have an iterator over the txns vs an empty iterator.

At the next level down we just use None if the iterator has no next element.

Copy link

Choose a reason for hiding this comment

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

How about we pass Option<&[&SanitizedTransaction]> directly?

Choose a reason for hiding this comment

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

This suggestion from jstarry makes sense to me.

Copy link
Author

Choose a reason for hiding this comment

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

@apfitzge
Copy link
Author

apfitzge commented Aug 26, 2024

@jstarry & @LucasSte as a follow-up to this I plan to move this module out of SVM.
This functionality, as far as I can tell, is not actually used inside the svm crate - it is really needed by our runtime.

The reason I'd like to move it is to make the transition to the new transaction type not depend on the geyser interface changing, since I am seeing push-back on that front.
Instead, an extension-trait of SVMTransaction will be provided to create a SanitizedTransaction from the abstracted tx type, which will then be fed to geyser.
That behavior really has no place being part of SVMTransaction imo, so this module living here makes the abstraction impossible.

Instead of this collecting whatever transaction-type we pass in, it will (until geyser transitions) return SanitizedTransaction regardless of input type.
Once geyser transitions it will instead return the transaction interface type.
Additionally, these transactions will only be collected if geyser is enabled - otherwise we do not want to waste time for the conversion.

@apfitzge apfitzge marked this pull request as ready for review August 26, 2024 15:27
@jstarry
Copy link

jstarry commented Aug 27, 2024

Wherever you move account_saver, I think account_loader should be moved as well I think. Wouldn't it be strange if account loading was part of the SVM but account saving was not?

jstarry
jstarry previously approved these changes Aug 27, 2024
Copy link

@jstarry jstarry left a comment

Choose a reason for hiding this comment

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

Implementation looks fine as is but had some suggestions

} else {
Box::new(std::iter::empty())
};
let mut write_version_producer = if self.accounts_update_notifier.is_some() {
Copy link

Choose a reason for hiding this comment

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

How about just using a u64 to track the latest write version and increment it whenever we notify?

let (account_infos, cached_accounts) = txn_iter
.enumerate()
.map(|(i, txn)| {
let (account_infos, cached_accounts) = (0..accounts_and_meta_to_store.len())
Copy link

Choose a reason for hiding this comment

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

That sounds nice to me

assert_eq!(transactions.len(), accounts.len());
transactions.iter().copied()
}
None => [].iter().copied(),
Copy link

Choose a reason for hiding this comment

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

How about we pass Option<&[&SanitizedTransaction]> directly?

brooksprumo
brooksprumo previously approved these changes Aug 27, 2024
Copy link

@brooksprumo brooksprumo left a comment

Choose a reason for hiding this comment

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

:shipit:

Looks fine to me. The changes are mainly localized to if there's an account_update_notifier (aka geyser) plugin. The rest of the changes are refactors.

assert_eq!(transactions.len(), accounts.len());
transactions.iter().copied()
}
None => [].iter().copied(),

Choose a reason for hiding this comment

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

This suggestion from jstarry makes sense to me.

@apfitzge apfitzge dismissed stale reviews from brooksprumo and jstarry via de664e8 August 27, 2024 13:19
@apfitzge
Copy link
Author

Wouldn't it be strange if account loading was part of the SVM but account saving was not?

I don't really think it'd be that strange - this is not actually where we "save" accounts anyways. We're just unrolling the TransactionProcessingResult into a more convenient form to then be saved (by accounts-db).

That said, I think I could probably work around the conversion issue I mentioned previously and just put the conversion trait in runtime - so we could leave this here.

Think @buffalojoec has some opinions on what should/should not be in SVM, so he may want to weigh in here.

jstarry
jstarry previously approved these changes Aug 28, 2024
Copy link

@jstarry jstarry left a comment

Choose a reason for hiding this comment

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

lgtm

} else {
Box::new(std::iter::empty())
};
let mut current_write_version = self
Copy link

Choose a reason for hiding this comment

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

We used to only update the write_version atomic counter for if self.accounts_update_notifier.is_some(). I don't see anything wrong with updating it always. @brooksprumo any thoughts here?

Copy link
Author

Choose a reason for hiding this comment

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

That's just a mistake on my part - reverted: ae2284a

@buffalojoec
Copy link

@jstarry & @LucasSte as a follow-up to this I plan to move this module out of SVM.
This functionality, as far as I can tell, is not actually used inside the svm crate - it is really needed by our runtime.

You read my mind. I was going to look into this as well, but I didn't want to block anyone pushing bugfixes for now.

Wouldn't it be strange if account loading was part of the SVM but account saving was not?

I don't really think it'd be that strange - this is not actually where we "save" accounts anyways. We're just unrolling the TransactionProcessingResult into a more convenient form to then be saved (by accounts-db).

That said, I think I could probably work around the conversion issue I mentioned previously and just put the conversion trait in runtime - so we could leave this here.

Think @buffalojoec has some opinions on what should/should not be in SVM, so he may want to weigh in here.

I'm fully on board with this approach. I just did something similar with rent evaluation/collection in #2753 and may also do something similar with other pieces like fees. Are you going to take it on for account saver @apfitzge ? If not I can do it!

@apfitzge apfitzge merged commit d651409 into anza-xyz:master Aug 28, 2024
40 checks passed
@apfitzge apfitzge deleted the account_saver_refactor branch August 28, 2024 18:15
ray-kast pushed a commit to abklabs/agave that referenced this pull request Nov 27, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants