Ethereum sends blob data beside the beacon block that commits to it. The commitment is consensus data. The blob, cell, and proof data use separate gossip topics and request streams.
The live object is BlobSidecar. Gossip carries one blob index, and Req/Resp
recovers blob sidecars by range or root.
The live object is DataColumnSidecar. Gossip carries one column subnet, and
Req/Resp recovers selected columns by range or root.
Blob sidecars
BlobSidecar contains one blob, its KZG commitment and proof, a signed beacon
block header, the blob index, and a Merkle proof that binds the commitment to
the block body.
Each blob index maps to blob_sidecar_{subnet_id}. The sender publishes the
whole sidecar. A receiver rejects a sidecar if:
- its index is outside the active fork's subnet count (
BLOB_SIDECAR_SUBNET_COUNT= 6 until Pectra,BLOB_SIDECAR_SUBNET_COUNT_ELECTRA= 9 from Pectra) or maps to another subnet; - its header has a bad proposer index, signature, parent, slot, or finalized ancestor;
- its commitment inclusion proof fails; or
- its blob, commitment, and KZG proof do not match.
A receiver ignores a duplicate (slot, proposer_index, blob_index), a
too-early future message, or a sidecar whose parent is not yet known. It can
queue the latter two cases.
BlobSidecarsByRange requests a half-open slot range. The server returns
sidecars in (slot, blob_index) order from one fork-choice chain. It skips
slots with no known blobs.
BlobSidecarsByRoot requests (block_root, blob_index) identifiers. Each
successful response chunk contains one fork-digest-framed, SSZ-snappy
BlobSidecar.
MAX_REQUEST_BLOCKS_DENEB is 128. The maximum response object count is
MAX_REQUEST_BLOCKS_DENEB * MAX_BLOBS_PER_BLOCK, using the blob limit in
force for the requested epoch.
Data column sidecars
PeerDAS extends each blob into 128 cells. Cells at the same position in all
blobs form a column. Every DataColumnSidecar contains index, the column
index.
In this profile, column is
List[Cell, MAX_BLOB_COMMITMENTS_PER_BLOCK] and kzg_proofs is the matching
List[KZGProof, MAX_BLOB_COMMITMENTS_PER_BLOCK]. The sidecar also contains the
matching KZG commitments, a signed block header, and a Merkle proof for the
commitment list.
In this profile, column is ProgressiveList[Cell] and kzg_proofs is the
matching ProgressiveList[KZGProof]. The sidecar contains slot and
beacon_block_root, but no signed header, commitment list, or commitment
inclusion proof. The selected execution payload bid in the named block supplies
the KZG commitments.
On mainnet, NUMBER_OF_COLUMNS,
NUMBER_OF_CUSTODY_GROUPS, and
DATA_COLUMN_SIDECAR_SUBNET_COUNT are all 128. Thus each custody group owns one
column and each column maps to one subnet.
The subnet is column_index % DATA_COLUMN_SIDECAR_SUBNET_COUNT. A node
subscribes to every subnet in its custody groups. A proposer publishes all
columns. Other nodes publish or expose reconstructed columns according to
their subscriptions.
A receiver first rejects malformed structure. The index must be below
NUMBER_OF_COLUMNS; the column and proof lists must have the same non-zero
length; and the subnet must match the index.
It then checks that the non-empty commitment list has no more items than the
active epoch's blob limit and has the same length as the column. It checks the
signed header, parent and finalized ancestry, commitment inclusion proof, and
all cell KZG proofs. It ignores a duplicate
(slot, proposer_index, column_index), a future sidecar, or a sidecar whose
parent is not yet available.
It ignores a duplicate (beacon_block_root, column_index) or a future sidecar
and may queue a future message until its slot. It ignores a sidecar for an
unknown beacon block and may queue it until that block arrives. Once the block
is known, it rejects the sidecar if the block has no valid post-state, the
block slot differs from sidecar.slot, the commitment count differs from the
column length, or any cell proof fails against the commitments in the block's
selected bid. A client must rebroadcast a sidecar after deferred validation
succeeds and must retroactively downscore its forwarding peers if that
validation fails.
Request streams
DataColumnSidecarsByRange requests:
start_slot: Slot
count: Uint64
columns: List[ColumnIndex, 128]
The server follows one fork-choice chain and returns known sidecars in
(slot, column_index) order. If it returns any sidecar for a block, it returns
all requested sidecars that it has for that block. The response contains at
most count * NUMBER_OF_COLUMNS sidecars and at most
compute_max_request_data_column_sidecars() sidecars.
DataColumnSidecarsByRoot accepts at most 128
DataColumnsByRootIdentifier values. Each identifier has one block root and a
list of column indices. The response can omit data that the peer does not have,
but cannot exceed the number of requested columns.
compute_max_request_data_column_sidecars() is
MAX_REQUEST_BLOCKS_DENEB * NUMBER_OF_COLUMNS. The mainnet value is
128 * 128 = 16,384. Each successful response chunk contains one
fork-digest-framed, SSZ-snappy sidecar. A requester should check structure,
the fork-specific block binding, and KZG proofs before it reads the next
chunk.
Serving window
MIN_EPOCHS_FOR_DATA_COLUMN_SIDECARS_REQUESTS is 4,096 epochs. A node must
retain and serve its custody columns from:
max(current_epoch - 4096, FULU_FORK_EPOCH)
through the current wall-clock epoch. A node that cannot serve required data
in that range should return 3: ResourceUnavailable. Peers may downscore or
disconnect a node that does not meet its advertised custody and serving
contract.
Status/2 adds earliest_available_slot. The value is the earliest slot from
which the node can serve the complete block-and-required-sidecar contract. If
its blocks reach farther back than its sidecars, it advertises the sidecar
boundary. If its sidecars cover the full required window, it advertises the
block boundary.
Sampling and reconstruction
Each slot, a node samples
max(SAMPLES_PER_SLOT, custody_group_count) deterministic groups.
SAMPLES_PER_SLOT is 8 on mainnet. A sample succeeds only when every selected
column arrives and passes proof checks.
A node that obtains at least half of all columns should reconstruct the full matrix. It must expose each reconstructed column as received data. It sends the column to mesh peers when it subscribes to that subnet and should still announce its availability otherwise.
Blob-sidecar transition
Fusaka deprecates both blob-sidecar request methods only after
FULU_FORK_EPOCH + 4096 epochs. During that interval, servers must still
answer requests for blob sidecars in:
[min(current_epoch - 4096, FULU_FORK_EPOCH), FULU_FORK_EPOCH)
when the request overlaps that range. They may return no blob sidecars when a
range or every requested root is at or after FULU_FORK_EPOCH. During the
transition, a node should not penalize a peer for requesting blob sidecars
from FULU_FORK_EPOCH.
Failure and recovery
- Do not count missing, malformed, or unproved cells as a successful sample.
- Retry missing columns through peers whose derived custody groups cover them.
- Do not penalize a peer for data older than its required or advertised serving window.
- Keep fork-digest framing tied to the sidecar's slot. Do not decode a response only from the local wall-clock fork.
- Treat gossip as the fast path and Req/Resp as bounded recovery. Neither path makes an invalid proof acceptable.