Skip to content

Commit def3348

Browse files
authored
chore(tests|forks): add max blobs per tx limit in eip4844 & eip7954 (#1884)
1 parent 21cf4fb commit def3348

File tree

14 files changed

+575
-193
lines changed

14 files changed

+575
-193
lines changed

docs/CHANGELOG.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -129,17 +129,18 @@ Users can select any of the artifacts depending on their testing needs for their
129129

130130
- 🔀 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)).
131131
- 🔀 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)).
132-
- ✨ 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)).
132+
- ✨ 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)).
133133
- 🔀 Refactored EIP-145 static tests into python ([#1683](https://github.com/ethereum/execution-spec-tests/pull/1683)).
134134
- ✨ 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)).
135135
-[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)).
136136
-[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).
137137
- ✨ Introduce blockchain tests for benchmark to cover the scenario of pure ether transfers [#1742](https://github.com/ethereum/execution-spec-tests/pull/1742).
138138
-[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)).
139139
-[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)).
140-
-[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)).
141140
-[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)).
142141
-[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)).
142+
-[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)).
143+
- 🔀 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)).
143144

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

src/ethereum_test_forks/base_fork.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,12 @@ def target_blobs_per_block(cls, block_number: int = 0, timestamp: int = 0) -> in
324324
"""Return the target blobs per block at a given fork."""
325325
pass
326326

327+
@classmethod
328+
@abstractmethod
329+
def max_blobs_per_tx(cls, block_number: int = 0, timestamp: int = 0) -> int:
330+
"""Return the max blobs per transaction at a given fork."""
331+
pass
332+
327333
@classmethod
328334
@abstractmethod
329335
def max_blobs_per_block(cls, block_number: int = 0, timestamp: int = 0) -> int:

src/ethereum_test_forks/forks/forks.py

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,11 @@ def full_blob_tx_wrapper_version(cls, block_number: int = 0, timestamp: int = 0)
257257
f"Full blob transaction wrapper version is not supported in {cls.name()}"
258258
)
259259

260+
@classmethod
261+
def max_blobs_per_tx(cls, block_number: int = 0, timestamp: int = 0) -> int:
262+
"""Return the max number of blobs per tx at a given fork."""
263+
raise NotImplementedError(f"Max blobs per tx is not supported in {cls.name()}")
264+
260265
@classmethod
261266
def blob_schedule(cls, block_number: int = 0, timestamp: int = 0) -> BlobSchedule | None:
262267
"""At genesis, no blob schedule is used."""
@@ -1039,19 +1044,24 @@ def supports_blobs(cls, block_number: int = 0, timestamp: int = 0) -> bool:
10391044

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

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

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

1060+
@classmethod
1061+
def max_blobs_per_tx(cls, block_number: int = 0, timestamp: int = 0) -> int:
1062+
"""Blobs are enabled starting from Cancun, with a static max equal to the max per block."""
1063+
return cls.max_blobs_per_block(block_number, timestamp)
1064+
10551065
@classmethod
10561066
def blob_schedule(cls, block_number: int = 0, timestamp: int = 0) -> BlobSchedule | None:
10571067
"""
@@ -1306,12 +1316,12 @@ def blob_base_fee_update_fraction(cls, block_number: int = 0, timestamp: int = 0
13061316

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

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

13171327
@classmethod
@@ -1519,6 +1529,11 @@ def fn(
15191529

15201530
return fn
15211531

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

15231538
class EOFv1(Prague, solc_name="cancun"):
15241539
"""EOF fork."""

tests/cancun/eip4844_blobs/conftest.py

Lines changed: 44 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,12 @@ def max_blobs_per_block(fork: Fork) -> int:
2626
return fork.max_blobs_per_block()
2727

2828

29+
@pytest.fixture
30+
def max_blobs_per_tx(fork: Fork) -> int:
31+
"""Return max number of blobs per transaction."""
32+
return fork.max_blobs_per_tx()
33+
34+
2935
@pytest.fixture
3036
def blob_gas_per_blob(fork: Fork) -> int:
3137
"""Return default blob gas cost per blob."""
@@ -269,6 +275,10 @@ def non_zero_blob_gas_used_genesis_block(
269275
genesis value, expecting an appropriate drop to the intermediate block.
270276
Similarly, we must add parent_blobs to the intermediate block within
271277
a blob tx such that an equivalent blobGasUsed field is wrote.
278+
279+
For forks >= Osaka where the MAX_BLOBS_PER_TX is introduced, we
280+
split the blobs across multiple transactions to respect the
281+
MAX_BLOBS_PER_TX limit.
272282
"""
273283
if parent_blobs == 0:
274284
return None
@@ -287,30 +297,41 @@ def non_zero_blob_gas_used_genesis_block(
287297
)
288298

