Skip to content
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

Open
wants to merge 6 commits into
base: develop
Choose a base branch
from

Conversation

aleksuss
Copy link
Member

@aleksuss aleksuss commented Jan 24, 2023

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.

engine-tests/src/tests/sanity.rs Outdated Show resolved Hide resolved
@aleksuss aleksuss marked this pull request as draft February 1, 2023 10:35
@joshuajbouw joshuajbouw force-pushed the chore/aleksuss/charge_gas_optim branch from e45e098 to 19f3a12 Compare February 1, 2023 15:10
@aleksuss aleksuss changed the base branch from feat/erc20-gas-token to develop February 6, 2023 15:39
@aleksuss aleksuss force-pushed the chore/aleksuss/charge_gas_optim branch from b30b5db to 3a22fb0 Compare February 6, 2023 15:40
@aleksuss aleksuss changed the title chore: some charge gas optimisations feat: some charge gas optimisations Feb 6, 2023
@aleksuss aleksuss marked this pull request as ready for review February 6, 2023 15:51
Copy link
Member

@birchmd birchmd left a 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).

.ok_or(GasPaymentError::OutOfFund)?;

set_balance(&mut self.io, sender, &new_balance);
if balance < prepaid_amount {
Copy link
Member

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 { ...

Copy link
Contributor

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 Show resolved Hide resolved
engine/src/engine.rs Show resolved Hide resolved
.ok_or(GasPaymentError::OutOfFund)?;

set_balance(&mut self.io, sender, &new_balance);
if balance < prepaid_amount {
Copy link
Contributor

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)
Copy link
Member

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 need mul it
  • new_balance is the same as balance, we don't need sub it
  • we don't need cache it, as the balance was not changed

Copy link
Member Author

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.

Copy link
Member Author

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.

Copy link
Member

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.

Copy link
Member

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?

Copy link
Member Author

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.

Copy link
Member

@mrLSD mrLSD Feb 7, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Not free. but 4 math instructions are cheaper and memory operation flow than 1 JUMP instruction?!!
  2. 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.

Copy link
Contributor

@joshuajbouw joshuajbouw left a 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

engine-tests/src/tests/sanity.rs Show resolved Hide resolved
engine-tests/src/tests/sanity.rs Show resolved Hide resolved
Copy link
Member

@birchmd birchmd left a 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?

@aleksuss
Copy link
Member Author

aleksuss commented Feb 8, 2023

I should mention before. Upcoming commit brings this changes.

@aleksuss aleksuss force-pushed the chore/aleksuss/charge_gas_optim branch from 76452c1 to 9c91d91 Compare February 8, 2023 12:29
@@ -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);
Copy link
Member Author

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?

Copy link
Member

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.

@aleksuss aleksuss force-pushed the chore/aleksuss/charge_gas_optim branch from 9c91d91 to 3b46826 Compare February 8, 2023 12:53
@aleksuss
Copy link
Member Author

aleksuss commented Feb 8, 2023

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?

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).

@birchmd
Copy link
Member

birchmd commented Feb 8, 2023

in the tests, we don't use the cache effectively

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.

@joshuajbouw
Copy link
Contributor

joshuajbouw commented Feb 9, 2023

in the tests, we don't use the cache effectively

Could you elaborate on this point more?

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.

@aleksuss
Copy link
Member Author

aleksuss commented Feb 9, 2023

in the tests, we don't use the cache effectively

Could you elaborate on this point more?

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.

@aleksuss
Copy link
Member Author

aleksuss commented Feb 9, 2023

Some comparisons. First value is a result from repro_* test from develop branch and second one is from current branch.

repro_GdASJ3KESs:
122721043002795 - 122596036542627 = 125006460168 
repro_FRcorNv:
181030562726220 - 180905374436976 = 125188289244
repro_D98vwmi:
182732573474634 - 182607493789182 = 125079685452 
repro_8ru7VEA:
223178543337084 - 223056899480688 = 121643856396
repro_Emufid2:
306428381815360 - 306038787160204 = 389594655156
repro_5bEgfRQ:
657955355677707 - 657565031237979 = 390324439728

@aleksuss aleksuss added the C-research Category: Research, may not end up as a feature. label Mar 21, 2023
@joshuajbouw
Copy link
Contributor

I feel like we should go forward with this, if possible, for the release @aleksuss thoughts?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
C-research Category: Research, may not end up as a feature.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants