From 769f8ddf2a6c69fe065c7e1bd7f888e98438014c Mon Sep 17 00:00:00 2001 From: Felix H Date: Wed, 20 Aug 2025 08:55:32 +0000 Subject: [PATCH 1/4] wip --- pyproject.toml | 2 +- src/ethereum_clis/clis/execution_specs.py | 6 ++- .../blob_transaction.py | 42 +++++++++++++++---- src/ethereum_test_rpc/rpc.py | 15 ++++--- src/ethereum_test_specs/blobs.py | 4 +- src/pytest_plugins/forks/forks.py | 3 ++ tests/osaka/eip7594_peerdas/test_get_blobs.py | 12 ++++-- 7 files changed, 64 insertions(+), 20 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 48ba580d04a..a2154f9b8ac 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -174,4 +174,4 @@ addopts = [ required-version = ">=0.7.0" [tool.uv.sources] -ethereum-spec-evm-resolver = { git = "https://github.com/spencer-tb/ethereum-spec-evm-resolver", rev = "aec6a628b8d0f1c791a8378c5417a089566135ac" } +ethereum-spec-evm-resolver = { git = "https://github.com/spencer-tb/ethereum-spec-evm-resolver", rev = "aec6a628b8d0f1c791a8378c5417a089566135ac" } \ No newline at end of file diff --git a/src/ethereum_clis/clis/execution_specs.py b/src/ethereum_clis/clis/execution_specs.py index a4915843585..984a1ad284f 100644 --- a/src/ethereum_clis/clis/execution_specs.py +++ b/src/ethereum_clis/clis/execution_specs.py @@ -19,10 +19,12 @@ TransactionException, ) from ethereum_test_forks import Fork +from pytest_plugins.logging import get_logger from ..transition_tool import TransitionTool DAEMON_STARTUP_TIMEOUT_SECONDS = 5 +logger = get_logger(__name__) class ExecutionSpecsTransitionTool(TransitionTool): @@ -116,7 +118,9 @@ def is_fork_supported(self, fork: Fork) -> bool: `ethereum-spec-evm` appends newlines to forks in the help string. """ - return (fork.transition_tool_name() + "\n") in self.help_string + fork_is_supported = (fork.transition_tool_name() + "\n") in self.help_string + logger.debug(f"EELS supports fork {fork}: {fork_is_supported}") + return fork_is_supported def _generate_post_args( self, t8n_data: TransitionTool.TransitionToolData diff --git a/src/ethereum_test_execution/blob_transaction.py b/src/ethereum_test_execution/blob_transaction.py index 30b3e79d9ef..964f7e74d97 100644 --- a/src/ethereum_test_execution/blob_transaction.py +++ b/src/ethereum_test_execution/blob_transaction.py @@ -9,7 +9,9 @@ from ethereum_test_base_types.base_types import Bytes from ethereum_test_forks import Fork from ethereum_test_rpc import BlobAndProofV1, BlobAndProofV2, EngineRPC, EthRPC -from ethereum_test_types import NetworkWrappedTransaction, Transaction, TransactionTestMetadata +from ethereum_test_rpc.types import GetBlobsResponse +from ethereum_test_types import NetworkWrappedTransaction, Transaction +from ethereum_test_types.transaction_types import TransactionTestMetadata from .base import BaseExecute @@ -54,6 +56,7 @@ class BlobTransaction(BaseExecute): requires_engine_rpc: ClassVar[bool] = True txs: List[NetworkWrappedTransaction | Transaction] + nonexisting_blob_hashes: List[Hash] | None = None def execute( self, fork: Fork, eth_rpc: EthRPC, engine_rpc: EngineRPC | None, request: FixtureRequest @@ -87,15 +90,38 @@ def execute( ) version = fork.engine_get_blobs_version() assert version is not None, "Engine get blobs version is not supported by the fork." - blob_response = engine_rpc.get_blobs(list(versioned_hashes.keys()), version=version) + + # ensure that clients respond 'null' when they have no access to at least one blob + list_versioned_hashes = list(versioned_hashes.keys()) + if self.nonexisting_blob_hashes is not None: + list_versioned_hashes.extend(self.nonexisting_blob_hashes) + + blob_response: GetBlobsResponse | None = engine_rpc.get_blobs( + list_versioned_hashes, version=version + ) # noqa: E501 + + # if non-existing blob hashes were request then the response must be 'null' + if self.nonexisting_blob_hashes is not None: + if blob_response is not None: + raise ValueError( + f"Non-existing blob hashes were requested and " + "the client was expected to respond with 'null', but instead it replied: " + f"{blob_response.root}" + ) + else: + print( + "Test was passed (partial responses are not allowed and the client " + "correctly returned 'null')" + ) + return + + assert blob_response is not None local_blobs_and_proofs = list(versioned_hashes.values()) - if len(blob_response) != len(local_blobs_and_proofs): - raise ValueError( - f"Expected {len(local_blobs_and_proofs)} blobs and proofs, " - f"got {len(blob_response)}." - ) + assert len(blob_response) == len(local_blobs_and_proofs), "Expected " + f"{len(local_blobs_and_proofs)} blobs and proofs, got {len(blob_response)}." + for expected_blob, received_blob in zip( - local_blobs_and_proofs, blob_response.root, strict=False + local_blobs_and_proofs, blob_response.root, strict=True ): if received_blob is None: raise ValueError("Received blob is empty.") diff --git a/src/ethereum_test_rpc/rpc.py b/src/ethereum_test_rpc/rpc.py index 244591576c4..0a21b4f9b9c 100644 --- a/src/ethereum_test_rpc/rpc.py +++ b/src/ethereum_test_rpc/rpc.py @@ -400,13 +400,18 @@ def get_blobs( versioned_hashes: List[Hash], *, version: int, - ) -> GetBlobsResponse: + ) -> GetBlobsResponse | None: """`engine_getBlobsVX`: Retrieves blobs from an execution layers tx pool.""" + response = self.post_request( + f"getBlobsV{version}", + [f"{h}" for h in versioned_hashes], + ) + if response is None: # for tests that request non-existing blobs + print("get_blobs response received but it has value: None") + return None + return GetBlobsResponse.model_validate( - self.post_request( - f"getBlobsV{version}", - [f"{h}" for h in versioned_hashes], - ), + response, context=self.response_validation_context, ) diff --git a/src/ethereum_test_specs/blobs.py b/src/ethereum_test_specs/blobs.py index 7fbce96c5d8..484e11b05b5 100644 --- a/src/ethereum_test_specs/blobs.py +++ b/src/ethereum_test_specs/blobs.py @@ -4,6 +4,7 @@ from ethereum_clis import TransitionTool from ethereum_test_base_types import Alloc +from ethereum_test_base_types.base_types import Hash from ethereum_test_execution import BaseExecute, BlobTransaction from ethereum_test_fixtures import ( BaseFixture, @@ -20,6 +21,7 @@ class BlobsTest(BaseTest): pre: Alloc txs: List[NetworkWrappedTransaction | Transaction] + nonexisting_blob_hashes: List[Hash] | None = None supported_execute_formats: ClassVar[Sequence[LabeledExecuteFormat]] = [ LabeledExecuteFormat( @@ -48,7 +50,7 @@ def execute( """Generate the list of test fixtures.""" if execute_format == BlobTransaction: return BlobTransaction( - txs=self.txs, + txs=self.txs, nonexisting_blob_hashes=self.nonexisting_blob_hashes ) raise Exception(f"Unsupported execute format: {execute_format}") diff --git a/src/pytest_plugins/forks/forks.py b/src/pytest_plugins/forks/forks.py index 4ff96df7611..61db09a664f 100644 --- a/src/pytest_plugins/forks/forks.py +++ b/src/pytest_plugins/forks/forks.py @@ -25,6 +25,9 @@ get_transition_forks, transition_fork_to, ) +from pytest_plugins.logging import get_logger + +logger = get_logger(__name__) def pytest_addoption(parser): diff --git a/tests/osaka/eip7594_peerdas/test_get_blobs.py b/tests/osaka/eip7594_peerdas/test_get_blobs.py index ee777f3ba3a..5bd16b63530 100644 --- a/tests/osaka/eip7594_peerdas/test_get_blobs.py +++ b/tests/osaka/eip7594_peerdas/test_get_blobs.py @@ -3,10 +3,12 @@ Test get blobs engine endpoint for [EIP-7594: PeerDAS - Peer Data Availability Sampling](https://eips.ethereum.org/EIPS/eip-7594). """ # noqa: E501 +from hashlib import sha256 from typing import List, Optional import pytest +from ethereum_test_base_types.base_types import Hash from ethereum_test_forks import Fork from ethereum_test_tools import ( Address, @@ -320,7 +322,9 @@ def test_get_blobs( Test valid blob combinations where one or more txs in the block serialized version contain a full blob (network version) tx. """ - blobs_test( - pre=pre, - txs=txs, - ) + nonexisting_blob_hashes = [Hash(sha256(str(i).encode()).digest()) for i in range(5)] + # for index, h in enumerate(nonexisting_blob_hashes): + # print(f"\nIndex: {index}, Hash: {h.hex()}") + # print() + + blobs_test(pre=pre, txs=txs, nonexisting_blob_hashes=nonexisting_blob_hashes) From 11927712cc1d1dbe5bc89f07895d019bfb0fb4a9 Mon Sep 17 00:00:00 2001 From: Mario Vega Date: Mon, 25 Aug 2025 20:49:17 +0000 Subject: [PATCH 2/4] fix(plugins/forks): Remove evm_bin usage from forks plugin --- src/pytest_plugins/forks/forks.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pytest_plugins/forks/forks.py b/src/pytest_plugins/forks/forks.py index 61db09a664f..c8fd596042f 100644 --- a/src/pytest_plugins/forks/forks.py +++ b/src/pytest_plugins/forks/forks.py @@ -527,6 +527,7 @@ def get_fork_option(config, option_name: str, parameter_name: str) -> Set[Fork]: config.unsupported_forks = frozenset( # type: ignore fork for fork in selected_fork_set if not t8n.is_fork_supported(fork) ) + logger.debug(f"List of unsupported forks: {list(config.unsupported_forks)}") # type: ignore @pytest.hookimpl(trylast=True) From 57d2eb8f09e36bbfdbe7afa3a25dea7ddf8de02a Mon Sep 17 00:00:00 2001 From: Felix H Date: Tue, 26 Aug 2025 12:29:28 +0000 Subject: [PATCH 3/4] cherry-picked mario pr for removing evm-bin dependency --- pyproject.toml | 2 +- src/ethereum_clis/clis/execution_specs.py | 1 + src/ethereum_test_execution/blob_transaction.py | 1 + tests/osaka/eip7594_peerdas/test_get_blobs.py | 16 ++++++++++++++++ 4 files changed, 19 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index a2154f9b8ac..48ba580d04a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -174,4 +174,4 @@ addopts = [ required-version = ">=0.7.0" [tool.uv.sources] -ethereum-spec-evm-resolver = { git = "https://github.com/spencer-tb/ethereum-spec-evm-resolver", rev = "aec6a628b8d0f1c791a8378c5417a089566135ac" } \ No newline at end of file +ethereum-spec-evm-resolver = { git = "https://github.com/spencer-tb/ethereum-spec-evm-resolver", rev = "aec6a628b8d0f1c791a8378c5417a089566135ac" } diff --git a/src/ethereum_clis/clis/execution_specs.py b/src/ethereum_clis/clis/execution_specs.py index 984a1ad284f..b706599fc73 100644 --- a/src/ethereum_clis/clis/execution_specs.py +++ b/src/ethereum_clis/clis/execution_specs.py @@ -120,6 +120,7 @@ def is_fork_supported(self, fork: Fork) -> bool: """ fork_is_supported = (fork.transition_tool_name() + "\n") in self.help_string logger.debug(f"EELS supports fork {fork}: {fork_is_supported}") + return fork_is_supported def _generate_post_args( diff --git a/src/ethereum_test_execution/blob_transaction.py b/src/ethereum_test_execution/blob_transaction.py index 964f7e74d97..977c7871458 100644 --- a/src/ethereum_test_execution/blob_transaction.py +++ b/src/ethereum_test_execution/blob_transaction.py @@ -113,6 +113,7 @@ def execute( "Test was passed (partial responses are not allowed and the client " "correctly returned 'null')" ) + eth_rpc.wait_for_transactions(sent_txs) return assert blob_response is not None diff --git a/tests/osaka/eip7594_peerdas/test_get_blobs.py b/tests/osaka/eip7594_peerdas/test_get_blobs.py index 5bd16b63530..eeaa2123486 100644 --- a/tests/osaka/eip7594_peerdas/test_get_blobs.py +++ b/tests/osaka/eip7594_peerdas/test_get_blobs.py @@ -322,9 +322,25 @@ def test_get_blobs( Test valid blob combinations where one or more txs in the block serialized version contain a full blob (network version) tx. """ + blobs_test(pre=pre, txs=txs) + + +@pytest.mark.parametrize_by_fork( + "txs_blobs", + generate_valid_blob_tests, +) +@pytest.mark.exception_test +@pytest.mark.valid_from("Cancun") +def test_get_blobs_nonexisting( + blobs_test: BlobsTestFiller, + pre: Alloc, + txs: List[NetworkWrappedTransaction | Transaction], +): + """Test that ensures clients respond with 'null' when at least one requested blob is not available.""" # noqa: E501 nonexisting_blob_hashes = [Hash(sha256(str(i).encode()).digest()) for i in range(5)] # for index, h in enumerate(nonexisting_blob_hashes): # print(f"\nIndex: {index}, Hash: {h.hex()}") # print() blobs_test(pre=pre, txs=txs, nonexisting_blob_hashes=nonexisting_blob_hashes) + # TODO: why do all tests run twice (and fail the second time)? ethereum_test_rpc.rpc.SendTransactionExceptionError: JSONRPCError(code=-32000, message=address already reserved) # noqa: E501 From 6ad89a0a571a6a2024aec376d54e73ec08cceefb Mon Sep 17 00:00:00 2001 From: Felix H Date: Thu, 28 Aug 2025 10:15:08 +0000 Subject: [PATCH 4/4] mario feedback --- src/ethereum_test_execution/blob_transaction.py | 5 ++++- src/ethereum_test_rpc/rpc.py | 5 ++++- tests/osaka/eip7594_peerdas/test_get_blobs.py | 5 ----- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/ethereum_test_execution/blob_transaction.py b/src/ethereum_test_execution/blob_transaction.py index 977c7871458..8be0a25b440 100644 --- a/src/ethereum_test_execution/blob_transaction.py +++ b/src/ethereum_test_execution/blob_transaction.py @@ -12,9 +12,12 @@ from ethereum_test_rpc.types import GetBlobsResponse from ethereum_test_types import NetworkWrappedTransaction, Transaction from ethereum_test_types.transaction_types import TransactionTestMetadata +from pytest_plugins.logging import get_logger from .base import BaseExecute +logger = get_logger(__name__) + def versioned_hashes_with_blobs_and_proofs( tx: NetworkWrappedTransaction, @@ -109,7 +112,7 @@ def execute( f"{blob_response.root}" ) else: - print( + logger.info( "Test was passed (partial responses are not allowed and the client " "correctly returned 'null')" ) diff --git a/src/ethereum_test_rpc/rpc.py b/src/ethereum_test_rpc/rpc.py index 0a21b4f9b9c..e02bcd961ae 100644 --- a/src/ethereum_test_rpc/rpc.py +++ b/src/ethereum_test_rpc/rpc.py @@ -11,6 +11,7 @@ from ethereum_test_base_types import Address, Bytes, Hash, to_json from ethereum_test_types import Transaction +from pytest_plugins.logging import get_logger from .types import ( EthConfigResponse, @@ -24,6 +25,8 @@ TransactionByHashResponse, ) +logger = get_logger(__name__) + BlockNumberType = int | Literal["latest", "earliest", "pending"] @@ -407,7 +410,7 @@ def get_blobs( [f"{h}" for h in versioned_hashes], ) if response is None: # for tests that request non-existing blobs - print("get_blobs response received but it has value: None") + logger.debug("get_blobs response received but it has value: None") return None return GetBlobsResponse.model_validate( diff --git a/tests/osaka/eip7594_peerdas/test_get_blobs.py b/tests/osaka/eip7594_peerdas/test_get_blobs.py index eeaa2123486..5e8e9a350fe 100644 --- a/tests/osaka/eip7594_peerdas/test_get_blobs.py +++ b/tests/osaka/eip7594_peerdas/test_get_blobs.py @@ -338,9 +338,4 @@ def test_get_blobs_nonexisting( ): """Test that ensures clients respond with 'null' when at least one requested blob is not available.""" # noqa: E501 nonexisting_blob_hashes = [Hash(sha256(str(i).encode()).digest()) for i in range(5)] - # for index, h in enumerate(nonexisting_blob_hashes): - # print(f"\nIndex: {index}, Hash: {h.hex()}") - # print() - blobs_test(pre=pre, txs=txs, nonexisting_blob_hashes=nonexisting_blob_hashes) - # TODO: why do all tests run twice (and fail the second time)? ethereum_test_rpc.rpc.SendTransactionExceptionError: JSONRPCError(code=-32000, message=address already reserved) # noqa: E501