diff --git a/.github/configs/feature.yaml b/.github/configs/feature.yaml index 9f8cbfa260e..b3d0d6df71c 100644 --- a/.github/configs/feature.yaml +++ b/.github/configs/feature.yaml @@ -8,9 +8,4 @@ monad: # --suppress-no-test-exit-code works around a problem where multi-phase fill # (triggered by tarball output) fails to proceed on no tests processed # in 1st phase (exit code 5) - fill-params: --suppress-no-test-exit-code -m blockchain_test --from=MONAD_EIGHT --until=MONAD_NEXT --chain-id=143 -k "not invalid_header" - -monad_runloop: - evm-type: eels - # Like `monad`, but `--monad-runloop` and eestnet chain id `30143` - fill-params: --suppress-no-test-exit-code -m blockchain_test --from=MONAD_EIGHT --until=MONAD_NEXT --chain-id=30143 --monad-runloop -k "not invalid_header" + fill-params: --suppress-no-test-exit-code -m "blockchain_test or runloop_test" --from=MONAD_EIGHT --until=MONAD_NEXT --chain-id=143 -k "not invalid_header" diff --git a/.github/workflows/consume_release.yaml b/.github/workflows/consume_release.yaml index e0b18c7ed45..63413849d62 100644 --- a/.github/workflows/consume_release.yaml +++ b/.github/workflows/consume_release.yaml @@ -7,9 +7,9 @@ on: workflow_dispatch: inputs: release: - description: "Fixtures source: release spec (e.g. tests-monad_runloop@v1.1.1-rc2), a fixtures.tar.gz URL, or latest" + description: "Fixtures source: release spec (e.g. tests-monad@v1.1.2), a fixtures.tar.gz URL, or latest" required: true - default: "tests-monad_runloop@latest" + default: "tests-monad@latest" harness_ref: description: "monad-eest-rust-harness ref (branch/tag/SHA)" required: false @@ -201,6 +201,7 @@ jobs: uv run consume direct \ --input "$RELEASE" \ --bin "$GITHUB_WORKSPACE/monad-eest-rust-harness/bin/eest-runner" \ + -m runloop_test \ "${FILTER_ARG[@]}" -v | tee consume.log - name: Summary diff --git a/MONAD_RUNLOOP_TESTING.md b/MONAD_RUNLOOP_TESTING.md index dda1a92041b..29424a90344 100644 --- a/MONAD_RUNLOOP_TESTING.md +++ b/MONAD_RUNLOOP_TESTING.md @@ -1,6 +1,6 @@ # Running EEST fixtures on the monad runloop -Executes blockchain fixtures (including those generated from state +Executes runloop test fixtures (generated from blockchain and state tests) against the monad execution client's `runloop`, using the consensus ledger directory as the block source. Each `postState` account is compared (balance/nonce/code/storage) against the @@ -47,19 +47,23 @@ that fails silently). ## Fill + consume ```sh -uv run fill --clean -m blockchain_test \ - --fork MONAD_NINE --chain-id 30143 --monad-runloop \ - --output ../fixtures_eestnet +uv run fill --clean -m runloop_test \ + --fork MONAD_NINE --output ../fixtures_eestnet uv run consume direct --input ../fixtures_eestnet \ --bin ../monad-eest-rust-harness/bin/eest-runner ``` -- `--monad-runloop` stamps monad blocks with the consensus-derived - header fields the runloop produces (prev_randao from the round-0 BLS - signature, 32-byte extra_data, the proposal gas limit, zero - requests_hash). -- `--chain-id 30143` is EestNet's chain id. +- `-m runloop_test` selects the runloop fixture format, written under + `runloop_tests` (next to `blockchain_tests`). It fills with + EestNet's chain id (30143) and stamps monad blocks with the + consensus-derived header fields the runloop produces (prev_randao + from the round-0 BLS signature, 32-byte extra_data, the proposal gas + limit, zero requests_hash), regardless of `--chain-id`. +- The `tests-monad` fixture release includes `runloop_tests` next to + the other formats; when consuming it (or any mixed fixtures + directory), pass `-m runloop_test` so only runloop fixtures reach + `eest-runner`. - Block timestamps need no special handling: the consumer derives the monad revision schedule from the fixture's `network` (`FORK_REVISION_SCHEDULES` in `clis/monad.py`) and the harness diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/consume/direct/conftest.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/consume/direct/conftest.py index 980c4218768..dac2b1cf5b8 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/consume/direct/conftest.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/consume/direct/conftest.py @@ -24,6 +24,7 @@ from execution_testing.fixtures import ( BaseFixture, BlockchainFixture, + BlockchainRunloopFixture, StateFixture, ) from execution_testing.fixtures.consume import ( @@ -94,6 +95,7 @@ def pytest_configure(config: pytest.Config) -> None: # noqa: D103 config.supported_fixture_formats = [ # type: ignore[attr-defined] StateFixture, BlockchainFixture, + BlockchainRunloopFixture, ] fixture_consumers = [] for fixture_consumer_bin_path in config.getoption("fixture_consumer_bin"): diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/filler.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/filler.py index d446fbef525..9cc826e0c73 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/filler.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/filler.py @@ -46,6 +46,7 @@ BlockchainEngineFixture, BlockchainEngineXFixture, BlockchainFixture, + BlockchainRunloopFixture, FixtureCollector, FixtureConsumer, FixtureFillingPhase, @@ -576,18 +577,6 @@ def pytest_addoption(parser: pytest.Parser) -> None: f"(Default: {EnvironmentDefaults.gas_limit})" ), ) - test_group.addoption( - "--monad-runloop", - action="store_true", - dest="monad_runloop", - default=False, - help=( - "Stamp monad blocks with the consensus-derived header fields " - "the production runloop produces (prev_randao, extra_data, " - "gas_limit, requests_hash), so filled block hashes and EIP-2935 " - "history storage match the monad `eest-runner` consumer." - ), - ) test_group.addoption( "--generate-pre-alloc-groups", action="store_true", @@ -709,17 +698,6 @@ def pytest_configure(config: pytest.Config) -> None: if option_was_explicitly_set(config, "--block-gas-limit"): EnvironmentDefaults.gas_limit = config.getoption("block_gas_limit") - # Stamp monad blocks with runloop-conformant header fields if requested. - if config.getoption("monad_runloop"): - MonadRunloopDefaults.enabled = True - # The runloop builds every block at the monad proposal gas limit, so - # make it the default block gas limit too: tests that read - # env.gas_limit (the GASLIMIT opcode, tx-gas-vs-block-limit checks) - # then build expectations that match execution. An explicit - # --block-gas-limit still wins. - if not option_was_explicitly_set(config, "--block-gas-limit"): - EnvironmentDefaults.gas_limit = MonadRunloopDefaults.gas_limit - # Initialize fixture output configuration config.fixture_output = FixtureOutput.from_config( # type: ignore[attr-defined] config @@ -1853,8 +1831,9 @@ def pytest_collection_modifyitems( if marker.name == "fill": for mark in marker.args: item.add_marker(mark) - if marker.name == "monad_runloop" and config.getoption( - "monad_runloop", False + if ( + marker.name == "monad_runloop" + and item.get_closest_marker("runloop_test") is not None ): for mark in marker.args: item.add_marker(mark) @@ -1969,6 +1948,47 @@ def sort_key(item: pytest.Item) -> tuple[bool, str, bool, str]: item.add_marker(pytest.mark.xdist_group(name=group_name)) +runloop_saved_defaults_key: pytest.StashKey[tuple[int, int]] = ( + pytest.StashKey() +) + + +def pytest_runtest_setup(item: pytest.Item) -> None: + """ + Switch chain defaults to the runloop's for `runloop_test` items. + + The chain id and block gas limit feed transaction signing and spec + construction in the test body, which runs once per fixture format + item, so the switch is per item and restored on teardown. + """ + if item.get_closest_marker("runloop_test") is None: + return + item.stash[runloop_saved_defaults_key] = ( + ChainConfigDefaults.chain_id, + EnvironmentDefaults.gas_limit, + ) + ChainConfigDefaults.chain_id = MonadRunloopDefaults.chain_id + # The runloop builds every block at the monad proposal gas limit, so + # make it the default block gas limit too: tests that read + # env.gas_limit (the GASLIMIT opcode, tx-gas-vs-block-limit checks) + # then build expectations that match execution. An explicit + # --block-gas-limit still wins. + if not option_was_explicitly_set(item.config, "--block-gas-limit"): + EnvironmentDefaults.gas_limit = MonadRunloopDefaults.gas_limit + + +def pytest_runtest_teardown(item: pytest.Item) -> None: + """Restore the session chain defaults after a `runloop_test` item.""" + saved = item.stash.get(runloop_saved_defaults_key, None) + if saved is None: + return + ( + ChainConfigDefaults.chain_id, + EnvironmentDefaults.gas_limit, + ) = saved + del item.stash[runloop_saved_defaults_key] + + def _verify_fixtures_post_merge( config: pytest.Config, output_dir: Path ) -> None: @@ -2002,6 +2022,9 @@ def _verify_fixtures_post_merge( dir_to_format: dict[str, type[BaseFixture]] = { StateFixture.output_base_dir_name(): StateFixture, BlockchainFixture.output_base_dir_name(): BlockchainFixture, + BlockchainRunloopFixture.output_base_dir_name(): ( + BlockchainRunloopFixture + ), BlockchainEngineFixture.output_base_dir_name(): ( BlockchainEngineFixture ), diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/tests/test_collect_only.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/tests/test_collect_only.py index 687f93c975f..2b705ce94cf 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/tests/test_collect_only.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/tests/test_collect_only.py @@ -53,6 +53,9 @@ def test_collect_only_output(pytester: pytest.Pytester) -> None: in line for line in result.outlines ), f"Expected test output: {result.outlines}" - # fill generates 3 test variants: state_test, - # blockchain_test_from_state_test, blockchain_test_engine_from_state_test - assert any("3 tests collected" in line for line in result.outlines) + # fill generates 4 test variants: state_test, + # blockchain_test_from_state_test, + # blockchain_test_engine_from_state_test and + # runloop_test_from_state_test (the last two are collected but + # removed for Istanbul, which neither format supports) + assert any("4 tests collected" in line for line in result.outlines) diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/tests/test_runloop_format.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/tests/test_runloop_format.py new file mode 100644 index 00000000000..399d303e6f5 --- /dev/null +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/tests/test_runloop_format.py @@ -0,0 +1,148 @@ +"""Test filling the `runloop_test` fixture format.""" + +import json +import textwrap +from pathlib import Path +from typing import Any + +import pytest + +from execution_testing.test_types import MonadRunloopDefaults + +test_module = textwrap.dedent( + """\ + import pytest + + from execution_testing import Transaction + + @pytest.mark.valid_from("MONAD_NINE") + def test_runloop(state_test, pre) -> None: + tx = Transaction(sender=pre.fund_eoa(), gas_limit=100_000) + state_test(pre=pre, post={}, tx=tx) + """ +) + + +@pytest.fixture() +def fill_args(testdir: pytest.Testdir) -> list[str]: + """Copy fill ini and return base fill args.""" + testdir.copy_example( + name=( + "src/execution_testing/cli/pytest_commands" + "/pytest_ini_files/pytest-fill.ini" + ) + ) + testdir.makepyfile(test_module) + return ["-c", "pytest-fill.ini", "--no-html", "--output", "fixtures"] + + +def load_single_fixture(testdir: pytest.Testdir, format_dir: str) -> Any: + """Load the only fixture written under the given format directory.""" + files = list( + (Path(testdir.tmpdir) / "fixtures" / format_dir).rglob("*.json") + ) + assert len(files) == 1 + fixtures = json.loads(files[0].read_text()) + assert len(fixtures) == 1 + return next(iter(fixtures.values())) + + +def test_fill_blockchain_and_runloop_formats( + testdir: pytest.Testdir, fill_args: list[str] +) -> None: + """ + One fill with both markers writes `blockchain_tests` and + `runloop_tests` siblings; the runloop fixture carries EestNet's + chain id and the runloop-stamped header fields regardless of + `--chain-id`, while the blockchain fixture is unaffected. + """ + result = testdir.runpytest( + *fill_args, + "-m", + "blockchain_test or runloop_test", + "--fork", + "MONAD_NINE", + "--chain-id", + "143", + ) + result.assert_outcomes(passed=2) + + blockchain = load_single_fixture(testdir, "blockchain_tests") + assert int(blockchain["config"]["chainid"], 16) == 143 + header = blockchain["blocks"][0]["blockHeader"] + assert header["extraData"] != "0x" + "00" * 32 + assert int(header["gasLimit"], 16) != MonadRunloopDefaults.gas_limit + assert int(header["mixHash"], 16) != MonadRunloopDefaults.prev_randao + + runloop = load_single_fixture(testdir, "runloop_tests") + assert ( + int(runloop["config"]["chainid"], 16) == MonadRunloopDefaults.chain_id + ) + header = runloop["blocks"][0]["blockHeader"] + assert header["extraData"] == "0x" + "00" * 32 + assert int(header["gasLimit"], 16) == MonadRunloopDefaults.gas_limit + assert int(header["mixHash"], 16) == MonadRunloopDefaults.prev_randao + + +def test_runloop_format_not_generated_for_canonical_forks( + testdir: pytest.Testdir, fill_args: list[str] +) -> None: + """ + `runloop_test` items only exist for monad forks, so canonical-fork + fills (e.g. the mainnet release with `--generate-all-formats`) never + produce `runloop_tests` fixtures. + """ + testdir.makepyfile( + test_module.replace('valid_from("MONAD_NINE")', 'valid_from("Paris")') + ) + result = testdir.runpytest( + *fill_args, + "-m", + "runloop_test", + "--fork", + "Prague", + ) + assert result.ret == pytest.ExitCode.NO_TESTS_COLLECTED + + +test_module_chain_config = textwrap.dedent( + """\ + import pytest + + from execution_testing import Account, Op, Transaction + + @pytest.mark.valid_from("MONAD_NINE") + def test_chain_config(state_test, pre, chain_config) -> None: + contract = pre.deploy_contract(Op.SSTORE(1, Op.CHAINID) + Op.STOP) + tx = Transaction( + sender=pre.fund_eoa(), to=contract, gas_limit=100_000 + ) + state_test( + pre=pre, + post={contract: Account(storage={1: chain_config.chain_id})}, + tx=tx, + ) + """ +) + + +def test_chain_config_follows_fixture_format( + testdir: pytest.Testdir, fill_args: list[str] +) -> None: + """ + The `chain_config` fixture must resolve per item: the blockchain + item runs first in the session, and a `chain_config` cached from it + would sign and verify the runloop item with the wrong chain id + (regression test for the session-scoped `chain_config`). + """ + testdir.makepyfile(test_module_chain_config) + result = testdir.runpytest( + *fill_args, + "-m", + "blockchain_test or runloop_test", + "--fork", + "MONAD_NINE", + "--chain-id", + "143", + ) + result.assert_outcomes(passed=2) diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/shared/execute_fill.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/shared/execute_fill.py index 2651faaac6b..95f936d2e51 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/shared/execute_fill.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/shared/execute_fill.py @@ -190,9 +190,9 @@ def pytest_configure(config: pytest.Config) -> None: ) config.addinivalue_line( "markers", - "monad_runloop: Markers to be added only when filling with " - "--monad-runloop (e.g. skips for tests that need a test-controlled " - "block gas limit the runloop overrides).", + "monad_runloop: Markers to be added only when filling a " + "runloop_test fixture (e.g. skips for tests that need a " + "test-controlled block gas limit the runloop overrides).", ) config.addinivalue_line( "markers", @@ -359,9 +359,16 @@ def sender(pre: Alloc) -> EOA: return pre.fund_eoa() -@pytest.fixture(scope="session") +@pytest.fixture def chain_config() -> ChainConfig: - """Return chain configuration.""" + """ + Return chain configuration. + + Function-scoped because the chain id differs per fixture format + within one session: `runloop_test` items fill with EestNet's chain + id, so a session-cached `ChainConfig` would leak one format's chain + id into the other's items. + """ return ChainConfig() diff --git a/packages/testing/src/execution_testing/client_clis/clis/monad.py b/packages/testing/src/execution_testing/client_clis/clis/monad.py index 7fa29612b21..8d0618a8459 100644 --- a/packages/testing/src/execution_testing/client_clis/clis/monad.py +++ b/packages/testing/src/execution_testing/client_clis/clis/monad.py @@ -1,7 +1,7 @@ """ Monad `eest-runner` fixture consumer. -Executes blockchain fixtures against the production monad runloop via +Executes runloop test fixtures against the production monad runloop via the `eest-runner` harness binary (built from monad-bft + monad execution, EestNet chain). The fixture is digested into a simple input document (genesis allocation + per-block timestamp/base fee/beneficiary @@ -23,7 +23,10 @@ import pytest -from execution_testing.fixtures import BlockchainFixture, FixtureFormat +from execution_testing.fixtures import ( + BlockchainRunloopFixture, + FixtureFormat, +) from execution_testing.test_types import Transaction from ..fixture_consumer_tool import FixtureConsumerTool @@ -160,9 +163,9 @@ def _compare_account( class MonadFixtureConsumer( FixtureConsumerTool, - fixture_formats=[BlockchainFixture], + fixture_formats=[BlockchainRunloopFixture], ): - """Monad's `eest-runner` fixture consumer for blockchain tests.""" + """Monad's `eest-runner` consumer for runloop test fixtures.""" default_binary = Path("eest-runner") detect_binary_pattern = re.compile(r"^eest-runner\b") @@ -298,8 +301,8 @@ def consume_fixture( fixture_name: Optional[str] = None, debug_output_path: Optional[Path] = None, ) -> None: - """Execute a blockchain fixture on the monad runloop and verify.""" - assert fixture_format == BlockchainFixture + """Execute a runloop fixture on the monad runloop and verify.""" + assert fixture_format == BlockchainRunloopFixture with open(fixture_path) as f: fixtures = json.load(f) diff --git a/packages/testing/src/execution_testing/fixtures/__init__.py b/packages/testing/src/execution_testing/fixtures/__init__.py index f74946a8c72..44120d8226e 100644 --- a/packages/testing/src/execution_testing/fixtures/__init__.py +++ b/packages/testing/src/execution_testing/fixtures/__init__.py @@ -15,6 +15,7 @@ BlockchainEngineXFixture, BlockchainFixture, BlockchainFixtureCommon, + BlockchainRunloopFixture, ) from .collector import ( FixtureCollector, @@ -41,6 +42,7 @@ "BlockchainEngineXFixture", "BlockchainFixture", "BlockchainFixtureCommon", + "BlockchainRunloopFixture", "FixtureCollector", "FixtureConsumer", "FixtureFillingPhase", diff --git a/packages/testing/src/execution_testing/fixtures/blockchain.py b/packages/testing/src/execution_testing/fixtures/blockchain.py index ff908059ba8..b490e82a149 100644 --- a/packages/testing/src/execution_testing/fixtures/blockchain.py +++ b/packages/testing/src/execution_testing/fixtures/blockchain.py @@ -53,7 +53,7 @@ EngineAPIError, ExceptionInstanceOrList, ) -from execution_testing.forks import Fork, Paris, TransitionFork +from execution_testing.forks import MONAD_EIGHT, Fork, Paris, TransitionFork from execution_testing.test_types import ( BlockAccessList, Environment, @@ -847,6 +847,30 @@ class BlockchainFixture(BlockchainFixtureCommon): transition_tool_cache_key: ClassVar[str] = "blockchain_test" +class BlockchainRunloopFixture(BlockchainFixture): + """ + Blockchain test fixture executable on the monad runloop. + + Same model as `BlockchainFixture`, but filled with EestNet's chain + id and the consensus-derived header fields the runloop produces + (see `MonadRunloopDefaults`), and written under `runloop_tests`. + """ + + format_name: ClassVar[str] = "runloop_test" + description: ClassVar[str] = ( + "Tests that generate a blockchain test fixture executable on " + "the monad runloop." + ) + # Filled with a different chain id and block environment than + # `blockchain_test`, so transition tool outputs cannot be shared. + transition_tool_cache_key: ClassVar[str] = "runloop_test" + + @classmethod + def supports_fork(cls, fork: Fork | TransitionFork) -> bool: + """The monad runloop only executes monad forks.""" + return fork.fork_at(block_number=0, timestamp=0) >= MONAD_EIGHT + + @post_state_validator() class BlockchainEngineFixtureCommon(BaseFixture): """ diff --git a/packages/testing/src/execution_testing/specs/blockchain.py b/packages/testing/src/execution_testing/specs/blockchain.py index d3c86e001ef..8965c045e98 100644 --- a/packages/testing/src/execution_testing/specs/blockchain.py +++ b/packages/testing/src/execution_testing/specs/blockchain.py @@ -64,6 +64,7 @@ BlockchainEngineSyncFixture, BlockchainEngineXFixture, BlockchainFixture, + BlockchainRunloopFixture, FixtureFormat, LabeledFixtureFormat, ) @@ -715,6 +716,7 @@ class BlockchainTest(BaseTest): Sequence[FixtureFormat | LabeledFixtureFormat] ] = [ BlockchainFixture, + BlockchainRunloopFixture, BlockchainEngineFixture, BlockchainEngineSyncFixture, BlockchainEngineXFixture, @@ -750,7 +752,7 @@ def discard_fixture_format_by_marks( marker_names = [m.name for m in markers] if ( - fixture_format != BlockchainFixture + fixture_format not in [BlockchainFixture, BlockchainRunloopFixture] and "blockchain_test_only" in marker_names ): return True @@ -770,7 +772,10 @@ def get_genesis_environment(self) -> Environment: return Environment(**(GENESIS_ENVIRONMENT_DEFAULTS | modified_values)) def make_genesis( - self, *, apply_pre_allocation_blockchain: bool + self, + *, + apply_pre_allocation_blockchain: bool, + monad_runloop: bool = False, ) -> Tuple[Alloc, FixtureBlock]: """Create a genesis block from the blockchain test definition.""" env = self.get_genesis_environment() @@ -784,7 +789,7 @@ def make_genesis( block_number=env.number, timestamp=env.timestamp ) if ( - MonadRunloopDefaults.enabled + monad_runloop and isinstance(genesis_fork, type) and issubclass(genesis_fork, MONAD_EIGHT) ): @@ -828,6 +833,7 @@ def generate_block_data( previous_env: Environment, previous_alloc: Alloc | LazyAlloc, previous_senders_authorities: Dict[int, List[Address]], + monad_runloop: bool = False, ) -> BuiltBlock: """ Generate common block data for both make_fixture and make_hive_fixture. @@ -843,7 +849,7 @@ def generate_block_data( ) env = env.set_fork_requirements(fork) - # When filling with --monad-runloop, monad blocks must carry the + # When filling a runloop_test fixture, monad blocks must carry the # consensus-derived header fields the production runloop produces. # gas_limit and prev_randao feed execution (gas, PREVRANDAO) and the # EIP-2935 block-hash chain, so they are set on the env before t8n; @@ -851,7 +857,7 @@ def generate_block_data( # Gate on the per-block resolved fork so transition forks (whose # `self.fork` is the transition class) are covered too. monad_runloop = ( - MonadRunloopDefaults.enabled + monad_runloop and isinstance(fork, type) and issubclass(fork, MONAD_EIGHT) ) @@ -1119,11 +1125,18 @@ def verify_post_state( def make_fixture( self, t8n: FillerBackend, + fixture_format: FixtureFormat = BlockchainFixture, ) -> FillResult: """Create a fixture from the blockchain test definition.""" + assert isinstance(fixture_format, type) and issubclass( + fixture_format, BlockchainFixture + ) + monad_runloop = issubclass(fixture_format, BlockchainRunloopFixture) fixture_blocks: List[FixtureBlock | InvalidFixtureBlock] = [] - pre, genesis = self.make_genesis(apply_pre_allocation_blockchain=True) + pre, genesis = self.make_genesis( + apply_pre_allocation_blockchain=True, monad_runloop=monad_runloop + ) alloc: Alloc | LazyAlloc = pre senders_authorities: dict[int, list[Address]] = {} @@ -1143,6 +1156,7 @@ def make_fixture( previous_env=env, previous_alloc=alloc, previous_senders_authorities=senders_authorities, + monad_runloop=monad_runloop, ) block_number = int(built_block.header.number) is_last_block = block is self.blocks[-1] @@ -1189,7 +1203,7 @@ def make_fixture( self.check_exception_test(exception=invalid_blocks > 0) alloc = alloc.get() if isinstance(alloc, LazyAlloc) else alloc self.verify_post_state(t8n, t8n_state=alloc) - fixture = BlockchainFixture( + fixture = fixture_format( fork=self.fork, genesis=genesis.header, genesis_rlp=genesis.rlp, @@ -1575,8 +1589,8 @@ def generate( BlockchainEngineSyncFixture, ]: return self.make_hive_fixture(t8n, fixture_format) - elif fixture_format == BlockchainFixture: - return self.make_fixture(t8n) + elif fixture_format in [BlockchainFixture, BlockchainRunloopFixture]: + return self.make_fixture(t8n, fixture_format) raise Exception(f"Unknown fixture format: {fixture_format}") diff --git a/packages/testing/src/execution_testing/test_types/block_types.py b/packages/testing/src/execution_testing/test_types/block_types.py index b9c8a93a792..43103e994e3 100644 --- a/packages/testing/src/execution_testing/test_types/block_types.py +++ b/packages/testing/src/execution_testing/test_types/block_types.py @@ -40,16 +40,18 @@ class EnvironmentDefaults: @dataclass class MonadRunloopDefaults: """ - Consensus-derived header values the monad runloop produces. + Defaults for filling fixtures the monad runloop executes. - When `fill` runs with `--monad-runloop`, monad blocks are stamped - with these so filled block hashes and EIP-2935 history storage match + Active while a `runloop_test` fixture is being filled. Monad blocks + are stamped with the consensus-derived header values the runloop + produces, so filled block hashes and EIP-2935 history storage match the production runloop (which the `eest-runner` consumer executes). - They are the only header fields that differ from the synchronous EEST - defaults; the execution-result roots already agree. + They are the only header fields that differ from the synchronous + EEST defaults; the execution-result roots already agree. """ - enabled: bool = False + # EestNet's chain id, compiled into the harness's EestNet chain. + chain_id: int = 30143 # prev_randao = blake3(rlp(BLS round-signature over Round(0))) with the # harness's all-zero proposer key. Constant because the harness never # advances the round (Round(0) for every block).