Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 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
18 changes: 18 additions & 0 deletions src/ethereum_test_fixtures/blockchain.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"""BlockchainTest types."""

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


@dataclass(slots=True)
class WitnessChunk:
"""Represents execution witness data for a block."""

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

@classmethod
def from_json(cls, s: str) -> List["WitnessChunk"]:
"""Parse witness chunks from JSON string."""
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 = Field(None, alias="executionWitness")

@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
119 changes: 119 additions & 0 deletions src/pytest_plugins/filler/witness.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
"""
Pytest plugin for witness functionality.
Provides --witness command-line option that installs the witness-filler tool
and generates execution witness data for blockchain test fixtures when enabled.
"""

import subprocess
from typing import Any, Callable

import pytest

from ethereum_test_fixtures.blockchain import FixtureBlock, WitnessChunk


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=(
"Install the witness-filler tool and generate execution witness data for blockchain "
"test fixtures."
),
)


def pytest_configure(config):
"""
Pytest hook called after command line options have been parsed.
If --witness is enabled, installs the witness-filler tool from the specified
git repository.
"""
if config.getoption("witness"):
print("🔧 Installing witness-filler tool from kevaundray/reth...")
print(" This may take several minutes for first-time compilation...")

result = subprocess.run(
[
"cargo",
"install",
"--git",
"https://github.com/kevaundray/reth.git",
"--rev",
"8016a8a5736e4427b3d285c82cd39c4ece70f8c4",
"witness-filler",
],
)

if result.returncode != 0:
pytest.exit(
f"Failed to install witness-filler tool (exit code: {result.returncode}). "
"Please ensure you have a compatible Rust toolchain installed. "
"You may need to update your Rust version to 1.86+ or run without --witness.",
1,
)
else:
print("✅ witness-filler tool installed successfully!")


@pytest.fixture
def witness_generator(request: pytest.FixtureRequest) -> Callable[[Any], 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 fixture if enabled.
"""
if not request.config.getoption("witness"):
return None

def generate_witness(fixture: Any) -> None:
"""Generate witness data for a fixture using the witness-filler tool."""
if not hasattr(fixture, "blocks") or not fixture.blocks:
return

# Hotfix: witness-filler expects "Merge" but execution-spec-tests uses "Paris"
original_fork = None
if hasattr(fixture, "fork") and str(fixture.fork) == "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:
# Restore original fork value
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:
witnesses = WitnessChunk.from_json(result.stdout)
for i, witness in enumerate(witnesses):
if i < len(fixture.blocks) and isinstance(fixture.blocks[i], FixtureBlock):
fixture.blocks[i].execution_witness = witness
except (ValueError, IndexError, AttributeError) 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