|
| 1 | +""" |
| 2 | +Pytest plugin for witness functionality. |
| 3 | +
|
| 4 | +Provides --witness command-line option that checks for the witness-filler tool in PATH |
| 5 | +and generates execution witness data for blockchain test fixtures when enabled. |
| 6 | +""" |
| 7 | + |
| 8 | +import shutil |
| 9 | +import subprocess |
| 10 | +from typing import Callable, List |
| 11 | + |
| 12 | +import pytest |
| 13 | + |
| 14 | +from ethereum_test_base_types import EthereumTestRootModel |
| 15 | +from ethereum_test_fixtures.blockchain import BlockchainFixture, FixtureBlock, WitnessChunk |
| 16 | +from ethereum_test_forks import Paris |
| 17 | + |
| 18 | + |
| 19 | +class WitnessFillerResult(EthereumTestRootModel[List[WitnessChunk]]): |
| 20 | + """Model that defines the expected result from the `witness-filler` command.""" |
| 21 | + |
| 22 | + root: List[WitnessChunk] |
| 23 | + |
| 24 | + |
| 25 | +class Merge(Paris): |
| 26 | + """ |
| 27 | + Paris fork that serializes as 'Merge' for witness-filler compatibility. |
| 28 | +
|
| 29 | + IMPORTANT: This class MUST be named 'Merge' (not 'MergeForWitness' or similar) |
| 30 | + because the class name is used directly in Pydantic serialization, and |
| 31 | + witness-filler expects exactly 'Merge' for this fork. |
| 32 | + """ |
| 33 | + |
| 34 | + pass |
| 35 | + |
| 36 | + |
| 37 | +def pytest_addoption(parser: pytest.Parser): |
| 38 | + """Add witness command-line options to pytest.""" |
| 39 | + witness_group = parser.getgroup("witness", "Arguments for witness functionality") |
| 40 | + witness_group.addoption( |
| 41 | + "--witness", |
| 42 | + "--witness-the-fitness", |
| 43 | + action="store_true", |
| 44 | + dest="witness", |
| 45 | + default=False, |
| 46 | + help=( |
| 47 | + "Generate execution witness data for blockchain test fixtures using the " |
| 48 | + "witness-filler tool (must be installed separately)." |
| 49 | + ), |
| 50 | + ) |
| 51 | + |
| 52 | + |
| 53 | +def pytest_configure(config): |
| 54 | + """ |
| 55 | + Pytest hook called after command line options have been parsed. |
| 56 | +
|
| 57 | + If --witness is enabled, checks that the witness-filler tool is available in PATH. |
| 58 | + """ |
| 59 | + if config.getoption("witness"): |
| 60 | + # Check if witness-filler binary is available in PATH |
| 61 | + if not shutil.which("witness-filler"): |
| 62 | + pytest.exit( |
| 63 | + "witness-filler tool not found in PATH. Please build and install witness-filler " |
| 64 | + "from https://github.com/kevaundray/reth.git before using --witness flag.\n" |
| 65 | + "Example: cargo install --git https://github.com/kevaundray/reth.git " |
| 66 | + "witness-filler", |
| 67 | + 1, |
| 68 | + ) |
| 69 | + |
| 70 | + |
| 71 | +@pytest.fixture |
| 72 | +def witness_generator( |
| 73 | + request: pytest.FixtureRequest, |
| 74 | +) -> Callable[[BlockchainFixture], None] | None: |
| 75 | + """ |
| 76 | + Provide a witness generator function if --witness is enabled. |
| 77 | +
|
| 78 | + Returns: |
| 79 | + None if witness functionality is disabled. |
| 80 | + Callable that generates witness data for a BlockchainFixture if enabled. |
| 81 | +
|
| 82 | + """ |
| 83 | + if not request.config.getoption("witness"): |
| 84 | + return None |
| 85 | + |
| 86 | + def generate_witness(fixture: BlockchainFixture) -> None: |
| 87 | + """Generate witness data for a blockchain fixture using the witness-filler tool.""" |
| 88 | + if not isinstance(fixture, BlockchainFixture): |
| 89 | + return None |
| 90 | + |
| 91 | + # Hotfix: witness-filler expects "Merge" but execution-spec-tests uses "Paris" |
| 92 | + original_fork = None |
| 93 | + if fixture.fork is Paris: |
| 94 | + original_fork = fixture.fork |
| 95 | + fixture.fork = Merge |
| 96 | + |
| 97 | + try: |
| 98 | + result = subprocess.run( |
| 99 | + ["witness-filler"], |
| 100 | + input=fixture.model_dump_json(by_alias=True), |
| 101 | + text=True, |
| 102 | + capture_output=True, |
| 103 | + ) |
| 104 | + finally: |
| 105 | + if original_fork is not None: |
| 106 | + fixture.fork = original_fork |
| 107 | + |
| 108 | + if result.returncode != 0: |
| 109 | + raise RuntimeError( |
| 110 | + f"witness-filler tool failed with exit code {result.returncode}. " |
| 111 | + f"stderr: {result.stderr}" |
| 112 | + ) |
| 113 | + |
| 114 | + try: |
| 115 | + result_model = WitnessFillerResult.model_validate_json(result.stdout) |
| 116 | + witnesses = result_model.root |
| 117 | + |
| 118 | + for i, witness in enumerate(witnesses): |
| 119 | + if i < len(fixture.blocks): |
| 120 | + block = fixture.blocks[i] |
| 121 | + if isinstance(block, FixtureBlock): |
| 122 | + block.execution_witness = witness |
| 123 | + except Exception as e: |
| 124 | + raise RuntimeError( |
| 125 | + f"Failed to parse witness data from witness-filler tool. " |
| 126 | + f"Output was: {result.stdout[:500]}{'...' if len(result.stdout) > 500 else ''}" |
| 127 | + ) from e |
| 128 | + |
| 129 | + return generate_witness |
0 commit comments