Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
5 changes: 3 additions & 2 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,17 +129,18 @@ Users can select any of the artifacts depending on their testing needs for their

- 🔀 Refactored `BLOBHASH` opcode context tests to use the `pre_alloc` plugin in order to avoid contract and EOA address collisions ([#1637](https://github.com/ethereum/execution-spec-tests/pull/1637)).
- 🔀 Refactored `SELFDESTRUCT` opcode collision tests to use the `pre_alloc` plugin in order to avoid contract and EOA address collisions ([#1643](https://github.com/ethereum/execution-spec-tests/pull/1643)).
- ✨ EIP-7594: Sanity test cases to send blob transactions and verify `engine_getBlobsVX` using the `execute` command ([#1644](https://github.com/ethereum/execution-spec-tests/pull/1644)).
- ✨ EIP-7594: Sanity test cases to send blob transactions and verify `engine_getBlobsVX` using the `execute` command ([#1644](https://github.com/ethereum/execution-spec-tests/pull/1644),[#1884](https://github.com/ethereum/execution-spec-tests/pull/1884)).
- 🔀 Refactored EIP-145 static tests into python ([#1683](https://github.com/ethereum/execution-spec-tests/pull/1683)).
- ✨ EIP-7823, EIP-7883: Add test cases for ModExp precompile gas-cost updates and input limits on Osaka ([#1579](https://github.com/ethereum/execution-spec-tests/pull/1579), [#1729](https://github.com/ethereum/execution-spec-tests/pull/1729), [#1881](https://github.com/ethereum/execution-spec-tests/pull/1881)).
- ✨ [EIP-7825](https://eips.ethereum.org/EIPS/eip-7825): Add test cases for the transaction gas limit of 2^24 gas ([#1711](https://github.com/ethereum/execution-spec-tests/pull/1711), [#1882](https://github.com/ethereum/execution-spec-tests/pull/1882)).
- ✨ [EIP-7951](https://eips.ethereum.org/EIPS/eip-7951): add test cases for `P256VERIFY` precompile to support secp256r1 curve [#1670](https://github.com/ethereum/execution-spec-tests/pull/1670).
- ✨ Introduce blockchain tests for benchmark to cover the scenario of pure ether transfers [#1742](https://github.com/ethereum/execution-spec-tests/pull/1742).
- ✨ [EIP-7934](https://eips.ethereum.org/EIPS/eip-7934): Add test cases for the block RLP max limit of 10MiB ([#1730](https://github.com/ethereum/execution-spec-tests/pull/1730)).
- ✨ [EIP-7939](https://eips.ethereum.org/EIPS/eip-7939) Add count leading zeros (CLZ) opcode tests for Osaka ([#1733](https://github.com/ethereum/execution-spec-tests/pull/1733)).
- ✨ [EIP-7918](https://eips.ethereum.org/EIPS/eip-7918): Blob base fee bounded by execution cost test cases (initial), includes some adjustments to [EIP-4844](https://eips.ethereum.org/EIPS/eip-4844) tests ([#1685](https://github.com/ethereum/execution-spec-tests/pull/1685)). Update the blob_base_cost ([#1915](https://github.com/ethereum/EIPs/pull/1915)).
- ✨ [EIP-7934](https://eips.ethereum.org/EIPS/eip-7934): Add additional test cases for block RLP max limit with all typed transactions and for a log-creating transactions ([#1890](https://github.com/ethereum/execution-spec-tests/pull/1890)).
- ✨ [EIP-7825](https://eips.ethereum.org/EIPS/eip-7825): Pre-Osaka tests have been updated to either (1) dynamically adapt to the transaction gas limit cap, or (2) reduce overall gas consumption to fit the new limit ([#1928](https://github.com/ethereum/EIPs/pull/1928)).
- ✨ [EIP-7918](https://eips.ethereum.org/EIPS/eip-7918): Blob base fee bounded by execution cost test cases (initial), includes some adjustments to [EIP-4844](https://eips.ethereum.org/EIPS/eip-4844) tests ([#1685](https://github.com/ethereum/execution-spec-tests/pull/1685)).
- 🔀 Adds the max blob transaction limit to the tests including updates to [EIP-4844](https://eips.ethereum.org/EIPS/eip-4844) for Osaka ([#1884](https://github.com/ethereum/execution-spec-tests/pull/1884)).

## [v4.5.0](https://github.com/ethereum/execution-spec-tests/releases/tag/v4.5.0) - 2025-05-14

Expand Down
6 changes: 6 additions & 0 deletions src/ethereum_test_forks/base_fork.py
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,12 @@ def target_blobs_per_block(cls, block_number: int = 0, timestamp: int = 0) -> in
"""Return the target blobs per block at a given fork."""
pass

@classmethod
@abstractmethod
def max_blobs_per_tx(cls, block_number: int = 0, timestamp: int = 0) -> int:
"""Return the max blobs per transaction at a given fork."""
pass

@classmethod
@abstractmethod
def max_blobs_per_block(cls, block_number: int = 0, timestamp: int = 0) -> int:
Expand Down
23 changes: 19 additions & 4 deletions src/ethereum_test_forks/forks/forks.py
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,11 @@ def full_blob_tx_wrapper_version(cls, block_number: int = 0, timestamp: int = 0)
f"Full blob transaction wrapper version is not supported in {cls.name()}"
)

@classmethod
def max_blobs_per_tx(cls, block_number: int = 0, timestamp: int = 0) -> int:
"""Return the max number of blobs per tx at a given fork."""
raise NotImplementedError(f"Max blobs per tx is not supported in {cls.name()}")

@classmethod
def blob_schedule(cls, block_number: int = 0, timestamp: int = 0) -> BlobSchedule | None:
"""At genesis, no blob schedule is used."""
Expand Down Expand Up @@ -1039,19 +1044,24 @@ def supports_blobs(cls, block_number: int = 0, timestamp: int = 0) -> bool:

@classmethod
def target_blobs_per_block(cls, block_number: int = 0, timestamp: int = 0) -> int:
"""Blobs are enabled starting from Cancun, with a static target of 3 blobs."""
"""Blobs are enabled starting from Cancun, with a static target of 3 blobs per block."""
return 3

@classmethod
def max_blobs_per_block(cls, block_number: int = 0, timestamp: int = 0) -> int:
"""Blobs are enabled starting from Cancun, with a static max of 6 blobs."""
"""Blobs are enabled starting from Cancun, with a static max of 6 blobs per block."""
return 6

@classmethod
def full_blob_tx_wrapper_version(cls, block_number: int = 0, timestamp: int = 0) -> int | None:
"""Pre-Osaka forks don't use tx wrapper versions for full blob transactions."""
return None

@classmethod
def max_blobs_per_tx(cls, block_number: int = 0, timestamp: int = 0) -> int:
"""Blobs are enabled starting from Cancun, with a static max equal to the max per block."""
return cls.max_blobs_per_block(block_number, timestamp)

@classmethod
def blob_schedule(cls, block_number: int = 0, timestamp: int = 0) -> BlobSchedule | None:
"""
Expand Down Expand Up @@ -1306,12 +1316,12 @@ def blob_base_fee_update_fraction(cls, block_number: int = 0, timestamp: int = 0

@classmethod
def target_blobs_per_block(cls, block_number: int = 0, timestamp: int = 0) -> int:
"""Target blob count of 6 for Prague."""
"""Blobs in Prague, have a static target of 6 blobs per block."""
return 6

@classmethod
def max_blobs_per_block(cls, block_number: int = 0, timestamp: int = 0) -> int:
"""Max blob count of 9 for Prague."""
"""Blobs in Prague, have a static max of 9 blobs per block."""
return 9

@classmethod
Expand Down Expand Up @@ -1519,6 +1529,11 @@ def fn(

return fn

@classmethod
def max_blobs_per_tx(cls, block_number: int = 0, timestamp: int = 0) -> int:
"""Blobs in Osaka, have a static max of 6 blobs per tx. Differs from the max per block."""
return 6


class EOFv1(Prague, solc_name="cancun"):
"""EOF fork."""
Expand Down
67 changes: 44 additions & 23 deletions tests/cancun/eip4844_blobs/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ def max_blobs_per_block(fork: Fork) -> int:
return fork.max_blobs_per_block()


@pytest.fixture
def max_blobs_per_tx(fork: Fork) -> int:
"""Return max number of blobs per transaction."""
return fork.max_blobs_per_tx()


@pytest.fixture
def blob_gas_per_blob(fork: Fork) -> int:
"""Return default blob gas cost per blob."""
Expand Down Expand Up @@ -269,6 +275,10 @@ def non_zero_blob_gas_used_genesis_block(
genesis value, expecting an appropriate drop to the intermediate block.
Similarly, we must add parent_blobs to the intermediate block within
a blob tx such that an equivalent blobGasUsed field is wrote.

For forks >= Osaka where the MAX_BLOBS_PER_TX is introduced, we
split the blobs across multiple transactions to respect the
MAX_BLOBS_PER_TX limit.
"""
if parent_blobs == 0:
return None
Expand All @@ -287,30 +297,41 @@ def non_zero_blob_gas_used_genesis_block(
)

sender = pre.fund_eoa(10**27)

# Address that contains no code, nor balance and is not a contract.
empty_account_destination = pre.fund_eoa(0)

blob_gas_price_calculator = fork.blob_gas_price_calculator(block_number=1)

return Block(
txs=[
Transaction(
ty=Spec.BLOB_TX_TYPE,
sender=sender,
to=empty_account_destination,
value=1,
gas_limit=21_000,
max_fee_per_gas=tx_max_fee_per_gas,
max_priority_fee_per_gas=0,
max_fee_per_blob_gas=blob_gas_price_calculator(
excess_blob_gas=parent_excess_blob_gas
),
access_list=[],
blob_versioned_hashes=add_kzg_version(
[Hash(x) for x in range(parent_blobs)],
Spec.BLOB_COMMITMENT_VERSION_KZG,
),
)
]
# Split blobs into chunks when MAX_BLOBS_PER_TX < MAX_BLOBS_PER_BLOCK to respect per-tx limits.
# Allows us to keep single txs for forks where per-tx and per-block limits are equal, hitting
# coverage for block level blob gas validation when parent_blobs > MAX_BLOBS_PER_BLOCK.
max_blobs_per_tx = (
fork.max_blobs_per_tx()
if fork.max_blobs_per_tx() < fork.max_blobs_per_block()
else parent_blobs
)
blob_chunks = [
range(i, min(i + max_blobs_per_tx, parent_blobs))
for i in range(0, parent_blobs, max_blobs_per_tx)
]

def create_blob_transaction(blob_range):
return Transaction(
ty=Spec.BLOB_TX_TYPE,
sender=sender,
to=empty_account_destination,
value=1,
gas_limit=21_000,
max_fee_per_gas=tx_max_fee_per_gas,
max_priority_fee_per_gas=0,
max_fee_per_blob_gas=blob_gas_price_calculator(
excess_blob_gas=parent_excess_blob_gas,
),
access_list=[],
blob_versioned_hashes=add_kzg_version(
[Hash(x) for x in blob_range],
Spec.BLOB_COMMITMENT_VERSION_KZG,
),
)

txs = [create_blob_transaction(chunk) for chunk in blob_chunks]

return Block(txs=txs)
22 changes: 16 additions & 6 deletions tests/cancun/eip4844_blobs/spec.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ def get_min_excess_blobs_for_blob_gas_price(
def get_blob_combinations(
cls,
blob_count: int,
max_blobs_per_tx: int,
) -> List[Tuple[int, ...]]:
"""Get all possible combinations of blobs that result in a given blob count."""
combinations = [
Expand All @@ -128,10 +129,11 @@ def get_blob_combinations(
blob_count + 1, 0, -1
) # We can have from 1 to at most MAX_BLOBS_PER_BLOCK blobs per block
for seq in itertools.combinations_with_replacement(
range(1, blob_count + 2), i
range(1, min(blob_count + 1, max_blobs_per_tx) + 1), i
) # We iterate through all possible combinations
if sum(seq) == blob_count # And we only keep the ones that match the
# expected invalid blob count
if sum(seq)
== blob_count # And we only keep the ones that match the expected blob count
and all(tx_blobs <= max_blobs_per_tx for tx_blobs in seq) # Validate each tx
]

# We also add the reversed version of each combination, only if it's not
Expand All @@ -146,12 +148,14 @@ def get_blob_combinations(
def all_valid_blob_combinations(cls, fork: Fork) -> List[Tuple[int, ...]]:
"""
Return all valid blob tx combinations for a given block,
assuming the given MAX_BLOBS_PER_BLOCK.
assuming the given MAX_BLOBS_PER_BLOCK, whilst respecting MAX_BLOBS_PER_TX.
"""
max_blobs_per_block = fork.max_blobs_per_block()
max_blobs_per_tx = fork.max_blobs_per_tx()

combinations: List[Tuple[int, ...]] = []
for i in range(1, max_blobs_per_block + 1):
combinations += cls.get_blob_combinations(i)
combinations += cls.get_blob_combinations(i, max_blobs_per_tx)
return combinations

@classmethod
Expand All @@ -161,4 +165,10 @@ def invalid_blob_combinations(cls, fork: Fork) -> List[Tuple[int, ...]]:
MAX_BLOBS_PER_BLOCK+1 blobs.
"""
max_blobs_per_block = fork.max_blobs_per_block()
return cls.get_blob_combinations(max_blobs_per_block + 1)
max_blobs_per_tx = fork.max_blobs_per_tx()
invalid_combinations = cls.get_blob_combinations(
max_blobs_per_block + 1,
max_blobs_per_tx,
)
invalid_combinations.append((max_blobs_per_block + 1,))
return invalid_combinations
8 changes: 4 additions & 4 deletions tests/cancun/eip4844_blobs/test_blob_txs.py
Original file line number Diff line number Diff line change
Expand Up @@ -651,7 +651,7 @@ def test_insufficient_balance_blob_tx(
"blobs_per_tx",
lambda fork: [
pytest.param([1], id="single_blob"),
pytest.param([fork.max_blobs_per_block()], id="max_blobs"),
pytest.param([fork.max_blobs_per_tx()], id="max_blobs"),
],
)
@pytest.mark.parametrize(
Expand Down Expand Up @@ -699,7 +699,7 @@ def test_sufficient_balance_blob_tx(
"blobs_per_tx",
lambda fork: [
pytest.param([1], id="single_blob"),
pytest.param([fork.max_blobs_per_block()], id="max_blobs"),
pytest.param([fork.max_blobs_per_tx()], id="max_blobs"),
],
)
@pytest.mark.parametrize(
Expand Down Expand Up @@ -764,7 +764,7 @@ def test_sufficient_balance_blob_tx_pre_fund_tx(
"blobs_per_tx",
lambda fork: [
pytest.param([1], id="single_blob"),
pytest.param([fork.max_blobs_per_block()], id="max_blobs"),
pytest.param([fork.max_blobs_per_tx()], id="max_blobs"),
],
)
@pytest.mark.parametrize(
Expand Down Expand Up @@ -875,7 +875,7 @@ def generate_invalid_tx_blob_count_tests(
id="too_few_blobs",
),
pytest.param(
[fork.max_blobs_per_block() + 1],
[fork.max_blobs_per_tx() + 1],
[
TransactionException.TYPE_3_TX_MAX_BLOB_GAS_ALLOWANCE_EXCEEDED,
TransactionException.TYPE_3_TX_BLOB_COUNT_EXCEEDED,
Expand Down
7 changes: 2 additions & 5 deletions tests/cancun/eip4844_blobs/test_blob_txs_full.py
Original file line number Diff line number Diff line change
Expand Up @@ -241,11 +241,8 @@ def blocks(
def generate_full_blob_tests(
fork: Fork,
) -> List:
"""
Return a list of tests for invalid blob transactions due to insufficient max fee per blob gas
parametrized for each different fork.
"""
max_blobs = fork.max_blobs_per_block()
"""Return a list of test cases for full blob transactions."""
max_blobs = fork.max_blobs_per_tx()
return [
pytest.param(
[ # Txs
Expand Down
Loading
Loading