# Week 0 - Notes on my EPF Project: EIP-7886 Delayed Execution for the EL client, Reth | References | TL;DR | |------------------|---------------------------------------------------------------------------------------------------| | [EIP-7886 Delayed Execution](https://eips.ethereum.org/EIPS/eip-7886) | Delayed Execution splits block validation into two steps. When you receive block N, the execution layer only checks its header and verifies that it correctly references block N – 1’s execution results (state root, receipts, logs). Running block N’s transactions happens later, so the validator can vote on the block much faster. | | [Engine Api Overview](https://hackmd.io/@danielrachi/engine_api)| The engine api is primarily used by the CL to alert the CL of a new block tip via LMD-Ghost's fork choice rules and also to notify the EL that there is a new block ready to be validated. | [Block Proposals](https://ethereum.org/en/developers/docs/consensus-mechanisms/pos/block-proposal/)| The block proposer includes an `ExecutionPayload` in the `BeaconBlockBody` when gossiping to peers. This payload contains the data needed by the EL to verify a block. | ## Project Motivation Implementing delayed execution per [EIP-7886](https://eips.ethereum.org/EIPS/eip-7886) will remove EL transaction processing from the critical path during the validator attestation flow, which should allow for higher gas limits and additionally move the protocol closer towards SSF since there will be more time for block propogation within a single slot. Additionally, I chose to implement this for Reth due to familiarity with Rust, and I've been contributing to them for some time now. The EIP is in pretty good shape to begin building out. There's still some ongoing conversation regarding how to prevent free DA in a block if a transaction fails, which we'll explore in the coming weeks. Check out the current discussion at the [Eth R&D Discord - Delayed Execution](https://discord.com/channels/595666850260713488/1364000536738664470). ## Architectural Changes It will be important to investigate the parts of the EL architecture that will be most impacted by delayed execution, namely: - engine api - local block building - block validation Today, we'll investigate the contents of a beacon block that is propogated across the network and how the receiving peer's CL will pass the new block's payload to the EL via the engine api for validation. This is important to understand so that way we can determine the modifications to the execution payload that will be constructed during block building, since this is the most upstream part of the flow that will be impacted by this EIP. In a follow-up post, we can dive deeper into modifications related to block validation itself, which is where the real value of the EIP lies. ## Current Block Proposal Flow The block proposer is expected to broadcast a signed beacon block that builds on top of the most recent head of the chain, based on the output of their locally running LMD-GHOST fork choice algorithm. The contents of the beacon block include both consensus layer data and the execution payload: ``` BeaconBlockBody(Container): randao_reveal: BLSSignature eth1_data: Eth1Data graffiti: Bytes32 proposer_slashings: List[ProposerSlashing, MAX_PROPOSER_SLASHINGS] attester_slashings: List[AttesterSlashing, MAX_ATTESTER_SLASHINGS] attestations: List[Attestation, MAX_ATTESTATIONS] deposits: List[Deposit, MAX_DEPOSITS] voluntary_exits: List[SignedVoluntaryExit, MAX_VOLUNTARY_EXITS] sync_aggregate: SyncAggregate execution_payload: ExecutionPayload // field of interest ``` ### Populating the ExecutionPayload We are most interested in how to populate the `execution_payload` field, which will contain the block built by the EL when using a local builder (i.e., no MEV-Boost). Here’s the current flow: 1. The proposer's CL will send a `engine_forkchoiceUpdated` to it's execution layer containing the following fields: ``` { "forkchoiceState": { "headBlockHash": "0x...", "safeBlockHash": "0x...", "finalizedBlockHash": "0x..." }, "payloadAttributes": { "timestamp": "0x...", "prevRandao": "0x...", "suggestedFeeRecipient": "0x...", "withdrawals": [...], // Optional (Shanghai and later) "parentBeaconBlockRoot": "0x...", // Optional (Cancun and later) "transactions": [...], // Optional "gasLimit": "0x..." // Optional } } ``` 2. The EL will start constructing a list of transactions using its local mempool. It will then execute the transactions, apply withdrawals, compute trie roots, and store the full payload. 3. When the CL queries again with `engine_getPayload` and passing the `payloadId`, the EL responds to the CL with the following `ExecutionPayload` fields: ``` { "executionPayload": { "parentHash": "0x...", "feeRecipient": "0x...", "stateRoot": "0x...", "receiptsRoot": "0x...", "logsBloom": "0x...", "prevRandao": "0x...", "blockNumber": "0x...", "gasLimit": "0x...", "gasUsed": "0x...", "timestamp": "0x...", "extraData": "0x...", "baseFeePerGas": "0x...", "blockHash": "0x...", "transactions": ["0x...", ...], "withdrawals": [...], "blobGasUsed": "0x...", "excessBlobGas": "0x...", "parentBeaconBlockRoot": "0x..." }, "blobsBundle": { "blobs": [...], "commitments": [...], "proofs": [...] }, "blockValue": "0x..." } ``` 4. This payload is entered into the `BeaconBlockBody`, and the `BeaconBlock` is then signed and gossiped over the network. ### Beacon Block Received by Peer Upon receiving the `BeaconBlock`, the peer extracts the `executionPayload` and sends it to the EL via `engine_newPayload`. The EL locally constructs a block header and block body using the `ExecutionPayload` data sent and will then run the STF to verify headers, execute transactions against the current state, check withdrawals and blob commitments, and update the world state if all is valid. If successful, the EL will respond with a status `VALID` and a `latestValidHash`, which is the hash of the most recent valid block in the branch and is typically the hash of the block header reconstructed from the submitted `ExecutionPayload`, assuming the payload was fully validated and applied. This header hash is deterministically computed from fields like `parentHash`, `stateRoot`, `receiptsRoot`, `logsBloom`, and others. The EL confirms that the block’s execution, trie updates, and structure are correct before acknowledging it as valid. When`latestValidHash` is passed to the CL and included in the fork choice logic alongside validator attestations, this helps LMD-GHOST determine the canonical head of the chain. If the payload is invalid, `latestValidHash` instead refers to the most recent valid ancestor. ## Delayed Execution Changes to Flow Described Above Well, `BeaconBlockBody` will remain the same, but its `ExecutionPayload` field, which is derived from the El's response to `engine_getPayload`, will require some field modifications. ### engine_getPayload The CL calls `engine_getPayload` currently in order to obtain a transaction list and block header when proposing a block. Now, the the payload response will contain the following fields. ``` { "executionPayload": { "preStateRoot": "0x...", "parentTransactionsRoot": "0x...", "parentReceiptRoot": "0x...", "parentBloom": "0x...", "parentRequestsHash": "0x...", "parentExecutionReverted": false, "gasUsed": "0x...", "transactions": [...], ... } } ``` Note that the `preStateRoot` and `parent*` fields will be held in memory from the previously executed block. The CL then assembles `BeaconBlockBody`, signs, and gossips it just like before. ### engine_forkchoiceUpdated: The `engine_forkchoiceUpdated` flow will remain the same. ### engine_newPayload When the EL needs to validate a block after a call from `engine_newPayload`, it will skip execution and immediately respond to the CL with a `Valid` status after performing some quick checks per the EIP such as: - header verification - Signature validity - Nonce and balance checks - inclusion gas checks The EL then executes the block's transactions asynchronously and caches the outcome in memory. Note that we will go more into the transaction validation flow in a follow-up post. ## Week 1 TODOs Next week, I plan to: - delve deeper into updates to the block validation flow itself - FOSS contributions to Reth