Skip to content
Merged
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 109 additions & 6 deletions tests/benchmark/test_worst_blocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,28 @@
Tests running worst-case block scenarios for EVMs.
"""

import random

import pytest

from ethereum_test_forks import Fork
from ethereum_test_tools import (
AccessList,
Account,
Address,
Alloc,
Block,
BlockchainTestFiller,
Environment,
Hash,
StateTestFiller,
Transaction,
)


@pytest.fixture
def iteration_count(intrinsic_cost: int):
def iteration_count(intrinsic_cost: int, gas_benchmark_value: int):
"""Calculate the number of iterations based on the gas limit and intrinsic cost."""
return Environment().gas_limit // intrinsic_cost
return gas_benchmark_value // intrinsic_cost


@pytest.fixture
Expand Down Expand Up @@ -151,7 +154,6 @@ def test_block_full_of_ether_transfers(
)

blockchain_test(
genesis_environment=Environment(),
pre=pre,
post=post_state,
blocks=[Block(txs=txs)],
Expand All @@ -165,6 +167,18 @@ def total_cost_floor_per_token():
return 10


@pytest.fixture
def total_cost_standard_per_token():
"""Total cost floor per token."""
return 4


@pytest.fixture
def gas_benchmark_value():
"""Gas benchmark value for testing."""
return 30_000_000


@pytest.mark.valid_from("Prague")
@pytest.mark.parametrize("zero_byte", [True, False])
def test_block_full_data(
Expand All @@ -173,9 +187,10 @@ def test_block_full_data(
zero_byte: bool,
intrinsic_cost: int,
total_cost_floor_per_token: int,
gas_benchmark_value: int,
):
"""Test a block with empty payload."""
attack_gas_limit = Environment().gas_limit
attack_gas_limit = gas_benchmark_value

# Gas cost calculation based on EIP-7683: (https://eips.ethereum.org/EIPS/eip-7683)
#
Expand Down Expand Up @@ -217,7 +232,95 @@ def test_block_full_data(
)

state_test(
env=Environment(),
pre=pre,
post={},
tx=tx,
)


@pytest.mark.valid_from("Prague")
def test_block_full_access_list_and_data(
state_test: StateTestFiller,
pre: Alloc,
intrinsic_cost: int,
total_cost_standard_per_token: int,
fork: Fork,
gas_benchmark_value: int,
):
"""Test a block with access lists (60% gas) and calldata (40% gas) using random mixed bytes."""
attack_gas_limit = gas_benchmark_value
gas_available = attack_gas_limit - intrinsic_cost

# Split available gas: 60% for access lists, 40% for calldata
gas_for_access_list = int(gas_available * 0.6)
gas_for_calldata = int(gas_available * 0.4)

# Access list gas costs from fork's gas_costs
gas_costs = fork.gas_costs()
gas_per_address = gas_costs.G_ACCESS_LIST_ADDRESS
gas_per_storage_key = gas_costs.G_ACCESS_LIST_STORAGE

# Calculate number of storage keys we can fit
gas_after_address = gas_for_access_list - gas_per_address
num_storage_keys = gas_after_address // gas_per_storage_key

# Create access list with 1 address and many storage keys
access_address = Address("0x1234567890123456789012345678901234567890")
storage_keys = []
for i in range(num_storage_keys):
# Generate random-looking storage keys
storage_keys.append(Hash(i))

access_list = [
AccessList(
address=access_address,
storage_keys=storage_keys,
)
]

# Calculate calldata with 29% of gas for zero bytes and 71% for non-zero bytes
# Token accounting: tokens_in_calldata = zero_bytes + 4 * non_zero_bytes
# We want to split the gas budget:
# - 29% of gas_for_calldata for zero bytes
# - 71% of gas_for_calldata for non-zero bytes

max_tokens_in_calldata = gas_for_calldata // total_cost_standard_per_token

# Calculate how many tokens to allocate to each type
tokens_for_zero_bytes = int(max_tokens_in_calldata * 0.29)
tokens_for_non_zero_bytes = max_tokens_in_calldata - tokens_for_zero_bytes

# Convert tokens to actual byte counts
# Zero bytes: 1 token per byte
# Non-zero bytes: 4 tokens per byte
num_zero_bytes = tokens_for_zero_bytes # 1 token = 1 zero byte
num_non_zero_bytes = tokens_for_non_zero_bytes // 4 # 4 tokens = 1 non-zero byte

# Create calldata with mixed bytes
calldata = bytearray()

# Add zero bytes
calldata.extend(b"\x00" * num_zero_bytes)

# Add non-zero bytes (random values from 0x01 to 0xff)
rng = random.Random(42) # For reproducibility
for _ in range(num_non_zero_bytes):
calldata.append(rng.randint(1, 255))

# Shuffle the bytes to mix zero and non-zero bytes
calldata_list = list(calldata)
rng.shuffle(calldata_list)
shuffled_calldata = bytes(calldata_list)

tx = Transaction(
to=pre.fund_eoa(amount=0),
data=shuffled_calldata,
gas_limit=attack_gas_limit,
sender=pre.fund_eoa(),
access_list=access_list,
)

state_test(
pre=pre,
post={},
tx=tx,
Expand Down