-
Notifications
You must be signed in to change notification settings - Fork 205
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
svm: interleave transaction validation and processing #3404
base: master
Are you sure you want to change the base?
Conversation
b351ffb
to
af8ef47
Compare
the first commit is the two files copied exactly from the previous pr, and the second commit deletes the intermediate account cache. this way you can look at the full changeset, or the difference between what you already reviewed, easily |
{ | ||
loaded_account.rent_collected = if usage_pattern == AccountUsagePattern::Writable { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This change has the side effect of collecting rent from writable accounts that come from account overrides. Probably fine? I'm not too sure yet
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this was intentional:
- the current code actually collects rent from an override if its the fee-payer, otherwise it doesnt. so this is at least consistent
- with simd83, we have to consider what happens to a writable overridden account after the first transaction: does it stay frozen, and all changes to it are ignored? or does it behave like an ordinary account that happens to have an overridden starting value? the latter is more in line with its current behavior, where it is freely allowed to change in the transaction, and then the mutated value is retained in the
LoadedTransactionAccount
returned from the svm entrypoint - if we let an overriden account change intrabatch then rent collection should happen on first use, otherwise we collect on second use, which is odd
- account overrides is only intended to be used for simulation. actually giving it a writable account for a real execution would be highly unwise because it would make its way all the way back to accounts-db to be committed
in fact, account overrides is an overly general mechanism for transaction simulation to pass svm a SlotHistory
sysvar from the previously frozen bank, and nothing else. andrew was suggesting we could change it if/when we change the load_and_execute_sanitized_transactions
interface
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great breakdown, completely onboard now
svm/src/transaction_processor.rs
Outdated
// in the same batch may modify the same accounts. Transaction order is | ||
// preserved within entries written to the ledger. | ||
for (tx, check_result) in sanitized_txs.iter().zip(check_results) { | ||
let (validate_result, single_validate_fees_us) = measure_us!(check_result |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: can you introduce a new function called validate_transaction
which wraps validate_transaction_nonce
and validate_transaction_fee_payer
? There's a lot of nesting here that makes the code hard to read IMO
fee_payer_account | ||
} else if let Some(fee_payer_account) = callbacks.get_account_shared_data(fee_payer_address) | ||
{ | ||
callbacks.inspect_account( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I love that all the inspect_account
call sites are inside AccountLoader
now
im putting this back in draft because alexander and alessandro are planning on fundamentally changing how the program cache interacts with account loading and putting it in 2.1 so we need to coordinate with them |
ok. this will be back in review soon. i spent a while talking with them and alexander thought he had an elegant fix to decouple account loading from program cache but it is now scuttled due to the bugs in how the account loader uses the program cache. but he is making some changes to the program cache tests that i want to rebase on |
svm/src/account_loader.rs
Outdated
} else if let Some(program) = is_invisible_read | ||
.then_some(()) | ||
.and_then(|_| self.program_cache.find(account_key)) | ||
{ | ||
self.callbacks.get_account_shared_data(account_key)?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
once i rebase on alexander, i am removing this get_account_shared_data()
call:
- its wrong. the changes to the flow means if it were ever hit, account loading would believe there is a new account to create rather than aborting
- it is impossible to ever trigger because program cache is built directly from accounts-db
alexander is removing the test that artificially triggers this condition
d871d13
to
c261e5d
Compare
c261e5d
to
776dffc
Compare
#[cfg_attr(feature = "dev-context-only-utils", derive(Clone))] | ||
pub(crate) struct AccountLoader<'a, CB: TransactionProcessingCallback> { | ||
account_overrides: Option<&'a AccountOverrides>, | ||
pub(crate) program_cache: ProgramCacheForTxBatch, | ||
program_accounts: HashMap<Pubkey, (&'a Pubkey, u64)>, | ||
callbacks: &'a CB, | ||
pub(crate) feature_set: Arc<FeatureSet>, | ||
} | ||
impl<'a, CB: TransactionProcessingCallback> AccountLoader<'a, CB> { | ||
pub fn new( | ||
account_overrides: Option<&'a AccountOverrides>, | ||
program_cache: ProgramCacheForTxBatch, | ||
program_accounts: HashMap<Pubkey, (&'a Pubkey, u64)>, | ||
callbacks: &'a CB, | ||
feature_set: Arc<FeatureSet>, | ||
) -> AccountLoader<'a, CB> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
instead of following the "pub first priv after" rule which made things hard to remember, the fields are in the order of load priority, overrides > program cache > accounts-db, with constructor args in the same order
the next pr immediately after this one adds account cache and removes overrides from the struct. when disable_account_loader_special_case
activates we also remove the two program cache items from the struct
ok! this is now ready for review again and i think we can finish all the account loader work next week. nothing i was trying to do in this pr has changed conceptually, but i had to rebase on five or six different commits that all changed the underlying code in master, so another look through with fresh eyes is necessary nothing in this pr is intended to affect behavior at all (other than rent collection on overrides, which does not affect anything since in the next pr we will enforce that
this pr will be immediately followed by #3453, which is based on this one and can be made ready for review once this one lands. that pr adds the account cache and fully supports simd83 account loading. it is also delightfully small, i think the actual code changes are like 100 lines and the vast majority is new tests i will also do a third pr getting loaders out of the account keys without changing size calculations, but thats basically just a cleanup to recap our exciting journey since this was put back into draft last week:
the merges were fairly involved and i have looked at the code itself and diffs against both master and the previous state of this branch to be sure i didnt misplace anything, but yea. i think we will be out of the weeds into greener pastures very soon |
Problem
simd83 intends to relax entry-level constraints, namely that a transaction which takes a write lock on an account cannot be batched with any other transaction which takes a read or write lock on it
before the locking code can be changed, however, svm must be able to support such batches. presently, if one transaction were to write to an account, and a subsequent transaction read from or wrote to that account, svm would not pass the updated account state to the second transaction; it gets them from accounts-db, which is not updated until after all transaction execution finishes
previously, in #3146, we attempted to implement a jit account loader with intermediate account cache to pass updated accounts forward to future transaction in the batch. however, due to existing patterns in how the program cache is used, this has proven quite difficult to get right, and the volume of code changes required is quite large
Summary of Changes
this pr extracts all code from #3146 which does not modify behavior. we would like to commit it standalone for ease of review, and focus specifically on the cache behaviors in a following pr
the existing transaction processing loop in master validates all transaction fee-payers together, loads all transaction accounts together, and then executes each transaction in serial. the new transaction processing loop validates one fee-payer, loads accounts for one transaction, and then executes that transaction before proceeding to the next. this prepares us for an svm where accounts can change in between transactions
we also validate nonce accounts before each transaction, because these accounts can also change in the future svm. a future pr may choose to eliminate most nonce validation code from the
check_transactions
, but that is out of scope herefuthermore, we create a new type,
AccountLoader
, which encapsulates the batch-local program cache, account overrides, and the accounts-db callback. it provides the methodload_account
, which is opaque to its caller, returning aLoadedTransactionAccount
according to the exact same rules as the currentload_transaction_account
this pr changes nothing about account loading, but introducing
AccountLoader
prepares us to add the internal account cache in support of simd83