svm: advance nonce for fee-only transactions sooner#2741
svm: advance nonce for fee-only transactions sooner#2741mergify[bot] merged 15 commits intoanza-xyz:masterfrom
Conversation
88a5da5 to
9768c4e
Compare
558e361 to
aee85d9
Compare
|
50a1e08 is the important commit, and all code changes are isolated to there its a relatively small change on its own. code that advances nonces has been deleted from the account saver, so it now accepts the transaction processing results as immutable references (my goal for this, so i can reuse the functions inside the transaction processing loop without the rest of the commits are either fixing existing unit tests, or (most of the loc) improving the new svm integration test setup to handle nonce transactions and the new fee-only transaction state. we test all possible nonce outcomes with and without the feature |
jstarry
left a comment
There was a problem hiding this comment.
this looks awesome, haven't looked at tests too closely yet but like what I see there too so far
dd7e417 to
62b2a2c
Compare
|
had to rebase for conflicts so the incremental diff is not useful but most changes are in 15ee97b i moved nonce advancing into the check transaction step. thought a lot about the cleanest way to do this, since i didnt want to break the encapsulation of also removed the unnecessary timing metric and transaction result mut method |
| self.load_message_nonce_account(message) | ||
| { | ||
| let lamports_per_signature = nonce_data.get_lamports_per_signature(); | ||
| let next_nonce_state = NonceState::new_initialized( |
There was a problem hiding this comment.
This optimization can come later if we want it but I think we can delay the serialization of the advanced nonce by including the advanced NonceData in the NonceInfo and only serialize that data into the account shared data when NonceInfo::account is called. I think this would make the same fee payer / nonce account case a little cleaner too because we wouldn't need to copy the nonce account's data over during rollback accounts creation, we could delay it until nonce.account() is called when we collect accounts for storage.
There was a problem hiding this comment.
im going to leave this as is for now but may end up doing it in #2702 because ill need to reverify nonces mid-batch
There was a problem hiding this comment.
Sounds good to me. And just in case it wasn't fully clear, delaying the serialization will be nice because if the transaction succeeds, we won't need to waste any time serializing a rollback nonce account that is never used.
|
ok added the new tests and other cleanups |
| let (nonce_address, mut nonce_account, nonce_data) = | ||
| self.load_message_nonce_account(message)?; | ||
|
|
||
| let lamports_per_signature = self.get_lamports_per_signature(); |
There was a problem hiding this comment.
This isn't the correct lamports_per_signature value. Instead of getting it from the fee calculator, it needs to come from the blockhash queue. You should grab the value from the hash_queue in check_age
There was a problem hiding this comment.
thank you for catching this, i used an unwrap as this is identical to last_blockhash_and_lamports_per_signature() but let me know if youd rather i zip and match on an option
btw what is the difference between the lamports_per_signature on the bank and the one on the blockhash queue? im not familiar with how variable fees would function if they were ever turned on/employed
There was a problem hiding this comment.
btw what is the difference between the
lamports_per_signatureon the bank and the one on the blockhash queue? im not familiar with how variable fees would function if they were ever turned on/employed
In register_recent_blockhash we would have recorded the derived lps for the blockhash of the current bank's parent block. Then in the current bank's constructor (_new_from_parent) we derive a new lps based on the signature count of the parent block's signature count (FeeRateGovernor::new_derived). So it's important that we are using the lps value from the parent bank when populating the nonce, not the value from the current bank.
Now, let's look into how lps could change inside the fee rate governor.. I pulled the fee rate governor fields from a mainnet beta snapshot:
[snapshot-tool/src/main.rs:200:13] bank.fee_rate_governor = FeeRateGovernor {
lamports_per_signature: 5000,
target_lamports_per_signature: 10000,
target_signatures_per_slot: 20000,
min_lamports_per_signature: 5000,
max_lamports_per_signature: 100000,
burn_percent: 50,
}
Based on the logic for FeeRateGovernor::new_derived it looks like a parent bank would need to have over 10,000 signatures in order to increase the desired lps beyond 5000. So this code definitely is enabled but...
Also it's important to call out that for fee calculation we don't really use lps from the FeeRateGovernor anymore, we get it from FeeStructure (see how get_fee_for_message_with_lamports_per_signature only uses lps from fee rate governor to disable fees for tests). We only use the fee rate governor's lps value for storing in the nonce account right now and that's why it's important to get correct for consensus right now. But we should clean up all uses of the fee rate governor altogether at some point.
Btw, in the past we did have a separate mechanism for congestion driven fees but it was buggy and removed. Discussion here: https://discord.com/channels/428295358100013066/488758828330909706/1201405954256818176
| bank.store_account(&nonce_pubkey, &nonce_account); | ||
|
|
||
| let nonce_account = bank.get_account(&nonce_pubkey).unwrap(); | ||
| let current_lamports_per_signature = bank.get_lamports_per_signature(); |
There was a problem hiding this comment.
So this test should be using the lps value from last_blockhash_and_lamports_per_signature
9a295a7 to
642ca9c
Compare
|
Haven't looked at the code yet. Have some quick thoughts based on PR description:
I think loading and advancing early only works because we force nonce ixs to be the first instruction, let's make sure we have some sort of assertion on relevant code. If we advance early, wrt SIMD83 we must be careful that we do not advance if we hit some sort of non-recordable error, i.e. tx gets dropped. |
I'm not sure I follow what you mean here. This change is for advancing nonces ahead of time in case the transaction fails. It has no effect on processing successful transactions (besides wasting a bit of work to serialize the rollback nonce account). |
| Ok(CheckedTransactionDetails { | ||
| nonce: Some(nonce), | ||
| lamports_per_signature: nonce_data.get_lamports_per_signature(), | ||
| lamports_per_signature, |
There was a problem hiding this comment.
This is technically wrong but currently harmless (because this lps value is only used for turning off fees inside validate_transaction_fee_payer). Let's play it safe and keep the old logic of using the previous nonce data lps value rather than changing it to the blockhash queue value.
There was a problem hiding this comment.
ok i believe we should be perfectly in line with master now. the nonce data is updated with the lps value from the tip of the blockhash queue. but the previous nonce data lps value is assigned in the check details, which is ultimately only compared to 0 in svm to see if fees are in play
There was a problem hiding this comment.
Cool that matches my understanding as well, thanks for making the change
when it comes to "is this a valid nonce transaction" ie will i vaguely remember we might have discussed a mechanism to skip executing the
ill update the description to be clearer, this does not modify the accounts store in any way prior to |
3dac928 to
4131482
Compare
these pass against master as-written
previously rollback accounts carried the fee payer that was to be committed, but an untouched nonce now, the account saver can use the nonce and fee payer from rollback accounts identically, possibly merging them for a fee-paying nonce
4131482 to
3aa9d06
Compare
|
@jstarry i had to fix merge conflicts again because these files keep changing in master, if you wouldnt mind approving again once ci passes |
|
automerge label removed due to a CI failure |
|
(noting that the failure is just the partitions job failing to curl something from github) |
|
automerge label removed due to a CI failure |
* write integration tests for nonce transactions these pass against master as-written * advance nonce when creating rollback accounts previously rollback accounts carried the fee payer that was to be committed, but an untouched nonce now, the account saver can use the nonce and fee payer from rollback accounts identically, possibly merging them for a fee-paying nonce * fix nonce-related unit tests * remove nonce hack from integration test * support four tx states in svm integration * advance nonce in check transactions * fix tests for new interfaces * remove last blockhash timing and tx result mutator * change copy mut to proper accountshareddata function * adddress feedback * please clippy * fix merge issues * get lamports_per_signature from blockhash * revert niche lps change * fix merge issues again
Problem
as part of SIMD-83, transaction loading needs to track state changes made by previous transactions in the same batch.
RollbackAccountsis a struct used to manage state changes that must be committed in the case of transaction processing failure. successful transactions, on the other hand, hold the data to be committed on their loaded transaction accounts structpresently,
RollbackAccountsis set up with the prior nonce account state and the expected fee-payer account state. for failed transactions, the nonce data here is not advanced until state changes are being committed, necessitating that the account saver mutateTransactionProcessingResultobjectsSummary of Changes
store the already advanced nonce data in
RollbackAccounts, in line with how the fee-payer is handled. this means transaction post-processing can reuse the account saver methodcollect_accounts_to_storewithout cloningTransactionProcessingResultbecause the account saver will no longer need mutable references to them