Skip to content
Open
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
17 changes: 15 additions & 2 deletions .github/workflows/hive-consume.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,28 @@ jobs:
- name: consume-engine
mode: simulator
simulator: ethereum/eels/consume-engine
sim_limit: ".*test_block_at_rlp_limit_with_logs.*Osaka.*"
# TODO: Enable once eels/consume-enginex simulator is added to Hive
# - name: consume-enginex
# mode: simulator
# simulator: ethereum/eels/consume-enginex
# sim_limit: ".*push0.*(Shanghai|Cancun).*"
- name: consume-rlp
mode: simulator
simulator: ethereum/eels/consume-rlp
sim_limit: ".*test_block_at_rlp_limit_with_logs.*Osaka.*"
- name: consume-sync
mode: simulator
simulator: ethereum/eels/consume-sync
sim_limit: ".*test_block_at_rlp_limit_with_logs.*Osaka.*"
- name: dev-mode
mode: dev
consume_command: engine
test_filter: "Osaka and test_block_at_rlp_limit_with_logs"
- name: dev-mode-enginex
mode: dev
consume_command: enginex
test_filter: "(fork_shanghai or fork_cancun) and push0"
steps:
- name: Checkout execution-specs
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
Expand Down Expand Up @@ -85,7 +98,7 @@ jobs:
--client go-ethereum \
--client-file ../execution-specs/.github/configs/hive/latest.yaml \
--sim.buildarg fixtures=${{ env.FIXTURES_URL }} \
--sim.limit=".*test_block_at_rlp_limit_with_logs.*Osaka.*" \
--sim.limit="${{ matrix.sim_limit }}" \
--docker.output

