Skip to main content
netspecsEthereum networking

3.8.Req/Resp protocol

Stage
In progress
Version
Sources
7 pinned sources

Each request uses one libp2p stream and one exact protocol ID:

/eth2/beacon_chain/req/{method}/{version}/ssz_snappy

Multistream-select negotiates the full ID. The requester writes one SSZ-Snappy request, closes its write side, and reads response chunks until the responder closes, an error arrives, or the chunk fails validation. The requester MUST NOT open more than MAX_CONCURRENT_REQUESTS = 2 streams for one protocol ID.

Chunk framing

Every response chunk starts with one unsigned byte:

Code Meaning Payload
0 Success optional context, length-prefixed SSZ-Snappy object
1 InvalidRequest ErrorMessage
2 ServerError ErrorMessage
3 ResourceUnavailable ErrorMessage

Codes 4 through 127 are reserved. Codes 128 through 255 are method-specific. ErrorMessage is List[Byte, 256]; bytes need not be valid UTF-8. Only the final chunk may have a non-zero result. A list response sends one list element per successful chunk, not one SSZ list container.

The ssz_snappy header is an unsigned protobuf varint containing the raw SSZ length. It must use at most ten bytes, fit the lower type or MAX_PAYLOAD_SIZE bound, and be followed by exactly that many decompressed bytes. A reader MUST reject early EOF, trailing bytes, or compressed input over 32 + n + n / 6. A requester treats a bad response as server behavior and may reduce the peer's local score. A responder returns InvalidRequest for a bad request header or payload.

Common methods and versions

The common method set includes /status/1/, /goodbye/1/, /ping/1/, /metadata/1/, /beacon_blocks_by_range/1/, and /beacon_blocks_by_root/1/. Status/1 contains fork_digest, finalized root and epoch, head root, and head slot. Ping/1 carries the metadata sequence number. Goodbye/1 carries a reason.

The method set also includes /beacon_blocks_by_range/2/, /beacon_blocks_by_root/2/, and /metadata/2/. Successful block chunks include a fixed four-byte fork-digest context. The context epoch comes from the block slot and selects the fork specific SignedBeaconBlock SSZ type. A client may serve v1 and v2 during negotiation; v1 returns InvalidRequest for a payload selected by v2.

The method set also includes /status/2/ and /metadata/3/. Status/2 extends Status/1 with earliest_available_slot, which covers the earliest slot from which the peer can serve all required blocks and sidecars. Metadata/3 extends the metadata container with custody_group_count.

Gloas payload-envelope methods

The method set includes these exact protocol IDs:

/eth2/beacon_chain/req/execution_payload_envelopes_by_range/1/
/eth2/beacon_chain/req/execution_payload_envelopes_by_root/1/

The range request is (start_slot, count) and the response is a list of at most MAX_REQUEST_PAYLOADS = 128 SignedExecutionPayloadEnvelope values. The root request is an SSZ list of at most 128 block roots and returns zero or more envelopes for those roots. A successful chunk carries the Gloas fork-digest context and gloas.SignedExecutionPayloadEnvelope. The root method serves the recent block-request window and may return ResourceUnavailable for an older root.

Hegota inclusion-list method

The method set includes:

/eth2/beacon_chain/req/inclusion_lists_by_indices/1/

The SSZ-container request is (slot, inclusion_list_committee_root, indices: BitVector[INCLUSION_LIST_COMMITTEE_SIZE]). The response contains at most MAX_REQUEST_INCLUSION_LIST = 16 SignedInclusionList values. A successful chunk carries the Heze fork-digest context. A node serves lists from max(current_slot - 1, compute_start_slot_at_epoch(HEZE_FORK_EPOCH)) and may return ResourceUnavailable for an older slot.

Request bounds

The block methods permit at most MAX_REQUEST_BLOCKS = 1,024 block roots or range items.

Block methods use MAX_REQUEST_BLOCKS_DENEB = 128.

Blob-sidecar requests are bounded by compute_max_request_blob_sidecars() = MAX_REQUEST_BLOCKS_DENEB × MAX_BLOBS_PER_BLOCK (768). A range response carries at most count × MAX_BLOBS_PER_BLOCK sidecars.

From Pectra the function returns MAX_REQUEST_BLOCKS_DENEB × MAX_BLOBS_PER_BLOCK_ELECTRA (1,152), and the request bound and the count × response cap track the new per-block bound.

Light-client updates are capped at MAX_REQUEST_LIGHT_CLIENT_UPDATES = 128.

Fulu data-column requests derive their bound as MAX_REQUEST_BLOCKS_DENEB * NUMBER_OF_COLUMNS.

Block serving covers the range returned by compute_min_epochs_for_block_requests() and may return ResourceUnavailable for earlier data. Sidecar, light-client, payload-envelope, and inclusion-list pages define their own retention windows and context types.

The pinned consensus specifications define no protocol-wide numeric TTFB_TIMEOUT or RESP_TIMEOUT. Implementations may use local timers, reset a stream when a response is no longer useful, and must not treat a local timer as an interoperable wire rule.

Failure handling

The responder validates the request before processing it, rate-limits by withholding complete chunks rather than emitting a false error, and closes its write side after the final chunk. The requester keeps a valid prefix from a partial stream but never treats it as a complete response. Unknown IDs fail negotiation; invalid contexts, lengths, decompression, SSZ, or response codes fail the stream. Retry and peer pruning remain local policy.

Scope and status

Req/Resp framing and error codes are shared by all methods. Data availability, light-client, inclusion-list, and payload-envelope methods remain separate surfaces even though they share this framing.