zkEVMs allow validators to verify the correctness of an execution payload using a proof, without downloading or executing the payload itself. However, removing the requirement to download the execution payload, also removes the implicit data availability (DA) guarantee; a block producer can publish a valid proof and withhold the execution-payload data since attesters no longer need them for consensus.
This EIP introduces Block-in-Blobs (BiB), a mechanism that requires the execution-payload data (transactions and BALs) to be published in blob data, in the same beacon block that carries the corresponding execution payload's header. This ensures that execution payloads and their associated data are always published even when validators no longer require them to verify the state transition function (STF).
In short, BiB works by having the block producer encode the execution-payload data into blobs as part of the execution layer's STF, requiring the beacon block’s blob KZG commitments to commit to those payload-blobs.
Validation via re-execution
Today, validators verify execution payloads by:
1) Downloading the execution payload 2) Executing the payload locally 3) Checking the resulting state root and other fields against the fields in the header
Implicitly this guarantees execution payload availability because the payload cannot be verified unless the node downloads it.
Validation with zkEVMs
With zkEVMs, validators instead:
1) Download a proof attesting to the correctness of the execution payload 2) Download the execution payload header 3) Verify the proof with respects to the payload header (and other commitments)
In this model, validators no longer require access to the full execution payload data itself in order to verify its correctness.
The DA problem
Removing the re-execution requirement in consensus removes the implicit requirement that consensus clients download the execution payload.
A malicious or rational builder could:
Builders: Since builders will always need to re-execute in order to build blocks, a malicious builder would not publish the execution payload ensuring that they are the only ones that can build on top of the current chain.
RPC and indexers: Many nodes such as RPC providers and indexers cannot solely rely on execution proofs and must re-execute the execution payload.
BiB addresses this by making the execution payload available via blobs.
Type-3 transaction: Refers to EIP-4844 blob-carrying transactions (transaction type 0x03). These transactions include blob versioned hashes that commit to blobs carrying user data.
BiB ensures the proven payload is published:
Payload availability invariant: A valid block implies there exists an ordered list of blobs whose bytes decode to the canonical execution-payload data, and the KZG commitments for these blobs match the first payload_blob_count blob commitments referenced by the block. The existing DAS mechanism will ensure that those blobs are available.
These parameters are defined in EIP-4844 and related specs:
| Name | Value | Source |
|---|---|---|
FIELD_ELEMENTS_PER_BLOB |
4096 |
EIP-4844 |
BYTES_PER_FIELD_ELEMENT |
32 |
EIP-4844 |
GAS_PER_BLOB |
2**17 |
EIP-4844 |
MAX_BLOBS_PER_BLOCK |
Network-specific | EIP-7892 |
Note on MAX_BLOBS_PER_BLOCK: This constant represents the maximum number of blobs (both payload-blobs and type-3 transaction blobs) that can be included in a block. Per EIP-7892, the execution layer's blobSchedule.max MUST equal the consensus layer's MAX_BLOBS_PER_BLOCK configuration value at any given time. This value may change across forks (e.g., initially 6 in EIP-4844, potentially increased in subsequent blob throughput increase proposals).
| Name | Value | Description |
|---|---|---|
USABLE_BYTES_PER_FIELD_ELEMENT |
BYTES_PER_FIELD_ELEMENT - 1 (31) |
Usable bytes per field element (final byte must be zero to stay under BLS modulus) |
USABLE_BYTES_PER_BLOB |
FIELD_ELEMENTS_PER_BLOB * USABLE_BYTES_PER_FIELD_ELEMENT |
Total usable bytes per blob |
Summary: The execution layer is modified in the following ways:
payload_blob_count field so that we can accurately compute the total blob_gas_used. We include the payload-blobs in this calculation and not just type-3 transactions, so that blob_gas_used accurately represents how many blobs the CL used.engine_getPayload computes the payload-blobs when building a block, sets payload_blob_count in the header, and returns the payload blobs (with their commitments and proofs) alongside type-3 transaction blobs in the response.engine_newPayload takes the ExecutionPayload and before passing it to the EL STF, it computes the payload-blobs, checks that the amount of blobs needed is equal to the payload_blob_count value in the ExecutionPayload header and verifies that the expected version hashes match.The execution layer uses methods and classes defined in the corresponding consensus 4844/7594 specs.
Specifically, we use the following methods from polynomial-commitments.md:
And the following methods from polynomial-commitments-sampling.md:
And the following methods from beacon-chain.md:
def bytes_to_blobs(data: bytes) -> List[Blob]:
"""
Pack arbitrary bytes into one or more blobs.
Remaining space in final blob is zero-padded.
"""
blobs = []
offset = 0
while offset < len(data):
chunk = data[offset : offset + USABLE_BYTES_PER_BLOB]
blob = chunk_to_blob(chunk)
blobs.append(blob)
offset += USABLE_BYTES_PER_BLOB
return blobs
def chunk_to_blob(data: bytes) -> Blob:
"""
Pack up to USABLE_BYTES_PER_BLOB bytes into a single blob.
If data is shorter than USABLE_BYTES_PER_BLOB, it is zero-padded.
Each 31-byte chunk is stored in bytes [0:31] of a field element,
with byte [31] (the final byte) set to zero to ensure value < BLS modulus.
"""
assert len(data) <= USABLE_BYTES_PER_BLOB
# Pad to exactly USABLE_BYTES_PER_BLOB if needed
if len(data) < USABLE_BYTES_PER_BLOB:
data = data + bytes(USABLE_BYTES_PER_BLOB - len(data))
blob = bytearray(FIELD_ELEMENTS_PER_BLOB * BYTES_PER_FIELD_ELEMENT)
for i in range(FIELD_ELEMENTS_PER_BLOB):
chunk_start = i * USABLE_BYTES_PER_FIELD_ELEMENT
chunk = data[chunk_start : chunk_start + USABLE_BYTES_PER_FIELD_ELEMENT]
# Store 31 data bytes in [0:31], the final byte [31] stays zero
blob[i * BYTES_PER_FIELD_ELEMENT : i * BYTES_PER_FIELD_ELEMENT + USABLE_BYTES_PER_FIELD_ELEMENT] = chunk
return Blob(blob)
def blobs_to_bytes(blobs: List[Blob]) -> bytes:
"""
Unpack blobs back to bytes.
Returns all usable bytes from all blobs (including any padding).
"""
raw = bytearray()
for blob in blobs:
raw.extend(blob_to_chunk(blob))
return bytes(raw)
def blob_to_chunk(blob: Blob) -> bytes:
"""
Extract the 31 usable bytes from each field element.
Validates that the final byte is zero for each field element.
"""
result = bytearray()
for i in range(FIELD_ELEMENTS_PER_BLOB):
# Validate final byte is zero
assert blob[i * BYTES_PER_FIELD_ELEMENT + USABLE_BYTES_PER_FIELD_ELEMENT] == 0x00, "Invalid blob: final byte must be zero"
# Extract usable data bytes
result.extend(blob[i * BYTES_PER_FIELD_ELEMENT : i * BYTES_PER_FIELD_ELEMENT + USABLE_BYTES_PER_FIELD_ELEMENT])
return bytes(result)
def get_execution_payload_data(payload: ExecutionPayload) -> ExecutionPayloadData:
"""
Extract the data from an ExecutionPayload that must be made available via blobs.
"""
return ExecutionPayloadData(
blockAccessList=payload.blockAccessList,
transactions=payload.transactions,
)
def execution_payload_data_to_blobs(data: ExecutionPayloadData) -> List[Blob]:
"""
Canonically encode the execution-payload data into an ordered list of blobs.
Encoding steps:
1. bal_bytes = data.blockAccessList
2. transactions_bytes = RLP.encode(data.transactions)
3. Create 10-byte header: [2 bytes version][4 bytes BAL length][4 bytes tx length]
4. payload_bytes = header + bal_bytes + transactions_bytes
5. return bytes_to_blobs(payload_bytes)
The first blob will contain (in order):
- [2 bytes] Payload encoding version (currently 0)
- [4 bytes] BAL data length
- [4 bytes] Transaction data length
- [variable] BAL data (may span multiple blobs)
- [variable] Transaction data (may span multiple blobs)
This allows extracting just the BAL data without transactions.
Note: blockAccessList is already RLP-encoded per EIP-7928. Transactions are RLP-encoded as a list.
"""
bal_bytes = data.blockAccessList
transactions_bytes = RLP.encode(data.transactions)
# Create 10-byte header: [2 bytes version][4 bytes BAL length][4 bytes tx length]
version = (0).to_bytes(2, 'little')
bal_length = len(bal_bytes).to_bytes(4, 'little')
txs_length = len(transactions_bytes).to_bytes(4, 'little')
header = version + bal_length + txs_length
# Combine header + data
payload_bytes = header + bal_bytes + transactions_bytes
return bytes_to_blobs(payload_bytes)
def blobs_to_execution_payload_data(blobs: List[Blob]) -> ExecutionPayloadData:
"""
Canonically decode an ordered list of blobs into execution-payload data.
Decoding steps:
1. raw = blobs_to_bytes(blobs)
2. Read 10-byte header: [2 bytes version][4 bytes BAL length][4 bytes tx length]
3. Validate version is 0
4. Split into bal_bytes and transactions_bytes
5. return ExecutionPayloadData(blockAccessList, transactions)
"""
# Extract raw bytes from blobs
raw = blobs_to_bytes(blobs)
# Read 10-byte header
version = int.from_bytes(raw[0:2], 'little')
assert version == 0, f"Unsupported payload encoding version: {version}"
bal_length = int.from_bytes(raw[2:6], 'little')
txs_length = int.from_bytes(raw[6:10], 'little')
# Extract data portions
bal_bytes = raw[10 : 10 + bal_length]
transactions_bytes = raw[10 + bal_length : 10 + bal_length + txs_length]
# Decode transactions
transactions = RLP.decode(transactions_bytes)
return ExecutionPayloadData(blockAccessList=bal_bytes, transactions=transactions)
This method will be used to implement the modified logic in engine_getPayload.
def extract_data_from_type_3_tx(transactions: List[Transaction]) -> Tuple[List[Blob], List[KZGCommitment], List[List[KZGProof]]]:
"""
Extract blobs, KZG commitments, and cell proofs from type-3 (blob) transactions.
Returns a tuple of (blobs, commitments, cell_proofs) in the order they appear
in the transaction list.
Implementation note: This is not new logic - a function(s) like this should already be available in your existing getPayload/blob bundle implementation.
"""
...
Invertibility invariant: execution_payload_data_to_blobs and blobs_to_execution_payload_data are mutual inverses on valid execution-payload data.
Execution-payload data refers to the subset of the ExecutionPayload that must be made available via blobs. This includes:
bals (Block Access List added in EIP-7928)transactionsSee What is included in execution-payload data? in the Rationale for details.
class ExecutionPayloadData(Container):
# Block Access List (BAL) from EIP-7928, RLP-encoded
blockAccessList: bytes
# List of transactions, each transaction is RLP-encoded
transactions: List[Transaction, MAX_TRANSACTIONS_PER_PAYLOAD]
Note: This is not an SSZ Container - fields are RLP-encoded as described in the encoding functions. The MAX_TRANSACTIONS_PER_PAYLOAD bound is inherited from the ExecutionPayload field limit defined in the consensus specification.
This EIP adds a new field to the ExecutionPayloadHeader:
Semantics:
blob_kzg_commitments be the ordered list of kzg commitments referenced by the beacon blockpayload_blob_count entries of blob_kzg_commitments are the payload-blob commitments (i.e. commitments to the blobs that correspond to the payload data)For the zkEVM-optimized variant of engine_getPayload, this EIP extends BlobsBundle with an additional field:
payload_kzg_proofs: List[KZGProof]Semantics:
verify_blob_kzg_proof_batchpayload_blob_countOn the execution layer, the block validation rules are modified as follows:
def validate_block(block: Block):
# Initialize blob_gas_used to account for payload-blobs
blob_gas_used = block.payload_blob_count * GAS_PER_BLOB
# ... rest of validation, including executing transactions
# Type-3 transactions will add their blob gas to blob_gas_used during execution
Rationale: Instead of starting blob_gas_used at 0, we initialize it with the gas consumed by payload-blobs. As transactions execute, type-3 transactions will increment blob_gas_used by their blob gas usage. The final blob_gas_used value thus equals: (payload_blob_count * GAS_PER_BLOB) + (type-3 blob gas).
Verification: The EL STF in isolation trusts that payload_blob_count in the header is correct, as it cannot verify blob contents in validate_block. Correctness of payload_blob_count is enforced at the Engine API boundary (in engine_newPayload), which recomputes the payload blobs from execution-payload data and verifies consistency with the beacon block's blob commitments.
Note: This change does not affect Consensus Layer blob accounting rules; it only ensures that blob_gas_used in the execution payload header accurately reflects total blob usage, including both payload-blobs and type-3 transaction blobs.
This section specifies two equivalent formulations of new_payload. Implementers choose one based on their execution context:
blob_to_kzg_commitment directly. Suitable for pre mandatory proofs implementations.verify_blob_kzg_proof_batch. Avoids the multiscalar multiplication (MSM) which is expensive to prove in a zkEVM circuit.Both variants enforce identical validity conditions. A block valid under one is valid under the other.
The builder must compute the payload blob count when constructing the block:
def get_payload(payload_id: PayloadId) -> GetPayloadResponse:
# 1. Build the block (select transactions, etc.)
payload = build_execution_payload(payload_id)
# 2. Compute payload blobs to determine count
payload_data = get_execution_payload_data(payload)
payload_blobs = execution_payload_data_to_blobs(payload_data)
payload_blob_count = len(payload_blobs)
# 4. Set the count in the header
payload.payload_blob_count = payload_blob_count
# 5. Compute blob commitments and cell proofs for payload blobs
payload_commitments = [blob_to_kzg_commitment(b) for b in payload_blobs]
payload_versioned_hashes = [kzg_commitment_to_versioned_hash(c) for c in payload_commitments]
# Compute cells and cell proofs
payload_cells_and_proofs = [compute_cells_and_kzg_proofs(b) for b in payload_blobs]
payload_cell_proofs = [proofs for _, proofs in payload_cells_and_proofs]
# 6. Extract type-3 transaction blobs, commitments, and cell proofs
type3_blobs, type3_commitments, type3_cell_proofs = extract_data_from_type_3_tx(payload.transactions)
type3_versioned_hashes = []
for tx in payload.transactions:
if tx.type == BLOB_TX_TYPE:
type3_versioned_hashes.extend(tx.blob_versioned_hashes)
# 7. Combine: payload blobs first, then type-3 blobs
all_blobs = payload_blobs + type3_blobs
all_commitments = payload_commitments + type3_commitments
all_cell_proofs = payload_cell_proofs + type3_cell_proofs
all_blob_versioned_hashes = payload_versioned_hashes + type3_versioned_hashes
return GetPayloadResponse(
execution_payload=payload,
blobs_bundle=BlobsBundle(
commitments=all_commitments,
blobs=all_blobs,
proofs=all_cell_proofs
),
block_value=calculate_block_value(payload)
)
Note: The builder must account for payload blob usage when selecting type-3 transactions to ensure the total blob count does not exceed MAX_BLOBS_PER_BLOCK.
For the zkEVM-optimized variant, the builder must additionally compute random point KZG proofs for the payload blobs, which will be used as private inputs in the zkEVM circuit for payload consistency verification.
This variant extends BlobsBundle with an additional payload_kzg_proofs field containing random point KZG proofs for payload blobs.
def get_payload_zk(payload_id: PayloadId) -> GetPayloadResponse:
# 1. Build the block (select transactions, etc.)
payload = build_execution_payload(payload_id)
# 2. Compute payload blobs to determine count
payload_data = get_execution_payload_data(payload)
payload_blobs = execution_payload_data_to_blobs(payload_data)
payload_blob_count = len(payload_blobs)
# 4. Set the count in the header
payload.payload_blob_count = payload_blob_count
# 5. Compute commitments, cell proofs, and random point proofs for payload blobs
payload_commitments = [blob_to_kzg_commitment(b) for b in payload_blobs]
payload_versioned_hashes = [kzg_commitment_to_versioned_hash(c) for c in payload_commitments]
# Cell proofs
payload_cells_and_proofs = [compute_cells_and_kzg_proofs(b) for b in payload_blobs]
payload_cell_proofs = [proofs for _, proofs in payload_cells_and_proofs]
# Random point proofs for payload consistency verification
payload_random_point_proofs = [compute_blob_kzg_proof(b, c) for b, c in zip(payload_blobs, payload_commitments)]
# 6. Extract type-3 transaction blobs, commitments, and cell proofs
type3_blobs, type3_commitments, type3_cell_proofs = extract_data_from_type_3_tx(payload.transactions)
type3_versioned_hashes = []
for tx in payload.transactions:
if tx.type == BLOB_TX_TYPE:
type3_versioned_hashes.extend(tx.blob_versioned_hashes)
# 7. Combine: payload blobs first, then type-3 blobs
all_blobs = payload_blobs + type3_blobs
all_commitments = payload_commitments + type3_commitments
all_cell_proofs = payload_cell_proofs + type3_cell_proofs
return GetPayloadResponse(
execution_payload=payload,
blobs_bundle=BlobsBundle(
commitments=all_commitments,
blobs=all_blobs,
proofs=all_cell_proofs,
payload_kzg_proofs=payload_random_point_proofs
),
block_value=calculate_block_value(payload)
)
Note for implementors:
payload_kzg_proofs field contains KZG opening proofs for payload blobs only. It is used for payload consistency verification via verify_blob_kzg_proof_batch.payload_blob_count commitments from all_commitments (i.e., all_commitments[:payload_blob_count]). This corresponds to the payload_kzg_commitments parameter in the zkEVM variant of engine_newPayload.def new_payload(
payload: ExecutionPayload,
expected_blob_versioned_hashes: List[VersionedHash],
...
) -> PayloadStatus:
# 1. Derive payload blobs and commitments
payload_data = get_execution_payload_data(payload)
payload_blobs = execution_payload_data_to_blobs(payload_data)
payload_blob_count = len(payload_blobs)
payload_commitments = [blob_to_kzg_commitment(b) for b in payload_blobs]
payload_versioned_hashes = [kzg_commitment_to_versioned_hash(c) for c in payload_commitments]
# 2. Verify payload_blob_count matches header
assert payload_blob_count == payload.payload_blob_count
# 3. Extract type-3 tx versioned hashes
type3_versioned_hashes = []
for tx in payload.transactions:
if tx.type == BLOB_TX_TYPE:
type3_versioned_hashes.extend(tx.blob_versioned_hashes)
# 4. Verify versioned hashes: payload blobs first, then type-3
assert expected_blob_versioned_hashes == payload_versioned_hashes + type3_versioned_hashes
# 5. Run EL STF (which now checks correct blob_gas_used blob limit using header.payload_blob_count)
return execute_payload(payload)
Note: Once zkEVM proofs are required for consensus, newPayload will be executed inside a zkEVM to generate a proof, rather than being executed natively by validators. This variant is designed to be cheaper in that context.
This variant replaces the MSM in blob_to_kzg_commitment with polynomial opening proofs, which are cheaper to verify inside a zkEVM. The payload, commitments and KZG proofs are private inputs to the zkEVM circuit, while the corresponding versioned hashes (and payload header) are public inputs.
def new_payload(
payload: ExecutionPayload,
expected_blob_versioned_hashes: List[VersionedHash], # public input
# BiB additions: prefix metadata for payload blobs
payload_kzg_commitments: List[KZGCommitment], # private input
payload_kzg_proofs: List[KZGProof], # private input
...
) -> PayloadStatus:
# 0. Declared payload blob count from the header
n = payload.payload_blob_count
assert len(payload_kzg_commitments) == n
assert len(payload_kzg_proofs) == n
# 1. Construct payload blobs from execution-payload data
payload_data = get_execution_payload_data(payload)
payload_blobs = execution_payload_data_to_blobs(payload_data)
assert len(payload_blobs) == n
# 2. Check the commitments correspond to the expected versioned hash prefix
payload_versioned_hashes = [
kzg_commitment_to_versioned_hash(c) for c in payload_kzg_commitments
]
assert expected_blob_versioned_hashes[:n] == payload_versioned_hashes
# 3. Verify blob–commitment consistency using batch KZG proof verification
assert verify_blob_kzg_proof_batch(
blobs=payload_blobs,
commitments=payload_kzg_commitments,
proofs=payload_kzg_proofs
)
# 4. Proceed with standard EL payload validation / execution
return execute_payload(payload)
The consensus layer does not introduce new blob specific validation rules for payload-blobs beyond what we have for EIP-4844/EIP-7594.
The Consensus Layer relies on payload_blob_count in the execution payload header to interpret the ordering of blob commitments, but otherwise treats payload blobs identically to other blobs for availability and networking.
BiB reuses the existing blob networking mechanism.
We note the following for consideration:
Once proofs are made mandatory, a mechanism will be needed for execution payload retrieval. EIP-7732 introduces an execution_payload gossip topic that we can use for this purpose. However, in the context of mandatory proofs where a super majority of stake operates zk validators (which only listen to header topics), a malicious builder could publish only the payload in blobs and gossip the execution payload header without gossiping on the execution_payload topic. This would allow zk validators to attest, but other nodes depending on the full payload from the gossip topic would be unable to progress.
To mitigate this, nodes can implement a fallback mechanism: if they don't receive the payload on the execution_payload topic, they reconstruct it from the first payload_blob_count blobs and then seed the execution_payload topic themselves. This creates a resilient system where every node acts as a "bridge node" when needed, similar to how rollups use L2 gossip as a fast path but fall back to L1 data availability.
Unlike most type-3 blob transactions, payload-blobs will not have been propagated to the network before a block is built. Depending on the deadlines imposed by ePBS, this may imply higher bandwidth requirements from block builders.
BiB introduces protocol mandated blob usage, rather than user initiated via type-3 transactions. Fee accounting for payload-blobs differ in nature from transaction blob fees as a result.
This EIP does not mandate that payload-blobs pay a per-blob fee like transaction blobs.
Instead payload-blobs are treated as a protocol-accepted cost when constructing the block. In particular:
Because payload blobs consume blob gas, they directly influence blob congestion and the blob base fee.
A potential concern is whether a malicious builder could create artificially large execution payloads to inflate blob gas usage.
This attack is economically constrained: to increase the size of the execution payload, a builder must include additional transactions with calldata. Since calldata costs execution gas, the builder would need to pay for this additional data through the normal gas mechanism. The cost of including extra calldata makes it economically unfavorable to artificially inflate payload size solely to manipulate blob fees.
Execution-payload data includes bals (Block Access Lists from EIP-7928) and transactions.
Why transactions? Transaction data is the only component of the execution payload that cannot be derived from other components and is not provided by the consensus layer.
Why BALs? While BALs are technically the output of executing transactions and could be recomputed, once zkEVM proofs become mandatory for consensus, validators no longer execute payloads. A malicious builder could publish a valid proof and withhold both the execution payload data and the BALs. This would prevent other builders from constructing subsequent blocks and prevent RPC providers from serving the full state. Including BALs in payload-blobs ensures they remain available.
Why not withdrawals? Withdrawals can be derived on the consensus layer.
Why not execution requests? Execution requests can be recomputed from transactions and do not suffer from the same withholding attack as BALs because they are required by the consensus layer for validation.
Why not the header? The header cannot be put into blobs because it contains payload_blob_count, which depends on the number of blobs; causing a circular dependency.
Encoding optimization: The encoding includes an 8-byte header: [4 bytes BAL length] [4 bytes transaction length]. This allows extracting just the tx data after fetching the first blob.
TODO: We could also put the number of blobs that the BAL occupies in the execution payload header.
k blobsThis EIP specifies that the block builder choose payload_blob_count, subject to the constraint imposed by MAX_BLOBS_PER_BLOCK.
An alternative would have been to always reserve k blobs, where k corresponds to the worst case execution payload size. While this provides better predictability, it reduces flexibility under blob congestion.
Doing it in the EL STF would require payload-blob commitments or versioned hashes to be made visible inside the core execution logic, rather than being handled at the Engine API boundary.
The execution payload grows linearly with the gas limit. Requiring attesters to download the payload for DA would create an increasing bandwidth burden as the gas limit grows.
Compression can be used on the serialized execution-payload data. This (in theory) should allow the usage of less payload-blobs, depending on the compression ratio. The tradeoffs being:
Whether we should use a compression algorithm and which one requires more investigation, in particular we need to investigate:
For now we recommend using no compression algorithm and operating on uncompressed data.
Serialization of the execution-payload data uses RLP. Since transactions in the ExecutionPayload are already RLP-encoded, we simply RLP-encode the list of transaction bytes without any additional transformation.
While a more zk-friendly serialization algorithm could be beneficial in the future, this EIP uses RLP for simplicity. Once EIP-7807 (SSZ execution blocks) is implemented, the encoding can be updated to SSZ-serialize the list of SSZ-encoded transaction bytes.
This requires changes to the execution payload header and the EL STF; so requires a fork. Nodes that do not implement BiB will not be able to validate blocks after activation.
TODO
TODO
Interaction with blob congestion and denial-of-service
Payload-blobs consume blob gas and therefore are subject to the same congestion control mechanisms and blob limits as transaction blobs.
As a byproduct, this ensures that a malicious block producer cannot make arbitrarily large execution payloads without accounting for blob gas limits. While a block producer could theoretically drive up the blob base fee by creating large payloads, this attack is economically constrained by calldata costs (see Fee Accounting for details).
Data withholding
An attacker cannot withhold execution payload data without also withholding blob data, which would violate existing DAS guarantees and cause the block to be rejected by the consensus layer.
Copyright and related rights waived via CC0.