Skip to content

Commit 1b7092c

Browse files
src(partial blob response): Framework changes and test added for unavailable blob situations (#2072)
* wip * fix(plugins/forks): Remove evm_bin usage from forks plugin * cherry-picked mario pr for removing evm-bin dependency * mario feedback --------- Co-authored-by: Mario Vega <[email protected]>
1 parent 2d66b26 commit 1b7092c

File tree

6 files changed

+83
-19
lines changed

6 files changed

+83
-19
lines changed

src/ethereum_clis/clis/execution_specs.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,12 @@
1919
TransactionException,
2020
)
2121
from ethereum_test_forks import Fork
22+
from pytest_plugins.logging import get_logger
2223

2324
from ..transition_tool import TransitionTool
2425

2526
DAEMON_STARTUP_TIMEOUT_SECONDS = 5
27+
logger = get_logger(__name__)
2628

2729

2830
class ExecutionSpecsTransitionTool(TransitionTool):
@@ -116,7 +118,10 @@ def is_fork_supported(self, fork: Fork) -> bool:
116118
117119
`ethereum-spec-evm` appends newlines to forks in the help string.
118120
"""
119-
return (fork.transition_tool_name() + "\n") in self.help_string
121+
fork_is_supported = (fork.transition_tool_name() + "\n") in self.help_string
122+
logger.debug(f"EELS supports fork {fork}: {fork_is_supported}")
123+
124+
return fork_is_supported
120125

121126
def _generate_post_args(
122127
self, t8n_data: TransitionTool.TransitionToolData

src/ethereum_test_execution/blob_transaction.py

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,15 @@
99
from ethereum_test_base_types.base_types import Bytes
1010
from ethereum_test_forks import Fork
1111
from ethereum_test_rpc import BlobAndProofV1, BlobAndProofV2, EngineRPC, EthRPC
12-
from ethereum_test_types import NetworkWrappedTransaction, Transaction, TransactionTestMetadata
12+
from ethereum_test_rpc.types import GetBlobsResponse
13+
from ethereum_test_types import NetworkWrappedTransaction, Transaction
14+
from ethereum_test_types.transaction_types import TransactionTestMetadata
15+
from pytest_plugins.logging import get_logger
1316

1417
from .base import BaseExecute
1518

19+
logger = get_logger(__name__)
20+
1621

1722
def versioned_hashes_with_blobs_and_proofs(
1823
tx: NetworkWrappedTransaction,
@@ -54,6 +59,7 @@ class BlobTransaction(BaseExecute):
5459
requires_engine_rpc: ClassVar[bool] = True
5560

5661
txs: List[NetworkWrappedTransaction | Transaction]
62+
nonexisting_blob_hashes: List[Hash] | None = None
5763

5864
def execute(
5965
self, fork: Fork, eth_rpc: EthRPC, engine_rpc: EngineRPC | None, request: FixtureRequest
@@ -87,15 +93,39 @@ def execute(
8793
)
8894
version = fork.engine_get_blobs_version()
8995
assert version is not None, "Engine get blobs version is not supported by the fork."
90-
blob_response = engine_rpc.get_blobs(list(versioned_hashes.keys()), version=version)
96+
97+
# ensure that clients respond 'null' when they have no access to at least one blob
98+
list_versioned_hashes = list(versioned_hashes.keys())
99+
if self.nonexisting_blob_hashes is not None:
100+
list_versioned_hashes.extend(self.nonexisting_blob_hashes)
101+
102+
blob_response: GetBlobsResponse | None = engine_rpc.get_blobs(
103+
list_versioned_hashes, version=version
104+
) # noqa: E501
105+
106+
# if non-existing blob hashes were request then the response must be 'null'
107+
if self.nonexisting_blob_hashes is not None:
108+
if blob_response is not None:
109+
raise ValueError(
110+
f"Non-existing blob hashes were requested and "
111+
"the client was expected to respond with 'null', but instead it replied: "
112+
f"{blob_response.root}"
113+
)
114+
else:
115+
logger.info(
116+
"Test was passed (partial responses are not allowed and the client "
117+
"correctly returned 'null')"
118+
)
119+
eth_rpc.wait_for_transactions(sent_txs)
120+
return
121+
122+
assert blob_response is not None
91123
local_blobs_and_proofs = list(versioned_hashes.values())
92-
if len(blob_response) != len(local_blobs_and_proofs):
93-
raise ValueError(
94-
f"Expected {len(local_blobs_and_proofs)} blobs and proofs, "
95-
f"got {len(blob_response)}."
96-
)
124+
assert len(blob_response) == len(local_blobs_and_proofs), "Expected "
125+
f"{len(local_blobs_and_proofs)} blobs and proofs, got {len(blob_response)}."
126+
97127
for expected_blob, received_blob in zip(
98-
local_blobs_and_proofs, blob_response.root, strict=False
128+
local_blobs_and_proofs, blob_response.root, strict=True
99129
):
100130
if received_blob is None:
101131
raise ValueError("Received blob is empty.")

src/ethereum_test_rpc/rpc.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
from ethereum_test_base_types import Address, Bytes, Hash, to_json
1313
from ethereum_test_types import Transaction
14+
from pytest_plugins.logging import get_logger
1415

1516
from .types import (
1617
EthConfigResponse,
@@ -24,6 +25,8 @@
2425
TransactionByHashResponse,
2526
)
2627

28+
logger = get_logger(__name__)
29+
2730
BlockNumberType = int | Literal["latest", "earliest", "pending"]
2831

2932

@@ -405,13 +408,18 @@ def get_blobs(
405408
versioned_hashes: List[Hash],
406409
*,
407410
version: int,
408-
) -> GetBlobsResponse:
411+
) -> GetBlobsResponse | None:
409412
"""`engine_getBlobsVX`: Retrieves blobs from an execution layers tx pool."""
413+
response = self.post_request(
414+
f"getBlobsV{version}",
415+
[f"{h}" for h in versioned_hashes],
416+
)
417+
if response is None: # for tests that request non-existing blobs
418+
logger.debug("get_blobs response received but it has value: None")
419+
return None
420+
410421
return GetBlobsResponse.model_validate(
411-
self.post_request(
412-
f"getBlobsV{version}",
413-
[f"{h}" for h in versioned_hashes],
414-
),
422+
response,
415423
context=self.response_validation_context,
416424
)
417425

src/ethereum_test_specs/blobs.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
from ethereum_clis import TransitionTool
66
from ethereum_test_base_types import Alloc
7+
from ethereum_test_base_types.base_types import Hash
78
from ethereum_test_execution import BaseExecute, BlobTransaction
89
from ethereum_test_fixtures import (
910
BaseFixture,
@@ -20,6 +21,7 @@ class BlobsTest(BaseTest):
2021

2122
pre: Alloc
2223
txs: List[NetworkWrappedTransaction | Transaction]
24+
nonexisting_blob_hashes: List[Hash] | None = None
2325

2426
supported_execute_formats: ClassVar[Sequence[LabeledExecuteFormat]] = [
2527
LabeledExecuteFormat(
@@ -48,7 +50,7 @@ def execute(
4850
"""Generate the list of test fixtures."""
4951
if execute_format == BlobTransaction:
5052
return BlobTransaction(
51-
txs=self.txs,
53+
txs=self.txs, nonexisting_blob_hashes=self.nonexisting_blob_hashes
5254
)
5355
raise Exception(f"Unsupported execute format: {execute_format}")
5456

src/pytest_plugins/forks/forks.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@
2525
get_transition_forks,
2626
transition_fork_to,
2727
)
28+
from pytest_plugins.logging import get_logger
29+
30+
logger = get_logger(__name__)
2831

2932

3033
def pytest_addoption(parser):
@@ -524,6 +527,7 @@ def get_fork_option(config, option_name: str, parameter_name: str) -> Set[Fork]:
524527
config.unsupported_forks = frozenset( # type: ignore
525528
fork for fork in selected_fork_set if not t8n.is_fork_supported(fork)
526529
)
530+
logger.debug(f"List of unsupported forks: {list(config.unsupported_forks)}") # type: ignore
527531

528532

529533
@pytest.hookimpl(trylast=True)

tests/osaka/eip7594_peerdas/test_get_blobs.py

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@
33
Test get blobs engine endpoint for [EIP-7594: PeerDAS - Peer Data Availability Sampling](https://eips.ethereum.org/EIPS/eip-7594).
44
""" # noqa: E501
55

