Skip to content
Merged
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
1 change: 1 addition & 0 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ Users can select any of the artifacts depending on their testing needs for their
- 💥 Flag `--flat-output` has been removed due to having been unneeded for an extended period of time ([#2018](https://github.com/ethereum/execution-spec-tests/pull/2018)).
- ✨ Add support for `BlockchainEngineSyncFixture` format for tests marked with `pytest.mark.verify_sync` to enable client synchronization testing via `consume sync` command ([#2007](https://github.com/ethereum/execution-spec-tests/pull/2007)).
- ✨ Framework is updated to include BPO ([EIP-7892](https://eips.ethereum.org/EIPS/eip-7892)) fork markers to enable the filling of BPO tests ([#2050](https://github.com/ethereum/execution-spec-tests/pull/2050)).
- ✨ Generate and include execution witness data in blockchain fixtures if `--witness` is specified ([#2066](https://github.com/ethereum/execution-spec-tests/pull/2066)).

#### `consume`

Expand Down
1 change: 1 addition & 0 deletions src/cli/pytest_commands/pytest_ini_files/pytest-fill.ini
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ addopts =
-p pytest_plugins.concurrency
-p pytest_plugins.filler.pre_alloc
-p pytest_plugins.filler.filler
-p pytest_plugins.filler.witness
-p pytest_plugins.shared.execute_fill
-p pytest_plugins.filler.ported_tests
-p pytest_plugins.filler.static_filler
Expand Down
20 changes: 20 additions & 0 deletions src/ethereum_test_fixtures/blockchain.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""BlockchainTest types."""

import json
from functools import cached_property
from typing import (
Annotated,
Expand Down Expand Up @@ -401,13 +402,32 @@ def from_withdrawal(cls, w: WithdrawalGeneric) -> "FixtureWithdrawal":
return cls(**w.model_dump())


class WitnessChunk(CamelModel):
"""Represents execution witness data for a block."""

state: List[str]
codes: List[str]
keys: List[str]
headers: List[str]

@classmethod
def parse_witness_chunks(cls, s: str) -> List["WitnessChunk"]:
"""
Parse multiple witness chunks from JSON string.

Returns a list of WitnessChunk instances parsed from the JSON array.
"""
return [cls(**obj) for obj in json.loads(s)]


class FixtureBlockBase(CamelModel):
"""Representation of an Ethereum block within a test Fixture without RLP bytes."""

header: FixtureHeader = Field(..., alias="blockHeader")
txs: List[FixtureTransaction] = Field(default_factory=list, alias="transactions")
ommers: List[FixtureHeader] = Field(default_factory=list, alias="uncleHeaders")
withdrawals: List[FixtureWithdrawal] | None = None
execution_witness: WitnessChunk | None = None

@computed_field(alias="blocknumber") # type: ignore[misc]
@cached_property
Expand Down
5 changes: 5 additions & 0 deletions src/pytest_plugins/filler/filler.py
Original file line number Diff line number Diff line change
Expand Up @@ -1140,6 +1140,7 @@ def base_test_parametrizer_func(
test_case_description: str,
fixture_source_url: str,
gas_benchmark_value: int,
witness_generator,
):
"""
Fixture used to instantiate an auto-fillable BaseTest object from within
Expand Down Expand Up @@ -1223,6 +1224,10 @@ def __init__(self, *args, **kwargs):
_info_metadata=t8n._info_metadata,
)

# Generate witness data if witness functionality is enabled via the witness plugin
if witness_generator is not None:
witness_generator(fixture)

fixture_path = fixture_collector.add_fixture(
node_to_test_info(request.node),
fixture,
Expand Down
129 changes: 129 additions & 0 deletions src/pytest_plugins/filler/witness.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
"""
Pytest plugin for witness functionality.

Provides --witness command-line option that checks for the witness-filler tool in PATH
and generates execution witness data for blockchain test fixtures when enabled.
"""

import shutil
import subprocess
from typing import Callable, List

import pytest

from ethereum_test_base_types import EthereumTestRootModel
from ethereum_test_fixtures.blockchain import BlockchainFixture, FixtureBlock, WitnessChunk
from ethereum_test_forks import Paris


class WitnessFillerResult(EthereumTestRootModel[List[WitnessChunk]]):
"""Model that defines the expected result from the `witness-filler` command."""

root: List[WitnessChunk]


class Merge(Paris):
"""
Paris fork that serializes as 'Merge' for witness-filler compatibility.

IMPORTANT: This class MUST be named 'Merge' (not 'MergeForWitness' or similar)
because the class name is used directly in Pydantic serialization, and
witness-filler expects exactly 'Merge' for this fork.
"""

pass


def pytest_addoption(parser: pytest.Parser):
"""Add witness command-line options to pytest."""
witness_group = parser.getgroup("witness", "Arguments for witness functionality")
witness_group.addoption(
"--witness",
"--witness-the-fitness",
action="store_true",
dest="witness",
default=False,
help=(
"Generate execution witness data for blockchain test fixtures using the "
"witness-filler tool (must be installed separately)."
),
)


def pytest_configure(config):
"""
Pytest hook called after command line options have been parsed.

If --witness is enabled, checks that the witness-filler tool is available in PATH.
"""
if config.getoption("witness"):
# Check if witness-filler binary is available in PATH
if not shutil.which("witness-filler"):
pytest.exit(
"witness-filler tool not found in PATH. Please build and install witness-filler "
"from https://github.com/kevaundray/reth.git before using --witness flag.\n"
"Example: cargo install --git https://github.com/kevaundray/reth.git "
"witness-filler",
1,
)


@pytest.fixture
def witness_generator(
request: pytest.FixtureRequest,
) -> Callable[[BlockchainFixture], None] | None:
"""
Provide a witness generator function if --witness is enabled.

Returns:
None if witness functionality is disabled.
Callable that generates witness data for a BlockchainFixture if enabled.

"""
if not request.config.getoption("witness"):
return None

def generate_witness(fixture: BlockchainFixture) -> None:
"""Generate witness data for a blockchain fixture using the witness-filler tool."""
if not isinstance(fixture, BlockchainFixture):
return None

# Hotfix: witness-filler expects "Merge" but execution-spec-tests uses "Paris"
original_fork = None
if fixture.fork is Paris:
original_fork = fixture.fork
fixture.fork = Merge

try:
result = subprocess.run(
["witness-filler"],
input=fixture.model_dump_json(by_alias=True),
text=True,
capture_output=True,
)
finally:
if original_fork is not None:
fixture.fork = original_fork

if result.returncode != 0:
raise RuntimeError(
f"witness-filler tool failed with exit code {result.returncode}. "
f"stderr: {result.stderr}"
)

try:
result_model = WitnessFillerResult.model_validate_json(result.stdout)
witnesses = result_model.root

for i, witness in enumerate(witnesses):
if i < len(fixture.blocks):
block = fixture.blocks[i]
if isinstance(block, FixtureBlock):
block.execution_witness = witness
except Exception as e:
raise RuntimeError(
f"Failed to parse witness data from witness-filler tool. "
f"Output was: {result.stdout[:500]}{'...' if len(result.stdout) > 500 else ''}"
) from e

return generate_witness
1 change: 1 addition & 0 deletions src/pytest_plugins/help/help.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ def pytest_configure(config):
"defining debug",
"pre-allocation behavior during test filling",
"ported",
"witness",
],
)
elif config.getoption("show_consume_help"):
Expand Down
Loading