Skip to content

feat(consume): add consume enginex simulator #1765

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 23 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
6b2a990
refactor(consume): rename `hive_simulators` to `simulators`.
danceratopz Jun 13, 2025
ece1a14
refactor(consume): move helper modules to a new `helpers` sub-package
danceratopz Jun 13, 2025
33b2a07
refactor(consume): rename `conftest` to `single_test_client` plugin
danceratopz Jun 13, 2025
7c40b47
refactor(consume): split into separate plugins by functionality
danceratopz Jun 13, 2025
c90f633
refactor(consume): split base functionality out to the `base` plugin
danceratopz Jun 13, 2025
44b7cd4
feat(consume): add initial implementation of consume enginex
danceratopz Jun 18, 2025
bb814ac
refactor(docs): move `engine_reorg` -> `engine_x`
danceratopz Jun 17, 2025
3537b50
refactor(all): rename 'engine reorg' to 'engine x'
danceratopz Jun 17, 2025
0288797
refactor(all): update lang from 'shared pre-alloc' to 'pre-allocation…
danceratopz Jun 17, 2025
d197aaf
refactor(all): update lang from 'shared pre-alloc' to 'pre-allocation…
danceratopz Jun 17, 2025
3694db0
docs: update changelog
danceratopz Jun 17, 2025
24303a1
refactor(filler): rename flags for consistency with class names
danceratopz Jun 17, 2025
b512c43
fix(consume): update according to renaming in #1760
danceratopz Jun 18, 2025
2a9ec2d
refactor(consume): rename & simplify consume enginex command
danceratopz Jun 18, 2025
f373b65
fix(consume): add `genesis_header` to `single_test_cilent` plugin
danceratopz Jun 18, 2025
e308b78
refactor(consume): rename simulator path to enginex
danceratopz Jun 18, 2025
dc03012
refactor(consume): rename/update w/enginex & pre-alloc group terminology
danceratopz Jun 18, 2025
8a13d1e
feat(consume): track tests by pre-alloc group for client cleanup
danceratopz Jun 18, 2025
3eb1379
feat(consume): add `--enginex-fcu-frequency` option
danceratopz Jun 18, 2025
d99ce2a
feat(consume): append enginexfixture pre_hash field to testid if avai…
danceratopz Jun 20, 2025
92c25d0
fix(consume): explicitly depend on hive_test fixture for result propa…
danceratopz Jun 20, 2025
7f29345
feat(consume): create pre_hash subgroups w/max size for better xdist …
danceratopz Jun 20, 2025
7bacee4
fix(consume): recognize `--enginex-max-group-size` option in consume.…
danceratopz Jun 20, 2025
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
6 changes: 3 additions & 3 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@ Users can select any of the artifacts depending on their testing needs for their

#### `fill`

