-
Notifications
You must be signed in to change notification settings - Fork 415
feat(tests): add worst-case BAL read test #2033
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
base: forks/amsterdam
Are you sure you want to change the base?
feat(tests): add worst-case BAL read test #2033
Conversation
4bbf963 to
b5a522d
Compare
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## forks/amsterdam #2033 +/- ##
================================================
Coverage 86.07% 86.07%
================================================
Files 599 599
Lines 39527 39527
Branches 3780 3780
================================================
Hits 34021 34021
Misses 4872 4872
Partials 634 634
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
jochem-brouwer
left a comment
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 did not review the tests in-depth, but in general: the current setup which uses calldata does not make BALs a requirement in order to execute the transactions in parallel (without BALs can run them in parallel). I suggest wasting some gas to store these pointers in the EVM. It saves gas calculation and we also directly get the transaction dependencies on the previous transactions (to make the BAL a requirement in order to execute them in parallel).
These tests also follow mostly the same format, I would also suggest to create a helper method or some kind of template to run these tests. The difference seems to be the attack code and the storage setup, but the files mostly (?) look the same.
More general question, should these tests be added in a BAL folder under benchmarks? I think these are not specifically consensus tests, they are rather targeted to perform DoS-like operations. @LouisTsai-Csie what do you think?
| """ | ||
| Create bytecode that first computes, then SLOADs. | ||
|
|
||
| Calldata: computation_iterations (32) + start_slot (32) + sload_count (32) |
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.
Putting these pointers in the calldata means in this test that the transactions can be executed in parallel in the block without the need of BALs. To solve this (to force transactions being executed sequentially without BALs or to make parallel execution available if a BAL is provided) we need to make the transactions depend on the previous transaction.
The simplest way to do this is to just use the EVM storage to store the current start slot in (initialize this slot to 0xffff..ff so we start at the highest slot). For the SLOAD loop, use a While loop and the condition is a simple check to see if gasleft is enough to perform one more loop and the termination logic (which stores the current slot into the storage).
Can also be added to the computation loop. We can hardcode the "gasleft" there which is the threshold to continue the loop. For instance if the compute loop is 50%, then we just take 50% of the tx gas limit and use that as GASLEFT > x as x value for the condition.
Note that if we use the gas threshold in the EVM we do not have to manually calculate gas per loop. We also ensure that transactions depend on each other, since the second transaction has to start at the slot where the first one ended (cannot be executed in parallel without BALs). The EVM loss in cycles is nonzero, but it is negligible compared to the total SLOAD count.
| assert max_tx_gas is not None | ||
|
|
||
| num_transactions = block_gas_limit // max_tx_gas | ||
| intrinsic_overhead = gas_costs.G_TRANSACTION + 600 |
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.
600 magic number? Is this enough to cover the calldata?
I think we don't have to calculate this, we can construct the transaction and then calculate the intrinsic fee from there.
| storage = Storage({i: i + 1 for i in range(total_slots)}) # type: ignore | ||
|
|
||
| contract_code = create_compute_then_sload_contract() | ||
| contract = pre.deploy_contract(code=contract_code, storage=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.
I wonder if this works if we deploy a contract with a rather large storage (which cannot be deployed in one transaction)
| total_slots, | ||
| ) = calculate_test_parameters(block_gas_limit, fork, compute_fraction) | ||
|
|
||
| storage = Storage({i: i + 1 for i in range(total_slots)}) # type: ignore |
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.
Doesn't this setup the storage up in a way 0 -> 1 -> 2? We want to read backwards now right?
|
Oh, hmm, I just realized: The max amount of cold SLOADs is 2 ** 24 / 2100 = 7989 per transaction. If we create a storage where the value of the storage points to the next key (I think this idea is already applied here?) then we do not need a loop because we can fit 8k bytes into the 24kb limit. (Code for the sload block is just Note that for different compute fractions this would thus influence the length of this SLOAD byte sequence. (Even if slots are added to access list for the discount 2 ** 24 / 1900 = 8830 would still fit in the 24 kb) |
🗒️ Description
Add EIP-7928 BAL test with maximum SLOADs in reverse lexicographic order.
✅ Checklist
uvx tox -e staticCute Animal Picture