A distributed system with a decentralized consensus referred to as a decentralized state machine or generally, a blockchain. To achieve the consensus in a decentralized manner, participants have to be trustworthy with some credential-basis method or a do or die system. Also, being able to tell it is a decentralized consensus, it has to be accessible to whoever wants to participate and contribute.
Not every blockchain has a decentralized consensus participation. They can whitelist certain participants to help achieve the consensus while other participants can do what they want on the blockchain. Whitelist operation can be done through a credit-base system or the protocol owner itself can be the only decider.
However, a protocol which works with a decentralized consensus achievement through decentralized contribution can face some issues. The participants can have some trouble while synchronizing the protocol or they may have to get rid of the censor blockage. In the protocols, the workers or the deciders of which block will contain which transaction can be malicious and block others. So, decentralized consensus achievement with censorship resistance in individual contributors is a key for the process.
EIP-7805, a.k.a. FOCIL, is a way to achieve censorship resistance Ethereum at the protocol level (EL (Execution Layer) + CL (Consensus Layer)). FOCIL, Fork Choice enforced Inclusion Lists, is a mechanism to allow a committee of validators to be able to force-include a set of transactions independent of the decider of transaction settlement.
The block building process in Ethereum is in the hands of validators (a set of participants who locked their funds to be an active contributor). In a certain time interval, the validator set receives the pending transactions to settle as a list. Validators have the right to choose the transactions and create a better valued transaction list to be included in the block. The chosen list will be chosen by the block builder and they mostly prefer the most valued transaction list proposal.
However, a mechanism called MEV-boost (we will talk about what is MEV and MEV-boost in this article soon) exists on Ethereum for block building. It helps the contribution of block building more distributed. This mechanism, optimizes the bad MEV for protection and lets block builders work together. The MEV-boost mechanism is a temporary fix until PBS (Proposer-Builder Separation) hits the mainnet.
MEV (Maximal Extractable Value) is the existence of differentiation between the resources and participants’ assets. With the derivation of this short explanation, MEV in a block is referred to as the maximal extractable value from the block production process based on block reward and gas fees with or without changing the order of transactions.
Since Ethereum is a decentralized protocol, MEV exists. Although it exists as good and bad versions, users mostly face some troubles with bad MEV. With the existence of MEV, validation is a profitable work through execution. However, searchers are taking the most profit out of this process. Searchers run algorithms on blockchain data to detect the most profitable opportunities and have bots to submit these profitable transactions to the network.
Searchers are independent participants which are willing to contribute to the network with their hardware for profit. This group is not locking a certain amount of their assets like validators or waiting in line for entering the active participation.
Validators look for the most profitable proposal coming from the independent searchers and choose the best fit for them to make/extract some profit out of it. Searchers got the most of the MEV but they have to pay high gas fees to send validators their transaction list with a priority. Because the gas fees go to validators, they extract their most profit from the transaction list receiving workflow.
MEV introduces certain gain ways like arbitrage between the DEXs. If the same token has different prices in different DEXs, users or bots can buy it from the low price and sell with the high price.
PBS aims to minimize the impact of bad MEV with the separation of block building and proposing.
For avoiding giving the full control of block building to a certain set of validators and block builders will make the protocol less-distributed and non-decentralized. It also opens up the blockage for certain transactions with censors. If a validator is the only one who decides which block including which transaction and settlement, a malicious validator could possibly change the pending transactions with others. They can even change the list with some of their transactions.
Ethereum has several improvement proposals and technologies in it for the censorship resistance structure.
In the basis of DVT, the key management process makes the most difference. DVT, spreads out the key management and signing responsibilities across multiple parties in time periods.

Distributed Validator Protocol/Technology (DVP/DVT) is the protocol of Distributed Validator Clients (DVC). Clients handle the networking (attestation share, signature listen, attestation construction, etc.) through them through this protocol. Beacon Node, is the access and infrastructure of interacting with Ethereum P2P layer. The DVP communicates with Beacon Node (BN) and Remote Signer (RS) parts. DVC plays the role of middleware between RS and BN and BN-RS communication handled over HTTP through Ethereum Beacon Node API.
RS, hosts a server for allowing incoming requests for signing messages. DVC instructs the RS to sign appropriate messages to serve the validator’s assigned duties. DVC request the duties from BN at the start of every epoch, schedule serving of the received duties at appropriate times, and when triggered it handles the consensus with co-validation, instructs the RS to sign over the dedicated data, broadcast the signature from RS to other co-validators, re-combinates the signature shares after receiving enough of shares to do a successful combination, and send the combined signature to the attached BN to be gossipped to the PoS (Proof-of-Stake) network.
In every epoch of forming the consensus with dedicated data, DVC checks the validity of the data for slashing purposes. For this process, DVC maintains a local cache because it doesn’t have direct access to RS’s slashing data.

