---
# System prepended metadata

title: EPF Project Proposal

---


# Fork-Choice Enforced Inclusion Lists (FOCIL) For Grandine 

## Motivation

FOCIL (Fork-choice Enforced Inclusion Lists), defined in [EIP-7805](https://eips.ethereum.org/EIPS/eip-7805), is a proposed upgrade to Ethereum protocol that introduces Inclusion Lists(*lists of transactions that must be included in a block for it to be considered valid*). These lists are generated by a rotating committee of validators and enforced at the consensus layer through the fork-choice rule. The goal of FOCIL is to ensure that critical or previously censored transactions are included in a timely and unbiased manner, regardless of the block builder’s preferences.

This proposal addresses a pressing challenge: the centralization of Ethereum block building.
Today, over 80% of Ethereum blocks are built by just four builders, and approximately 3% of these actively censor transactions. This undermines Ethereum’s foundational principle of credible neutrality. If left unchecked, this trend could compromise the network’s trustlessness and decentralization. FOCIL mitigates this risk by reshaping block building by enfoorcing inclusion lists, ensuring Ethereum remains a fair, open, and neutral settlement platform.

## Project description

This project aims to implement FOCIL in [Grandine](https://github.com/grandinetech/grandine)(an ethereum consensus client).
All the features will be implemented according to the [Focil's ethereum/consensus-specs](https://github.com/ethereum/consensus-specs/tree/e678deb772fe83edd1ea54cb6d2c1e4b1e45cec6/specs/_features/eip7805) and  [Focil implementation in lighthouse](https://hackmd.io/@haxry/H1iXrWr8eg).


## Specification

#### FOCIL proposes changes to both execution and the consensus layer but it's majorly a consensus layer feature with few changes to the execution layer.

#### Execution Layer Changes:
* Build ILs from the mempool when requested by the consensus client.
* Enforces ILs during block building and validation.
  ##### New Engine API methods:
* `engine_getInclusionListV1` endpoint to retrieve an IL from the EL client.
* `engine_updatePayloadWithInclusionListV1` endpoint to update a payload with the IL that should be used to build the block. 
* `engine_newPayload`endpoint to include a parameter for transactions in ILs determined by the IL committee member.



#### Consensus Layer Changes:

##### Presets:
* `DOMAIN_IL_COMMITTEE`
* `IL_COMMITTEE_SIZE`
* `MAX_BYTES_PER_INCLUSION_LIST`

---

#### New Containers:

*  **Inclusion List**



 ```rust!
pub struct InclusionList<P: Preset> {
    pub slot: Slot,
    #[serde(with = "serde_utils::quoted_u64")]
    pub validator_index: u64,
    pub inclusion_list_committee_root: Hash256,
    pub transactions: InclusionListTransactions<P>,
}
```

*  **Signed Inclusion List**

```rust!
pub struct SignedInclusionList<P: Preset> {
    pub message: InclusionList<P>,
    pub signature: Signature,
}
```
---
#### P2P changes:

* **A new global topic for broadcasting `SignedInclusionList` objects.**

```rust!
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash, AsRefStr)]
#[strum(serialize_all = "snake_case")]
pub enum GossipKind {
    SignedInclusionList, // [New in EIP-7805] update execution payload with inclusion list
    ...
}
```
* **A new RPC topic for request SignedInclusionList based on IL committee index**

```rust!
pub enum RequestType<P: Preset> {
    InclusionListByCommitteeIndices(InclusionListByCommitteeIndicesRequest),
    ...
}

#[derive(Debug, Clone, PartialEq)]
pub enum Response<P: Preset> {
    InclusionListByCommitteeIndices(Option<Arc<SignedInclusionList<P>>>),
...
}
```
---
#### Validator Client Changes:

*  **New inclusion list committee assignment:**
A validator may be a member of the new Inclusion List Committee (ILC) for a given slot. To check for ILC assignments the validator uses the helper `get_ilc_assignment(state, epoch, validator_index)` where epoch <= next_epoch.

```python!
def get_ilc_assignment(
        state: BeaconState,
        epoch: Epoch,
        validator_index: ValidatorIndex) -> Optional[Slot]:
    """
    Returns the slot during the requested epoch in which the validator with index `validator_index`
    is a member of the ILC. Returns None if no assignment is found. 
    """
    next_epoch = Epoch(get_current_epoch(state) + 1)
    assert epoch <= next_epoch

    start_slot = compute_start_slot_at_epoch(epoch)
    for slot in range(start_slot, start_slot + SLOTS_PER_EPOCH):
        if validator_index in get_inclusion_list_committee(state, Slot(slot)):
            return Slot(slot)
    return None
```
If validator is a member of the inclusion list commitee for a particular slot then it uses the helper function `get_inclusion_list_signature` to sign the IL retrived from the execution layer by calling the `engine_getInclusionListV1` api endpoint.
```python!
def get_inclusion_list_signature(
        state: BeaconState, inclusion_list: InclusionList, privkey: int) -> BLSSignature:
    domain = get_domain(state, DOMAIN_IL_COMMITTEE, compute_epoch_at_slot(inclusion_list.slot))
    signing_root = compute_signing_root(inclusion_list, domain)
    return bls.Sign(privkey, signing_root)
```
*  **New Proposer Duty:**
 
The proposer should call `engine_updateInclusionListV1` at `PROPOSER_INCLUSION_LIST_CUT_OFF` into the slot with the list of the inclusion lists that gathered since `inclusion_list_CUT_OFF`. This will be then used by the EL to build the execution payload containing all the transactions it gets via the `engine_updateInclusionListV1` endpoint for a particular slot.

---

#### Fork Choice Updates:

*  **Modified Store:**
```python!
@dataclass
class Store(object):
     ...
    inclusion_lists: Dict[Tuple[Slot, Root], List[InclusionList]] = field(default_factory=dict) # [New in EIP-7805]
    # list of validators submitting different IL's multiple times in a slot
    inclusion_list_equivocators: Dict[Tuple[Slot, Root], Set[ValidatorIndex]] = field(default_factory=dict) # [New in EIP-7805]
```
* **Validating a received IL before caching it in the Store:**

Whenever an Inclusion list is received over the gossipsub, this function is called to validate it before caching it in the Store.
```rust!
impl<P, E, A, W> Controller<P, E, A, W>
where
    P: Preset,
    E: ExecutionEngine<P> + Clone + Send + Sync + 'static,
    A: UnboundedSink<AttestationVerifierMessage<P, W>>,
    W: Wait,
{
    pub fn on_gossip_signed_inclusion_list(
        &self,
        signed_inclusion_list: Arc<SignedInclusionList<P>>,
        gossip_id: GossipId,
    ) {
        self.spawn_block_task(signed_inclusion_list, BlockOrigin::Gossip(gossip_id))
    }
    ...
}
```

* **Validating a block for inclusion list :**
```python!
def validate_inclusion_lists(store: Store, inclusion_list_transactions: List[Transaction, MAX_TRANSACTIONS_PER_INCLUSION_LIST * IL_COMMITTEE_SIZE], execution_payload: ExecutionPayload) -> bool:
    """
    Return ``True`` if and only if the input ``inclusion_list_transactions`` satifies validation, that to verify if the `execution_payload` satisfies `inclusion_list_transactions` validity conditions either when all transactions are present in payload or when any missing transactions are found to be invalid when appended to the end of the payload unless the block is full.
    """
    ...
```
---
**For more detailed changes see [here](https://hackmd.io/jV2ugVx8TpaNcONhXXLKtg?view)**.

---

## Roadmap

* **Milestone 0** (week-6) - Get familiar with the Grandine's Codebase and figure about the parts to change .

* **Milestone 1** (week 7-10) - Complete the basic types definition and networking change.
epic 1: Implement the basic types definition, ssz codec
epic 2: Implement the gossip message definition, handler for sending and receiving.

> Note: the optional RPC req/resp for query missing inclusion list by committee indices will be implemented later

* **Milestone 2**(week 11-14) - Complete the execution engine API change

    epic 1: Implement the two new endpoints
    epic 2: Implement the NewPayload endpoint modification
* **Milestone 3**(week 15-18) - Complete the validator duties

    epic 1: Update inclusion list committee assignment
    epic 2: Validate and store the inclusion list
    epic 3: Validate the block with the inclusion list
* **Milestone 4**(week 19-22) - Complete the proposer duties, attester duties, inclusion list committee duties

    epic 1: Update execution payload with inclusion list and publish the block
    epic 2: Vote the block which is satisfied with inclusion list rules
    epic 3: Build and publish the inclusion list
* **Milestone 5**(week 23 onwards) - Complete spec tests, the local interop devnet test and the optional p2p RPC req/resp endpoint

    epic 1: Implement spec tests
    epic 2: Implement the RPC message definition, handler for request and response.
    epic 3: Implement the local interop devnet

## Possible challenges

* Lack of Experience For Interop Testing.


## Goal of the project

The end goal of the project is to enable Grandine to support FOCIL and complete interop tests, follow FOCIL's time plan and ensure that Grandine continues to follow up.

## Collaborators

### Fellows 

NA

### Mentors

No Official Mentor

## Resources
* [FOCIL consensus specs](https://github.com/ethereum/consensus-specs/tree/e678deb772fe83edd1ea54cb6d2c1e4b1e45cec6/specs/_features/eip7805)
* [EIP-7805](https://eips.ethereum.org/EIPS/eip-7805)
* [Lighthous's FOCIL implementation](https://github.com/sigp/lighthouse/tree/electra-focil)
* [Understanding FOCIL in Lighthouse](https://hackmd.io/@haxry/H1iXrWr8eg)
