SIMD-0186: Loaded Transaction Data Sizes#6063
Conversation
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #6063 +/- ##
========================================
Coverage 82.8% 82.8%
========================================
Files 843 843
Lines 378209 378682 +473
========================================
+ Hits 313252 313674 +422
- Misses 64957 65008 +51 🚀 New features to boost your workflow:
|
208001e to
b579871
Compare
678faef to
635d9d3
Compare
635d9d3 to
d7d7dc2
Compare
tao-stones
left a comment
There was a problem hiding this comment.
Did a quick read, load_transaction_accounts_simd186() looks great! Thanks for making the change. Will check out tests next.
|
1fff4c0 just a little test improvement |
|
@anza-xyz/svm this is ready for review |
svm/src/account_loader.rs
Outdated
| } | ||
|
|
||
| for (program_id, instruction) in message.program_instructions_iter() { | ||
| if native_loader::check_id(program_id) { |
There was a problem hiding this comment.
I think we can use this opportunity to get rid of this as well. It is a relict from when we had chained loaders. This removal would have to be mentioned in the SIMD.
There was a problem hiding this comment.
which simd? i dont think its relevant to 186, which only talks about loaded sizes and not anything about the loader checks themselves. it could be relevant to 162 though
my view of the new message.program_instructions_iter() loop is that, since we no longer need to load the loaders to count their program data sizes, the new code is a simple refactor/optimization. the new PROGRAM_OWNERS check is the same as the block in process_executable_chain() which is activated by remove_accounts_executable_flag_checks, so since enable_transaction_loading_failure_fees is already enabled, the result is the same and it just happens earlier
likewise if we remove the native loader check here, we fail in the same manner as process_executable_chain() with remove_accounts_executable_flag_checks but earlier (because the account doesnt exist, and it cant be funded due to write-lock demotion, so loading it to check its owner fails)
edit: instead of relying on it never existing we can just hardcode it as invalid for execution. ultimately its all the same, transaction pays fees and fails
There was a problem hiding this comment.
Isn't this if here redundant and will run into !native_loader::check_id(owner_id) && !PROGRAM_OWNERS.contains(owner_id) with the same error being thrown anyway?
There was a problem hiding this comment.
in theory it should always hit the else branch of
let Some(program_account) = account_loader.load_account(program_id) else {
error_metrics.account_not_found += 1;
return Err(TransactionError::ProgramAccountNotFound);
};but i think that error would be confusing and it depends on write-lock demotion preventing anyone from ever adding lamports to the address (in which case it would hit the one you mentioned)
There was a problem hiding this comment.
That is kinda my point. Just remove the handling of this special case all together, the entire if block.
There was a problem hiding this comment.
I'm not a fan of making this change here. Transactions that invoke the native loader go from being executed and depleting cu's (pre-PR) to being fee-only and not depleting cu's (post-PR). This is a consensus breaking change and btw it's still not included in the SIMD anywhere.
There was a problem hiding this comment.
It needs to be documented in the SIMD for sure as it is a protocol change. But I think it is not consensus breaking because the entire code path load_transaction_accounts_simd186 is feature gated, right?
There was a problem hiding this comment.
Yeah, needs to be documented so that other validator implementations make the same changes.
|
|
||
| loaded_transaction_accounts | ||
| .program_indices | ||
| .push(vec![instruction.program_id_index as IndexOfAccount]); |
There was a problem hiding this comment.
If we remove the native_loader::check_id(program_id) special case above, then we also don't need a Vec around the program index here anymore.
There was a problem hiding this comment.
we can do this as part of the feature gate removal, for now the old code still needs the vec
5357ba1 to
34e58c9
Compare
implement a new version of `load_transaction_accounts()` which conforms to the simd186 transaction account data size algorithm also simplify accumulation of `program_indicies` and program owner checks; the program owner logic is functionally equivalent to the checks inside `InvokeContext` added by `remove_accounts_executable_flag_checks`, so it is an optimization rather than a new spec
implement a new version of `load_transaction_accounts()` which conforms to the simd186 transaction account data size algorithm also simplify accumulation of `program_indicies` and program owner checks; the program owner logic is functionally equivalent to the checks inside `InvokeContext` added by `remove_accounts_executable_flag_checks`, so it is an optimization rather than a new spec
|
|
||
| // Per SIMD-0186, resolved address lookup tables are assigned a base size of 8248 | ||
| // bytes: 8192 bytes for the maximum table size plus 56 bytes for metadata. | ||
| const ADDRESS_LOOKUP_TABLE_BASE_SIZE: usize = 8248; |
There was a problem hiding this comment.
Shouldn't this include the TRANSACTION_ACCOUNT_BASE_SIZE as well?
There was a problem hiding this comment.
2. Each loaded account's size is defined as the byte length of its data prior to
transaction execution plus 64 bytes to account for metadata.
3. There is an additional flat 8248 byte cost for each address lookup table used
by a transaction, accounting for the 8192 bytes for the maximum size of such a
table plus 56 bytes for metadata.
i interpret point 3 to unambiguously mean no because it makes no reference to point 2 and the 8248 cost is described as "flat." its hard for me to read "additional" as meaning anything but "added to the transaction total" because the base size is described as being added to the real byte length of a loaded account
im fine adding another 64 here but would definitely amend the simd, i think this rightly implements the current wording we arrived at
|
|
||
| account.map(|account| LoadedTransactionAccount { | ||
| loaded_size: account.data().len(), | ||
| loaded_size: base_account_size.saturating_add(account.data().len()), |
There was a problem hiding this comment.
Should we be adding the base account size to accounts that didn't exist before transaction execution as well? From the SIMD it's not super clear to me what the answer should be. Are we limiting the amount of IO reads or are we limiting the amount of memory used to track accounts when processing a transaction? If it's the former then we probably shouldn't include the base account size, but if it's the latter we should I think.
There was a problem hiding this comment.
i debated whether this was ambiguous in the simd or not when implementing it, and decided it wasnt because everything talks about "loaded account data size" and new accounts are not "loaded." but since you also raised it i think its worth adding a clarification
There was a problem hiding this comment.
correction: i debated whether this was ambiguous and decided that it was and already added the clarification to my open simd186 clarifications pr which foundation still needs to merge 😅 solana-foundation/solana-improvement-documents#282 (comment)
svm/src/account_loader.rs
Outdated
| } | ||
|
|
||
| for (program_id, instruction) in message.program_instructions_iter() { | ||
| if native_loader::check_id(program_id) { |
There was a problem hiding this comment.
I'm not a fan of making this change here. Transactions that invoke the native loader go from being executed and depleting cu's (pre-PR) to being fee-only and not depleting cu's (post-PR). This is a consensus breaking change and btw it's still not included in the SIMD anywhere.
Problem
simd186 declaratively defines a new transaction data size algorithm that replaces the existing ad hoc but consensus-relevant one. the current algorithm is difficult for new validators to implement correctlt because of certain surprising behaviors, and also routinely undercounts the true size of loaderv3 programs by a substantial margin
Summary of Changes
implement simd186. we do this by changing the existing
load_transaction_accounts()to simply branch on the feature gate to the two functionsload_transaction_accounts_simd186()orload_transaction_accounts_old(). the former is brand new, the latter is the existing impl unchanged. this entails some code duplication until the feature is activated, but the new function is substantially changed in a way that would be quite difficult to follow if we branched on the feature inline. namely:LoadedTransactionAccountsstruct as an accumulator rather than defining a ton of variables and passing ownership to the struct at the endincrease_calculated_data_size()which replacesaccumulate_and_check_loaded_account_data_size()program_indiceswith its proper capacity rather than mapping to itprogram_indiciesis substantially simplified:account_keysand it was very hard to see that this short-circuited them being counted for every program id) but its behavior still rather unintuitiveNativeLoader, we simply check if the program is owned byPROGRAM_OWNERSand abort if it is not. becauseenable_transaction_loading_failure_feesis now active plus a fix made by alexander toInvokeContext, the behavior is actually identical and this is strictly an optimizationwe also change all
transaction_processor.rsandaccount_loader.rsunit tests to enable all features by default as #6043 did for integration tests. this is substantially safer for implementing new svm featuresFeature Gate Issue: https://github.com/anza-xyz/feature-gate-tracker/issues/88