289299
sender = pre.fund_eoa(10**27)
290-
291-
# Address that contains no code, nor balance and is not a contract.
292300
empty_account_destination = pre.fund_eoa(0)
293-
294301
blob_gas_price_calculator = fork.blob_gas_price_calculator(block_number=1)
295302

296-
return Block(
297-
txs=[
298-
Transaction(
299-
ty=Spec.BLOB_TX_TYPE,
300-
sender=sender,
301-
to=empty_account_destination,
302-
value=1,
303-
gas_limit=21_000,
304-
max_fee_per_gas=tx_max_fee_per_gas,
305-
max_priority_fee_per_gas=0,
306-
max_fee_per_blob_gas=blob_gas_price_calculator(
307-
excess_blob_gas=parent_excess_blob_gas
308-
),
309-
access_list=[],
310-
blob_versioned_hashes=add_kzg_version(
311-
[Hash(x) for x in range(parent_blobs)],
312-
Spec.BLOB_COMMITMENT_VERSION_KZG,
313-
),
314-
)
315-
]
303+
# Split blobs into chunks when MAX_BLOBS_PER_TX < MAX_BLOBS_PER_BLOCK to respect per-tx limits.
304+
# Allows us to keep single txs for forks where per-tx and per-block limits are equal, hitting
305+
# coverage for block level blob gas validation when parent_blobs > MAX_BLOBS_PER_BLOCK.
306+
max_blobs_per_tx = (
307+
fork.max_blobs_per_tx()
308+
if fork.max_blobs_per_tx() < fork.max_blobs_per_block()
309+
else parent_blobs
316310
)
311+
blob_chunks = [
312+
range(i, min(i + max_blobs_per_tx, parent_blobs))
313+
for i in range(0, parent_blobs, max_blobs_per_tx)
314+
]
315+
316+
def create_blob_transaction(blob_range):
317+
return Transaction(
318+
ty=Spec.BLOB_TX_TYPE,
319+
sender=sender,
320+
to=empty_account_destination,
321+
value=1,
322+
gas_limit=21_000,
323+
max_fee_per_gas=tx_max_fee_per_gas,
324+
max_priority_fee_per_gas=0,
325+
max_fee_per_blob_gas=blob_gas_price_calculator(
326+
excess_blob_gas=parent_excess_blob_gas,
327+
),
328+
access_list=[],
329+
blob_versioned_hashes=add_kzg_version(
330+
[Hash(x) for x in blob_range],
331+
Spec.BLOB_COMMITMENT_VERSION_KZG,
332+
),
333+
)
334+
335+
txs = [create_blob_transaction(chunk) for chunk in blob_chunks]
336+
337+
return Block(txs=txs)

tests/cancun/eip4844_blobs/spec.py

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ def get_min_excess_blobs_for_blob_gas_price(
120120
def get_blob_combinations(
121121
cls,
122122
blob_count: int,
123+
max_blobs_per_tx: int,
123124
) -> List[Tuple[int, ...]]:
124125
"""Get all possible combinations of blobs that result in a given blob count."""
125126
combinations = [
@@ -128,10 +129,11 @@ def get_blob_combinations(
128129
blob_count + 1, 0, -1
129130
) # We can have from 1 to at most MAX_BLOBS_PER_BLOCK blobs per block
130131
for seq in itertools.combinations_with_replacement(
131-
range(1, blob_count + 2), i
132+
range(1, min(blob_count + 1, max_blobs_per_tx) + 1), i
132133
) # We iterate through all possible combinations
133-
if sum(seq) == blob_count # And we only keep the ones that match the
134-
# expected invalid blob count
134+
if sum(seq)
135+
== blob_count # And we only keep the ones that match the expected blob count
136+
and all(tx_blobs <= max_blobs_per_tx for tx_blobs in seq) # Validate each tx
135137
]
136138

137139
# We also add the reversed version of each combination, only if it's not
@@ -146,12 +148,14 @@ def get_blob_combinations(
146148
def all_valid_blob_combinations(cls, fork: Fork) -> List[Tuple[int, ...]]:
147149
"""
148150
Return all valid blob tx combinations for a given block,
149-
assuming the given MAX_BLOBS_PER_BLOCK.
151+
assuming the given MAX_BLOBS_PER_BLOCK, whilst respecting MAX_BLOBS_PER_TX.
150152
"""
151153
max_blobs_per_block = fork.max_blobs_per_block()
154+
max_blobs_per_tx = fork.max_blobs_per_tx()
155+
152156
combinations: List[Tuple[int, ...]] = []
153157
for i in range(1, max_blobs_per_block + 1):
154-
combinations += cls.get_blob_combinations(i)
158+
combinations += cls.get_blob_combinations(i, max_blobs_per_tx)
155159
return combinations
156160

157161
@classmethod
@@ -161,4 +165,10 @@ def invalid_blob_combinations(cls, fork: Fork) -> List[Tuple[int, ...]]:
161165
MAX_BLOBS_PER_BLOCK+1 blobs.
162166
"""
163167
max_blobs_per_block = fork.max_blobs_per_block()
164-
return cls.get_blob_combinations(max_blobs_per_block + 1)
168+
max_blobs_per_tx = fork.max_blobs_per_tx()
169+
invalid_combinations = cls.get_blob_combinations(
170+
max_blobs_per_block + 1,
171+
max_blobs_per_tx,
172+
)
173+
invalid_combinations.append((max_blobs_per_block + 1,))
174+
return invalid_combinations

tests/cancun/eip4844_blobs/test_blob_txs.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -651,7 +651,7 @@ def test_insufficient_balance_blob_tx(
651651
"blobs_per_tx",
652652
lambda fork: [
653653
pytest.param([1], id="single_blob"),
654-
pytest.param([fork.max_blobs_per_block()], id="max_blobs"),
654+
pytest.param([fork.max_blobs_per_tx()], id="max_blobs"),
655655
],
656656
)
657657
@pytest.mark.parametrize(
@@ -699,7 +699,7 @@ def test_sufficient_balance_blob_tx(
699699
"blobs_per_tx",
700700
lambda fork: [
701701
pytest.param([1], id="single_blob"),
702-
pytest.param([fork.max_blobs_per_block()], id="max_blobs"),
702+
pytest.param([fork.max_blobs_per_tx()], id="max_blobs"),
703703
],
704704
)
705705
@pytest.mark.parametrize(
@@ -764,7 +764,7 @@ def test_sufficient_balance_blob_tx_pre_fund_tx(
764764
"blobs_per_tx",
765765
lambda fork: [
766766
pytest.param([1], id="single_blob"),
767-
pytest.param([fork.max_blobs_per_block()], id="max_blobs"),
767+
pytest.param([fork.max_blobs_per_tx()], id="max_blobs"),
768768
],
769769
)
770770
@pytest.mark.parametrize(
@@ -875,7 +875,7 @@ def generate_invalid_tx_blob_count_tests(
875875
id="too_few_blobs",
876876
),
877877
pytest.param(
878-
[fork.max_blobs_per_block() + 1],
878+
[fork.max_blobs_per_tx() + 1],
879879
[
880880
TransactionException.TYPE_3_TX_MAX_BLOB_GAS_ALLOWANCE_EXCEEDED,
881881
TransactionException.TYPE_3_TX_BLOB_COUNT_EXCEEDED,

tests/cancun/eip4844_blobs/test_blob_txs_full.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -241,11 +241,8 @@ def blocks(
241241
def generate_full_blob_tests(
242242
fork: Fork,
243243
) -> List:
244-
"""
245-
Return a list of tests for invalid blob transactions due to insufficient max fee per blob gas
246-
parametrized for each different fork.
247-
"""
248-
max_blobs = fork.max_blobs_per_block()
244+
"""Return a list of test cases for full blob transactions."""
245+
max_blobs = fork.max_blobs_per_tx()
249246
return [
250247
pytest.param(
251248
[ # Txs

0 commit comments

Comments
 (0)