-
Notifications
You must be signed in to change notification settings - Fork 441
svm: collect transaction balances #5588
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
base: master
Are you sure you want to change the base?
svm: collect transaction balances #5588
Conversation
cd80dce
to
52ba648
Compare
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #5588 +/- ##
=========================================
- Coverage 83.0% 83.0% -0.1%
=========================================
Files 828 829 +1
Lines 375623 375370 -253
=========================================
- Hits 311918 311621 -297
- Misses 63705 63749 +44 🚀 New features to boost your workflow:
|
187f4ef
to
5459890
Compare
c93d734
to
79ba85f
Compare
solana-rent-debits = { workspace = true } | ||
solana-sdk = { workspace = true } | ||
solana-sdk-ids = { workspace = true } |
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 a pleasant surprise
.map(|_| Err(TransactionError::ProgramCacheHitMaxLimit)) | ||
.collect(), | ||
balance_collector: None, | ||
}; | ||
} |
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.
setting None
here is absolutely the right call because this branch discards all transactions and all pending state changes. what is less clear is whether it matters that compile_collected_balances()
in ledger/src/transaction_balances.rs
returns (vec![], vec![], vec![], vec![])
now that im going through this code with a reviewers eyes, maybe the function in ledger should return Option<(TransactionBalancesSet, TransactionTokenBalancesSet)>
instead, and its callers can create four vec![vec![]; batch.len()]
in the worst case callers would have to load the accounts to fill the native balance vecs. i really dont know whether anything on the TransactionStatusReceiver
side would need balances for an entirely discarded batch though. im leaning toward the "vec of empty vecs" option but curious to hear your thoughts
|
||
let (mut load_us, mut execution_us): (u64, u64) = (0, 0); | ||
|
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.
its only tangentially related to this pr, but instead of adding all timings at the bottom, i switched to adding as many timings as possible immediately after they are generated, and keeping load_us
and execution_us
as incremental accumulators because theyre printed in a debug message
this is because with collect_balances_us
we would have had four mut u64
here, and if the program cache fix (filter account owners) ever lands we would have seven. it doesnt make much sense to keep this pattern, since we are just saturating-adding to a stack variable that is then later saturating-added to the timing struct anyway
Problem
#4253 aka "simd83 locking" intends to allow multiple transactions in the same batch to write to the same account. presently, banking stage and blockstore processor collect pre- and post-execution balances by loading all accounts before and after commit. this does not work in a simd83 world because the intermediate balances of an account modified multiple times would be wrong (although the first pre- and final post-balance would be correct)
we have attempted to solve this in several different manners, as detailed in the notes below (not necessary to understand this pr). they dont work. what if there was a better way?
Summary of Changes
there is. well, a less bad way:
enable_transaction_balance_recording
toExecutionRecordingConfig
BalanceCollector
which isolates the logic fromload_and_execute_sanitized_transactions()
. we wrap this inOption<_>
throughout, so when the recording config is false no extra work is done. we parse the token accounts and mints using the new cratespl-generic-token
which provides the minimum necessary information common to both token programs (and can be extended if others are ever created, even if their buffer layouts are different)Option<BalanceCollector>
is added toLoadAndExecuteSanitizedTransactionsOutput
and passed up to the ultimate caller. this is important because one of the main challenges has been that banking stage and blockstore processor must gather what is essentially intermediate state internal to svm, passing this throughBank
itself without relying onBank
to store it or care about itBalanceCollector
. it no longer matters whether it handles it before or after commit because no more accounts need to be loadedcompile_collected_balances()
turns the four vecs inBalanceCollector
into the two structs containing the balances, transformingu64
token amounts intoUiTokenAmount
as required by the transaction status senderledger/src/token_balances.rs
is deleted and solana-ledger no longer depends on the token programs directly (although it does indirectly via account-decoder; in the future we could move theUiTokenAmount
builder somewhere and break this link)ConsumeWorkerTimingMetrics
andLeaderExecuteAndCommitTimings
and added toExecuteTimings
. we expect balance collection to be much faster in this pr because we will already have all token accounts pre-loaded from accounts-db, and mints have to be loaded at most once per batch. the version in master has to load all accounts for all transactionsBalanceCollector
has no special handling for pre- vs post-balances, ie it does not useTransactionLoadResult
orTransactionProcessingResult
for anything. in practice doing so makes the code more complex and brittle for uncertain speed gains, probably minimal since the accounts are already loadednote that there are bank tests that expect to see full balances even for failed or discarded transactions, so that behavior has been left unchanged
this pr unblocks #4253
Notes (feel free to ignore)
this is too jargony to probably make sense to most and more for my reference in case we have to rehash past attempts in code review of this pr:
bals1, the svm three card monte: add a balance recording flag to the recording config. rather messy balance collection in txp, directly with no helpers. the idea was to use a callback from ledger to parse the token accounts so svm doesnt see spl-token but otherwise do the work ourselves in svm. tack the bals onto the processing result to get picked up upstream. this was bad because it very poorly separated concerns and did too much in txp itself, and we thought we had better approaches we could try
bals2, the mysterious dinner guest: i dont remember what this one was
bals3, the gigafunction: something in ledger which attempted to construct balances post-hoc from the transaction results. this was a clever machine which attempted to allow svm to have no hand in any of this. unfortunately the approach is inherently imperfect and has tons of gimmickry to emulate balance changes as one steps through the transactions. we crashed into the rocks because to get decimals right we need to either:
furthermore all of these have the problem of, for blockstore, we need to put the logic in the precommit callback. and that code has some unusual and possibly dangerous-to-modify interactions with bank locking to support the unified scheduler
bals4, the trait object hand grenade: we package up a machine that does the work and lob it into svm. this was an inspired option but it is fiendish. we have a collector trait in svm and impl it in ledger, but the collector trait depends on account loading, and passing in the collector trait object is very difficult because the balance collector is generic over concrete impls of either a new loader trait or txpcb, both of which are created below blockstore, so they cannot be unified by rustc. it also, even if it did work, would make function signatures truly ghastly
bals5, the present concern: this pr. we take the general approach of bals1, with a new recording option and balance recording in svm, passing the balances up in the processing results. but we package all the logic into a struct so txp is unperturbed, use a very light token parser directly in svm instead of passing any function pointers or trait objects down from ledger, and do the final transform of balance+decimals -> uiamount in ledger because this depends on account-decoder. we originally did the token work with agave inline-spl, but because svm is being broken out of agave, and because we want new features for tokens anyway, we made a new spl library that does exactly what we want