6+
from hashlib import sha256
67
from typing import List, Optional
78

89
import pytest
910

11+
from ethereum_test_base_types.base_types import Hash
1012
from ethereum_test_forks import Fork
1113
from ethereum_test_tools import (
1214
Address,
@@ -320,7 +322,20 @@ def test_get_blobs(
320322
Test valid blob combinations where one or more txs in the block
321323
serialized version contain a full blob (network version) tx.
322324
"""
323-
blobs_test(
324-
pre=pre,
325-
txs=txs,
326-
)
325+
blobs_test(pre=pre, txs=txs)
326+
327+
328+
@pytest.mark.parametrize_by_fork(
329+
"txs_blobs",
330+
generate_valid_blob_tests,
331+
)
332+
@pytest.mark.exception_test
333+
@pytest.mark.valid_from("Cancun")
334+
def test_get_blobs_nonexisting(
335+
blobs_test: BlobsTestFiller,
336+
pre: Alloc,
337+
txs: List[NetworkWrappedTransaction | Transaction],
338+
):
339+
"""Test that ensures clients respond with 'null' when at least one requested blob is not available.""" # noqa: E501
340+
nonexisting_blob_hashes = [Hash(sha256(str(i).encode()).digest()) for i in range(5)]
341+
blobs_test(pre=pre, txs=txs, nonexisting_blob_hashes=nonexisting_blob_hashes)

0 commit comments

Comments
 (0)