-
Notifications
You must be signed in to change notification settings - Fork 635
SIMD-0186: Loaded Transaction Data Sizes #6063
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
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
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.
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 |
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.
lgtm
@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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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'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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we can do this as part of the feature gate removal, for now the old code still needs the vec
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.
Yes, exactly.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't this include the TRANSACTION_ACCOUNT_BASE_SIZE
as well?
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.
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
@@ -208,7 +224,7 @@ impl<'a, CB: TransactionProcessingCallback> AccountLoader<'a, CB> { | |||
); | |||
|
|||
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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:LoadedTransactionAccounts
struct 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_indices
with its proper capacity rather than mapping to itprogram_indicies
is substantially simplified:account_keys
and 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_OWNERS
and abort if it is not. becauseenable_transaction_loading_failure_fees
is 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.rs
andaccount_loader.rs
unit 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