Skip to content

Commit 42a8a07

Browse files
nerolationmarioevz
andauthored
feat(benchmark): add max block size test using access lists (#1932)
* test: add max block size test using access lists Add test_block_full_access_list_and_data to test maximum block size by splitting gas between access lists (60%) and calldata (40%). Uses 1 address with many storage keys to maximize block size. * Increase block size even further * integrate suggested changes * Update tests/benchmark/test_worst_blocks.py * Update tests/benchmark/test_worst_blocks.py --------- Co-authored-by: Mario Vega <[email protected]>
1 parent e31f319 commit 42a8a07

File tree

1 file changed

+103
-0
lines changed

1 file changed

+103
-0
lines changed

tests/benchmark/test_worst_blocks.py

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,19 @@
55
Tests running worst-case block scenarios for EVMs.
66
"""
77

8+
import random
9+
810
import pytest
911

1012
from ethereum_test_forks import Fork
1113
from ethereum_test_tools import (
14+
AccessList,
1215
Account,
1316
Address,
1417
Alloc,
1518
Block,
1619
BlockchainTestFiller,
20+
Hash,
1721
StateTestFiller,
1822
Transaction,
1923
)
@@ -164,6 +168,12 @@ def total_cost_floor_per_token():
164168
return 10
165169

166170

171+
@pytest.fixture
172+
def total_cost_standard_per_token():
173+
"""Total cost floor per token."""
174+
return 4
175+
176+
167177
@pytest.mark.valid_from("Prague")
168178
@pytest.mark.parametrize("zero_byte", [True, False])
169179
def test_block_full_data(
@@ -219,3 +229,96 @@ def test_block_full_data(
219229
post={},
220230
tx=tx,
221231
)
232+
233+
234+
@pytest.mark.valid_from("Prague")
235+
def test_block_full_access_list_and_data(
236+
state_test: StateTestFiller,
237+
pre: Alloc,
238+
intrinsic_cost: int,
239+
total_cost_standard_per_token: int,
240+
fork: Fork,
241+
gas_benchmark_value: int,
242+
):
243+
"""Test a block with access lists (60% gas) and calldata (40% gas) using random mixed bytes."""
244+
attack_gas_limit = gas_benchmark_value
245+
gas_available = attack_gas_limit - intrinsic_cost
246+
247+
# Split available gas: 60% for access lists, 40% for calldata
248+
gas_for_access_list = int(gas_available * 0.6)
249+
gas_for_calldata = int(gas_available * 0.4)
250+
251+
# Access list gas costs from fork's gas_costs
252+
gas_costs = fork.gas_costs()
253+
gas_per_address = gas_costs.G_ACCESS_LIST_ADDRESS
254+
gas_per_storage_key = gas_costs.G_ACCESS_LIST_STORAGE
255+
256+
# Calculate number of storage keys we can fit
257+
gas_after_address = gas_for_access_list - gas_per_address
258+
num_storage_keys = gas_after_address // gas_per_storage_key
259+
260+
# Create access list with 1 address and many storage keys
261+
access_address = Address("0x1234567890123456789012345678901234567890")
262+
storage_keys = []
263+
for i in range(num_storage_keys):
264+
# Generate random-looking storage keys
265+
storage_keys.append(Hash(i))
266+
267+
access_list = [
268+
AccessList(
269+
address=access_address,
270+
storage_keys=storage_keys,
271+
)
272+
]
273+
274+
# Calculate calldata with 29% of gas for zero bytes and 71% for non-zero bytes
275+
# Token accounting: tokens_in_calldata = zero_bytes + 4 * non_zero_bytes
276+
# We want to split the gas budget:
277+
# - 29% of gas_for_calldata for zero bytes
278+
# - 71% of gas_for_calldata for non-zero bytes
279+
280+
max_tokens_in_calldata = gas_for_calldata // total_cost_standard_per_token
281+
282+
# Calculate how many tokens to allocate to each type
283+
tokens_for_zero_bytes = int(max_tokens_in_calldata * 0.29)
284+
tokens_for_non_zero_bytes = max_tokens_in_calldata - tokens_for_zero_bytes
285+
286+
# Convert tokens to actual byte counts
287+
# Zero bytes: 1 token per byte
288+
# Non-zero bytes: 4 tokens per byte
289+
num_zero_bytes = tokens_for_zero_bytes # 1 token = 1 zero byte
290+
num_non_zero_bytes = tokens_for_non_zero_bytes // 4 # 4 tokens = 1 non-zero byte
291+
292+
# Create calldata with mixed bytes
293+
calldata = bytearray()
294+
295+
# Add zero bytes
296+
calldata.extend(b"\x00" * num_zero_bytes)
297+
298+
# Add non-zero bytes (random values from 0x01 to 0xff)
299+
rng = random.Random(42) # For reproducibility
300+
for _ in range(num_non_zero_bytes):
301+
calldata.append(rng.randint(1, 255))
302+
303+
# Shuffle the bytes to mix zero and non-zero bytes
304+
calldata_list = list(calldata)
305+
rng.shuffle(calldata_list)
306+
shuffled_calldata = bytes(calldata_list)
307+
308+
tx = Transaction(
309+
to=pre.fund_eoa(amount=0),
310+
data=shuffled_calldata,
311+
gas_limit=attack_gas_limit,
312+
sender=pre.fund_eoa(),
313+
access_list=access_list,
314+
)
315+
316+
state_test(
317+
pre=pre,
318+
post={},
319+
tx=tx,
320+
expected_benchmark_gas_used=fork.transaction_intrinsic_cost_calculator()(
321+
calldata=shuffled_calldata,
322+
access_list=access_list,
323+
),
324+
)

0 commit comments

Comments
 (0)