This EIP removes the block access list (BAL) from the ExecutionPayloadEnvelope and propagates it as an independent sidecar on a dedicated gossip topic. Builders commit to the BAL exactly once, by including keccak256(rlp(BAL)) (the same 32-byte value already defined as block_access_list_hash in the EL block header by EIP-7928) in their ExecutionPayloadBid. Sidecar verification uses this commitment; the consensus layer treats the BAL as opaque bytes and never needs to RLP-decode it. No separate sidecar signature is required. The Payload Timeliness Committee (PTC) enforces BAL availability at the attestation deadline.
EIP-7928 adds block access lists to the ExecutionPayload. Under EIP-7732, the execution payload travels inside a SignedExecutionPayloadEnvelope that the builder broadcasts after the beacon block. Including the BAL (~70 KiB average, up to 1 MiB) in the envelope increases its size and propagation latency on the critical path.
Separating the BAL into a sidecar reduces envelope size, improving propagation. The BAL remains required for execution validation; the PTC enforces availability so that the BAL is present before the payload processing deadline.
| Name | Value |
|---|---|
MAX_BLOCK_ACCESS_LIST_SIZE |
uint64(2**23) (= 8 MiB) |
The BlockAccessList type from EIP-7928:
| Name | SSZ equivalent | Description |
|---|---|---|
BlockAccessList |
ByteList[MAX_BLOCK_ACCESS_LIST_SIZE] |
RLP-encoded block access list |
ExecutionPayloadThe block_access_list field introduced by EIP-7928 is removed:
class ExecutionPayload(Container):
parent_hash: Hash32
fee_recipient: ExecutionAddress
state_root: Bytes32
receipts_root: Bytes32
logs_bloom: ByteVector[BYTES_PER_LOGS_BLOOM]
prev_randao: Bytes32
block_number: uint64
gas_limit: uint64
gas_used: uint64
timestamp: uint64
extra_data: ByteList[MAX_EXTRA_DATA_BYTES]
base_fee_per_gas: uint256
block_hash: Hash32
transactions: List[Transaction, MAX_TRANSACTIONS_PER_PAYLOAD]
withdrawals: List[Withdrawal, MAX_WITHDRAWALS_PER_PAYLOAD]
blob_gas_used: uint64
excess_blob_gas: uint64
# [Removed in EIP-8146]
# block_access_list: BlockAccessList
ExecutionPayloadBidA block_access_list_hash field is added. Its value is identical to the EL header field of the same name defined by EIP-7928, i.e. keccak256(rlp(BlockAccessList)). Using the EL's canonical commitment avoids introducing a second hashing scheme on the CL side.
class ExecutionPayloadBid(Container):
parent_block_hash: Hash32
parent_block_root: Root
block_hash: Hash32
prev_randao: Bytes32
fee_recipient: ExecutionAddress
gas_limit: uint64
builder_index: BuilderIndex
slot: Slot
value: Gwei
execution_payment: Gwei
blob_kzg_commitments: List[KZGCommitment, MAX_BLOB_COMMITMENTS_PER_BLOCK]
# [New in EIP-8146]
block_access_list_hash: Bytes32
PayloadAttestationDataA block_access_list_present field is added:
class PayloadAttestationData(Container):
beacon_block_root: Root
slot: Slot
payload_present: boolean
blob_data_available: boolean
# [New in EIP-8146]
block_access_list_present: boolean
BlockAccessListSidecarclass BlockAccessListSidecar(Container):
beacon_block_root: Root
slot: Slot
block_access_list: BlockAccessList
block_access_list_sidecarThis topic propagates BlockAccessListSidecar objects.
The following validations MUST pass before forwarding a sidecar on the network:
sidecar.beacon_block_root has been seen (via gossip or non-gossip sources). A client MAY queue the sidecar for processing once the block is retrieved.BlockAccessListSidecar for this beacon_block_root has been seen.sidecar.slot >= compute_start_slot_at_epoch(store.finalized_checkpoint.epoch).Let block be the beacon block with root sidecar.beacon_block_root. Let bid alias block.body.signed_execution_payload_bid.message:
block passes validation.sidecar.slot == block.slot.keccak256(sidecar.block_access_list) == bid.block_access_list_hash. The CL treats sidecar.block_access_list as opaque bytes; no RLP decoding is performed.BlockAccessListSidecarsByRange v1Protocol ID: /eth2/beacon_chain/req/block_access_list_sidecars_by_range/1/
Request Content:
(
start_slot: Slot
count: uint64
)
Response Content:
List[BlockAccessListSidecar, MAX_REQUEST_PAYLOADS]
Returns sidecars in slot range [start_slot, start_slot + count), ordered by slot. The response MUST contain no more than MAX_REQUEST_PAYLOADS sidecars. Clients SHOULD respond with at least one sidecar if available.
BlockAccessListSidecarsByRoot v1Protocol ID: /eth2/beacon_chain/req/block_access_list_sidecars_by_root/1/
Request Content:
List[Root, MAX_REQUEST_PAYLOADS]
Response Content:
List[BlockAccessListSidecar, MAX_REQUEST_PAYLOADS]
Returns sidecars matching the requested beacon block roots.
Store@dataclass
class Store(object):
# ... existing fields ...
# [New in EIP-8146]
block_access_lists: Dict[Root, BlockAccessList] = field(default_factory=dict)
# [New in EIP-8146]
block_access_list_availability_vote: Dict[Root, list[Optional[boolean]]] = field(
default_factory=dict
)
on_blockWhen a new block is added to the store, initialize the BAL PTC vote tracker (alongside the existing ptc_vote initialization):
# [New in EIP-8146] Add a new PTC BAL voting for this block to the store
store.block_access_list_availability_vote[block_root] = [None] * PTC_SIZE
on_payload_attestation_messageThe handler records the BAL availability vote alongside the existing payload presence vote:
def on_payload_attestation_message(
store: Store, ptc_message: PayloadAttestationMessage, is_from_block: bool = False
) -> None:
# ... existing validation and payload_present recording ...
# [New in EIP-8146] Update the BAL vote for the block
store.block_access_list_availability_vote[data.beacon_block_root][ptc_index] = (
data.block_access_list_present
)
notify_ptc_messagesWhen extracting PayloadAttestationMessage objects from PayloadAttestation aggregates in a beacon block, the block_access_list_present field is propagated from the attestation data.
on_block_access_list_sidecardef on_block_access_list_sidecar(store: Store, sidecar: BlockAccessListSidecar) -> None:
# The beacon block must be known
assert sidecar.beacon_block_root in store.blocks
block = store.blocks[sidecar.beacon_block_root]
# Verify slot consistency
assert sidecar.slot == block.slot
# Verify BAL matches commitment in bid (CL operates on opaque bytes; no RLP)
bid = block.body.signed_execution_payload_bid.message
assert keccak256(sidecar.block_access_list) == bid.block_access_list_hash
# Store BAL
store.block_access_lists[sidecar.beacon_block_root] = sidecar.block_access_list
# Notify the EL for early prefetching or post-state root calculation
EXECUTION_ENGINE.notify_block_access_list(sidecar.block_access_list, bid.block_hash)
on_execution_payload_envelopeBAL availability is required before processing the execution payload. The BAL has already been delivered to the EL via engine_notifyBlockAccessListV1 when the sidecar was received; verify_execution_payload_envelope itself is unchanged.
def on_execution_payload_envelope(
store: Store, signed_envelope: SignedExecutionPayloadEnvelope
) -> None:
envelope = signed_envelope.message
# The corresponding beacon block root needs to be known
assert envelope.beacon_block_root in store.block_states
# Check if blob data is available
assert is_data_available(envelope.beacon_block_root)
# [New in EIP-8146] Check if the BAL has been received locally
assert envelope.beacon_block_root in store.block_access_lists
state = store.block_states[envelope.beacon_block_root]
# Verify the execution payload envelope (unchanged)
verify_execution_payload_envelope(state, signed_envelope, EXECUTION_ENGINE)
# Add execution payload envelope to the store
store.payloads[envelope.beacon_block_root] = envelope
EIP-8146 splits BAL delivery and payload delivery into two engine calls so the EL can start work on the BAL while the payload envelope is still in flight. The CL forwards the BAL the instant the sidecar passes gossip validation, without waiting for the envelope. This is the operational core of the EIP.
engine_getPayloadV6Returns the BAL as a separate blockAccessList field (RLP-encoded bytes) alongside the ExecutionPayload. The builder computes block_access_list_hash = keccak256(blockAccessList) for inclusion in the bid; this is the same value the EL writes into the EL header per EIP-7928, so no additional hashing is required.
engine_notifyBlockAccessListV1Delivers the BAL to the EL independently of the execution payload.
Parameters:
blockAccessList: RLP-encoded BAL bytes.blockHash: ties the BAL to a specific payload.On receipt the EL:
blockHash.The CL MUST call this method from on_block_access_list_sidecar immediately after the keccak check against bid.block_access_list_hash passes. The CL MUST NOT wait for the payload envelope.
engine_newPayloadV5Unchanged from EIP-7732. It does not carry the BAL. The EL pairs the incoming payload with the previously delivered BAL by blockHash and runs execution against the warmed state. If the BAL has not yet arrived (out-of-order receipt), the EL MAY queue the payload until engine_notifyBlockAccessListV1 is called for the same blockHash.
t0: CL --engine_notifyBlockAccessListV1--> EL (BAL arrives; EL prefetches state, MAY precompute post-state root)
t1: CL --engine_newPayloadV5-------------> EL (envelope arrives; EL executes against warm cache)
t2: CL <----------- {status: VALID} ------ EL
The t1 - t0 gap is the head start the EIP creates. With the BAL inside the envelope (pre-EIP-8146), t0 = t1 and the head start is zero.
The execution block header field block_access_list_hash (keccak256 of RLP-encoded BAL) is unchanged from EIP-7928. BAL construction and validation rules are as specified in EIP-7928.
When constructing a bid, the builder:
engine_getPayloadV6.block_access_list_hash = keccak256(blockAccessList) and includes it in the ExecutionPayloadBid.BlockAccessListSidecar on the block_access_list_sidecar gossip topic.SignedExecutionPayloadEnvelope (which no longer contains the BAL).Builders SHOULD broadcast the BAL sidecar as early as possible, well before the envelope, to give the network's ELs a prefetch window. See BAL publication timing for the safety argument and worst-case discussion.
PTC members set block_access_list_present = True in their PayloadAttestationData only if a valid BlockAccessListSidecar for the block was received locally at least 1 second before the payload attestation deadline. The sidecar must pass gossip validation including the keccak256 commitment check.
PTC members set payload_present and blob_data_available per EIP-7732 rules, independently of block_access_list_present.
EIP-7928 already commits the BAL into the EL header as block_access_list_hash = keccak256(rlp(BAL)). Carrying the same 32 bytes in the bid gives the CL everything it needs to authenticate a sidecar without introducing a second hashing scheme. The CL holds the BAL only as opaque bytes; RLP decoding remains an EL concern. A builder cannot equivocate: bid.block_hash transitively commits to the EL header, so if bid.block_access_list_hash disagrees with the revealed payload, either payload.block_hash != bid.block_hash (envelope rejected) or the EL recomputes a BAL that does not match its own header (payload rejected per EIP-7928).
The BAL sidecar requires no BLS signature. The builder commits to block_access_list_hash in the signed ExecutionPayloadBid; sidecar verification is keccak256(sidecar.block_access_list) == bid.block_access_list_hash. This mirrors DataColumnSidecar verification via KZG commitments in the bid.
BAL availability is tracked as a dedicated block_access_list_present boolean in PayloadAttestationData, following the same pattern as blob_data_available. This keeps concerns separated: payload_present signals envelope timeliness, blob_data_available signals blob availability, and block_access_list_present signals BAL availability. The fork choice on_execution_payload_envelope handler independently gates on local BAL availability (assert root in store.block_access_lists), ensuring execution validation cannot proceed without the BAL regardless of PTC votes.
EIP-7732 applies a variable PTC deadline to the execution payload based on payload size. Since the BAL travels as an independent sidecar, and its size scales inversely with the payload size, this variable deadline does not apply to it.
In ePBS, builders generally delay publishing the execution payload envelope until close to the attestation deadline. Releasing the payload too early exposes its transactions to same-slot unbundling: a malicious proposer could see the payload contents and replace them with a competing block that extracts the same MEV.
The BAL is not exposed to this risk. It carries post-state values and access lists but contains no independently-signed items: the transactions are not in the BAL, the sidecar itself has no signature, and the keccak commitment inside the signed bid binds the BAL to the builder so it cannot equivocate between BALs. A proposer who sees the BAL early learns coarse activity (which pools were touched, which balances moved) but cannot lift transactions into a competing block.
To guarantee a minimum prefetch window for the worst case (BAL arriving with or after the envelope), PTC members vote block_access_list_present = True only if the sidecar was received at least 1 second before the payload attestation deadline. This 1-second gap is the protocol-enforced lower bound on the prefetch window the EL gets before execution begins. Builders SHOULD publish well before this deadline; they have no economic reason to delay.
The BAL is delivered to the EL exclusively via engine_notifyBlockAccessListV1, separate from engine_newPayloadV5. This enables early delivery: the BAL sidecar typically arrives before the execution payload envelope, so the EL can begin prefetching storage slots immediately, or start calculating the post-state root. When the payload arrives later, engine_newPayloadV5 proceeds without the BAL -- the EL already has it, matched by blockHash.
Clients MUST retain BAL sidecars for at least MIN_EPOCHS_FOR_BAL_SIDECARS_REQUESTS epochs to support syncing nodes. After this period, clients MAY prune sidecars.
This EIP requires a hard fork. It modifies the ExecutionPayload and ExecutionPayloadBid containers from EIP-7732 and changes how EIP-7928 BALs are propagated.
Withholding: A builder can withhold the BAL sidecar. PTC members will vote block_access_list_present = False, providing network-wide visibility. The fork choice on_execution_payload_envelope handler gates on local BAL availability, so the payload cannot be validated without the BAL. The builder withhold safety properties from EIP-7732 apply: if the beacon block was not timely, the builder is not charged.
Network overhead: The BAL sidecar adds ~70 KiB average per slot to gossip traffic, equal to the current overhead when BAL is inside the envelope. Total network load is unchanged; it is redistributed across topics.
Verification cost: Sidecar verification requires one keccak256 computation on up to MAX_BLOCK_ACCESS_LIST_SIZE bytes. This is negligible compared to execution validation. CL implementations gain a keccak256 dependency, satisfied by well-tested off-the-shelf libraries.
Copyright and related rights waived via CC0.