- ✨ Add the `ported_from` test marker to track Python test cases that were converted from static fillers in [ethereum/tests](https://github.com/ethereum/tests) repository [#1590](https://github.com/ethereum/execution-spec-tests/pull/1590).
- ✨ Add a new pytest plugin, `ported_tests`, that lists the static fillers and PRs from `ported_from` markers for use in the coverage Github Workflow [#1634](https://github.com/ethereum/execution-spec-tests/pull/1634).
- ✨ Enable two-phase filling of fixtures with shared pre-allocation groups and add a `BlockchainEngineReorgFixture` format [#1606](https://github.com/ethereum/execution-spec-tests/pull/1706).
- ✨ Add the `ported_from` test marker to track Python test cases that were converted from static fillers in [ethereum/tests](https://github.com/ethereum/tests) repository ([#1590](https://github.com/ethereum/execution-spec-tests/pull/1590)).
- ✨ Add a new pytest plugin, `ported_tests`, that lists the static fillers and PRs from `ported_from` markers for use in the coverage Github Workflow ([#1634](https://github.com/ethereum/execution-spec-tests/pull/1634)).
- ✨ Enable two-phase filling of fixtures with pre-allocation groups and add a `BlockchainEngineXFixture` format ([#1706](https://github.com/ethereum/execution-spec-tests/pull/1706), [#1760](https://github.com/ethereum/execution-spec-tests/pull/1760)).
- 🔀 Refactor: Encapsulate `fill`'s fixture output options (`--output`, `--flat-output`, `--single-fixture-per-file`) into a `FixtureOutput` class ([#1471](https://github.com/ethereum/execution-spec-tests/pull/1471),[#1612](https://github.com/ethereum/execution-spec-tests/pull/1612)).
- ✨ Don't warn about a "high Transaction gas_limit" for `zkevm` tests ([#1598](https://github.com/ethereum/execution-spec-tests/pull/1598)).
- 🐞 `fill` no longer writes generated fixtures into an existing, non-empty output directory; it must now be empty or `--clean` must be used to delete it first ([#1608](https://github.com/ethereum/execution-spec-tests/pull/1608)).
Expand Down
2 changes: 1 addition & 1 deletion docs/library/cli/extract_config.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ Since Hive doesn't directly expose container IDs, the tool uses a detection mech
The tool supports:

- Individual fixture JSON files (BlockchainFixture format)
- SharedPreStateGroup JSON files
- PreAllocGroup JSON files
- Directories containing multiple fixture files

## Troubleshooting
Expand Down
2 changes: 1 addition & 1 deletion docs/navigation.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
* [State Tests](running_tests/test_formats/state_test.md)
* [Blockchain Tests](running_tests/test_formats/blockchain_test.md)
* [Blockchain Engine Tests](running_tests/test_formats/blockchain_test_engine.md)
* [Blockchain Engine Reorg Tests](running_tests/test_formats/blockchain_test_engine_reorg.md)
* [Blockchain Engine X Tests](running_tests/test_formats/blockchain_test_engine_x.md)
* [EOF Tests](running_tests/test_formats/eof_test.md)
* [Transaction Tests](running_tests/test_formats/transaction_test.md)
* [Common Types](running_tests/test_formats/common_types.md)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,30 +1,30 @@
# Blockchain Engine Reorg Tests <!-- markdownlint-disable MD051 (MD051=link-fragments "Link fragments should be valid") -->
# Blockchain Engine X Tests <!-- markdownlint-disable MD051 (MD051=link-fragments "Link fragments should be valid") -->

The Blockchain Engine Reorg Test fixture format tests are included in the fixtures subdirectory `blockchain_tests_engine_reorg`, and use Engine API directives with optimized shared pre-allocation for improved execution performance.
The Blockchain Engine X Test fixture format tests are included in the fixtures subdirectory `blockchain_tests_engine_x`, and use Engine API directives with optimized pre-allocation groups for improved execution performance.

These are produced by the `StateTest` and `BlockchainTest` test specs when using the `--generate-shared-pre` and `--use-shared-pre` flags.
These are produced by the `StateTest` and `BlockchainTest` test specs when using the `--generate-pre-alloc-groups` and `--use-pre-alloc-groups` flags.

## Description

The Blockchain Engine Reorg Test fixture format is an optimized variant of the [Blockchain Engine Test](./blockchain_test_engine.md) format designed for large-scale test execution with performance optimizations.
The Blockchain Engine X Test fixture format is an optimized variant of the [Blockchain Engine Test](./blockchain_test_engine.md) format designed for large-scale test execution with performance optimizations.

It uses the Engine API to test block validation and consensus rules while leveraging **shared pre-allocation state** to significantly reduce test execution time and resource usage. Tests are grouped by their initial state (fork + environment + pre-allocation) and share common genesis states through blockchain reorganization.
It uses the Engine API to test block validation and consensus rules while leveraging **pre-allocation groups** to significantly reduce test execution time and resource usage. Tests are grouped by their initial state (fork + environment + pre-allocation). Each group is executed against the same client instance using a common genesis state.

The key optimization is that **clients need only be started once per group** instead of once per test (as in the original engine fixture format), dramatically improving execution performance for large test suites.

Instead of including large pre-allocation state in each test fixture, this format references a shared pre-allocation folder (`pre_alloc`) which includes all different pre-allocation combinations used for any test fixture group.
Instead of including large pre-allocation state in each test fixture, this format references a pre-allocation groups folder (`pre_alloc`) which contains all different pre-allocation combinations organized by group.

A single JSON fixture file is composed of a JSON object where each key-value pair is a different [`ReorgFixture`](#reorgfixture) test object, with the key string representing the test name.
A single JSON fixture file is composed of a JSON object where each key-value pair is a different [`BlockchainTestEngineXFixture`](#BlockchainTestEngineXFixture) test object, with the key string representing the test name.

The JSON file path plus the test name are used as the unique test identifier.

## Shared Pre-Allocation File
## Pre-Allocation Groups Folder

The `blockchain_tests_engine_reorg` directory contains a special directory `pre_alloc` that stores shared pre-allocation state file used by all tests in this format, one per pre-allocation group with the name of the pre-alloc hash. This folder is essential for test execution and must be present alongside the test fixtures.
The `blockchain_tests_engine_x` directory contains a special directory `pre_alloc` that stores pre-allocation group files used by all tests in this format, one per pre-allocation group with the name of the pre-alloc hash. This folder is essential for test execution and must be present alongside the test fixtures.

### Pre-Allocation File Structure
### Pre-Allocation Group File Structure

Each file in the `pre_alloc` folder corresponds to a pre-allocation hash to shared state groups:
Each file in the `pre_alloc` folder corresponds to a pre-allocation group identified by a hash:

```json
{
Expand All @@ -37,28 +37,28 @@ Each file in the `pre_alloc` folder corresponds to a pre-allocation hash to shar
}
```

#### SharedPreStateGroup Fields
#### Pre-Allocation Group Fields

- **`test_count`**: Number of tests sharing this pre-allocation group
- **`pre_account_count`**: Number of accounts in the shared pre-allocation state
- **`testIds`**: Array of test identifiers that use this shared state
- **`test_count`**: Number of tests in this pre-allocation group
- **`pre_account_count`**: Number of accounts in the pre-allocation group
- **`testIds`**: Array of test identifiers that belong to this group
- **`network`**: Fork name (e.g., "Prague", "Cancun")
- **`environment`**: Complete [`Environment`](./common_types.md#environment) object with execution context
- **`pre`**: Shared [`Alloc`](./common_types.md#alloc-mappingaddressaccount) object containing initial account states
- **`pre`**: Pre-allocation group [`Alloc`](./common_types.md#alloc-mappingaddressaccount) object containing initial account states

## Consumption

For each [`ReorgFixture`](#reorgfixture) test object in the JSON fixture file, perform the following steps:
For each [`BlockchainTestEngineXFixture`](#BlockchainTestEngineXFixture) test object in the JSON fixture file, perform the following steps:

1. **Load Shared Pre-Allocation**:
1. **Load Pre-Allocation Group**:
- Read the appropriate file from the `pre_alloc` folder in the same directory
- Locate the shared state group using [`preHash`](#-prehash-string)
- Extract the `pre` allocation and `environment` from the shared group
- Locate the pre-allocation group using [`preHash`](#-prehash-string)
- Extract the `pre` allocation and `environment` from the group

2. **Initialize Client**:
- Use [`network`](#-network-fork) to configure the execution fork schedule
- Use the shared `pre` allocation as the starting state
- Use the shared `environment` as the execution context
- Use the pre-allocation group's `pre` allocation as the starting state
- Use the pre-allocation group's `environment` as the execution context
- Use [`genesisBlockHeader`](#-genesisblockheader-fixtureheader) as the genesis block header

3. **Execute Engine API Sequence**:
Expand All @@ -70,13 +70,13 @@ For each [`ReorgFixture`](#reorgfixture) test object in the JSON fixture file, p
4. **Verify Final State**:
- Compare the final chain head against [`lastblockhash`](#-lastblockhash-hash)
- If [`postStateDiff`](#-poststatediff-optionalalloc) is present:
- Apply the state differences to the shared pre-allocation
- Apply the state differences to the pre-allocation group
- Verify the resulting state matches the client's final state
- If `post` field were present (not typical), verify it directly

## Structures

### `ReorgFixture`
### `BlockchainTestEngineXFixture`

#### - `network`: [`Fork`](./common_types.md#fork)

Expand All @@ -88,39 +88,39 @@ This field is going to be replaced by the value contained in `config.network`.

#### - `preHash`: `string`

Hash identifier referencing a shared pre-allocation group in the `pre_alloc` folder. This hash uniquely identifies the combination of fork, environment, and pre-allocation state shared by multiple tests.
Hash identifier referencing a pre-allocation group in the `pre_alloc` folder. This hash uniquely identifies the combination of fork, environment, and pre-allocation state that defines the group.

#### - `genesisBlockHeader`: [`FixtureHeader`](./blockchain_test.md#fixtureheader)

Genesis block header. The state root in this header must match the state root calculated from the shared pre-allocation referenced by [`preHash`](#-prehash-string).
Genesis block header. The state root in this header must match the state root calculated from the pre-allocation group referenced by [`preHash`](#-prehash-string).

#### - `engineNewPayloads`: [`List`](./common_types.md#list)`[`[`FixtureEngineNewPayload`](#fixtureenginenewpayload)`]`

List of `engine_newPayloadVX` directives to be processed after the genesis block. These define the sequence of blocks to be executed via the Engine API.

#### - `syncPayload`: [`Optional`](./common_types.md#optional)`[`[`FixtureEngineNewPayload`](#fixtureenginenewpayload)`]`

Optional synchronization payload used for blockchain reorganization scenarios. When present, this payload is typically used to sync the chain to a specific state before or after the main payload sequence.
Optional synchronization payload. When present, this payload is typically used to sync the chain to a specific state before or after the main payload sequence.

#### - `lastblockhash`: [`Hash`](./common_types.md#hash)

Hash of the last valid block after all payloads have been processed, or the genesis block hash if all payloads are invalid.

#### - `postStateDiff`: [`Optional`](./common_types.md#optional)`[`[`Alloc`](./common_types.md#alloc-mappingaddressaccount)`]`

State differences from the shared pre-allocation state after test execution. This optimization stores only the accounts that changed, were created, or were deleted during test execution, rather than the complete final state.
State differences from the pre-allocation group after test execution. This optimization stores only the accounts that changed, were created, or were deleted during test execution, rather than the complete final state.

To reconstruct the final state:

1. Start with the shared pre-allocation from the `pre_alloc` folder
1. Start with the pre-allocation group from the `pre_alloc` folder
2. Apply the changes in `postStateDiff`:
- **Modified accounts**: Replace existing accounts with new values
- **New accounts**: Add accounts not present in pre-allocation
- **Deleted accounts**: Remove accounts (represented as `null` values)

#### - `config`: [`FixtureConfig`](#fixtureconfig)

Chain configuration object to be applied to the client running the blockchain engine reorg test.
Chain configuration object to be applied to the client running the blockchain engine x test.

### `FixtureConfig`

Expand All @@ -138,8 +138,7 @@ Engine API payload structure identical to the one defined in [Blockchain Engine

## Usage Notes

- This format is only generated when using `--generate-shared-pre` and `--use-shared-pre` flags
- This format is only generated when using `--generate-pre-alloc-groups` and `--use-pre-alloc-groups` flags
- The `pre_alloc` folder is essential and must be distributed with the test fixtures
- Tests are grouped by identical (fork + environment + pre-allocation) combinations
- The format is optimized for Engine API testing (post-Paris forks)
- Reorganization scenarios are supported through the `forkChoiceUpdate` mechanism
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ classifiers = [
dependencies = [
"click>=8.1.0,<9",
"ethereum-execution==1.17.0rc6.dev1",
"hive.py @ git+https://github.com/marioevz/hive.py",
"hive-py",
"ethereum-spec-evm-resolver",
"gitpython>=3.1.31,<4",
"PyJWT>=2.3.0,<3",
Expand Down Expand Up @@ -148,3 +148,4 @@ ignore-words-list = "ingenuous"

[tool.uv.sources]
ethereum-spec-evm-resolver = { git = "https://github.com/petertdavies/ethereum-spec-evm-resolver", rev = "0e5609737ce4f86dc98cca1a5cf0eb64b8cddef2" }
hive-py = { git = "https://github.com/marioevz/hive.py", rev = "582703e2f94b4d5e61ae495d90d684852c87a580" }
6 changes: 3 additions & 3 deletions pytest-framework.ini
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ addopts =
--ignore=src/pytest_plugins/consume/test_cache.py
--ignore=src/pytest_plugins/consume/direct/
--ignore=src/pytest_plugins/consume/direct/test_via_direct.py
--ignore=src/pytest_plugins/consume/hive_simulators/
--ignore=src/pytest_plugins/consume/hive_simulators/engine/test_via_engine.py
--ignore=src/pytest_plugins/consume/hive_simulators/rlp/test_via_rlp.py
--ignore=src/pytest_plugins/consume/simulators/
--ignore=src/pytest_plugins/consume/simulators/engine/test_via_engine.py
--ignore=src/pytest_plugins/consume/simulators/rlp/test_via_rlp.py
--ignore=src/pytest_plugins/execute/test_recover.py
10 changes: 5 additions & 5 deletions src/cli/extract_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@
from ethereum_test_fixtures import BlockchainFixtureCommon
from ethereum_test_fixtures.blockchain import FixtureHeader
from ethereum_test_fixtures.file import Fixtures
from ethereum_test_fixtures.shared_alloc import SharedPreStateGroup
from ethereum_test_fixtures.pre_alloc_groups import PreAllocGroup
from ethereum_test_forks import Fork
from pytest_plugins.consume.hive_simulators.ruleset import ruleset
from pytest_plugins.consume.simulators.helpers.ruleset import ruleset


def get_docker_containers() -> set[str]:
Expand Down Expand Up @@ -111,9 +111,9 @@ def create_genesis_from_fixture(fixture_path: Path) -> Tuple[FixtureHeader, Allo
alloc = fixture.pre
chain_id = int(fixture.config.chain_id)
else:
shared_alloc = SharedPreStateGroup.model_validate(fixture_json)
genesis = shared_alloc.genesis # type: ignore
alloc = shared_alloc.pre
pre_alloc_group = PreAllocGroup.model_validate(fixture_json)
genesis = pre_alloc_group.genesis # type: ignore
alloc = pre_alloc_group.pre

return genesis, alloc, chain_id

Expand Down
56 changes: 45 additions & 11 deletions src/cli/pytest_commands/consume.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,14 @@
class ConsumeCommand(PytestCommand):
"""Pytest command for consume operations."""

def __init__(self, command_paths: List[Path], is_hive: bool = False):
def __init__(self, command_paths: List[Path], is_hive: bool = False, command_name: str = ""):
"""Initialize consume command with paths and processors."""
processors: List[ArgumentProcessor] = [HelpFlagsProcessor("consume")]

if is_hive:
processors.extend(
[
HiveEnvironmentProcessor(),
HiveEnvironmentProcessor(command_name=command_name),
ConsumeCommandProcessor(is_hive=True),
]
)
Expand Down Expand Up @@ -54,13 +54,15 @@ def get_command_paths(command_name: str, is_hive: bool) -> List[Path]:
base_path = Path("src/pytest_plugins/consume")
if command_name == "hive":
commands = ["rlp", "engine"]
command_paths = [base_path / "simulators" / cmd / f"test_via_{cmd}.py" for cmd in commands]
elif command_name in ["engine", "enginex"]:
command_paths = [base_path / "simulators" / "hive_tests" / "test_via_engine.py"]
elif command_name == "rlp":
command_paths = [base_path / "simulators" / "hive_tests" / "test_via_rlp.py"]
elif command_name == "direct":
command_paths = [base_path / "direct" / "test_via_direct.py"]
else:
commands = [command_name]

command_paths = [
base_path / ("hive_simulators" if is_hive else "") / cmd / f"test_via_{cmd}.py"
for cmd in commands
]
raise ValueError(f"Unexpected command: {command_name}.")
return command_paths


Expand All @@ -86,7 +88,7 @@ def decorator(func: Callable[..., Any]) -> click.Command:
@common_pytest_options
@functools.wraps(func)
def command(pytest_args: List[str], **kwargs) -> None:
consume_cmd = ConsumeCommand(command_paths, is_hive)
consume_cmd = ConsumeCommand(command_paths, is_hive, command_name)
consume_cmd.execute(list(pytest_args))

return command
Expand All @@ -108,13 +110,45 @@ def rlp() -> None:

@consume_command(is_hive=True)
def engine() -> None:
"""Client consumes via the Engine API."""
"""Client consumes Engine Fixtures via the Engine API."""
pass


@consume.command(
name="enginex",
help="Client consumes Engine X Fixtures via the Engine API.",
context_settings={"ignore_unknown_options": True},
)
@click.option(
"--enginex-fcu-frequency",
type=int,
default=1,
help=(
"Control forkchoice update frequency for enginex simulator. "
"0=disable FCUs, 1=FCU every test (default), N=FCU every Nth test per "
"pre-allocation group."
),
)
@common_pytest_options
def enginex(enginex_fcu_frequency: int, pytest_args: List[str], **_kwargs) -> None:
"""Client consumes Engine X Fixtures via the Engine API."""
command_name = "enginex"
command_paths = get_command_paths(command_name, is_hive=True)

# Validate the frequency parameter
if enginex_fcu_frequency < 0:
raise click.BadParameter("FCU frequency must be non-negative")

# Add the FCU frequency to pytest args as a custom config option
pytest_args_with_fcu = [f"--enginex-fcu-frequency={enginex_fcu_frequency}"] + list(pytest_args)

consume_cmd = ConsumeCommand(command_paths, is_hive=True, command_name=command_name)
consume_cmd.execute(pytest_args_with_fcu)


@consume_command(is_hive=True)
def hive() -> None:
"""Client consumes via all available hive methods (rlp, engine)."""
"""Client consumes via rlp & engine hive methods."""
pass


Expand Down
Loading
Loading