-
Notifications
You must be signed in to change notification settings - Fork 171
feat(tests): add first few single-opcode test for state access in BloatNet #2040
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: main
Are you sure you want to change the base?
Changes from 23 commits
a1f2153
02d65b4
e721cc6
d1cad25
16f6d30
374e08a
333c876
79a95b8
8131e98
5f805fd
090a400
cd02a02
1f3c381
f6def7e
7e20a50
c24ad35
fc27e53
7d87262
8556014
326915e
bc7ee84
4164f3c
6d12da6
b3aa527
add1a36
134dc8c
20c47b7
f668f86
edfc381
65d500f
6df6895
f3f7a0d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,165 @@ | ||
""" | ||
abstract: Tests that benchmarks EVMs to estimate the costs of stateful opcodes. | ||
Tests that benchmarks EVMs to estimate the costs of stateful opcodes.. | ||
""" | ||
|
||
import pytest | ||
|
||
from ethereum_test_base_types import HashInt | ||
from ethereum_test_forks import Fork | ||
from ethereum_test_tools import ( | ||
Account, | ||
Alloc, | ||
Block, | ||
BlockchainTestFiller, | ||
Environment, | ||
Storage, | ||
Transaction, | ||
) | ||
from ethereum_test_tools.vm.opcode import Opcodes as Op | ||
|
||
|
||
@pytest.mark.valid_from("Prague") | ||
@pytest.mark.parametrize("final_storage_value", [0x02 << 248, 0x02]) | ||
def test_bloatnet( | ||
blockchain_test: BlockchainTestFiller, pre: Alloc, fork: Fork, final_storage_value: int | ||
fselmo marked this conversation as resolved.
Show resolved
Hide resolved
|
||
): | ||
""" | ||
A test that calls a contract with many SSTOREs. | ||
|
||
The first block will have many SSTORES that go from 0 -> 1 | ||
fselmo marked this conversation as resolved.
Show resolved
Hide resolved
fselmo marked this conversation as resolved.
Show resolved
Hide resolved
|
||
and the 2nd block will have many SSTORES that go from 1 -> 2 | ||
""" | ||
# Get gas costs for the current fork | ||
gas_costs = fork.gas_costs() | ||
|
||
# this is only used for computing the intinsic gas | ||
data = final_storage_value.to_bytes(32, "big").rstrip(b"\x00") | ||
|
||
storage = Storage() | ||
|
||
# Initial gas for PUSH0 + CALLDATALOAD + POP (at the end) | ||
totalgas = gas_costs.G_BASE * 2 + gas_costs.G_VERY_LOW | ||
totalgas = totalgas + fork.transaction_intrinsic_cost_calculator()(calldata=data) | ||
gas_increment = gas_costs.G_VERY_LOW * 2 + gas_costs.G_STORAGE_SET + gas_costs.G_COLD_SLOAD | ||
sstore_code = Op.PUSH0 + Op.CALLDATALOAD | ||
storage_slot: int = 0 | ||
while totalgas + gas_increment < Environment().gas_limit: | ||
fselmo marked this conversation as resolved.
Show resolved
Hide resolved
|
||
totalgas += gas_increment | ||
sstore_code = sstore_code + Op.SSTORE(storage_slot, Op.DUP1) | ||
storage[storage_slot] = final_storage_value | ||
storage_slot += 1 | ||
|
||
sstore_code = sstore_code + Op.POP # Drop last value on the stack | ||
|
||
sender = pre.fund_eoa() | ||
print(sender) | ||
fselmo marked this conversation as resolved.
Show resolved
Hide resolved
|
||
contract_address = pre.deploy_contract( | ||
code=sstore_code, | ||
storage=Storage(), | ||
gballet marked this conversation as resolved.
Show resolved
Hide resolved
|
||
) | ||
|
||
tx_0_1 = Transaction( | ||
to=contract_address, | ||
gas_limit=Environment().gas_limit, | ||
data=(final_storage_value // 2).to_bytes(32, "big").rstrip(b"\x00"), | ||
value=0, | ||
sender=sender, | ||
) | ||
tx_1_2 = Transaction( | ||
to=contract_address, | ||
gas_limit=Environment().gas_limit, | ||
data=final_storage_value.to_bytes(32, "big").rstrip(b"\x00"), | ||
value=0, | ||
sender=sender, | ||
) | ||
|
||
post = {contract_address: Account(storage=storage)} | ||
|
||
blockchain_test(pre=pre, blocks=[Block(txs=[tx_0_1]), Block(txs=[tx_1_2])], post=post) | ||
|
||
|
||
# Warm reads are very cheap, which means you can really fill a block | ||
# with them. Only fill the block by a factor of SPEEDUP. | ||
SPEEDUP: int = 100 | ||
|
||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm yeah, good observation. The biggest question I suppose is whether those worst case scenarios are the same as the ones in the bloatnet doc here? If they are, maybe creating a marker on existing relevant tests, so we don't have to redefine them, would be a good approach. Then, instead of trying to run only the tests in this file, we could use a marker like Just a thought. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's not conflate two different things together, the objectives are clearly different:
Adding an extra coupling here is causing more work for no benefit, since these tests are maintained by different sets of people. Regarding the worst case, the tests are doing different things since the code itself is different. The goal of the bloatnet test it to measure the sole performance of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @gballet, the tests in The test I linked are executing blocks where the full gas limit is used to do cold or warm reads, or to write slots to existent or non-existent storage slots. There's nothing specific to zkvms there, thus why I ask how these tests are different -- mainly to avoid duplication or explain better what different variant is trying to be benchmarked. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This needs to be addressed before merging! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I consider it has already been addressed by my first comment: it's not the same bytecode, not the same objectives (worst case vs perf), and not the same constraints. Ignacio's tests might not be specific to zkvms, but we do need something specific for us. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the answer here is I think it would be a good idea to apply a pytest marker for @LouisTsai-Csie, do you agree here? I know we discussed some of this but I just want a sanity check. And if you do agree, do you think you can help implement this here? I think this will be important for us to establish some organization in the codebase before merging this and then having tests leak to different teams that don't care about each other's test cases. I know also that @marioevz expressed a very similar direction for this but he is not available the next few weeks. I think this will help us get all of this in, in an organized fashion that doesn't change how the benchmark teams use their marker and would also help better categorize |
||
@pytest.mark.valid_from("Prague") | ||
def test_bloatnet_sload_warm(blockchain_test: BlockchainTestFiller, pre: Alloc, fork: Fork): | ||
"""Test that loads warm storage locations many times.""" | ||
gas_costs = fork.gas_costs() | ||
|
||
# Pre-fill storage with values | ||
num_slots = 100 # Number of storage slots to warm up | ||
storage = Storage({HashInt(i): HashInt(0xDEADBEEF + i) for i in range(num_slots)}) | ||
|
||
# Calculate gas costs | ||
totalgas = fork.transaction_intrinsic_cost_calculator()(calldata=b"") | ||
|
||
# First pass - warm up all slots (cold access) | ||
warmup_gas = num_slots * (gas_costs.G_COLD_SLOAD + gas_costs.G_BASE) | ||
totalgas += warmup_gas | ||
|
||
# Calculate how many warm loads we can fit | ||
gas_increment = gas_costs.G_WARM_SLOAD + gas_costs.G_BASE # Warm SLOAD + POP | ||
remaining_gas = Environment().gas_limit - totalgas | ||
num_warm_loads = remaining_gas // (SPEEDUP * gas_increment) | ||
|
||
# Build the complete code: warmup + repeated warm loads | ||
sload_code = Op.SLOAD(0) + Op.POP if num_slots > 0 else Op.STOP | ||
for i in range(1, num_slots): | ||
sload_code = sload_code + Op.SLOAD(i) + Op.POP | ||
for i in range(num_warm_loads): | ||
sload_code = sload_code + Op.SLOAD(i % num_slots) + Op.POP | ||
|
||
sender = pre.fund_eoa() | ||
contract_address = pre.deploy_contract( | ||
code=sload_code, | ||
storage=storage, | ||
) | ||
|
||
tx = Transaction( | ||
to=contract_address, | ||
gas_limit=Environment().gas_limit, | ||
data=b"", | ||
value=0, | ||
sender=sender, | ||
) | ||
|
||
post = {contract_address: Account(storage=storage)} | ||
blockchain_test(pre=pre, blocks=[Block(txs=[tx])], post=post) | ||
|
||
|
||
@pytest.mark.valid_from("Prague") | ||
def test_bloatnet_sload_cold(blockchain_test: BlockchainTestFiller, pre: Alloc, fork: Fork): | ||
"""Test that loads many different cold storage locations.""" | ||
gas_costs = fork.gas_costs() | ||
|
||
# Calculate gas costs and max slots | ||
totalgas = fork.transaction_intrinsic_cost_calculator()(calldata=b"") | ||
# PUSH + Cold SLOAD + POP | ||
gas_increment = gas_costs.G_VERY_LOW + gas_costs.G_COLD_SLOAD + gas_costs.G_BASE | ||
max_slots = (Environment().gas_limit - totalgas) // gas_increment | ||
|
||
# Build storage and code for all slots | ||
storage = Storage({HashInt(i): HashInt(0xC0FFEE + i) for i in range(max_slots)}) | ||
sload_code = Op.SLOAD(0) + Op.POP if max_slots > 0 else Op.STOP | ||
for i in range(1, max_slots): | ||
sload_code = sload_code + Op.SLOAD(i) + Op.POP | ||
|
||
sender = pre.fund_eoa() | ||
contract_address = pre.deploy_contract( | ||
code=sload_code, | ||
storage=storage, | ||
) | ||
LouisTsai-Csie marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
tx = Transaction( | ||
to=contract_address, | ||
gas_limit=Environment().gas_limit, | ||
data=b"", | ||
value=0, | ||
sender=sender, | ||
) | ||
|
||
post = {contract_address: Account(storage=storage)} | ||
blockchain_test(pre=pre, blocks=[Block(txs=[tx])], post=post) |
Uh oh!
There was an error while loading. Please reload this page.