Skip to content

Commit cbcac72

Browse files
refactor: remove blockchain filler in worst block cases
1 parent 4664c31 commit cbcac72

File tree

1 file changed

+137
-105
lines changed

1 file changed

+137
-105
lines changed

tests/benchmark/test_worst_blocks.py

Lines changed: 137 additions & 105 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
from typing import Generator, Tuple
78

@@ -14,11 +15,11 @@
1415
Address,
1516
Alloc,
1617
AuthorizationTuple,
18+
BenchmarkTestFiller,
1719
Block,
1820
BlockchainTestFiller,
1921
Environment,
2022
Hash,
21-
StateTestFiller,
2223
Transaction,
2324
)
2425
from ethereum_test_vm import Opcodes as Op
@@ -114,7 +115,7 @@ def ether_transfer_case(
114115
["a_to_a", "a_to_b", "diff_acc_to_b", "a_to_diff_acc", "diff_acc_to_diff_acc"],
115116
)
116117
def test_block_full_of_ether_transfers(
117-
blockchain_test: BlockchainTestFiller,
118+
benchmark_test: BenchmarkTestFiller,
118119
pre: Alloc,
119120
env: Environment,
120121
case_id: str,
@@ -158,8 +159,7 @@ def test_block_full_of_ether_transfers(
158159
else {receiver: Account(balance=balance) for receiver, balance in balances.items()}
159160
)
160161

161-
blockchain_test(
162-
genesis_environment=env,
162+
benchmark_test(
163163
pre=pre,
164164
post=post_state,
165165
blocks=[Block(txs=txs)],
@@ -179,18 +179,14 @@ def total_cost_standard_per_token() -> int:
179179
return 4
180180

181181

182-
@pytest.mark.parametrize("zero_byte", [True, False])
183-
def test_block_full_data(
184-
state_test: StateTestFiller,
185-
pre: Alloc,
186-
zero_byte: bool,
187-
intrinsic_cost: int,
182+
def calldata_generator(
183+
gas_amount: int,
184+
zero_byte: int,
188185
total_cost_floor_per_token: int,
189-
gas_benchmark_value: int,
190-
) -> None:
191-
"""Test a block with empty payload."""
192-
# Gas cost calculation based on EIP-7683:
193-
# (https://eips.ethereum.org/EIPS/eip-7683)
186+
total_cost_standard_per_token: int,
187+
):
188+
"""Calculate the calldata based on the gas amount and zero byte."""
189+
# Gas cost calculation based on EIP-7683: (https://eips.ethereum.org/EIPS/eip-7683)
194190
#
195191
# tx.gasUsed = 21000 + max(
196192
# STANDARD_TOKEN_COST * tokens_in_calldata
@@ -213,125 +209,161 @@ def test_block_full_data(
213209
# Token accounting:
214210
# tokens_in_calldata = zero_bytes + 4 * non_zero_bytes
215211
#
216-
# So we calculate how many bytes we can fit into calldata based on
217-
# available gas.
218-
219-
gas_available = gas_benchmark_value - intrinsic_cost
220-
221-
# Calculate the token_in_calldata
222-
max_tokens_in_calldata = gas_available // total_cost_floor_per_token
223-
# Calculate the number of bytes that can be stored in the calldata
212+
# So we calculate how many bytes we can fit into calldata based on available gas.
213+
max_tokens_in_calldata = gas_amount // total_cost_floor_per_token
224214
num_of_bytes = max_tokens_in_calldata if zero_byte else max_tokens_in_calldata // 4
225215
byte_data = b"\x00" if zero_byte else b"\xff"
216+
return byte_data * num_of_bytes
226217

227-
tx = Transaction(
228-
to=pre.fund_eoa(),
229-
data=byte_data * num_of_bytes,
230-
gas_limit=gas_benchmark_value,
231-
sender=pre.fund_eoa(),
232-
)
233218

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

240265

241266
def test_block_full_access_list_and_data(
242-
state_test: StateTestFiller,
267+
benchmark_test: BenchmarkTestFiller,
243268
pre: Alloc,
244269
intrinsic_cost: int,
245270
total_cost_standard_per_token: int,
246271
fork: Fork,
247272
gas_benchmark_value: int,
248-
) -> None:
249-
"""
250-
Test a block with access lists (60% gas) and calldata (40% gas) using
251-
random mixed bytes.
252-
"""
253-
attack_gas_limit = gas_benchmark_value
254-
gas_available = attack_gas_limit - intrinsic_cost
273+
tx_gas_limit_cap: int,
274+
):
275+
"""Test a block with access lists (60% gas) and calldata (40% gas) using random mixed bytes."""
276+
iteration_count = math.ceil(gas_benchmark_value / tx_gas_limit_cap)
255277

256-
# Split available gas: 60% for access lists, 40% for calldata
257-
gas_for_access_list = int(gas_available * 0.6)
258-
gas_for_calldata = int(gas_available * 0.4)
278+
gas_remaining = gas_benchmark_value
279+
total_gas_used = 0
259280

260-
# Access list gas costs from fork's gas_costs
261-
gas_costs = fork.gas_costs()
262-
gas_per_address = gas_costs.G_ACCESS_LIST_ADDRESS
263-
gas_per_storage_key = gas_costs.G_ACCESS_LIST_STORAGE
264-
265-
# Calculate number of storage keys we can fit
266-
gas_after_address = gas_for_access_list - gas_per_address
267-
num_storage_keys = gas_after_address // gas_per_storage_key
268-
269-
# Create access list with 1 address and many storage keys
270-
access_address = Address("0x1234567890123456789012345678901234567890")
271-
storage_keys = []
272-
for i in range(num_storage_keys):
273-
# Generate random-looking storage keys
274-
storage_keys.append(Hash(i))
275-
276-
access_list = [
277-
AccessList(
278-
address=access_address,
279-
storage_keys=storage_keys,
280-
)
281-
]
281+
txs = []
282+
for _ in range(iteration_count):
283+
gas_available = min(tx_gas_limit_cap, gas_remaining) - intrinsic_cost
284+
285+
# Split available gas: 60% for access lists, 40% for calldata
286+
gas_for_access_list = int(gas_available * 0.6)
287+
gas_for_calldata = int(gas_available * 0.4)
288+
289+
# Access list gas costs from fork's gas_costs
290+
gas_costs = fork.gas_costs()
291+
gas_per_address = gas_costs.G_ACCESS_LIST_ADDRESS
292+
gas_per_storage_key = gas_costs.G_ACCESS_LIST_STORAGE
293+
294+
# Calculate number of storage keys we can fit
295+
gas_after_address = gas_for_access_list - gas_per_address
296+
num_storage_keys = gas_after_address // gas_per_storage_key
297+
298+
# Create access list with 1 address and many storage keys
299+
access_address = Address("0x1234567890123456789012345678901234567890")
300+
storage_keys = []
301+
for i in range(num_storage_keys):
302+
# Generate random-looking storage keys
303+
storage_keys.append(Hash(i))
304+
305+
access_list = [
306+
AccessList(
307+
address=access_address,
308+
storage_keys=storage_keys,
309+
)
310+
]
282311

283-
# Calculate calldata with 29% of gas for zero bytes and 71% for non-zero
284-
# bytes
285-
# Token accounting: tokens_in_calldata = zero_bytes + 4 * non_zero_bytes
286-
# We want to split the gas budget:
287-
# - 29% of gas_for_calldata for zero bytes
288-
# - 71% of gas_for_calldata for non-zero bytes
312+
# Calculate calldata with 29% of gas for zero bytes and 71% for non-zero bytes
313+
# Token accounting: tokens_in_calldata = zero_bytes + 4 * non_zero_bytes
314+
# We want to split the gas budget:
315+
# - 29% of gas_for_calldata for zero bytes
316+
# - 71% of gas_for_calldata for non-zero bytes
289317

290-
max_tokens_in_calldata = gas_for_calldata // total_cost_standard_per_token
318+
max_tokens_in_calldata = gas_for_calldata // total_cost_standard_per_token
291319

292-
# Calculate how many tokens to allocate to each type
293-
tokens_for_zero_bytes = int(max_tokens_in_calldata * 0.29)
294-
tokens_for_non_zero_bytes = max_tokens_in_calldata - tokens_for_zero_bytes
320+
# Calculate how many tokens to allocate to each type
321+
tokens_for_zero_bytes = int(max_tokens_in_calldata * 0.29)
322+
tokens_for_non_zero_bytes = max_tokens_in_calldata - tokens_for_zero_bytes
295323

296-
# Convert tokens to actual byte counts
297-
# Zero bytes: 1 token per byte
298-
# Non-zero bytes: 4 tokens per byte
299-
num_zero_bytes = tokens_for_zero_bytes # 1 token = 1 zero byte
300-
# 4 tokens = 1 non-zero byte
301-
num_non_zero_bytes = tokens_for_non_zero_bytes // 4
324+
# Convert tokens to actual byte counts
325+
# Zero bytes: 1 token per byte
326+
# Non-zero bytes: 4 tokens per byte
327+
num_zero_bytes = tokens_for_zero_bytes # 1 token = 1 zero byte
328+
num_non_zero_bytes = tokens_for_non_zero_bytes // 4 # 4 tokens = 1 non-zero byte
302329

303-
# Create calldata with mixed bytes
304-
calldata = bytearray()
330+
# Create calldata with mixed bytes
331+
calldata = bytearray()
305332

306-
# Add zero bytes
307-
calldata.extend(b"\x00" * num_zero_bytes)
333+
# Add zero bytes
334+
calldata.extend(b"\x00" * num_zero_bytes)
308335

309-
# Add non-zero bytes (random values from 0x01 to 0xff)
310-
rng = random.Random(42) # For reproducibility
311-
for _ in range(num_non_zero_bytes):
312-
calldata.append(rng.randint(1, 255))
336+
# Add non-zero bytes (random values from 0x01 to 0xff)
337+
rng = random.Random(42) # For reproducibility
338+
for _ in range(num_non_zero_bytes):
339+
calldata.append(rng.randint(1, 255))
313340

314-
# Shuffle the bytes to mix zero and non-zero bytes
315-
calldata_list = list(calldata)
316-
rng.shuffle(calldata_list)
317-
shuffled_calldata = bytes(calldata_list)
341+
# Shuffle the bytes to mix zero and non-zero bytes
342+
calldata_list = list(calldata)
343+
rng.shuffle(calldata_list)
344+
shuffled_calldata = bytes(calldata_list)
318345

319-
tx = Transaction(
320-
to=pre.fund_eoa(amount=0),
321-
data=shuffled_calldata,
322-
gas_limit=attack_gas_limit,
323-
sender=pre.fund_eoa(),
324-
access_list=access_list,
325-
)
346+
txs.append(
347+
Transaction(
348+
to=pre.fund_eoa(amount=0),
349+
data=shuffled_calldata,
350+
gas_limit=gas_available + intrinsic_cost,
351+
sender=pre.fund_eoa(),
352+
access_list=access_list,
353+
)
354+
)
326355

327-
state_test(
328-
pre=pre,
329-
post={},
330-
tx=tx,
331-
expected_benchmark_gas_used=fork.transaction_intrinsic_cost_calculator()(
356+
gas_remaining -= gas_for_access_list + intrinsic_cost
357+
total_gas_used += fork.transaction_intrinsic_cost_calculator()(
332358
calldata=shuffled_calldata,
333359
access_list=access_list,
334-
),
360+
)
361+
362+
benchmark_test(
363+
pre=pre,
364+
post={},
365+
blocks=[Block(txs=txs)],
366+
expected_benchmark_gas_used=total_gas_used,
335367
)
336368

337369

0 commit comments

Comments
 (0)