There has been plenty of discussions that have been repeated and going in circles about whether to bundle the IL sidecar and gossip it with the block or not. As we have been repeating the same arguments over and over again for several months now, I think it's better to put our thoughts in one place in writing.
We have learnt with blobs that asynchronous validation of blocks pose a high complexity danger with several edge cases to be handled separately. Had we known this in advance we would have thought twice before deciding to unbundling the blobs and we would have tested for longer the bundled version. In view of the early versions of the IL spec, this was a very valid concern: we would not import a block if the IL was missing and would even mark it as invalid without an available IL, exactly in parallel with the blob paths. As far as I understand, this was the main concern raised by @tersec from Nimbus.
However, the parallel with blobs is not really that close. In fact, as long as validators do not attest to blocks where they have not processed a valid IL and as long as they do not propose blocks on top of such parents, a validating client can validate blocks in a synchronous manner exactly as they do today. That is, IL availability is not a validity condition for blocks, but rather an off-chain criteria to check for viability for head.
There is a line of tought that bundling makes the code simpler. I argue otherwise. Lets consider what a validating code would need to do to handle bundled ILs+Blocks:
There is the argument that even if we import the blocks to forkchoice without seeing the valid IL, if these blocks are heavily attested and we are proposing the next slot then we would need to request the IL by RPC to propose. To this I would argue that the bundled situation is even worse, and this is analogous to something that we currently are subject to: if you are the proposer of slot N+1 and during slot N you do not receive the block at all, but you see a lot of attestations for N, you will be requesting the block N by RPC anyway. In the context of unbundled IL this actually becomes easier to identify, since in this case we even know for a fact what is the blockroot of the head block so we know by counting attestations that we have been isolated and we would have to request an IL to satisfy if we have not received it. More on timings below.
@terencechain mentions that there are less edge cases to consider, but I think this is simply not true. If we had tied the validity condition of blocks to the availability of the IL then this would be the case. But in general, it is much simpler to validate two independent messages than to validate the union of both. When the validation depends on validating a union, we have to deal with each of the parts failing reverting the full validation and potentially re-requesting the full block+IL, resulting in multiple times the bandwidth from these RPC requests for the full block when perhaps the validator only needs a valid IL. When the validation is independent, the client only needs to set independent handlers for each message: a valid block in the call to on_block
results in a forkchoice node with the corresponding block. A valid IL in the call to on_inclusion_list
results in the turning of the il_available
boolean into true. There are no edge cases to consider. The one line change to forkchoice to handle unavailable ILs is simply filter from is_viable_for_head
any tip that has this boolean as false.
Besides the strong case about the split views forcing validators to requests by RPCs, we gain the following from unbundling the IL