ethereum / ethereum/execution-specs
feat: new test spec type, fixture format and consume simulator for `engine_getInclusionListV1` (EIP-7805)
- Dominant language
- Python
- Stars
- 1.2k
- Forks
- 505
- Avg merge
- 2d 14h
- Merged PRs (30d)
- 116
Description
Disclaimer: Issue produced by AI with human verification.
### Motivation
EIP-7805 (FOCIL) support in EELS currently covers only the **validation** side of
inclusion lists: `Block.inclusion_list_txs` attaches an IL to the generated payloads and
the consumer checks that the client reports the expected `inclusionListSatisfied` when
the payload is delivered via `engine_newPayloadV6` / `engine_forkchoiceUpdatedV5` (see
`InclusionListVariantFixtureFormat` in `specs/blockchain.py` and the `inclusion_test`
marker).
Nothing exercises the **production** side: `engine_getInclusionListV1`, where the client
must derive an inclusion list from its own local mempool view. This is the FOCIL
counterpart of what `build-block` does for payload building — a fixture-driven test of a
client-generated artifact rather than of a client-validated one.
Spec:
```text
method: engine_getInclusionListV1
params: []
timeout: 1s
result: Array of DATA (EIP-2718 encoded transactions)
```
with three normative requirements:
1. The list **MUST** be derived from the client's local mempool view; the selection
strategy is **implementation dependent**.
2. The RLP-encoded byte length of the returned list **MUST NOT** exceed
`MAX_BYTES_PER_INCLUSION_LIST` (8192).
3. The list **MUST NOT** contain any blob transaction.
### Proposal
A new hive simulator, invoked as `consume inclusion_list`, driven by a new test spec
type and a new fixture format, running four steps per test:
1. **Start the client** with the genesis defined by the fixture: Client must started from genesis for every test because
we cannot alter the mempool view once it has been affected by a previous test.
2. **Send the fixture's transactions** to the client's mempool via `eth_sendRawTransaction`.
3. **Send the fixture's payloads** via `engine_newPayloadVX` + `engine_forkchoiceUpdatedVX`,
advancing the head as usual.
4. **Call `engine_getInclusionListV1`** and check the response against the fixture's
expectation.
The step-2-before-step-3 ordering is the interesting part: it lets a test seed the pool
and then invalidate part of it with the payloads (transactions mined by a block, senders
drained of balance, nonces bumped), so the check in step 4 covers mempool
re-validation and not just "give me back what I sent you".
### New test spec type
None of the existing spec types can express this test. `BlockchainTest` only knows
transactions that live *inside* a block, `StateTest`/`TransactionTest` have no block list
at all, and none of them carry an expected inclusion list. The new type needs three
independent things:
- an arbitrary list of transactions that are submitted to the mempool and are **not**
necessarily included in any block,
- a separate list of blocks, and
- an expected inclusion list.
Proposed: `InclusionListTest(BaseTest)` in a new `specs/inclusion_list.py`, exposed to
test modules as the `inclusion_list_test` filler fixture (registered automatically via
`BaseTest.spec_types` / `pytest_parameter_name()`), alongside an
`InclusionListTestFiller` alias.
```python
class InclusionListTest(BaseTest):
pre: Alloc
blocks: List[Block] = [] # optional; may be empty
mempool_txs: List[Transaction] # submitted, never guaranteed to be mined
must_include: List[Transaction] = [] # see "expectation model" below
must_not_include: List[Transaction] = []
post: Alloc = {}
genesis_environment: Environment = Field(default_factory=Environment)
```
Notes:
- Block generation is the same t8n loop `BlockchainTest` already runs, so `generate()`
can reuse/share that code path rather than reimplement it; only the extra mempool
transactions and the expectation are new.
- `supported_fixture_formats` is just the new format below; the spec type only fills for
forks where `fork.engine_new_payload_inclusion_list_transactions()` is true
(`EIP7805`, `forks/forks/eips/bogota/eip_7805.py`).
- **There is no t8n oracle for step 4.** t8n can tell us what the blocks do, but nothing
can tell us at fill time what a client's mempool will return. The expectation is
therefore author-declared, and fill time should sanity-check it (every `must_include`
transaction was actually submitted, none of them is mined by one of the blocks, the
encoded `must_include` set fits under `MAX_BYTES_PER_INCLUSION_LIST`) rather than
compute it.
- Open: do we also want a `supported_execute_formats` entry so the same tests can run
against a live devnet later, or keep this fixture-only for now?
### Expectation model: two lists, not an exact match
The spec makes transaction selection explicitly implementation dependent, so comparing
the response against a literal expected list would fail conformant clients that order or
prioritise differently. The fixture instead declares two lists:
- **`mustInclude`** — transactions that **must** appear in the returned list. Use it for
transactions that any reasonable selection strategy has to pick: the pool is small,
they are executable, and they fit well under the byte cap.
- **`mustNotInclude`** — transactions that **must not** appear: mined by one of the
fixture's payloads, invalidated by them, replaced, blob transactions, and so on.
Anything submitted in step 2 and named in neither list is unconstrained — the client may
include it or not.
On top of the two lists, the simulator asserts the spec invariants on every test,
independent of what the fixture declares:
- the RLP encoding of the returned list is ≤ `MAX_BYTES_PER_INCLUSION_LIST` (8192 bytes),
- no blob transaction appears,
- no duplicate transactions,
- every returned transaction is one the simulator actually submitted.
This keeps a "byte cap" test (submitting more than 8192 bytes of transactions) expressible:
it declares an empty `mustInclude`, a `mustNotInclude` where applicable, and relies on the
invariants for the rest.
### Other open questions
- **Head before pool.** Some clients validate incoming transactions against the current
head state, which at step 2 is genesis. If a fixture's transactions are only valid
after the payloads, submission would fail. Should the spec type allow transactions to
be attached per-block (submit before block *N*), or is a pre-payload batch plus a
post-payload batch enough? An ordered list of actions is the flexible option but a
heavier format.
- **Initial FCU.** Step 1 presumably needs the usual FCU-to-genesis bootstrap (as in
`_bootstrap_engine_at_genesis`) before transactions are accepted; worth making explicit.
- **Timing.** The IL comes from the local mempool view; `eth_sendRawTransaction` returns
once the transaction is in the pool, but pool re-validation after a new head may lag.
The simulator likely needs a bounded retry around the call, even though the endpoint
itself carries a 1s timeout.
- **Blob transactions.** Requirement 3 is directly testable, but submitting a blob
transaction requires the network-wrapper encoding (blobs, commitments, proofs). Needs a
check of what `EthRPC.send_raw_transaction` currently emits for type-3 transactions.
- **Pool rejections.** Should a rejected `eth_sendRawTransaction` fail the test, or can a
test mark transactions as expected-to-be-rejected by the pool?
### New fixture format
Proposed `BlockchainEngineInclusionListFixture`, `format_name =
"blockchain_test_engine_inclusion_list"`, deriving from `BlockchainEngineFixtureCommon`
so it stays consistent with the existing engine formats (`blockchain_test_engine`,
`blockchain_test_engine_x`, `blockchain_test_sync`).
Fields beyond the common ones (`network`, `lastblockhash`, `config`, `pre`,
`genesisBlockHeader`):
| Field | Type | Purpose |
|----------------------------------------|---------------------------------|-------------------------------------------------------|
| `mempoolTransactions` | `List[Bytes]` | Raw EIP-2718 transactions submitted in step 2 |
| `engineNewPayloads` | `List[FixtureEngineNewPayload]` | Payloads applied in step 3 (as in the engine formats) |
| `expectedInclusionList.mustInclude` | `List[Bytes]` | Transactions that must be in the response |
| `expectedInclusionList.mustNotInclude` | `List[Bytes]` | Transactions that must not be in the response |
### Candidate test cases
- All pending transactions returned when the pool is small and unambiguous
(`mustInclude` = everything submitted).
- Transactions mined by the fixture's payloads are excluded (`mustNotInclude`).
- Transactions invalidated by the payloads (balance drained, nonce bumped) are excluded.
- Blob transactions never appear, even when they are the only pending transactions.
- More than `MAX_BYTES_PER_INCLUSION_LIST` of pending transactions: response stays under
the cap and is a subset of the pool (invariants only).
- Nonce gaps: queued (non-executable) transactions.
- Empty pool returns an empty list.
### Implementation touchpoints
- `packages/testing/src/execution_testing/specs/inclusion_list.py` — new spec type;
export from `specs/__init__.py`.
- `packages/testing/src/execution_testing/fixtures/blockchain.py` — new fixture model;
export from `fixtures/__init__.py`.
- `packages/testing/src/execution_testing/rpc/rpc.py` — `EngineRPC.get_inclusion_list()`.
- `.../plugins/consume/simulators/inclusion_list/conftest.py` — client/hive wiring,
`supported_fixture_formats`, suite name (e.g. `eels/consume-inclusion-list`).
- `.../plugins/consume/simulators/simulator_logic/test_via_inclusion_list.py` — the four
steps.
- `cli/pytest_commands/consume.py` — new `inclusion_list()` subcommand plus an entry in
`get_command_logic_test_paths()`; `processors.py` — add `inclusion_list` to
`simulator_commands` (which also derives the conftest plugin path). No new
`[project.scripts]` entry is needed since this is a `consume` subcommand, and the
command name falls straight out of the function name.
- Docs: `docs/writing_tests/` (new spec type), `docs/running_tests/consume/simulators.md`,
`docs/running_tests/running.md`, and the fixture format documentation.
- Hive simulator registration + CI workflow entry.
### References
- [EIP-7805: Fork-choice enforced Inclusion Lists (FOCIL)](https://eips.ethereum.org/EIPS/eip-7805)
- [Bogota engine API](https://github.com/ethereum/execution-apis/blob/main/src/engine/bogota.md)
- Existing producer-side simulator for comparison: `build-block` via
`testing_buildBlockV1` (`simulator_logic/test_via_build.py`).
Contributor guide
Research direction
Start by reading the existing BlockchainTest flow, blockchain.py fixture models, and the build-block simulator in simulator_logic/test_via_build.py. Then trace EngineRPC and the consume command wiring in rpc.py, consume.py, and processors.py. Done means the new spec and fixture format, simulator steps, CLI command, registrations, tests, documentation, and CI entry are implemented with the stated inclusion-list invariants.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- cli, testing, tooling
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100