-
Notifications
You must be signed in to change notification settings - Fork 82
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
feat: some charge gas optimisations #664
base: develop
Are you sure you want to change the base?
Conversation
e45e098
to
19f3a12
Compare
b30b5db
to
3a22fb0
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.
Looks to me like the balance used to cover the gas limit is not deducted from the user's account before executing the transaction.
Could you add a test where the user attempts a double-spend in this way? The set up is that the user has enough ETH to cover their gas limit, and enough ETH to cover a transfer they want to do, but not enough ETH to cover both. The expected behaviour is that the EVM transaction reverts with OutOfFunds when the transfer is attempted (the Near transaction is still a success so the user is charged for the gas they consumed in attempting the transfer; it is only EVM transaction that fails).
engine/src/engine.rs
Outdated
.ok_or(GasPaymentError::OutOfFund)?; | ||
|
||
set_balance(&mut self.io, sender, &new_balance); | ||
if balance < prepaid_amount { |
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.
The logic was changed. It's not similar to:
get_balance(&self.io, sender)
.checked_sub(prepaid_amount)
Correct is:
if balance <= prepaid_amount { ...
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.
Correct, the gas must be deducted THEN stored in memory. From there Sputnik MUST get the balance from memory, and no longer from storage in the basic
method of the Backend
impl
for Engine
. That also is missing.
engine/src/engine.rs
Outdated
.ok_or(GasPaymentError::OutOfFund)?; | ||
|
||
set_balance(&mut self.io, sender, &new_balance); | ||
if balance < prepaid_amount { |
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.
Correct, the gas must be deducted THEN stored in memory. From there Sputnik MUST get the balance from memory, and no longer from storage in the basic
method of the Backend
impl
for Engine
. That also is missing.
@@ -431,13 +431,12 @@ impl<'env, I: IO + Copy, E: Env> Engine<'env, I, E> { | |||
.checked_mul(effective_gas_price) |
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 effective_gas_price
is zero, we can do a bit optimize it:
prepaid_amount
- in that case always = 0, we don't needmul
itnew_balance
is the same asbalance
, we don't needsub
it- we don't need cache it, as the balance was not changed
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 don't think that checks are much much cheaper than mul
and sub
. IMO, it's not worse it.
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.
And the goal of this PR is to reduce the number of operations with storage.
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 do not need to do mathematical calculations, but you still need to do these calculations because there is no benefit from this? Given that these mathematical operations are strict with verification (checked)?
What exactly concerns this PR is that there is no need to update the cache. It's definitely a redundant operation.
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 just reduce 2 strict math operations, and 1 memory operation. Why it's not cheaper?
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 just reduce 2 strict math operations, and 1 memory operation. Why it's not cheaper?
Because checking values is not free.
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.
- Not free. but 4 math instructions are cheaper and memory operation flow than 1 JUMP instruction?!!
- When using the Aurora+, zero gas operations are common. In this case,
update_cached_balance
is a guaranteed read of the Storage. And it is not needed in that particular case.
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 would like to request one last test, just to ensure that Sputnik is getting the correct information. May sound redundant, but will protect us from potential changes to Engine::basic
which could cause double spends:
- Charge gas
- Use method
Engine::basic
to get the balance (nonce is irrelevant for this test) - Assert gas is deducted from
basic
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.
One question I have about this: it's supposed to be a gas optimization, but none of the benchmark tests (repro, uniswap, etc) needed to be updated after this change. So did this optimization really have an impact on our gas usage?
I should mention before. Upcoming commit brings this changes. |
76452c1
to
9c91d91
Compare
@@ -16,7 +16,7 @@ pub mod costs { | |||
use crate::prelude::types::EthGas; | |||
|
|||
/// This cost is always charged for calling this precompile. | |||
pub const PROMISE_RESULT_BASE_COST: EthGas = EthGas::new(105); | |||
pub const PROMISE_RESULT_BASE_COST: EthGas = EthGas::new(53); |
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.
@joshuajbouw @birchmd this change is a bit confusing to me. Could you check it out?
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 gas cost is computed from this test. The basic idea of this test is to measure the gas cost by doing a linear regression of the actual cost (measured in Near gas) of calling this precompile for different sizes of promise data. It's actually not much of a linear regression because I just take two points and compute the line through them, but we also don't need this to be super accurate (the gas cost is only to prevent abuse) so I think that's ok.
Anyway, the cost is supposed to exclude overhead that applies to all transactions by subtracting a baseline transaction. So it is surprising to me that this change impacts this value. But the test also allows a 5% wiggle room on the value, so the change might be smaller than it looks if we were on the edge of being outside the 5% range before. Overall, I think this change is ok even if it is unexpected.
9c91d91
to
3b46826
Compare
Yes. As I said, the last commit brings some TGas decreasing. And I think a more significant decrease in the costs we would see in the process of working the aurora because, in the tests, we don't use the cache effectively (the first call gets value from the storage rather than from the cache). |
Could you elaborate on this point more? I thought the tests should be accurate compared with the production deployment of the contract because they are both running in the Near runtime. |
I'm curious about this as well, if so, then tests would need to be updated to accurately reflect this. We should be seeing gas reductions though. |
No. It's ok. It was my misunderstanding of how exactly the contract works. |
Some comparisons. First value is a result from repro_* test from develop branch and second one is from current branch.
|
I feel like we should go forward with this, if possible, for the release @aleksuss thoughts? |
Description
The PR adds some gas cost optimisations by decreasing the number of reads/saves calls using cache.
Performance / NEAR gas cost considerations
Gas costs should be decreased.
Testing
Units and integrational tests cover the changes.