# 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)