The sequence diagram above shows the attestation production process. DVC, asks BN to attestation duties for the next epoch with a get function for the attestation duties for the epoch (bn_get_attestation_duties_for_epoch()). This function takes the input of the validator indice (validator_indices) and next epoch’s number (epoch+1). For each attestation duty received, a function to serve attestation duty (serve_attestation_duty()) runs including the database data of slashing (slashing_db) and received attestation duty (attestation_duty).
After receiving the duty, BN prepared a committee subnet for checking the aggregator by computing the signature of the slot (slot_signature) with an checker for aggregation (is_aggregator()) and a function to get slot signature (get_slot_signature()) as a start.
def get_slot_signature(state: BeaconState, slot: Slot, privkey: int) -> BLSSignature:
domain = get_domain(state, DOMAIN_SELECTION_PROOF, compute_epoch_at_slot(slot))
signing_root = compute_signing_root(slot, domain)
return bls.Sign(privkey, signing_root)
def is_aggregator(state: BeaconState, slot: Slot, index: CommitteeIndex, slot_signature: BLSSignature) -> bool:
committee = get_beacon_committee(state, slot, index)
modulo = max(1, len(committee) // TARGET_AGGREGATORS_PER_COMMITTEE)
return bytes_to_uint64(hash(slot_signature)[0:8]) % modulo == 0
If the checked validator selected as aggregator (output of is_aggregator() == True), it constructs an aggregate attestation with, collecting the attestation via the gossip network during the slot that have an equivalent attestation data (attestation_data) to that constructed by the validator. And if the length of attestation is greater than 0 (len(attestations) > 0), meaning that it exists, creates an aggregated attestation (aggregate_attestation: Attestation) with the data, aggregation bits, and aggregate signature.
Data is formed as the aggregated attestation’s data with the construction through aggregate_attestation.data = attestation_data where the attestation_data parameter is the AttestationData object that is the same for each individual attestation being aggregated.
Aggregation bits part uses the function of aggregate_attestation.aggregation_bits be a Bitlist[MAX_VALIDATORS_PER_COMMMITEE] of length the committee (len(committee)).
Aggregating the signature part is handled with setting the aggregate_attestation.signature = aggregate_signature where aggregate_signature is obtained from:
def get_aggregate_signature(attestations: Sequence[Attestation]) -> BLSSignature:
signatures = [attestation.signature for attestation in attestations]
return bls.Aggregate(signatures)
The aggregated signature broadcasted through the validator which is selected as aggregator (with is_aggregator()), to the network. This process starts with receiving the duty, aggregating the attestation and serving the attestation as a duty handled with the serve_attestation_duty() function. Whole attestation serving process handled in DVC.
If the selected DVC is a proposer, a function (bn_produce_attestation_duties_for_epoch()) is activated for checking and producing the attestation duty for the BN. If it is not, the system listens to the proposal value to continue.
Next step is the consensus part of attestation. Attestation data (attestation_data) is assigned to a newly function consensus_on_attestation with slashing_db and attestation_duty parameters.
attestation_data = consensus_on_attestation(slashing_db, attestation_duty)
Consensus checks whether or not the attestation is valid:
assert consensus_is_valid_attestation_data(slashing_db, attestation_data, attestation_duty)
If there are more consensus processes remaining, it is handled in this timezone.
And it updates the attestation slashing database’s data with the output information:
update_attestation_slashing_db(slashing_db, attestation_data, attestation_duty.pubkey)
Next step is the RS show up for signing the attestation data through rs_sign_attestation(). Attestation data has to be signed by RS and shared to the participants for further movement. Sharing the attestation signature needs the signed attestation by RS.
attestation_signature_share = rs_sign_attestation(attestation_data, attestation_signing_root, fork_version)
attestation_signature_share = Attestation(data=attestation_data, signature=attestation_signature_share)
After the attestation signing by RS, it got broadcasted by shares into the DVC.
broadcast_attestation_signature_share(attestation_signature_share)
The next process is to combine attestations (attestation_combination()). This process is done in 3 steps: The one in charge needs to always listen for new attestation signature shares from other DVCs. Whenever a set of shares is found, it is combined to construct a complete signed attestation which constructs the attestation itself. The attestation got sended to BN for Ethereum P2P gossip network.
def attestation_combination() -> None:
attestation_signature_shares = listen_for_attestation_signature_shares()
complete_signed_attestation = construct_signed_attestation(attestation_signature_shares)
bn_submit_attestation(complete_signed_attestation)

Block production process is almost same as attestation production. DVC talks with BN for getting the proposer duties for the epoch (get_proposer_duties_for_epoch()), with the response DVT serves the regarding duties (serve_proposer_duties()) but there are small changes in these processes. Next step in block production is to compute randao reveal signature and which root it contains the related attachment.
randao_reveal_signing_root = compute_randao_reveal_signing_root(proposer_duty.slot)
This time, RS signs the output of which root RANDAO data is on and gives back the output to DVC again. DVC broadcasts the RANDAO signature share for other clients.
randao_reveal_signature_share = rs_sign_randao_reveal(compute_epoch_at_slot(proposer_duty.slot),
fork_version, randao_reveal_signing_root)
broadcast_randao_reveal_signature_share(randao_reveal_signature_share)
After the broadcast, each DVC peer shares their part with the same broadcasting and computing process. The system listens for these RANDAO reveal signature shares from DVC peers for further combination of the shares.
Combination of shares handled with randao_reveal_combination() function. At the beginning, the system listens to the DVC peers for RANDAO reveal signature shares, whenever a set of shares found is combined for a complete RANDAO reveal construction, and output is the reveal data.
def randao_reveal_combination() -> BLSSignature:
randao_reveal_signature_shares = listen_for_randao_reveal_signature_shares()
complete_signed_randao_reveal = construct_signed_randao_reveal(randao_reveal_signature_shares)
return complete_signed_randao_reveal
If the targeted DVC is a proposer, peer interacts with BN to produce (bn_produce_block()) the block. If not, DVC peer listens to the proposal value from the proposer peer.
def bn_produce_block(slot: Slot, randao_reveal: BLSSignature, graffiti: Bytes32) -> BeaconBlock: pass
bn_produce_block() interacts with Beacon APIs to learn the provided data for a given block and produces a valid block depending on the data. DVC checks whether or not the block is valid with consensus (consensus_is_valid_block()). If the output is positive, consensus does its further jobs for remaining parts.
assert consensus_is_valid_block(slashing_db, block, proposer_duty, randao_reveal)
Likely the attestation production process, next step is to update the slashing database with the data from the consensus. Decision of slashing occurred in a certain slot with the slashing database (slashing_db), specified block (block), and proposer’s duty with its public key (proposer_duty.pubkey) parameters.
update_block_slashing_db(slashing_db, block, proposer_duty.pubkey)
RS signs the block. But first, the block signing root (block_signing_root) computed with the compute_block_signing_root() function. RS, signs the block’s signature share (block_signature_share), block signature share parameter got specified and broadcasted to other DVC peers.
block_signing_root = compute_block_signing_root(block)
block_signature_share = rs_sign_block(block, fork_version, block_signing_root)
block_signature_share = SignedBeaconBlock(message=block, signature=block_signature_share)
broadcast_block_signature_share(block_signature_share)
After RS sends the related output to DVC, broadcasting the block signature share for each DVC peer starts. Next is to combine (block_combination()) the block signature shares for finalizing the block construction. The system listens to the DVC peers for block signature shares. Whenever a set of shares is found, a complete signed block gets constructed and sended to the BN for Ethereum P2P gossip network’s interactions.
def block_combination() -> None:
block_signature_shares = listen_for_block_signature_shares()
complete_signed_block = construct_signed_block(block_signature_shares)
bn_submit_block(complete_signed_block)
As a conclusion for DVT, validators enter into a group which is named as cluster. This cluster is a pre-organized group of validators. DVT, uses this cluster’s existence to split the private key for signing purposes across many validators. DVT uses BLS signature scheme, which enables it to add partitions of the private key into one in need.
With DVT, the private key is not fully stored in one machine. This increases the security in many terms like eliminating the possibility of single points of failure. Also, it introduces new risks like making it hard to find bugs around the validator clients.
With the Merge, staking required validators to stake at least 32 ETH for being activated. The beginning started fair, however people trusted staking providers for their ETH and it makes Ethereum’s validator hub a little bit centralized. DVT achieves decentralization through private key management, so staking providers could exist with high staked amounts without being a risk for censorship resistance of Ethereum. DVT is a different validation method with complete new instructions rather than PBS, ePBS, and APS.
PBS, is the way Ethereum's current block building works. Before PBS, validators are the only ones in charge to create and broadcast blocks. They bundle the transactions they heard from Ethereum's P2P gossip network and insert them into a block that is sent out to peers on the Ethereum network.
PBS, separates this task across multiple validators to achieve censorship resistance networking and decentralization. In the current scheme, block builders are the ones who are responsible for creating the blocks and offering them to the block proposer in each slot. For blocking the malicious actions, block proposers cannot see the contents of the block and they choose the most profitable one. Block proposer gives a fee to block builder and sends the block to peers, so both parties get incentivized.
MEV on PBS is a little bit different. PBS reconfigures the MEV extraction in a better way. Instead of block proposers doing their own research for better MEV, they simply pick the blocks from block builders’ offers. Block builders may have done some MEV research but the reward for it will go to the block proposer. This route change protects the MEV extraction centralization and makes home staking better.
Vitalik wrote a piece about how to set the PBS with a friendly fee market design for MEV extraction. He mentioned that the centralization risk possibility can increase because of the economics of MEV if tricks exist from the ability to choose the contents of the next block. Vitalik himself sees PBS as the best known solution today. Because two parties are working together, one will not want to extract too much profit because it will go to the other party and so on.
PBS has a lot of proposals to make it greater. One of them includes MEV Boost. Mev boost is a middleware which is run by validators to access the competitive block-building market, in other words, auctions. MEV Boost, directly designed for PBS to make it more fair. Validators access a marketplace of builders for accessing the blocks. Builders propose the blocks and a fee for the block proposing validator. This way, makes the parties separated (as proposer and builder) and creates a competition in a way of auction.

Each node operator has to run a validator client, a consensus client, and an execution client. MEV-boost itself is an extension software to the consensus client which enables validators to maximize their staking rewards by selling the block space into an open market. MEV-boost queries and outsources the block building into a network of builders. If we look at the diagram above, block builders prepare the full blocks, send it to relays, relays aggregates the blocks from multiple builders and mark the best block to submit to the block proposer. The proposing validators’ consensus client propagates the block received from MEV-boost to the Ethereum network for attestation and block inclusion process.
Vitalik also proposed an alternative PBS with MEV-boost usage but replacing the relayer with a size of 256 committee of validators. In this proposal, instead of making it more centralized with relayers, the builder erasure-codes their payloads into 256 chunks and encrypts the i’th chunk to the public key of i’th committee member and sends this into a P2P subnet.

This proposal aimed to achieve a more decentralized method and more secure enabling with cryptography.
ePBS is the enshrined version of PBS which aims to evolve the consensus layer to implement PBS at the protocol level of Ethereum. ePBS is a proposal that is included in The Scourge which will be handled in the future as an upgrade. However, since Ethereum itself is a little bit slow on development of the core protocol (argumentative) people like Flashbots built MEV-boost rather than the in-protocol PBS. Flashbots, tried to create a fair block building structure until the ePBS and it got massively adopted in the process. However, as we talked, MEV-boost needs a third-party called relays and this part is centralized.

MEV-boost relays are run by a small group of companies (currently 6 companies run the total of 12 relays). While writing this article, MEV-boost has 12 relays and 28 builders working.

These relays are highly costly for operational purposes and don't have a clear funding mechanism to even neutralize itself. ePBS, with integrating PBS in the protocol level, will cancel out the centralized and outsourced relay mechanism and will depend on the decentralized Ethereum P2P network. Integration of PBS into protocol level is also a bit tricky. It might have broken some parts because of some bugs. There are a lot of design considerations like Two-slot PBS, optimistic relays, stateless relays, payload-timeliness committee, etc.
Also, we need a little mention here for MEV Burn which is a simple extension to ePBS. It specifies payload base fees. Without MEV Burn, ePBS incentives builders to compete for the largest builder balance. In a MEV spike, it makes MEV go to the most capitalised builder and makes it centralized. Until a solution with L1 zkEVM, MEV Burn can solve this issue by relaxing the requirements on the pre-execution builder balance to cover a maximum upfront payload base fee and the payload tip. In this approach, a malicious builder can only force it to be empty for certain slots. Forcing slots to stay empty, can’t steal the MEV.
Next step for greater PBS technology is inclusion lists. Inclusion lists are not in the production but they secure the privacy while serving better experience for block proposers. In inclusion lists, validators know the transactions (they can see the mempool) but they can’t see them included in the blocks. Validators can impose the must-have transactions in the next block but they can’t censor the whole process. These lists are generated from the block proposers’ local mempools and sent to their peers before the block proposal. If any of the transactions are missing from the inclusion lists, the list can be rejected. It is an open research area like adding encrypted mempools for full privacy etc.
Attester Proposer Separation or Execution Auctions aims to enshrine an ahead-of-time slot auction into the consensus layer. A slot auction allocates the entire block to the winner of the auction but they no longer need to commit to the specific contents of the block when bidding.

Basicly, the builder bids the auction to the proposer and the committee members, proposer commits to the highest bid by signing and publishing the winning bid, and committee enforces that the proposer selected a high bid according to them.
This mechanism allows the protocol to serve as the trusted third-party in the sale of the block building rights for a future slot. Or in other words, it separates the attester from the proposer for better decentralization in block building. Some may talk about APS as block space neutralizer.

In this talk, Justin Drake mentioned APS is one of the key factors of the Beacon chain design. At the first glance, searchers and builders got separated with MEV-geth in 2021, builders and proposers got separated with MEV-boost in 2022 and as a final boss APS will separate proposer and attester.
APS comes with certain advantages, mainly quoted as, validator decentralization, proposer sophistication, and others. With APS, the validator set becomes more decentralized than its current set structure. Validator decentralization increases with the advantages that come from attester segregation, reward smoothing, and MEV stealing. Proposer sophistication mainly noted in SSLE deprioritisation, ePBS deprioritisation, and easier preconfirmations. And others can be considered as efficient MEV-burn and enshrined lottery.
Like other developments around protocol’s core, Inclusion Lists aims to provide censorship resistance with a solution. The most known EIP of this mechanism, called EIP-7547, tries to create lists to allow proposers specify a set of transactions that has to be included in the subsequent blocks to be considered as valid. Or in basic words, a list that includes a set of transactions that has to be agreed by the proposer to be included in the next block.
Due to the risks of block building outsourced after the Merge (with PBS), which gives the block building duties of proposers to builders, the transactions can be extracted maliciously from the set of they have to be. This can cause up with internal block building at the proposer level. Inclusion lists here, is the proposed way to force certain transactions without personal opinions.
It works as the next proposer chooses a certain set of transactions that must be included in the slot without prioritizing the MEV reward. Since this whole process will be non-incentivized, EIP-7547 proposes it with a MEV reward with a new name, “Forward Inclusion Lists”.
In the “forward” approach, if the proposer got the inclusion warning in slot N then they will have to include the specified parts into slot N+1. This way, it got incentivized and promptly included. EIP-7547 proposed two constant parameters as the value of maximum includeable transaction and how much these transactions can be referred as gas. Also, it defines the minimum transaction value as 1 for avoiding the causable errors that can come from 0.
MAX_TRANSACTIONS_PER_INCLUSION_LIST: 16MAX_GAS_PER_INCLUSION_LIST: 2^21MIN_SLOTS_FOR_INCLUSION_LIST_REQUEST: 1
This proposal specifies the process with certain class structures named as; InclusionListSummaryEntry, InclusionListSummary, SignedInclusionListSummary, and InclusionList. It also specifies some changes on existing structures as known as; ExecutionPayload, ExecutionPayloadHeader, and BeaconBlockBody.
class InclusionListSummaryEntry(Container):
address: ExecutionAddress
gas_limit: uint64
InclusionListSummaryEntry takes execution address (ExecutionAddress) and gas limit (gas_limit) as parameters. This way, inclusion lists proposed address and gas limit of the list is processed.
class InclusionListSummary(Container):
slot: Slot
proposer_index: ValidatorIndex
summary: List[InclusionListSummaryEntry, MAX_TRANSACTIONS_PER_INCLUSION_LIST]
InclusionListSummary takes InclusionListSummaryEntry as a starting point. InclusionListSummary takes slot (slot), proposer’s index value (proposer_index), and summary (summary) as parameters. Summary (summary) is represented as a list and includes InclusionListSummaryEntry and MAX_TRANSACTIONS_PER_INCLUSION_LIST constant.
class SignedInclusionListSummary(Container):
message: InclusionListSummary
signature: BLSSignature
SignedInclusionListSummary takes two parameters: message and signature. This part is where the inclusion lists are approved and signed by the proposer validator. After the signing process, the system starts to create the inclusion lists in general structure.
class InclusionList(Container):
summary: SignedInclusionListSummary
transactions: List[Transactions, MAX_TRANSACTION_PER_INCLUSION_LIST]
InclusionList takes summary and transactions as parameters. transactions include transaction data and the constant for maximum transaction data count as inputs. For making inclusion lists work, it needs several changes on block structure or specifically on consensus and execution layers. Firstly, ExecutionPayload, ExecutionPayloadHeader, and BeaconBlockBody include new parts for inclusion lists.
class ExecutionPayload(Container):
…
inclusion_list_summary: List[InclusionListSummaryEntry, MAX_TRANSACTIONS_PER_INCLUSION_LIST]
inclusion_list_exclusions: List[uint64, MAX_TRANSACTIONS_PER_INCLUSION_LIST]
class ExecutionPayloadHeader(Container):
…
inclusion_list_summary_root: Root
inclusion_list_exclusions_root: Root
class BeaconBlockBody(Container):
…
inclusion_list_summary: SignedInclusionListSummary
At the end, whole process is looking like this:

FOCIL, Fork-choice Enforced Inclusion Lists, is the next superior mechanism to make Ethereum’s censorship resistance more robust than ever with timing included transactions to be included in the blocks. As we mentioned, inclusion lists are the list of transactions that are proposed by validators. Inclusion lists aim to get rid of out-sourcing block building by builders (MEV-boost) rather than validators after the Merge.
What makes FOCIL good is, it is enforced, independent, and guaranteed by time with protecting the MEV reward override. FOCIL doesn’t aim to destroy builder structure but it aims to force builders for certain duties. Right now, %95 of block building is done by builders which causes the increase of centralization and decrease of censorship resistance. If validators force builders to include transactions as a list (with (slot N+1) or without MEV gain (slot N)), all parties can do their duties however Ethereum protects itself from the censorship risks.
FOCIL, relies on multiple validators rather than a single proposer to construct and broadcast ILs. The problem FOCIL is trying to solve is mainly the centralization and censorship risk caused by the outsourcing block building to centralized entities known as builders. Since this is the case, FOCIL aims to fix the problem at the core with a committee-based approach.
In other words, this approach tries to reduce the probable attack surfaces for bribery and extortion attacks while increasing the censorship resistancy.
Fork-choice is a consensus rule which is defined as a node's decision based on the information given to it which block is the “best” head of the chain. In blockchains, like Ethereum, blocks are connected to each other in a canonical structure. Which means certain straight lines are the blockchains and these can only change their way with approved forks.
Ethereum itself has a different consensus fork-choice rule than others. It consists of a hybrid version of Casper FFG and LMD Ghost, or in other words Gasper. Gasper, allows the blocks which exist from the last finalized checkpoint to be candidates for the head of the canonical chain.

For canonical chain continuation, Casper FFG is used to finalize the checkpoints and LMD Ghost is used to select the best head block in any time of the canonical chain. At the end, the fork-choice rule eliminates and prevents the attackers from bypassing the system. A fork-choice decision is a hard process in too many ways and it is risky. Ethereum had several attacks and is still open to attack vectors around finalization with this complexity.
In Gasper, attestations are accepted with three votes: a vote for a source checkpoint, a vote for a target checkpoint, and a vote for a head block. In FOCIL, attesters vote only for the blocks that include transactions from a set of ILs which are provided by the committee of ILs and also these ILs have to satisfy certain constraints. Any block which may not be voted by the attesters or not meet certain criteria for IL properties or block structure in general cannot be canonical (or cannot be in the canonical chain).
FOCIL doesn’t use Forward Inclusion Lists aka EIP-7547 which we mentioned in this article. FOCIL rather chooses not to incentivize the mechanism in this way. With the FOCIL, parallel block building process for slot N+1 while slot N is building, the constraints imposed on block B for slot N+1 can include transactions which are submitted during slot N. Transactions here are specified by the committee of validators in ILs.

Same slot approach inclusion lists makes the guarantee of inclusion better but makes it less incentivized than forward inclusion lists.
FOCIL accepts the blocks which may not exist or cannot be included but included in the ILs if they cannot append the transactions to the end of the block or if they’re full. This conditional inclusion approach, increases the ratio of transaction inclusion into the blocks in unfortunate conditions.
FOCIL doesn’t approach the subject to make it incentivized like we saw in the slot process. If it chooses to work for the slot N+1 for slot N processes, it would make a MEV incentive for the workers. However it is proposed to work exactly in the same slot, slot N. Like the slot logic, FOCIL is unopinionated about the placement of transactions from the ILs within a block. This approach blocks the possible attack vectors to bypass the mechanism by using side channels. With the combination of the Property 4 (Conditional Inclusion), this makes the placement market less attractive for MEV searchers.
FOCIL doesn't have any incentive mechanism for the IL committee members. However, MEV-boost has a certain incentive mechanism for builders but it makes the block building more centralized and censorable while outsourcing the block building. There are some articles and some proposals to change the transaction fee mechanism for making FOCIL much better and incentivized but it doesn’t have a specific decision around. FOCIL’s current approach is to believe at least 1 of the IL committee members are trustable (1-out-of-N honesty assumption).
With these properties, FOCIL makes sense to achieve credibly neutral Ethereum. All of these properties are combined to make the censorship resistance greater. Since IL includes a committee-based protocol, the committee itself broadcasts along with a P2P network. If no broadcast happens in 9 seconds, the committee runs the get_head() function to get and save the head of the canonical chain.
get_head(store: Store) -> Root:
blocks = get_filtered_block_tree(store)
head = store.justified_checkpoint.root
while True:
children = [
root for root in blocks.keys()
if blocks[root].parent_root == head
]
if len(children) == 0:
return head
head = max(children, key=lambda root: (get_weight(store, root), root))
Store is an object that contains all of the data that is necessary for determining the best head of the canonical chain.

This is the complete workflow of FOCIL.
At t=0s:
Proposer:
Broadcasts the execution payload which satisfies the IL transactions over the P2P network.
Between t=0 and 4s:
Attesters:
Monitor the P2P network for the proposer’s block
If a block is detected check the validity of transactions against local ILs which are included in the proposer’s execution payload
If validity is absent or not fully-filled use EL to verify the validity of missing transactions in the IL.
At t=8s:
ILC Members:
If no block is received until 8s, run get_head() and build and release local ILs based on the node’s canonical head
Between t=0 and 9s:
ILC Members:
Construct local ILs and broadcast these over P2P network after processing the block for slot N and confirming it as the head of the canonical chain
Nodes:
Receive local ILs from the P2P network and only forward and cache those that pass the CL P2P validation rules
At t=9s:
Nodes:
Freeze the local ILs view
Stop forwarding and caching new local ILs
At t=11s:
Proposer:
Freeze the local ILs view
Ask EL to update execution payload by adding transactions from its view
Attesters check the validity by the Valid function. Valid function is likely a function below:
function Valid(payload, localIL, EL, ctx):
payload_tx_hashes = set(tx.hash for tx in payload)
missing = []
for tx in localIL:
if tx.hash not in payload_tx_hashes:
missing.append(tx)
if missing.is_empty():
return true
postState = EL.apply_payload(payload, ctx)
if postState.status != SUCCESS:
return false
tempState = postState
for tx in missing:
sim = EL.apply_tx(tempState, tx, ctx)
if sim.status == SUCCESS:
return false
return true
Approach based on the Valid function clearly solves the invalidation problems in the FOCIL structure.
FOCIL tries to improve Ethereum’s censorship resistance in the manner of block building. MEV-boost is innocent, however, the requirements of running the instances make it run by a low number of entities and some of these entities gain a significant dominance in current status. FOCIL, having a committee of validators for specific time intervals, inclusion lists and enforcement makes it one of the best ways for us to improve Ethereum’s censorship resistance.

If we explain the diagrams above:
ILC looks up to Mempool and build their local Inclusion Lists
Each member uses P2P network between to communicate with other committee members to agree on set of transactions
After the agreement, ILC members can broadcast their ILs with the transactions and a BLS signature
Builders see the signed ILs, store them and produce the blocks including the transactions in the ILs into the Execution Payload
Committee of Attesters checks the validity of the transactions before voting for the builders’ choose of transactions
Block got broadcasted
FOCIL requires certain changes in execution and consensus layers. In the execution layer, an additional validation check requirement is introduced for new execution payloads. After the payload execution, the validity check runs by checking whether or not any transaction from Inclusion Lists are not included in the payload and has validity to inclusion. In this scenario, the block got the validation itself but CL won’t attest to it. In addition to the set of transactions, the specified gas limit is also controlled for inclusion in this step.
Engine API under the execution layer gets new specifications:
For retrieving an IL from the
ExecutionEngine,engine_getInclusionListV1endpoint addition,For updating the payload with the IL that should be used in the block,
engine_updatePlayoadWithInclusionListV1endpoint (withpayloadIdargument for ongoing payload build) addition,For including a parameter for the transactions in ILs, modify
engine_newPayload,In case of inclusion errors that IL is not properly satisfied for the block,
INVALID_INCLUSION_LISTerror addition.
IL Building under the execution layer gets new specifications:
Building rules of ILs will be determined by the implementers,
ILs will have a maximum size of
MAX_BYTES_PER_INCLUSION_LIST = 8 KiBfor all RLP encoded transactions.
The consensus layer itself has certain changes in Beacon Chain, Fork-choice Rule, P2P Network, and Honest Validator Guide.
As preset, Beacon chain changes DOMAIN_IL_COMMITTEE, IL_COMMITTEE_SIZE, and MAX_TRANSACTIONS_PER_INLUSION_LIST parameters’ values.
DOMAIN_IL_COMMITTEE->DomainType(‘0x0C000000’)IL_COMMITTEE_SIZE->uint64(2**4)MAX_TRANSACTIONS_PER_INLUSION_LIST->uint64(1)
In the beacon chain, there are new containers too. In this article we saw them similarly/exactly in the “Forward Inclusion Lists” part.
class InclusionList(Container):
slot: Slot
validator_index: ValidatorIndex
inclusion_list_committee_root: Root
transactions: List[Transaction, MAX_TRANSACTION_PER_INCLUSION_LIST]
class SignedInclusionList(Container):
message: InclusionList
signature: BLSSignature
A new predicate just for the BLS signature validation on new class SignedInclusionList.
def is_valid_inclusion_list_signature(state: BeaconState, signed_inclusion_list: SignedInclusionList) -> bool:
message = signed_inclusion_list.message
index = message.validator_index
pubkey = state.validator[index].pubkey
domain = get_domain(state, DOMAIN_IL_COMMITTEE, compute_epoch_at_slot(message.slot))
signing_root = compute_signing_root(message, domain)
return bls.FastAggregateVerify(pubkey, signing_root, signed_inclusion_list.signature)
A new Beacon State accessors for getting the data of the inclusion list committee.
def get_inclusion_list_committee(state: BeaconState, slot: Slot) -> Vector[ValidatorIndex, IL_COMMITTEE_SIZE]:
epoch = compute_epoch_at_slot(slot)
seed = get_seed(state, epoch, DOMAIN_IL_COMMITTEE)
indices = get_active_validator_indices(state, epoch)
start = (slot % SLOTS_PER_EPOCH) * IL_COMMITTEE_SIZE
end = start + IL_COMMITTEE_SIZE
return [indices[compute_shuffled_index(uint64(i), uint64(len(indices)), seed] for i in range(start, end)]
There are some changes proposed in the Beacon chain state transition function that bothers Execution Engine and Engine APIs.
Modified NewPayloadRequest:
@dataclass
class NewPayloadRequest(object):
execution_payload: ExecutionPayload
versioned_hashes: Sequence[VersionedHash]
parent_beacon_block_root: Root
execution_requests: ExecutionRequests
il_transactions: List[Transaction, MAX_TRANSACTIONS_PER_INCLUSION_LIST] # [New in EIP-7805]
Modified notify_new_payload:
def notify_new_payload(self: ExecutionEngine, execution_payload: ExecutionPayload, execution_requests: ExecutionRequests, parent_beacon_block_root: Root, il_transactions: List[Transaction, MAX_TRANSACTIONS_PER_INCLUSION_LIST]) -> bool:
Modified verify_and_notify_new_payload:
def verify_and_notify_new_payload(self: ExecutionEngine,
new_payload_request: NewPayloadRequest) -> bool:
execution_payload = new_payload_request.execution_payload
execution_requests = new_payload_request.execution_requests
parent_beacon_block_root = new_payload_request.parent_beacon_block_root
il_transactions = new_payload_request.il_transactions # [New in EIP-7805]
if not self.is_valid_block_hash(execution_payload, parent_beacon_block_root):
return False
if not self.is_valid_versioned_hashes(new_payload_request):
return False
# [Modified in EIP-7805]
if not self.notify_new_payload(
execution_payload,
execution_requests,
parent_beacon_block_root,
il_transactions):
return False
return True
In the fork-choice rule certain configurations and helpers are introduced.
VIEW_FREEZE_DEADLINE->uint64(9)
def validate_inclusion_lists(store: Store, inclusion_list_transactions: List[Transaction, MAX_TRANSACTIONS_PER_INCLUSION_LIST * IL_COMMITTEE_SIZE], execution_payload: ExecutionPayload) -> bool:
@dataclass
class Store(object):
time: uint64
genesis_time: uint64
justified_checkpoint: Checkpoint
finalized_checkpoint: Checkpoint
unrealized_justified_checkpoint: Checkpoint
unrealized_finalized_checkpoint: Checkpoint
proposer_boost_root: Root
equivocating_indices: Set[ValidatorIndex]
blocks: Dict[Root, BeaconBlock] = field(default_factory=dict)
block_states: Dict[Root, BeaconState] = field(default_factory=dict)
block_timeliness: Dict[Root, boolean] = field(default_factory=dict)
checkpoint_states: Dict[Checkpoint, BeaconState] = field(default_factory=dict)
latest_messages: Dict[ValidatorIndex, LatestMessage] = field(default_factory=dict)
unrealized_justifications: Dict[Root, Checkpoint] = field(default_factory=dict)
inclusion_lists: Dict[Tuple[Slot, Root], List[InclusionList]] = field(default_factory=dict) # [New in EIP-7805]
inclusion_list_equivocators: Dict[Tuple[Slot, Root], Set[ValidatorIndex]] = field(default_factory=dict) # [New in EIP-7805]
def on_inclusion_list(
store: Store,
signed_inclusion_list: SignedInclusionList,
inclusion_list_committee: Vector[ValidatorIndex, IL_COMMITTEE_SIZE]]) -> None:
message = signed_inclusion_list.message
assert get_current_slot(store) in [message.slot, message.slot + 1]
time_into_slot = (store.time - store.genesis_time) % SECONDS_PER_SLOT
is_before_attesting_interval = time_into_slot < SECONDS_PER_SLOT // INTERVALS_PER_SLOT
if get_current_slot(store) == message.slot + 1:
assert is_before_attesting_interval
root = message.inclusion_list_committee_root
assert hash_tree_root(inclusion_list_committee) == root
validator_index = message.validator_index
assert validator_index in inclusion_list_committee
assert is_valid_inclusion_list_signature(state, signed_inclusion_list)
is_before_freeze_deadline = get_current_slot(store) == message.slot and time_into_slot < VIEW_FREEZE_DEADLINE
if validator_index not in inclusion_list_equivocators[(message.slot, root)]:
if validator_index in [il.validator_index for il in inclusion_lists[(message.slot, root)]]:
il = [il for il in inclusion_lists[(message.slot, root)] if il.validator_index == validator_index][0]
if not il == message:
inclusion_list_equivocators[(message.slot, root)].add(validator_index)
elif is_before_freeze_deadline:
inclusion_lists[(message.slot, root)].append(message)
In the P2P network, certain changes are required for the domains and message structures.
Time parameter adjustment: attestation_deadline -> uint64(4)
Inclusion list parameter adjustment: MAX_REQUEST_INCLUSION_LIST -> 2**4 = 16
MAX_REQUEST_INCLUSION_LIST represents the amount of transactions that can be included in a single request in an inclusion list.
Along with the data field, inclusion_list named SıgnedInclusionList is newly proposed as gossipsub message type.
inclusion_list as a topic is used to propagate the signed inclusion lists as SıgnedInclusionList. Assuming the message is equal to the message of the signed inclusion list (message = signed_inclusion_list.message), followings has to met before inclusion_list parameter got broadcasted to the network:
[REJECT] The specified slot (
message.slot) has to be equal to the previous or the current slot[IGNORE] The specified slot (
message.slot) is equal to the previous or the current slot and the current time is in the interval of attestation submission deadline (attestation_deadline) into the slot[IGNORE] The parameter includes the specification of the validator index and maximum possible number of the committee (
inclusion_list_committee) for the specified slot (message.slot)[REJECT] The validator index in the message (
message.validator_index) is within the inclusion list committee (inclusion_list_committee) and that has to be corresponds to the message that specified for inclusion list committee parameter’s root (message.inclusion_list_committee_root)[REJECT] The message of transactions (
message.transactions) length has been within the upperbound of maximum transaction per inclusion list amount (MAX_TRANSACTIONS_PER_INCLUSION_LIST)[IGNORE] The message (
message) is either the first or second valid message that received from the validator within the validator index as message (message.validator_index)[REJECT] The signature of inclusion lists (
inclusion_list.signature) is valid with respect to the validator index itself (validator_index)
The request and response domains have to be changed accordingly. New specifications referred as:
Protocol ID:
/eth2/beacon_chain/req/inclusion_list_by_committee_indices/1/<context-bytes>field got calculated as:context = compute_fork_digest(fork_version, genesis_validators_root)fork_version: EIP-7805_FORK_VERSION-> Chunk SSZ type:EIP-7805.SignedInclusionList
Request Content:
(slot: Slot, committee_indices: Bitvector[IL_COMMITTEE_SIZE])
Response Content:
(List[SignedInclusionList, MAX_REQUEST_INCLUSION_LIST])
Honest Validator Guide needs some changes in the matter of implementation. EIP-7805 Honest Validator Guide is an extension to the already existing Honest Validator Guide.
A time parameter got changes:
PROPOSER_INCLUSION_LIST_CUT_OFF->uint64(11)
Honest Validator Guide changes have affected the protocol directly and introduced new duties for the validators.
In the part of protocol, ExecutionEngine got changes and new assignments and duties got introduced.
engine_getInclusionListV1 and engine_updateBlockWithInclusionListV1 functions are added to the ExecutionEngine protocol for the usage in the validator side. Note that these functions are just extensions for EIP-7805 implementation. The body part of these functions has to be implemented before because extensions are dependent on it.
The new inclusion list committee assignment becomes a whole new structure. In the case of a validator is the member of ILC for a given slot, for checking the assignments of the validator we need to use the helper get_ilc_assignment(state, epoch, validator_index) where epoch <= next_epoch. This helps us to directly show the assignment of the validator index (which is specified with the validator_index) in specified state and epoch.
def get_ilc_assignment(
state: BeaconState,
epoch: Epoch,
validator_index: ValidatorIndex) -> Optional[Slot]:
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
ILC selection has to be done within the content of the current and next epoch and get_ilc_assignment() has to be called at the start of the epoch (epoch N, current epoch) to get the assignment of the next epoch (epoch N+1, next epoch). This way, the validators can plan their duty beforehand.
Proposers have a new duty. In the block proposal process, proposers are still expected to propose a signed Beacon block (SignedBeaconBlock) at the beginning of any slot which is validated with the function is_proposer(state, validator_index). However, the change is related to inclusion lists with an update to the execution client. Now, the proposer should call engine_updateInclusionListV1 function at PROPOSER_INCLUSION_LIST_CUT_OFF into the slot with the list of the inclusion lists gathered since inclusion_list_CUT_OFF.
The inclusion list committee also has a new duty. Some validators are selected to submit the signed inclusion list. They should call the get_ilc_assignment() function at the beginning of an epoch to be prepared for the next epoch’s duties. A validator has to create and broadcast the signed inclusion list (signed_inclusion_list) to the global inclusion_list subnet by the inclusion_list_CUT_OFF in the slot unless a block for the current slot has been processed already and is the head of the chain and broadcast to the network.
The construction of the signed inclusion list (signed_inclusion_list) by the validator is as follows:
Validator creates the inclusion list (
inclusion_list)Sets the slot of the inclusion list (
inclusion_list.slot) to the assigned slot returned by the assignment getter function from ILC (get_ilc_assignment())Sets the inclusion list’s validator index (
inclusion_list.validator_index) to the validator’s indexSets the parent hash of the inclusion list (
inclusion_list.parent_hash) to the block hash of the fork-choice headSets the parent root of the inclusion list (
inclusion_list.parent_root) to the block root of the fork-choice headSets the transactions in the inclusion list (
inclusion_list.transactions) using the response fromengine_getInclusionListV1()function from the execution layer clientSigns the inclusion list (
inclusion_list) using the specified helper to get the signature of the inclusion list (get_inclusion_list_signature()) and obtains the signature parameter (signature)Sets the message of the signed inclusion list (
signed_inclusion_list.message) to the inclusion list parameter (inclusion_list)Sets the signature of the signed inclusion list (
signed_inclusion_list.signature) to the signature parameter (signature)
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)
As the matter of attesters, they only had one modified duty. For the FOCIL inclusion, attesters should not vote for the head of the block if the validation function of inclusion lists (validate_inclusion_lists()) of the head block returns false.
IL equivocation is a problem that can be caused by the differences of different ILC member’s local ILs. This is handled by the new P2P network rule: Allowing the forwarding up to two ILs per ILC member. If the proposer or attester detects two different ILs sent by the same ILC member, they start to ignore all the ILs from that ILC member.
The consensus risk considerations start if the current slot’s builder can’t get the related information from the previous slot’s builder. As an example, if the builder of slot N+1 doesn’t receive the ILs broadcast during slot N, it can’t construct a canonical block. To defeat this risk, the builder has to be perfectly connected to the IL committee members to ensure the access of the inclusion lists in a certain time interval.
There is a need for sufficient time between the view freeze deadline and the proposer's broadcast moment to the rest of the network. To handle this, a buffer allows the builder to gather all accessible and available ILs and update the execution payload of the next broadcastable block accordingly.
Since the builder is responsible for constructing the execution payload for each of the blocks it will produce and broadcast, the builder has to ensure that the IL for each block is satisfied through the committees. One way to do it is defining an initial payload for each block and execute the following:
Sequentially check the validation of IL transactions that could be included against the post state. If none, a payload is built.
If found, append it to the end of the payload and update the post state.
To efficiently and completely ensure that all valid IL transactions have been included in the payload, builders can simply build the payload priorly and store the nonce and balance of EOAs that are involved in IL transactions. Through the process of execution payload build, the system can change the execution payload related to the information built on top of the initial execution payload.
The Transaction-Fee Mechanism of Ethereum is currently EIP-1559. There are some newly proposed EIPs that try to achieve multi-dimensional gas mechanisms on the core protocol. In this article, authors propose a way to solve the TFM problem related to the FOCIL inclusion considering several already proposed methods:
Double TFM, this model specifies the user fees as two. One that is given to the committee which is composed of proposers who only input the transactions and one that is given to the block producer by the proposer who inputs and orders all transactions.
Single TFM, requires users to give only one fee at a time. The system itself determines how the fee will be split between the committee and the block producer.
Single Prioritized TFM, also takes one fee portion from the users but specified in the split process with prioritizing the committee.
In the article, authors calculate the incentivizing power of these TFMs to Ethereum. Besides the SSTFM (Single Prioritized TFM) they found others have similar incentivization mechanisms but SSTFM is not-fairly incentivized. The main difference between DTFM (Double TFM) and Single TFM (STFM) is how the incentivization works. In DTFM, users are the ones who distribute the incentivization, or in other words, it is not protectable for censorship. Although, in STFM, the censorship resistancy depends on the fraction of the distribution. As a result, split versions of TFMs are not pretty good at maximizing the censorship resistancy but DTFM is better at increasing the censorship resistance because that user selects the appropriate fee in the perspective of them.
Paper proposes a TFM that the parties involved in the Bayesian game as users (who sends transactions to the mempool), includers and the block producer. And, there is a virtual external malicious briber that wants to censor a transaction. The malicious briber wants to take the fee reward from the participants. However, proposers know their function and how to check the validation with it and with the determinations of the includers and block producer, it can validate that the external briber is not part of the Bayesian game. If, block will obey the IL rules, then the malicious actors will be directly rejected by the attesters. As a result, if a malicious bribe function tries to work in the system, users won’t incur any fees and includers and the block producer won’t receive any payments.
ePBS and FOCIL can be integrated without any conflict. Although both of them specify changes in slot structure, ILs can be easily compatible with ePBS. The synergy depends on the separation of beacon blocks and execution payloads. This enables, to enforce IL conditions by rejecting non-compliant payloads without affecting the beacon chain stability.
The main thing about BRAID is it aims to solve/improve censorship resistancy and MEV. FOCIL just aims to improve censorship resistancy. However, both of them are proposing multi-proposer block building structure gadgets on top of Ethereum’s core protocol.
In BRAID, there is a multiple proposer system but with parallel chains. Each proposer of the concurrent system is responsible for building an entire sub-block. At the end of the process, these sub-blocks get aggregated and create a complete block for the specific slot. After the completed block is confirmed by the consensus, an ordering rule (e.g. FCFS or priority fees) determines the execution sequence of the set of transactions.
BRAID does the almost same work of FOCIL but in a different way. It proposes a structural change on consensus or we can say that it proposes a completely new protocol with small and big changes on every part of the core protocol. BRAID doesn't specify a certain leader in block production like FOCIL, it is leaderless. In FOCIL, leader is the main organization but in BRAID, it is leaderless in terms but it has a leader without even the ability to execute transactions.
Also, BRAID makes the process rewardable through MEV because one proposer has the ability of seeing the other proposers’ sub-blocks even before processing. It makes it a bit risky because the one proposer who is doing the last work can see and is able to capture the MEV opportunities in the sub-blocks from other proposers. A specified proposal suggests making sub-blocks encrypted for this further MEV steal positions.
In the matter of development, FOCIL is one step ahead. Because FOCIL is using an already proposed system, Inclusion Lists (EIP-7547), it is more ready for production than BRAID. In the case of BRAID, it has only a research innovation for now. There was a lot going on at BRAID, however Max Resnick (Paradigm research workshop talk) introduced it and now works for a Solana-based company Anza.
In the end, FOCIL is now our best choice to make Ethereum’s censorship resistance greater in current block building structure. MEV-boost is good, but as always it has some bad sides. FOCIL aims to solve the transaction inclusion problems in the cases of malicious builders on MEV-boost with a force mechanism to set off transactions specified by the proposers.
FOCIL has some similar proposals like BRAID but it is the one that most people have worked on for years. The development of inclusion lists and externals will resume and FOCIL may be included in Ethereum in the future.
This article is written for EIPsforNerds under EIPsforNerds. Follow eipsfornerds on X and Mirror.