- name: Start Hive in dev mode
Expand All @@ -105,4 +118,4 @@ jobs:
HIVE_SIMULATOR: ${{ steps.start-hive.outputs.hive-url }}
run: |
uv sync --all-extras
uv run consume ${{ matrix.consume_command }} --input ${{ env.FIXTURES_URL }} -k "Osaka and test_block_at_rlp_limit_with_logs"
uv run consume ${{ matrix.consume_command }} --input ${{ env.FIXTURES_URL }} -k "${{ matrix.test_filter }}"
43 changes: 43 additions & 0 deletions docs/running_tests/running.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ Both `consume` and `execute` provide sub-commands which correspond to different
| [`consume direct`](#direct) | Client consume tests via a `statetest` interface | EVM | None | Module test |
| [`consume direct`](#direct) | Client consume tests via a `blocktest` interface | EVM, block processing | None | Module test,</br>Integration test |
| [`consume engine`](#engine) | Client imports blocks via Engine API `EngineNewPayload` in Hive | EVM, block processing, Engine API | Staging, Hive | System test |
| [`consume enginex`](#enginex) | Client imports blocks via Engine API in Hive with, optimized by client reuse | EVM, block processing, Engine API | Staging, Hive | System test |
| [`consume sync`](#sync) | Client syncs from another client using Engine API in Hive | EVM, block processing, Engine API, P2P sync | Staging, Hive | System test |
| [`consume rlp`](#rlp) | Client imports RLP-encoded blocks upon start-up in Hive | EVM, block processing, RLP import (sync\*) | Staging, Hive | System test |
| [`execute hive`](./execute/hive.md) | Tests executed against a client via JSON RPC `eth_sendRawTransaction` in Hive | EVM, JSON RPC, mempool | Staging, Hive | System test |
Expand Down Expand Up @@ -62,6 +63,48 @@ The `consume engine` command:
5. **Validates responses** against expected results.
6. **Tests error conditions** and exception handling.

## EngineX

| Nomenclature | |
| -------------- | -------------------------- |
| Command | `consume enginex` |
| Simulator | `eels/consume-enginex` |
| Fixture format | `blockchain_test_engine_x` |

The EngineX method is a faster alternative to `consume engine` that executes multiple tests against a single client instance. This is achieved via the [Blockchain Engine X Test fixture format](./test_formats/blockchain_test_engine_x.md) which groups tests that share the same fork and EVM [Environment](./test_formats/state_test.md#fixtureenvironment) together and contains a larger, shared pre-allocation state that all tests in the group use. This allows the EngineX simulator to execute multiple tests against the same client instance, whereas the Engine Simulator starts a fresh client for each test.

The `consume enginex` command, for each pre-allocation group:

1. **Initializes the execution client** with the group's shared genesis state.
2. **Connects via Engine API** (port 8551).
3. **Executes all tests in the group** against the same client:

- Submits payloads from each test using `engine_newPayload` calls.
- Validates responses against expected results.
- Tests error conditions and exception handling.

4. **Stops the client** when all tests in the group complete.

### Engine vs EngineX

| | `consume engine` | `consume enginex` |
| -------------------- | ---------------------------------------------------------------------- | ------------------------------------------------------------------------ |
| **Fixture format** | [`blockchain_test_engine`](./test_formats/blockchain_test_engine_x.md) | [`blockchain_test_engine_x`](./test_formats/blockchain_test_engine_x.md) |
| **Client lifecycle** | New client per test | Client reused across tests with same pre-alloc |
| **Fork choice update** | FCU called for genesis and final payload | FCU for genesis and final payload skipped |
| **Execution speed** | Slower (client startup overhead) | Faster (amortized startup cost) |
| **Test isolation** | Full isolation | Shared genesis state within group |

EngineX achieves faster execution by:

1. **Grouping tests** by their pre-allocation state (genesis configuration).
2. **Reusing clients** across all tests in a group, avoiding repeated client startup.
3. **Skipping redundant initialization** since the client is already at the expected genesis state.

!!! note "When to use EngineX vs Engine"

Use `consume enginex` for faster test runs when full per-test isolation is not required. Use `consume engine` when you need complete isolation between tests or when debugging issues triggered by a single test case.

## RLP

| Nomenclature | |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,13 @@ def create_consume_command(
def get_command_logic_test_paths(command_name: str) -> List[Path]:
"""Determine the command paths based on the command name and hive flag."""
base_path = Path("cli/pytest_commands/plugins/consume")
if command_name in ["engine", "rlp"]:
if command_name in ["engine", "enginex", "rlp"]:
test_command = "engine" if command_name == "enginex" else command_name
command_logic_test_paths = [
base_path
/ "simulators"
/ "simulator_logic"
/ f"test_via_{command_name}.py"
/ f"test_via_{test_command}.py"
]
elif command_name == "sync":
command_logic_test_paths = [
Expand Down Expand Up @@ -119,6 +120,12 @@ def engine() -> None:
pass


@consume_command(is_hive=True)
def enginex() -> None:
"""Client consumes via the Engine API using Engine X fixtures with pre-alloc optimization."""
pass


@consume_command(is_hive=True)
def sync() -> None:
"""Client consumes via the Engine API with sync testing."""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,11 @@
from execution_testing.cli.gen_index import (
generate_fixtures_index,
)
from execution_testing.fixtures import BaseFixture, FixtureFormat
from execution_testing.fixtures import (
BaseFixture,
BlockchainEngineXFixture,
FixtureFormat,
)
from execution_testing.fixtures.consume import IndexFile, TestCases
from execution_testing.forks import (
get_forks,
Expand Down Expand Up @@ -588,11 +592,24 @@ def pytest_generate_tests(metafunc: pytest.Metafunc) -> None:
fork_markers = get_relative_fork_markers(
test_case.fork, strict_mode=False
)

# Build base marks (fork and format)
marks = [getattr(pytest.mark, m) for m in fork_markers] + [
getattr(pytest.mark, test_case.format.format_name)
]

# Add xdist_group marker for engine x tests to enable client reuse tracking
if test_case.format is BlockchainEngineXFixture:
assert hasattr(test_case, "pre_hash") and test_case.pre_hash, (
f"BlockchainEngineXFixture test case '{test_case.id}' missing pre_hash"
)
group_identifier = test_case.pre_hash
marks.append(pytest.mark.xdist_group(name=group_identifier))

param = pytest.param(
test_case,
id=test_case.id,
marks=[getattr(pytest.mark, m) for m in fork_markers]
+ [getattr(pytest.mark, test_case.format.format_name)],
marks=marks,
)
param_list.append(param)

Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1 @@
"""Consume hive simulators test functions."""
"""Consume Hive simulators test functions."""
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,11 @@ def check_live_port(test_suite_name: str) -> Literal[8545, 8551]:
"""Port used by hive to check for liveness of the client."""
if test_suite_name == "eels/consume-rlp":
return 8545
elif test_suite_name in {"eels/consume-engine", "eels/consume-sync"}:
elif test_suite_name in {
"eels/consume-engine",
"eels/consume-enginex",
"eels/consume-sync",
}:
return 8551
raise ValueError(
f"Unexpected test suite name '{test_suite_name}' while setting HIVE_CHECK_LIVE_PORT."
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,9 @@
from typing import Mapping

import pytest
from hive.client import Client

from execution_testing.exceptions import ExceptionMapper
from execution_testing.fixtures import BlockchainEngineFixture
from execution_testing.rpc import EngineRPC
from execution_testing.fixtures.blockchain import FixtureHeader

pytest_plugins = (
"execution_testing.cli.pytest_commands.plugins.pytest_hive.pytest_hive",
Expand All @@ -21,6 +19,7 @@
"execution_testing.cli.pytest_commands.plugins.consume.simulators.test_case_description",
"execution_testing.cli.pytest_commands.plugins.consume.simulators.timing_data",
"execution_testing.cli.pytest_commands.plugins.consume.simulators.exceptions",
"execution_testing.cli.pytest_commands.plugins.consume.simulators.engine_api",
)


Expand All @@ -29,21 +28,6 @@ def pytest_configure(config: pytest.Config) -> None:
config.supported_fixture_formats = [BlockchainEngineFixture] # type: ignore[attr-defined]


@pytest.fixture(scope="function")
def engine_rpc(
client: Client, client_exception_mapper: ExceptionMapper | None
) -> EngineRPC:
"""Initialize engine RPC client for the execution client under test."""
if client_exception_mapper:
return EngineRPC(
f"http://{client.ip}:8551",
response_validation_context={
"exception_mapper": client_exception_mapper,
},
)
return EngineRPC(f"http://{client.ip}:8551")


@pytest.fixture(scope="module")
def test_suite_name() -> str:
"""The name of the hive test suite used in this simulator."""
Expand All @@ -64,3 +48,9 @@ def client_files(
files = {}
files["/genesis.json"] = buffered_genesis
return files


@pytest.fixture(scope="function")
def genesis_header(fixture: BlockchainEngineFixture) -> "FixtureHeader":
"""Provide the genesis header from the fixture (engine mode compatibility)."""
return fixture.genesis
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
"""Pytest fixtures for Engine API RPC clients."""

import pytest
from hive.client import Client

from execution_testing.exceptions import ExceptionMapper
from execution_testing.rpc import EngineRPC


@pytest.fixture(scope="function")
def engine_rpc(
client: Client, client_exception_mapper: ExceptionMapper | None
) -> EngineRPC:
"""
Initialize Engine RPC client for the execution client under test.

This fixture provides a configured EngineRPC instance that communicates with
the client's Engine API endpoint (port 8551). If an exception mapper is
available, it will be used for response validation to map client-specific
error messages to standard exception types.

Args:
client: The Hive client instance to connect to.
client_exception_mapper: Optional exception mapper for response validation.

Returns:
Configured EngineRPC instance for making Engine API calls.
"""
if client_exception_mapper:
return EngineRPC(
f"http://{client.ip}:8551",
response_validation_context={
"exception_mapper": client_exception_mapper,
},
)
return EngineRPC(f"http://{client.ip}:8551")
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Engine X simulator for `blockchain_test_engine_x` fixtures."""
Loading
Loading