# Inclusion lists: execution, consensus, & engine spec overview <img src=https://storage.googleapis.com/ethereum-hackmd/upload_5ee731c2d23b28abfea66b13bbac0dc7.jpg width=49%> $\cdot$ *by mike based on discussions with [Potuz](https://twitter.com/potuz1), [Terence](https://twitter.com/terencechain), [Francesco](https://twitter.com/fradamt), [Hsaio-Wei](https://twitter.com/icebearhww), [Danny](https://twitter.com/dannyryan), [Tim](https://twitter.com/TimBeiko), [Roman](https://twitter.com/r_krasiuk), [Dan](https://twitter.com/Rjected), [Alexey](https://twitter.com/ashekhirin), [Lion](https://twitter.com/dapplion), [Mikhail](https://twitter.com/mkalinin2), & [Matt](https://twitter.com/lightclients).* $\cdot$ *february 7, 2024* $\cdot$ ***tl;dr;*** -- *This document presents a minimal view of what a potential spec for [EIP-7547](https://eips.ethereum.org/EIPS/eip-7547) could look like. We outline changes to the execution specs, consensus specs, and execution APIs to clarify how the pieces fit together. These changes are in draft mode; the goal of this document is to* *(i) convey the relative simplicity of the changes, (ii) highlight the key design considerations that came up in discussion.* *The main non-goal of the document is to present the "final-form specification," because the design is continuing to evolve. Rather this exercise aims to be maximally simple \~from the specification\~ perspective and highlight the portion of the specs where changes will be. There may be engineering reasons to add/modify/remove some of what is presented below, and some of these discussions are laid out in the ["key design considerations"](#Key-design-considerations) section.* *Note that inclusion lists are mainly useful in the context of PBS, where the proposer wants to externalize their block-building while retaining some agency over the transactions in the canonical chain. If the proposer is building their block, they can instead choose to insert the inclusion list transactions directly into their block.* $\cdot$ [**related work**](https://gist.github.com/michaelneuder/ba32e608c75d48719a7ecba29ec3d64b) --- <!-- - slot n block contains slot n summary (sign over the full thing) - slot n block is gossiped with a signed txn list (that satisfies summary) - slot n+1 block validation depends on satisfying summary entries from slot n --> ## Specs | repo | draft pr link | | -- | -- | | execution-specs | https://github.com/michaelneuder/execution-specs/pull/1 | | consensus-specs | https://github.com/hwwhww/consensus-specs/pull/2 | | execution-apis | https://github.com/michaelneuder/execution-apis/pull/1 | --- ### Execution specs – [link to draft PR](https://github.com/michaelneuder/execution-specs/pull/1) We update the `state_transition` function (called when validating a block) to remove entries from the summary that are satisfied in the parent block: ```python= def state_transition(chain: BlockChain, block: Block) -> None: ... parent_transactions_addresses = [decode_transaction(tx).address for tx in parent.transactions] inclusion_list_summary = [entry for entry in block.inclusion_list_summary if entry.address not in parent_transactions_addresses] ... ``` We also modify the `apply_body` function (called from `state_transition`) to calculate the added gas from the inclusion list and assert that the inclusion list entries are satisfied. ```python= def apply_body( ... inclusion_list_summary: Tuple[InclusionListSummaryEntry, ...], ): ... # Calculate the additional gas added by the inclusion list and construct # a map of addresses is required. inclusion_list_gas = sum(entry.gas_limit for entry in inclusion_list_summary) inclusion_list_addresses = {entry.address: False for entry in inclusion_list_summary} gas_available = block_gas_limit + inclusion_list_gas ... if sender_address in inclusion_list_addresses: # Mark this address as satisfied inclusion_list_addresses[sender_address] = True ... # If any inclusion list addresses are not satisfied, the block is invalid. for entry in inclusion_list_addresses: if inclusion_list_addresses[entry] is False: raise InvalidBlock ... ``` The figure below encapsulates the full block validation flow: <img src=https://storage.googleapis.com/ethereum-hackmd/upload_648040632641d7f5e37ed342c53720b7.png width=99%> ### Consensus specs – [link to draft PR](https://github.com/hwwhww/consensus-specs/pull/2) The main beacon chain spec (`specs/_features/eip7547/beacon-chain.md`) has extremely minimal changes. - Define the `InclusionListSummaryEntry` and `InclusionListSummary` ```python= class InclusionListSummaryEntry(Container): address: ExecutionAddress gas_limit: uint64 class InclusionListSummary(Container): slot: Slot proposer_index: ValidatorIndex summary: List[InclusionListSummaryEntry, MAX_TRANSACTIONS_PER_INCLUSION_LIST] ``` - Add `InclusionListSummary` to the `BeaconBlockBody` ```python= class BeaconBlockBody(Container): ... inclusion_list_summary: InclusionListSummary # [New in EIP7547] ``` <!-- - Modify `process_execution_payload`, part of the state transition function called when processing a new block (`on_block`). ```python= def process_execution_payload(state: BeaconState, body: BeaconBlockBody, execution_engine: ExecutionEngine) -> None: ... # Verify the inclusion list signature domain = get_domain(state, DOMAIN_BEACON_PROPOSER) signed_summary = payload.signed_inclusion_list_summary signing_root = compute_signing_root(signed_summary.message, domain) pubkey = state.validators[signed_summary.message.proposer_index] assert(bls.Verify(pubkey, signing_root, signed_summary.signature)) ... ``` --> The fork-choice spec (`specs/_features/eip7547/fork-choice.md`) only updates the `PayloadAttributes` type to include the inclusion list transactions that are passed from the CL to the EL when block construction is initiated. Other engine APIs use the updates `ExecutionPayload` type (covered in more detail in [Execution API](#Execution-API) below). ```python= class PayloadAttributes(object): ... inclusion_list_transactions: List[Transaction, MAX_TRANSACTIONS_PER_INCLUSION_LIST] # [New in EIP7547] ``` The p2p spec (`specs/_features/eip7547/p2p-networking.md`) creates the signed transactions list type. ```python= class SignedInclusionListTransactions(Container): transactions: transactions: List[Transaction, MAX_TRANSACTIONS_PER_PAYLOAD] signature: BLSSignature ``` It also updates the type of the gossip object for the `beacon_block` topic. ```python= class SignedBeaconBlockAndInclusionList(Container): signed_block: SignedBeaconBlock signed_transactions: SignedInclusionListTransactions ``` The block is transmitted with the transactions because block validation requires the existence of an inclusion list. The validator spec (`specs/_features/eip7547/validator.md`) updates the proposer behavior to construct and broadcast the inclusion list transactions alongside the block. ```python= Proposer must construct and broadcast `SignedInclusionListTransactions` alongside `SignedBeaconBlock`. ``` ### Execution API – [link to draft PR](https://github.com/michaelneuder/execution-apis/pull/1) We modify the engine API with a new API and three modified APIs. Recall that the point of inclusion lists is to allow validators to flag transactions for inclusion even when outsourcing their block production. In `mev-boost` (out-of-protocol PBS), the proposer may need to communicate their inclusion list details to the builder through the relay. - `engine_newPayloadV4` (updated API) - CL requests validation of a block from the EL. Updated with inclusion list types and validation requirements. - `engine_forkChoiceUpdatedV4` (updated API) - CL notifies the EL of a new head. If the payload attributes are supplied, the EL begins building a block. The payload attributes type is updated to include the transactions. - `engine_getInclusionListV1` (new API) - CL requests the construction of an inclusion list from the EL. - `engine_getPayloadV4` (updated API) - CL requests the current best block from the EL. Updated with inclusion list types. The sequence diagram below shows the block production flow, using each of the above APIs. <img src=https://storage.googleapis.com/ethereum-hackmd/upload_8949f63d04b0652af179d5a2c5cf7379.png width=69%> ### Builder & Relay API - Inclusion lists will likely change the builder API and the relay API. Leaving this out for now until the exact specifics are ironed out. - https://ethereum.github.io/builder-specs/#/Builder - https://flashbots.github.io/relay-specs/#/Data ## Key design considerations - ***Conditional vs unconditional enforcement of inclusion lists.*** - See [unconditional ILs](https://ethresear.ch/t/unconditional-inclusion-lists/18500#core-design-philosophy-3) for tradeoff discussion. - In the case of unconditional enforcement, should the IL transactions extend the gas limit beyond 30mm? Or require the transaction inclusion within the 30mm gas? - ***Top-of-block vs anywhere-in-block*** - Where in the `slot n+1` block should the `slot n` IL transactions go? - Top-of-block is more MEV risk (slot-specific MEV is capturable by getting in the inclusion list). There exist differing views on how much risk this induces. - ***Gossip inclusion list with the block or not?*** - By gossiping them together over the `beacon_block` topic (as described in this document), there is no chance of a validator seeing the block but no inclusion list. It is also simpler from the specification perspective. - Engineering considerations may make separating them over different gossip channels preferable. - We already have a lot of infra for sidecars with 4844. - Rejecting duplicate blocks over the wire is easier with them separate given there may be multiple ILs. - ***Gas limit & transaction limit to constrain the inclusion lists?*** - The IL needs to be limited in some way (if it is unconditional). Right now we have both a limit on the number of transactions in the IL (16) and the amount of gas in the IL (2mm). We should consider these numbers and the who/when/where of their enforcement.