-
Notifications
You must be signed in to change notification settings - Fork 3.7k
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
[movevm] improve automatic creation of account for sponsored txn #11076
Conversation
f619c6d
to
419e9d4
Compare
Codecov ReportAttention:
Additional details and impacted files@@ Coverage Diff @@
## main #11076 +/- ##
========================================
Coverage 68.7% 68.7%
========================================
Files 764 764
Lines 176995 177145 +150
========================================
+ Hits 121630 121819 +189
+ Misses 55365 55326 -39 ☔ View full report in Codecov by Sentry. |
7967a8f
to
e9ebbc1
Compare
e9ebbc1
to
7c9d326
Compare
c8fed55
to
b0bddb7
Compare
aptos-move/aptos-vm/src/aptos_vm.rs
Outdated
self.vm_impl | ||
if matches!(transaction_status, TransactionStatus::Keep(_)) | ||
&& txn_data.fee_payer().is_some() | ||
&& txn_data.sequence_number == 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.
I think account sequence number starts from 0 right? so there could be a valid account with sequence number 0, in that case if the txn fails, I guess it just needs to pay the extra gas for calling create_account_if_does_not_exist
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 would have to pay that in the happy path anyway...
b0bddb7
to
b5b0224
Compare
aptos-move/aptos-vm/src/aptos_vm.rs
Outdated
|
||
// Need to zero this out, because that's the legacy behavior. Otherwise we charge for txn | ||
// size where we used to not. | ||
let mut null_txn_data = txn_data.clone(); |
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.
Why not pass a size to charge_change_set_and_respawn_session
? seems better than cloning the whole struct
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.
hmm... because this isn't common case and it will be removed, so I like to leave the minimal foot print... I'm fine either way.
5264d3e
to
369a4ad
Compare
})?; | ||
|
||
let mut change_set = session.finish(change_set_configs)?; | ||
if let Err(err) = self.charge_change_set(&mut change_set, gas_meter, txn_data) { |
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.
Shall we add a flag to not record deposits on the new slots created (so no refund for these slots)? Makes me feel safer.
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.
why do that? if we can give them, we should
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.
So we will never create a bug where you can get more refund then you've paid. I know you defend against that by requiring more that two slots worth of it upfront, but I'm not feeling safe -- one can accidentally remove the requirement.
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.
thinking outloud - can we somehow make this more robust, either:
- make sure that charge we are missing here is below what we already charged, or
- refund what we already charged, charge here expecting no failures, and then try to charge back how much we refunded?
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 the current form, it's also a bit odd we are checking whether we have enough only for the storage deposit, but not for the other costs. they might not be high by default, but might be quite high if transaction has set a high gas price for example.
either of the above should account for the extras.
if we don't have enough funds for all the costs of account creation alone, we should probably be discarding the whole transaction, instead of forcing account creation.
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 am wondering if we can at least capture these cases in tests, e.g. have a test to check the refund is < what was paid? In case some checks are removed we will see something is off
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.
To be clear, this is not just a test issue.
The cheapest way now to create an account is to have account with 2x storage slot fee, and submit a fee payer txn.
In idle state difference is like 700 octas, but if network is under load - you can freely submit txn with large gas price, get the priority, and get it executed without paying high gas price.
If nothing else, we should check balance for 2* storage + 10 * gas_price , and have test to check its enough
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.
Assuming you landed on the idea that the validation function should test (2* storage + 10*gas_price) which I think is much, much more expensive then it is to create an account. I think that is acceptable... we should be willing to charge more for aborted transactions.
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.
@georgemitenkov , we cannot really test this because we don't have the ability to delete an account. I might propose that we only allow those that upgrade from v1 to v2 to actual delete. In which case, there's conservation here, because we'll need to create new storage anyway.
&& self | ||
.get_features() | ||
.is_enabled(FeatureFlag::SPONSORED_AUTOMATIC_ACCOUNT_CREATION) | ||
&& max_gas_amount < 2 * storage_slot_cost |
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.
You are comparing gas units with octas -- see how it's dangerous?
.sign_fee_payer(); | ||
|
||
let output = h.run_raw(transaction); | ||
// ECOIN_STORE_NOT_PUBLISHED |
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.
When we run a transaction, we split into prologue, user txn, epilogue, right? The error you are checking here comes from prologue, I believe, so you are not testing the user txn execution path. Adding a test which does divide by zero allows us to test the behaviour when failure comes inside of a user txn.
.max_gas_amount(100_010) // This is the minimum to execute this transaction | ||
.gas_unit_price(1) | ||
.sign_fee_payer(); | ||
let status = h.run_raw(transaction); |
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: status should be called output? In some other places as well?
401af5d
to
561c349
Compare
1132a43
to
361fa80
Compare
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
.gas_unit_price(1) | ||
.sign_fee_payer(); | ||
let result = h.run_raw(transaction); | ||
assert_eq!(result.gas_used(), 100_011); |
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: if we trigger out of gas, would be nice to assert the returned status 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.
these run out of gas in the epilogue, so we don't get those errors. only the logger would
let bob_start = h.read_aptos_balance(bob.address()); | ||
|
||
// will trigger a failed execution | ||
let data = |
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 copy the comment which says that this does divide by zero? So we don't forget what these bytes are :)
&TransactionStatus::Discard(StatusCode::MAX_GAS_UNITS_BELOW_MIN_TRANSACTION_GAS_UNITS) | ||
)); | ||
|
||
let alice_after = |
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 assert accounts exist/do not exist explicitly? I have seen there are checks for sequence number which is sort of an existence check, but I think would be nice to see that in the test?
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.
From a framework perspective, the sequence number is pretty explicit.
}; | ||
use move_core_types::{move_resource::MoveStructType, vm_status::StatusCode}; | ||
|
||
// Fee payer has several modes and requires several tests to validate: |
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.
Thank you!
// * Account exists and transaction executes successfully | ||
// * Account exists and transaction aborts but is kept | ||
// * Account doesn't exist (seq num 0) and transaction executes successfully | ||
// * Account doesn't exist (seq num 0), transaction aborts due move abort, and account is created |
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: due --> due to
Thanks everyone for the thorough reviews. |
361fa80
to
f817de8
Compare
* If an account is created during sponsored transaction and the txn aborts. * The transaction is now kept but aborted. * The state associated with account creation is wiped and The account is no longer created. * During epilogue, we then hit a invariant violation, because the sequence number for the account cannot be incremented. This ensures that we create an account even on transaction failure in the epilogue: * during validation, sponsored transactions on sequence number 0, must have sufficient gas to create an account * if the transaction aborts, call create account and charge gas appropriately
f817de8
to
b003b9f
Compare
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
✅ Forge suite
|
✅ Forge suite
|
❌ Forge suite
|
This ensures that we create an account even on transaction failure in the epilogue. We then charge as much as possible by recording the error on processing the change set. An error indicates we have run out of gas.
Note, the min gas amount is effectively 2, where we need roughly 4 for this operation, so more needs to be figured out. We could consider updating the simulation, but it is probably easier to just mention in the AIP this specific scenario as that minimizes further code hacking.
There are tests that validate that this works even with enough gas to execute and that it fails to execute without enough gas.