Skip to content

Commit c2b8dd7

Browse files
refactor: remove blockchain filler in worst block cases
1 parent f85f304 commit c2b8dd7

File tree

1 file changed

+145
-114
lines changed

1 file changed

+145
-114
lines changed

tests/benchmark/test_worst_blocks.py

Lines changed: 145 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
Tests that benchmark EVMs in worst-case block scenarios.
33
"""
44

5+
import math
56
import random
67

78
import pytest
@@ -12,11 +13,10 @@
1213
AccessList,
1314
Address,
1415
Alloc,
16+
BenchmarkTestFiller,
1517
Block,
16-
BlockchainTestFiller,
1718
Environment,
1819
Hash,
19-
StateTestFiller,
2020
Transaction,
2121
)
2222

@@ -111,7 +111,7 @@ def ether_transfer_case(
111111
["a_to_a", "a_to_b", "diff_acc_to_b", "a_to_diff_acc", "diff_acc_to_diff_acc"],
112112
)
113113
def test_block_full_of_ether_transfers(
114-
blockchain_test: BlockchainTestFiller,
114+
benchmark_test: BenchmarkTestFiller,
115115
pre: Alloc,
116116
env: Environment,
117117
case_id: str,
@@ -155,8 +155,7 @@ def test_block_full_of_ether_transfers(
155155
else {receiver: Account(balance=balance) for receiver, balance in balances.items()}
156156
)
157157

158-
blockchain_test(
159-
genesis_environment=env,
158+
benchmark_test(
160159
pre=pre,
161160
post=post_state,
162161
blocks=[Block(txs=txs)],
@@ -176,18 +175,14 @@ def total_cost_standard_per_token():
176175
return 4
177176

178177

179-
@pytest.mark.parametrize("zero_byte", [True, False])
180-
def test_block_full_data(
181-
state_test: StateTestFiller,
182-
pre: Alloc,
183-
zero_byte: bool,
184-
intrinsic_cost: int,
178+
def calldata_generator(
179+
gas_amount: int,
180+
zero_byte: int,
185181
total_cost_floor_per_token: int,
186-
gas_benchmark_value: int,
182+
total_cost_standard_per_token: int,
187183
):
188-
"""Test a block with empty payload."""
189-
# Gas cost calculation based on EIP-7683:
190-
# (https://eips.ethereum.org/EIPS/eip-7683)
184+
"""Calculate the calldata based on the gas amount and zero byte."""
185+
# Gas cost calculation based on EIP-7683: (https://eips.ethereum.org/EIPS/eip-7683)
191186
#
192187
# tx.gasUsed = 21000 + max(
193188
# STANDARD_TOKEN_COST * tokens_in_calldata
@@ -210,123 +205,159 @@ def test_block_full_data(
210205
# Token accounting:
211206
# tokens_in_calldata = zero_bytes + 4 * non_zero_bytes
212207
#
213-
# So we calculate how many bytes we can fit into calldata based on
214-
# available gas.
215-
216-
gas_available = gas_benchmark_value - intrinsic_cost
217-
218-
# Calculate the token_in_calldata
219-
max_tokens_in_calldata = gas_available // total_cost_floor_per_token
220-
# Calculate the number of bytes that can be stored in the calldata
208+
# So we calculate how many bytes we can fit into calldata based on available gas.
209+
max_tokens_in_calldata = gas_amount // total_cost_floor_per_token
221210
num_of_bytes = max_tokens_in_calldata if zero_byte else max_tokens_in_calldata // 4
222211
byte_data = b"\x00" if zero_byte else b"\xff"
212+
return byte_data * num_of_bytes
223213

224-
tx = Transaction(
225-
to=pre.fund_eoa(),
226-
data=byte_data * num_of_bytes,
227-
gas_limit=gas_benchmark_value,
228-
sender=pre.fund_eoa(),
229-
)
230214

231-
state_test(
215+
@pytest.mark.parametrize("zero_byte", [True, False])
216+
def test_block_full_data(
217+
benchmark_test: BenchmarkTestFiller,
218+
pre: Alloc,
219+
zero_byte: bool,
220+
intrinsic_cost: int,
221+
total_cost_floor_per_token: int,
222+
gas_benchmark_value: int,
223+
tx_gas_limit_cap: int,
224+
total_cost_standard_per_token: int,
225+
fork: Fork,
226+
):
227+
"""Test a block with empty payload."""
228+
iteration_count = math.ceil(gas_benchmark_value / tx_gas_limit_cap)
229+
230+
gas_remaining = gas_benchmark_value
231+
total_gas_used = 0
232+
txs = []
233+
for _ in range(iteration_count):
234+
gas_available = min(tx_gas_limit_cap, gas_remaining) - intrinsic_cost
235+
data = calldata_generator(
236+
gas_available,
237+
zero_byte,
238+
total_cost_floor_per_token,
239+
total_cost_standard_per_token,
240+
)
241+
242+
total_gas_used += fork.transaction_intrinsic_cost_calculator()(calldata=data)
243+
gas_remaining -= gas_available + intrinsic_cost
244+
245+
txs.append(
246+
Transaction(
247+
to=pre.fund_eoa(),
248+
data=data,
249+
gas_limit=gas_available + intrinsic_cost,
250+
sender=pre.fund_eoa(),
251+
)
252+
)
253+
254+
benchmark_test(
232255
pre=pre,
233256
post={},
234-
tx=tx,
257+
blocks=[Block(txs=txs)],
258+
expected_benchmark_gas_used=total_gas_used,
235259
)
236260

237261

238262
def test_block_full_access_list_and_data(
239-
state_test: StateTestFiller,
263+
benchmark_test: BenchmarkTestFiller,
240264
pre: Alloc,
241265
intrinsic_cost: int,
242266
total_cost_standard_per_token: int,
243267
fork: Fork,
244268
gas_benchmark_value: int,
269+
tx_gas_limit_cap: int,
245270
):
246-
"""
247-
Test a block with access lists (60% gas) and calldata (40% gas) using
248-
random mixed bytes.
249-
"""
250-
attack_gas_limit = gas_benchmark_value
251-
gas_available = attack_gas_limit - intrinsic_cost
252-
253-
# Split available gas: 60% for access lists, 40% for calldata
254-
gas_for_access_list = int(gas_available * 0.6)
255-
gas_for_calldata = int(gas_available * 0.4)
256-
257-
# Access list gas costs from fork's gas_costs
258-
gas_costs = fork.gas_costs()
259-
gas_per_address = gas_costs.G_ACCESS_LIST_ADDRESS
260-
gas_per_storage_key = gas_costs.G_ACCESS_LIST_STORAGE
261-
262-
# Calculate number of storage keys we can fit
263-
gas_after_address = gas_for_access_list - gas_per_address
264-
num_storage_keys = gas_after_address // gas_per_storage_key
265-
266-
# Create access list with 1 address and many storage keys
267-
access_address = Address("0x1234567890123456789012345678901234567890")
268-
storage_keys = []
269-
for i in range(num_storage_keys):
270-
# Generate random-looking storage keys
271-
storage_keys.append(Hash(i))
272-
273-
access_list = [
274-
AccessList(
275-
address=access_address,
276-
storage_keys=storage_keys,
271+
"""Test a block with access lists (60% gas) and calldata (40% gas) using random mixed bytes."""
272+
iteration_count = math.ceil(gas_benchmark_value / tx_gas_limit_cap)
273+
274+
gas_remaining = gas_benchmark_value
275+
total_gas_used = 0
276+
277+
txs = []
278+
for _ in range(iteration_count):
279+
gas_available = min(tx_gas_limit_cap, gas_remaining) - intrinsic_cost
280+
281+
# Split available gas: 60% for access lists, 40% for calldata
282+
gas_for_access_list = int(gas_available * 0.6)
283+
gas_for_calldata = int(gas_available * 0.4)
284+
285+
# Access list gas costs from fork's gas_costs
286+
gas_costs = fork.gas_costs()
287+
gas_per_address = gas_costs.G_ACCESS_LIST_ADDRESS
288+
gas_per_storage_key = gas_costs.G_ACCESS_LIST_STORAGE
289+
290+
# Calculate number of storage keys we can fit
291+
gas_after_address = gas_for_access_list - gas_per_address
292+
num_storage_keys = gas_after_address // gas_per_storage_key
293+
294+
# Create access list with 1 address and many storage keys
295+
access_address = Address("0x1234567890123456789012345678901234567890")
296+
storage_keys = []
297+
for i in range(num_storage_keys):
298+
# Generate random-looking storage keys
299+
storage_keys.append(Hash(i))
300+
301+
access_list = [
302+
AccessList(
303+
address=access_address,
304+
storage_keys=storage_keys,
305+
)
306+
]
307+
308+
# Calculate calldata with 29% of gas for zero bytes and 71% for non-zero bytes
309+
# Token accounting: tokens_in_calldata = zero_bytes + 4 * non_zero_bytes
310+
# We want to split the gas budget:
311+
# - 29% of gas_for_calldata for zero bytes
312+
# - 71% of gas_for_calldata for non-zero bytes
313+
314+
max_tokens_in_calldata = gas_for_calldata // total_cost_standard_per_token
315+
316+
# Calculate how many tokens to allocate to each type
317+
tokens_for_zero_bytes = int(max_tokens_in_calldata * 0.29)
318+
tokens_for_non_zero_bytes = max_tokens_in_calldata - tokens_for_zero_bytes
319+
320+
# Convert tokens to actual byte counts
321+
# Zero bytes: 1 token per byte
322+
# Non-zero bytes: 4 tokens per byte
323+
num_zero_bytes = tokens_for_zero_bytes # 1 token = 1 zero byte
324+
num_non_zero_bytes = tokens_for_non_zero_bytes // 4 # 4 tokens = 1 non-zero byte
325+
326+
# Create calldata with mixed bytes
327+
calldata = bytearray()
328+
329+
# Add zero bytes
330+
calldata.extend(b"\x00" * num_zero_bytes)
331+
332+
# Add non-zero bytes (random values from 0x01 to 0xff)
333+
rng = random.Random(42) # For reproducibility
334+
for _ in range(num_non_zero_bytes):
335+
calldata.append(rng.randint(1, 255))
336+
337+
# Shuffle the bytes to mix zero and non-zero bytes
338+
calldata_list = list(calldata)
339+
rng.shuffle(calldata_list)
340+
shuffled_calldata = bytes(calldata_list)
341+
342+
txs.append(
343+
Transaction(
344+
to=pre.fund_eoa(amount=0),
345+
data=shuffled_calldata,
346+
gas_limit=gas_available + intrinsic_cost,
347+
sender=pre.fund_eoa(),
348+
access_list=access_list,
349+
)
277350
)
278-
]
279-
280-
# Calculate calldata with 29% of gas for zero bytes and 71% for non-zero
281-
# bytes
282-
# Token accounting: tokens_in_calldata = zero_bytes + 4 * non_zero_bytes
283-
# We want to split the gas budget:
284-
# - 29% of gas_for_calldata for zero bytes
285-
# - 71% of gas_for_calldata for non-zero bytes
286-
287-
max_tokens_in_calldata = gas_for_calldata // total_cost_standard_per_token
288-
289-
# Calculate how many tokens to allocate to each type
290-
tokens_for_zero_bytes = int(max_tokens_in_calldata * 0.29)
291-
tokens_for_non_zero_bytes = max_tokens_in_calldata - tokens_for_zero_bytes
292-
293-
# Convert tokens to actual byte counts
294-
# Zero bytes: 1 token per byte
295-
# Non-zero bytes: 4 tokens per byte
296-
num_zero_bytes = tokens_for_zero_bytes # 1 token = 1 zero byte
297-
# 4 tokens = 1 non-zero byte
298-
num_non_zero_bytes = tokens_for_non_zero_bytes // 4
299-
300-
# Create calldata with mixed bytes
301-
calldata = bytearray()
302-
303-
# Add zero bytes
304-
calldata.extend(b"\x00" * num_zero_bytes)
305-
306-
# Add non-zero bytes (random values from 0x01 to 0xff)
307-
rng = random.Random(42) # For reproducibility
308-
for _ in range(num_non_zero_bytes):
309-
calldata.append(rng.randint(1, 255))
310-
311-
# Shuffle the bytes to mix zero and non-zero bytes
312-
calldata_list = list(calldata)
313-
rng.shuffle(calldata_list)
314-
shuffled_calldata = bytes(calldata_list)
315-
316-
tx = Transaction(
317-
to=pre.fund_eoa(amount=0),
318-
data=shuffled_calldata,
319-
gas_limit=attack_gas_limit,
320-
sender=pre.fund_eoa(),
321-
access_list=access_list,
322-
)
323351

324-
state_test(
325-
pre=pre,
326-
post={},
327-
tx=tx,
328-
expected_benchmark_gas_used=fork.transaction_intrinsic_cost_calculator()(
352+
gas_remaining -= gas_for_access_list + intrinsic_cost
353+
total_gas_used += fork.transaction_intrinsic_cost_calculator()(
329354
calldata=shuffled_calldata,
330355
access_list=access_list,
331-
),
356+
)
357+
358+
benchmark_test(
359+
pre=pre,
360+
post={},
361+
blocks=[Block(txs=txs)],
362+
expected_benchmark_gas_used=total_gas_used,
332363
)

0 commit comments

Comments
 (0)