# Delayed Execution: EIP-7886 - Implementation Proposal
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.
A nice overview of the delayed execution proposal, EIP-7886, is detailed in https://nerolation.github.io/delayed-execution-docs/.
## Motivation
- Reduce block validation and attestation latency
- Significant throughput improvements across the network
- Higher block gas limit
## Project Description
We will implement this EIP-7886 across Lighthouse and Reth since it contains both CL and EL changes respectively. Then, we'll be able to hook up both layers in a Kurtosis devnet for testing and benchmarking.
Both the EL and CL will need to modify their `Header` structs to include the parent block's execution outcomes.
Reth Specific Changes:
- Update the live sync path and backfill sync path:
- validate the previous block's execution outcomes against the header
- take snapshots before running transactions
- deduct fees from senders upfront
- roll back tsx on gas mismatches
Lighthouse Specific Changes:
- Update both the block proposal and validation flows to support the new `parent_*` fields
## Specification
### EL Changes (Reth)
1. Block Header `struct` updates:
```rust
struct Header {
pre_state_root: B256, // was state_root
parent_transactions_root: B256, // was receipt_root
parent_receipt_root: B256, // was receipt_root
parent_bloom: Bloom, // was logs_bloom
parent_requests_hash: B256, // was request_hash
parent_execution_reverted: bool, // tracks execution reversion
// unchanged fields below
}
```
2. A new struct `DelayedExecutionOutcome` will be used to cache block execution results and can be used for both live and backfill sync.
```rust
pub struct DelayedExecutionOutcome {
pub last_transactions_root: B256,
pub last_receipt_root: B256,
pub last_block_logs_bloom: Bloom,
pub last_requests_hash: B256,
pub last_execution_reverted: bool,
}
```
- Note that in the live sync path, these execution outcomes will likely be stored in a `Hashmap` on the in-memory `Tree_State` as to have a cache of outcomes that can be used to validate the canonical chain and also for re-orgs.
- For the backfill sync path, we can likely store these results between each block being executed in the `ExecutionStage` and overwrite the value after each block executes since backfill sync is a canonical linear chain from my understanding.
3. Before executing the block, the live sync's `on_new_payload` and the backfill sync's `ExecutionStage` will perform the following static checks past the fork boundary:
``` rust
fn validate_static(hdr, parent_outcome, current_state_root) {
// pre_state_root must match the cached state root of block N‑1
assert hdr.pre_state_root == current_state_root
// all other parent_* fields vs outcome
assert hdr.parent_transactions_root == parent_outcome.last_transactions_root
assert hdr.parent_receipt_root == parent_outcome.last_receipt_root
assert hdr.parent_bloom == parent_outcome.last_logs_bloom
assert hdr.parent_gas_used == parent_outcome.last_gas_used
assert hdr.parent_requests_hash == parent_outcome.last_requests_hash
assert hdr.parent_execution_reverted == parent_outcome.last_execution_reverted
// Additional checks per the EIP: (signatures, nonces, balances, inclusion gas ≤ header.gas_used, blob gas match, withdrawals root)
}
```
Note that once these checks are complete, the live sync path is free to send a `Valid` status if successful back to the CL.
4. Following static validation, the live sync path (`insert_block`) and backfill sync path (`ExecutionStage`) will then be responsible for the execution flow:
```rust
fn execute(hdr) {
// New (past fork boundary):
// 1. Take a snapshot of the state before execution.
// Live sync and backfill sync path should be able to use
// revm's snapshot mechanism for this
//
// 2. Pre-charge senders for maximum possible fees upfront
//
// 3. Process the transactions sequentially.
// If block_output.block_gas_used + tx.gas > block_env.block_gas_limit
// or the resulting block_output.gas_used != hdr.gas_used,
// set outcome.last_execution_reverted = true,
// stop executing the transactions
//
// 4. If outcome.last_execution_reverted = true,
// rollback the state to the snapshot,
// set gas_used = 0,
// and reset the execution outputs, i.e. receipts/logs.
// Else, save the state
//
// 5. Save the gas_used, transactions_root, receipt_root,
// logs_bloom, and requests_hash to an in-memory cache
// to be used when validating the next block's headers
}
```
5. `EngineApi` trait mods:
- Add `new_payload_v5`
- accepts `ExecutionPayloadV4` with the additional fields for block validation. We will have to modify the payload status to be returned after static validation.
- `engine_getPayloadV5`
- returns `ExecutionPayloadV4` with new fields for block building
- extend `EngineTypes` to define a new`ExecutionPayloadEnvelopeV5` and related types.
- add `EngineApiMessageVersion::V5` for the new hardfork.
6. Structs like `ForkTracker` will need to have a placeholder hard fork property for use as to apply the logic above only after the fork boundary.
### CL Changes (Lighthouse)
1. The `ExecutionPayload` struct is included in the `BeaconBlockBody`, so we will need to add a new variant to apply past the fork boundary that will include our new fields.
```
enum ExecutionPayload<E: EthSpec> {
Eip7886(ExecutionPayloadEip7886<E>), // new variant
}
```
2. Additionally, the evergreen `BeaconState` will need to be updated past the fork boundary to have its `ExecutionPayloadHeader` hold the new `parent_*` fields.
3. The EL's block header is RLP encoded and then hashed to obtain the`block_hash`. Since `block_hash` is stored in our `ExecutionPayload`, we'll need an `ExecutionBlockHeader` variant that includes the new fields. The `block_hash` is later recalculated by the EL to check for validity. The `block_hash` will also be persisted in the `BeaconState::ExecutionPayloadHeader`.
4. Lighthouse will JSON serialize the `ExecutionPayload` to send to the EL via the engine api for block validation. Therefore, we'll need a `new_payload_eip7886` helper to forward the payload to the engine. Similarly, a `get_payload_eip7886` helper will return payloads for block building.
5. Validator clients can request blinded blocks where only a payload header is sent. We'll need to extend this header:
```rust
pub struct BlindedPayload<E: EthSpec> {
execution_payload_header_eip7886: ExecutionPayloadHeaderEip7886<E>,
// existing variants
}
```
With these updates, Lighthouse will properly store parent execution output fields in the `BeaconState` and the `BeaconBlockBody`.
## Roadmap
| Period | Goals |
|---------------------------|-----------------------------------------------------------------------------------------------------|
| July – mid August | Lighthouse - update `BeaconState` and `BeaconBlockBody` execution payloads |
| Mid August – Mid October| Header struct + engine api updates. Then, update `ExecutionStage` + live sync path |
| Mid October – DevConnect | Testing and benchmarking of proposed changes |
## Possible challenges
- Maintaining backward compatibility pre-fork boundary will be complex
## Goal of the project
- Fully implements EIP-7886 across Lighthouse and Reth in order to benchmark performance gains in a Kurtosis devnet
## Collaborators
### Fellows
N/A
### Mentors
Reth - [Roman](https://github.com/rkrasiuk)
Lighthouse - [Mark](https://github.com/ethdreamer)
## Resources
[Lighthouse Github](https://github.com/sigp/lighthouse)
[Reth Github](https://github.com/paradigmxyz/reth)
[Delayed Execution Info Doc](https://nerolation.github.io/delayed-execution-docs/)
[EIP-7886 Delayed Execution](https://eips.ethereum.org/EIPS/eip-7886)
[EELS Spec](https://github.com/fradamt/execution-specs/tree/delayed-exec-basic)