Skip to main content
netspecsEthereum networking

4.3.1.Data availability networking

Stage
In progress
Version
Sources
6 pinned sources

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.