Version 2 Zcash P2P Network Protocol
Terminology
The key words "MUST", "MUST NOT", "SHOULD", "SHOULD NOT", "MAY", and "RECOMMENDED" in this document are to be interpreted as described in BCP 14 1 when, and only when, they appear in all capitals.
The terms "Mainnet" and "Testnet" are to be interpreted as described in section 3.12 of the Zcash Protocol Specification. 2
The term "network upgrade" is to be interpreted as described in ZIP 200. 3
The term "block chain" in this document is to be interpreted as described in section 3.3 of the Zcash Protocol Specification. 4
The term "legacy protocol" in this document refers to the Zcash P2P network protocol specified in ZIP 204 5.
- peer
- A network node that participates in the Zcash P2P protocol by maintaining connections to other peers over one or more supported transports and exchanging protocol data over streams.
- transport
- A concrete mechanism, such as QUIC 6 or Tor onion services 7, that carries connections between peers and provides the stream layer of this protocol (see Stream Layer and Transports).
- connection
- A session between two peers over a supported transport, over which P2P streams are exchanged.
- initiator
- The peer that initiated a connection (the transport-level client).
- responder
- The peer that accepted a connection (the transport-level server). In the context of a single request stream, responder instead denotes the peer serving the request, irrespective of which peer accepted the connection (see Request Streams); which sense is meant is indicated by context.
- requester
- The peer that opened a request stream and sends the request on it. Either the initiator or the responder of a connection may be the requester on a given request stream.
- stream
- An ordered, reliable byte stream within a connection, provided by the transport. Streams are typed by their first byte (see Stream Layer).
- request stream
- A bidirectional stream carrying a single request and its response.
- announcement stream
- A long-lived unidirectional stream carrying unsolicited announcements (blocks, transactions, or peer addresses).
- record
- A length-delimited unit of data on an announcement or handshake stream.
- protocol version
- A 32-bit integer identifying the set of protocol features supported by a node. Higher values indicate support for more recent features.
Abstract
This ZIP specifies version 2 of the Zcash peer-to-peer network protocol, a successor to the protocol specified in ZIP 204 5. It specifies the protocol's transport-neutral stream layer, the QUIC and Tor transports that realize it, the connection handshake, stream and record types, and relay behavior, and is intended to be sufficient for an implementor to build a conformant Zcash network peer.
Motivation
ZIP 204 5 specifies the current Zcash P2P network protocol, whose message-passing architecture is inherited from Bitcoin: a single unencrypted TCP bytestream carrying framed messages with implicit correlation between requests and responses. This ZIP proposes a successor protocol that replaces that architecture with multiplexed streams carried over an arbitrary transport that provides a secure channel between peers and a means of identifying the network and addressing peers. The protocol is specified against that abstract transport interface; this ZIP defines two concrete transports, one based on QUIC 6 and one carried over Tor onion services 7. The motivations for this change are:
-
Transport security. The legacy transport was unencrypted and unauthenticated, exposing all P2P traffic to passive network observers. Every transport of this protocol provides an encrypted channel: the QUIC transport integrates TLS 1.3 8 9, and the Tor transport inherits the encryption of onion service connections. (Bitcoin addressed the same gap with a bespoke encrypted transport, BIP 324 10; this protocol provides equivalent protection using standardized protocols.)
-
Multiplexing without head-of-line blocking. On the legacy transport, a single 2 MiB
blockmessage stalled all other traffic on the connection. Streams allow concurrent block downloads, transaction relay, and control traffic to proceed independently — on the QUIC transport, at both the application and packet-loss level. -
Explicit request correlation. Legacy responses (
block,tx,headers,notfound) carried no request identifiers, forcing implementations to correlate them heuristically. In this protocol, each request occupies its own stream, so correlation, cancellation, and per-request timeouts are structural. -
Simplification. Transport-level mechanisms that the legacy protocol carried in the application layer — payload checksums, network magic, keep-alives and latency measurement, flow control — are provided by the transport and are deleted from the application protocol.
No backwards compatibility with the legacy protocol is provided; this ZIP replaces it entirely. See Deployment.
This ZIP does not specify the encoding of transactions and block headers; for those, refer to the Zcash Protocol Specification 11 12. The encoding of a whole block in terms of its header and transactions is part of the peer-to-peer protocol rather than the consensus protocol 12, and is specified in Serialized Blocks.
Changes from the Legacy Protocol
This section is non-normative. Relative to the legacy protocol specified in ZIP 204 5, this ZIP:
- Replaces network magic bytes with transport-level network identification (for the QUIC transport, ALPN protocol identifiers; for the Tor transport, an in-band network identifier; see QUIC Transport and Tor Transport).
- Removes per-message headers entirely: the 12-byte command string is replaced
by a 1-byte stream type, and the length and
SHA-256dchecksum fields are subsumed by QUIC framing and authenticated encryption. - Replaces the
version/verackhandshake with a singleinitrecord exchange on a dedicated handshake stream (see Connection Handshake). Thetimestamp,addr_recv, andaddr_fromfields are removed. - Removes
pingandpong; QUIC provides keep-alives and RTT measurement. - Removes
reject; QUIC application error codes are used instead (see Application Error Codes). - Removes
inv,getdata, andnotfound; announcements are carried on dedicated unidirectional streams, and object requests carry per-item results. - Removes the inventory-walk
getblocksmessage; headers-first synchronization is the baseline synchronization method, and new request streams support checkpoint-based bulk synchronization:get-hashes(known hashes and sync-cost metadata),get-block-range(anchored block streaming),get-tree-roots(note commitment tree roots, verified against header commitments), andget-object(content-addressed synchronization artifacts). See Synchronization and 13. - Removes the deprecated
alertmessage. - Removes BIP 37 Bloom filtering (
filterload,filteradd,filterclear) and theNODE_BLOOMservice flag. - Removes the legacy
addrmessage andCAddress/CServiceencodings; theaddrv2address encoding of ZIP 155 14 is the only address format. - Retains divided block relay — header-based block announcement and compact
block relay — recast onto announcement streams and request streams, with a
block's coinbase transaction and transaction IDs additionally available in
get-headersresponses (see Block Relay).
Specification
This specification is organized bottom-up. Transports defines how connections are established and secured (QUIC and Tor); Stream Layer defines the typed streams that carry all application data, and Data Types and Encoding the shared encodings. Connection Handshake and Protocol Versioning cover connection setup. Request Stream Types and Announcement Stream Types specify the individual request and announcement formats, and Block Relay, Synchronization, Transaction Relay, and Address Relay the behaviors built on them.
Network Parameters
Networks
There are three Zcash networks: Mainnet, Testnet, and Regtest. Every connection is bound to exactly one network, determined during connection establishment as each transport defines (see Transports); this replaces the network magic bytes of the legacy protocol.
DNS Seeds
The DNS seed hostnames used for initial peer discovery on each network are those listed in ZIP 204 15. Regtest does not use DNS seeds or hardcoded seed nodes.
Peer Discovery
A node discovers peers through the following mechanisms:
-
Persisted addresses. A node SHOULD persist the peer addresses it learns (see Address Relay) across restarts, and on startup SHOULD attempt connections to previously learned addresses before consulting seeds.
-
DNS seeding. When the node has no usable persisted addresses — on first start, or when too few persisted addresses yield successful connections — it queries the DNS seed hostnames listed above for A and AAAA records, and attempts connections to the resulting addresses on the default port of the QUIC transport.
-
Hardcoded seed nodes. If DNS seeding fails or is insufficient, the node MAY fall back to a compiled-in list of seed node addresses. DNS seeding is preferred over hardcoded seed nodes.
-
Address relay. Connected peers exchange address information using
get-addrrequest streams and address announcement streams (seeget-addrand Address Announcements). This mechanism operates independently of the above.
DNS seeding yields IP addresses only, and so bootstraps only IP-based
transports such as QUIC. Addresses of peers reachable over overlay transports —
such as the TORV3 addresses of the Tor transport (see
Tor Transport) — are learned through address relay or provided
by local configuration.
Transports
The protocol semantics in this document are defined against the abstract stream layer of Stream Layer. A transport is a concrete mechanism that carries connections between peers and realizes that stream layer. This ZIP defines two transports: one based on QUIC (see QUIC Transport), and one carried over Tor onion services (see Tor Transport). Future revisions may define additional transports — a transport carrying the stream layer over the Nym mixnet is anticipated (see Deployment) — and a node MAY support several transports simultaneously. The protocol semantics on a connection are identical regardless of its transport.
Every transport MUST provide:
- a secure channel between the two peers: confidentiality and integrity for all application data;
- identification of the network (see Networks) during connection establishment, such that a connection between peers on different networks fails before any application data is exchanged;
- a form of address by which peers reachable over the transport are identified, encodable as a network address record (see Network Address Record);
- the stream operations and guarantees of Transport Requirements.
Connection Management
The transport provides keep-alives and round-trip time measurement; the legacy
ping/pong messages have no equivalent in this protocol.
- A node SHOULD use an idle timeout of at least 120 seconds, and SHOULD use the transport's keep-alive mechanism to keep connections it wishes to retain from idling out. The specific values are implementation-defined.
- A node MUST close a connection on which the application handshake (see Connection Handshake) has not completed within a bounded time, measured from the establishment of the transport connection so that it covers everything a peer sends beforehand — including the Tor transport's connection preamble (see Tor Transport). The specific timeout is implementation-defined; values between 3 and 60 seconds were used by legacy implementations. Without such a timeout, a peer that connects and then sends nothing holds a connection slot indefinitely, and the transport's keep-alive mechanism — answered by the transport stack rather than by the application — will report the connection healthy.
- A node SHOULD bound the number of concurrent inbound connections it accepts, and SHOULD bound the share of them originating from any one transport, so that inbound slots on a transport whose addresses are free to generate cannot displace the rest.
- Per-request timeouts are implementation-defined and are applied per stream (see Request Streams).
The specific connection limits are implementation-defined; the legacy reference values in ZIP 204 5 remain reasonable defaults.
A node SHOULD maintain at most one connection to a given remote address. Self-connection detection via the handshake nonce is specified in Init Record.
QUIC Transport
The QUIC transport uses QUIC version 1 6 over UDP, secured with TLS 1.3 8 as specified by RFC 9001 9.
- A node MUST NOT use 0-RTT (early data). Note: a peer that attempts to use
0-RTT MUST have its connection closed — a responder MUST NOT enable the
acceptance of early data, and a node whose peer nevertheless attempts to
use 0-RTT MUST close the connection with the
PROTOCOL_ERRORerror code. (0-RTT data is replayable by an attacker.) - QUIC datagrams 16 are not used by this protocol. A node MUST ignore
the peer's
max_datagram_frame_sizetransport parameter and MUST NOT send DATAGRAM frames. - A node MAY support connection migration as specified in RFC 9000. Migration links the node's traffic across the old and new network paths; a node for which that linkage is a privacy concern SHOULD establish a fresh connection instead of migrating.
- The address validation and amplification limits of RFC 9000 (including Retry packets) apply; a responder under load SHOULD use Retry to validate client addresses before committing state.
- QUIC's loss recovery provides round-trip time measurement, and QUIC PING
frames provide keep-alives. A node using the QUIC transport SHOULD advertise
a
max_idle_timeouttransport parameter consistent with the idle timeout of Connection Management.
Network Identification
The network of a QUIC connection is identified by an Application-Layer Protocol Negotiation (ALPN) 17 protocol identifier, negotiated in the QUIC-TLS handshake:
| Network | ALPN Identifier |
|---|---|
| Mainnet | zcash/main |
| Testnet | zcash/test |
| Regtest | zcash/regtest |
A node MUST offer exactly the ALPN identifier of the network it operates on, and MUST NOT complete a connection on which ALPN negotiation did not select that identifier.
Incompatible future revisions of this transport will be assigned new ALPN identifiers.
Default Ports
The QUIC transport uses the default port numbers of the legacy protocol, as specified in ZIP 204 18, as UDP port numbers.
Certificates
Connections are encrypted but endpoints are not authenticated: the goal is protection against passive network observers, not endpoint identity.
- A node presents a self-signed X.509 certificate, or a raw public key 19, for an Ed25519 or ECDSA P-256 key. The key MAY be ephemeral or persistent, at the node's option; a node concerned about cross-connection linkability SHOULD use per-connection ephemeral keys.
- A node MUST be prepared to accept both certificate encodings (X.509 and raw public key) and both key types from its peer.
- A node MUST NOT require the peer's certificate to chain to any certification authority, and MUST NOT reject a connection on the basis of certificate contents (subject names, expiry, or extensions).
- Client certificates are not requested: the responder MUST NOT require TLS client authentication.
Bulk Transfer Performance
Bulk synchronization moves hundreds of gigabytes over the request streams of this protocol, and its throughput is governed by transport configuration rather than by the protocol itself. The following applies to nodes that serve or perform bulk synchronization (see 13):
- A node SHOULD size its flow control windows to the bandwidth-delay product it intends to sustain: at least 16 MiB of per-stream receive window and 64 MiB of connection receive window are RECOMMENDED. Default windows tuned for interactive traffic cap bulk throughput at a small fraction of link capacity once round-trip time is non-negligible.
- A node serving bulk streams SHOULD use a paced, model-based congestion controller suited to high bandwidth-delay paths; loss-triggered controllers collapse under modest jitter at these transfer sizes.
- Implementations SHOULD use platform UDP batching and segmentation offloads where available; per-packet processing, not cryptography, is the dominant CPU cost of QUIC bulk transfer.
- One QUIC connection is typically processed by one core, bounding per-connection throughput. Synchronizing nodes obtain aggregate throughput by downloading from several peers concurrently rather than by tuning a single connection (see 13).
Stream Layer Mapping
The abstract stream layer of Transport Requirements maps onto QUIC as follows:
| Stream layer concept | QUIC realization |
|---|---|
| Bidirectional / unidirectional stream | QUIC bidirectional / unidirectional streams. |
| Finishing a stream direction | A STREAM frame with the FIN bit. |
| Resetting a stream with an error code | RESET_STREAM carrying the application error code. |
| Cancelling a peer's sending with an error code | STOP_SENDING carrying the application error code. |
| Closing the connection with an error code | CONNECTION_CLOSE (application variant) carrying the application error code. |
| Per-stream backpressure | QUIC stream and connection flow control. |
| Stream concurrency limits | initial_max_streams_bidi, initial_max_streams_uni, and MAX_STREAMS frames. |
| Keep-alive | QUIC PING frames. |
Tor Transport
The Tor transport carries connections over the Tor network 20 as connections to version 3 onion services 7. It serves traffic for which the metadata protection of onion routing outweighs its latency cost (see Deployment and Security and Privacy Considerations), and allows a node to participate in the network without exposing an IP address.
- A node that accepts inbound connections over this transport runs a version 3
onion service and advertises its address as a
TORV3network address record (see Network Address Record); the record'sportfield is the onion service virtual port, which SHOULD be the default port of the network (see Default Ports). - The initiator connects to the responder's onion service through the Tor network. The initiator needs no onion service of its own and is anonymous to the responder.
- The secure channel is provided by Tor itself: onion service connections are
end-to-end encrypted, and the connection is authenticated to the responder's
onion address (the Ed25519 key that the
TORV3address encodes). TLS is not used inside the connection, and the initiator is deliberately not authenticated (cf. Certificates). - The transport provides a single ordered bytestream per connection; the stream layer is realized on top of it by the framing layer of Stream Framing.
Note: The underlying Tor connection is a single TCP-like bytestream, so loss or congestion on it stalls all streams of the connection; the framing layer removes head-of-line blocking between streams at the application level only (see Deployment for transport selection).
Connection Preamble
Immediately after the connection is established, each peer sends a preamble, before any frames:
| Size | Field | Description |
|---|---|---|
| varies | network | Network identifier string (CompactSize-prefixed; see below). |
| varies | initial_max_data | Initial connection-level flow control credit in bytes (CompactSize). |
| varies | initial_max_stream_data | Initial per-stream flow control credit in bytes (CompactSize). |
| varies | initial_max_streams_bidi | Initial limit on the peer's concurrent bidirectional streams (CompactSize). |
| varies | initial_max_streams_uni | Initial limit on the peer's concurrent unidirectional streams (CompactSize). |
The network string is the identifier of the node's network, using the same
values as the QUIC transport's ALPN identifiers (see
Network Identification): zcash/main, zcash/test,
or zcash/regtest. A node MUST close the connection if the peer's network
string differs from its own; this replaces ALPN-based network identification,
which is unavailable without TLS.
The flow control fields play the role of the corresponding QUIC transport
parameters: they are the initial credit available to the peer for the
corresponding limit (see Stream Framing). A node MUST allow
an initial_max_stream_data of at least 2,228,224 bytes (the maximum record
payload length plus framing headroom), and SHOULD allow the stream concurrency
minimums of Transport Requirements.
The preamble precedes every frame, and therefore precedes the record payload limit of Records and the malformed-frame rule of Stream Framing; neither bounds it. Its fields are bounded here instead, and a node MUST enforce these bounds while parsing the preamble, before allocating storage for a field:
- The
networkstring MUST NOT exceed 16 bytes. - Each of the four flow control fields MUST NOT exceed 2^62 − 1, the corresponding limit in QUIC 6.
- A node MUST close the connection immediately, without sending frames, if a preamble field exceeds its bound, if the preamble is not complete within the handshake timeout below, or if a CompactSize in the preamble is not canonically encoded (see Data Types and Encoding).
The handshake timeout of
Connection Management runs from the establishment of
the transport connection, so it covers the preamble as well as the init
record; an initiator that opens a connection and stalls mid-preamble is closed
by it. This matters most for this transport: onion service addresses are free
to generate and effectively unbannable (see
Misbehavior and Banning), so a connection slot held
open at no cost is the resource an attacker is actually consuming.
Incompatible future revisions of this transport will be assigned new network identifier strings.
Stream Framing
The stream layer is realized over the connection's ordered bytestream by a framing layer whose stream semantics — stream states, flow control, and error signalling — are modeled on those of QUIC 6.
Stream IDs are integers. The two least significant bits of a stream ID encode
its kind, as in QUIC: 0x0 initiator-opened bidirectional, 0x1
responder-opened bidirectional, 0x2 initiator-opened unidirectional, 0x3
responder-opened unidirectional. Successive streams of each kind are opened
with sequentially increasing stream IDs; a stream is opened implicitly by the
first frame that its opener sends referencing its ID.
Each frame is a 1-byte frame type followed by type-dependent fields. Integer fields, including stream IDs, are CompactSize-encoded.
| Type | Frame | Fields | Semantics |
|---|---|---|---|
0x00 | PING | 8-byte opaque value | Keep-alive and RTT measurement; the receiver MUST respond with PONG. |
0x01 | PONG | 8-byte echoed value | Response to PING. |
0x02 | STREAM | stream ID, flags (1 byte), length, data | Appends data to the stream. Flag bit 0 (FIN) finishes the sender's direction of the stream. |
0x03 | RESET_STREAM | stream ID, error code | Resets the sender's direction of the stream (see Transport Requirements). |
0x04 | STOP_SENDING | stream ID, error code | Requests that the peer stop sending on the stream. |
0x05 | MAX_DATA | maximum | Raises the connection-level flow control limit (cumulative bytes of stream data). |
0x06 | MAX_STREAM_DATA | stream ID, maximum | Raises the flow control limit of one stream (cumulative bytes). |
0x07 | MAX_STREAMS_BIDI | count | Raises the limit on the peer's cumulative count of opened bidirectional streams. |
0x08 | MAX_STREAMS_UNI | count | Raises the limit on the peer's cumulative count of opened unidirectional streams. |
0x09 | CLOSE | error code | Closes the connection with an application error code; the sender then closes the underlying connection. |
Flow control limits are cumulative, as in QUIC: a limit counts total bytes
sent on a stream (respectively, on all streams; respectively, total streams
opened) since the beginning of the connection, and each MAX_* frame
communicates a new absolute limit. Limits never decrease; a MAX_* frame with
a lower value than a previously communicated limit is ignored. A node MUST NOT
exceed a limit communicated by its peer; a peer that does so is a connection
error of type PROTOCOL_ERROR (equally, a frame that is malformed or has an
unrecognized frame type).
The length of a STREAM frame MUST NOT exceed 65,536 bytes; larger
application writes are split across multiple STREAM frames, allowing frames
of other streams to be interleaved between them.
The abstract stream layer maps onto this framing as follows:
| Stream layer concept | Framing realization |
|---|---|
| Bidirectional / unidirectional stream | Stream ID kinds 0x0/0x1 and 0x2/0x3. |
| Finishing a stream direction | A STREAM frame with the FIN flag. |
| Resetting a stream with an error code | RESET_STREAM carrying the application error code. |
| Cancelling a peer's sending with an error code | STOP_SENDING carrying the application error code. |
| Closing the connection with an error code | CLOSE carrying the application error code. |
| Per-stream backpressure | MAX_DATA and MAX_STREAM_DATA credit. |
| Stream concurrency limits | Preamble initial limits and MAX_STREAMS_* frames. |
| Keep-alive | PING frames. |
Stream Layer
All application data is carried on streams provided by the transport. The first byte of every stream is a stream type that determines the format and semantics of the remaining stream data.
Transport Requirements
The stream layer that every transport provides consists of bidirectional streams (both peers can send) and unidirectional streams (only the opener can send), with the following operations and guarantees:
- Either peer can open streams of either kind at any time. The receiver of a stream can tell which peer opened it and of which kind it is.
- Data written to a stream is delivered reliably and in order within that stream. Streams are mutually independent: delay or loss on one stream MUST NOT prevent delivery of data on other streams.
- A sender can finish its sending direction of a stream, signalling that no more data will be sent on it.
- A peer can reset a stream it is sending on, abandoning it with an application error code (see Application Error Codes), and can cancel the peer's sending direction of a stream, requesting with an application error code that the peer stop sending.
- Either peer can close the connection with an application error code.
- The receiver of a stream can apply per-stream backpressure to bound how much data it must buffer.
- Each peer can bound the number of streams of each kind that the other peer may have concurrently open. A node SHOULD allow at least 32 concurrent bidirectional streams, and SHOULD allow at least 8 concurrent unidirectional streams (sufficient for the announcement streams plus headroom for future types).
How each transport realizes these operations is specified in the corresponding transport section (for QUIC, see Stream Layer Mapping).
Stream Types
| Code | Name | Kind | Description |
|---|---|---|---|
0x00 | Handshake | Bidirectional, initiator only | Connection handshake and control (see Connection Handshake). |
0x01 | get-headers | Bidirectional | Request block headers, optionally with transaction IDs. |
0x02 | get-blocks | Bidirectional | Request full blocks. |
0x03 | get-tx | Bidirectional | Request transactions. |
0x04 | get-addr | Bidirectional | Request peer addresses. |
0x05 | get-mempool | Bidirectional | Subscribe to mempool contents. |
0x06 | get-hashes | Bidirectional | Request best-chain block hashes and per-block sync-cost metadata. |
0x07 | get-block-range | Bidirectional | Stream a contiguous range of blocks, verified against an anchor hash. |
0x08 | get-tree-roots | Bidirectional | Request per-block note commitment tree roots for a height range. |
0x09 | get-object | Bidirectional | Request a range of a content-addressed synchronization artifact. |
0x10 | Block announcements | Unidirectional | Announce new blocks (see Block Announcements). |
0x11 | Transaction announcements | Unidirectional | Announce new transactions (see Transaction Announcements). |
0x12 | Address announcements | Unidirectional | Gossip peer addresses (see Address Announcements). |
Stream type 0x00 and the ranges 0x01–0x0F and 0x10–0x1F are reserved
for the handshake, request stream types, and announcement stream types
respectively; future revisions are expected to assign new stream types from the
matching range.
To refuse a stream opened by its peer, a node cancels the peer's sending direction of that stream with the applicable error code and, if the stream is bidirectional, also resets its own sending direction with the same code; refusing a unidirectional stream involves only the cancellation.
A node receiving a stream whose type byte it does not recognize MUST refuse
that stream with the UNSUPPORTED_STREAM_TYPE error code (see
Application Error Codes) and MUST NOT treat it as a
connection error or assign a misbehavior penalty. This allows future stream
types to be deployed without version gating.
A stream that is finished by its opener before a complete type byte has been
received is a connection error of type PROTOCOL_ERROR.
Request Streams
A request stream is a bidirectional stream carrying exactly one request and its response:
- The requester opens a bidirectional stream, writes the stream type byte followed by the request in the format defined for that type, and finishes its sending direction.
- The responder writes the response in the format defined for that type and
finishes its sending direction. (For a stream type that defines an
open-ended response, the responder instead keeps its sending direction open;
see
get-mempool.)
Either peer may open request streams; there is no client/server asymmetry beyond the rules stated for individual stream types.
- A responder MAY begin serving a request as soon as the request is syntactically complete, without waiting for the requester to finish its sending direction.
- A responder that cannot or will not serve a request MAY reset its sending
direction of the stream with the
REFUSEDerror code instead of sending a response. - A requester that no longer needs a response SHOULD cancel the responder's
sending direction with the
CANCELLEDerror code; the responder SHOULD then reset the stream and abandon work on the request. - A reset or refused request stream is never resumed or reused. A requester that retries a request after a reset does so by opening a new stream, and the next byte the requesting side sends — the first byte of that new stream — MUST be the stream type byte, as for any other stream.
- A node SHOULD apply an implementation-defined timeout to each outstanding request, cancelling the stream if the response does not complete in time.
- A node MUST NOT send more than one request on a stream. Unless the stream
type defines an open-ended response, any data following a complete request
or response on a stream is a connection error of type
PROTOCOL_ERROR. - A request or response that does not conform to the format defined for its
stream type — including an unrecognized
resultvalue in a response — is a connection error of typePROTOCOL_ERROR, unless other handling is specified for the stream type.
A node bounds the number of concurrent requests a peer may have outstanding using the transport's stream concurrency limits (see Transport Requirements).
Announcement Streams
An announcement stream is a long-lived unidirectional stream carrying a sequence of records for one announcement topic.
- After the connection handshake completes, each peer SHOULD open one announcement stream of each type it intends to send announcements on, and keep it open for the life of the connection.
- A node MUST NOT open more than one announcement stream of a given type in
the same direction at a time. A second concurrent stream of the same type is
a connection error of type
PROTOCOL_ERROR. If an announcement stream is reset or finished, the sender MAY open a replacement. - Announcements are best-effort: if transport backpressure prevents a record from being written promptly, the sender MAY drop the announcement rather than queueing it indefinitely.
- A record on an announcement stream that does not conform to the format
defined for its stream type — a nonconforming record — is a connection
error of type
PROTOCOL_ERROR, unless other handling is specified for the stream type. (New record formats are introduced under new protocol versions, so an unrecognized format is a violation, not a newer peer.)
Records
Each record on an announcement or handshake stream is encoded as a fixed-size 4-byte length prefix followed by that many bytes of payload:
| Size | Field | Description |
|---|---|---|
| 4 | length | Payload length in bytes (uint32, little-endian). |
| varies | payload | Record payload; format depends on the stream type. |
The maximum length of a record payload — and of any individually
length-prefixed element in a request or response, such as a serialized block —
is 2,097,152 bytes (2 MiB). A node MUST treat a length prefix exceeding this
limit as a connection error of type FLOOD.
A record MUST NOT be processed until it is complete. A stream that is finished
in the middle of a record is a connection error of type PROTOCOL_ERROR.
Application Error Codes
The following application error codes are used when closing a connection, resetting a stream, or cancelling a peer's sending direction (see Transport Requirements):
| Code | Constant | Usage |
|---|---|---|
0x00 | NO_ERROR | Graceful connection close. |
0x01 | PROTOCOL_ERROR | The peer violated this specification. |
0x02 | UNSUPPORTED_STREAM_TYPE | Stream refusal: unrecognized stream type. |
0x03 | OBSOLETE | The peer's protocol version is below the minimum for the current network epoch (see Network Upgrade Epoch Enforcement). |
0x04 | SELF_CONNECTION | The connection is to the node itself (see Init Record). |
0x05 | FLOOD | The peer exceeded a size or rate limit. |
0x06 | MISBEHAVIOR | The peer's misbehavior score reached the ban threshold (see Misbehavior and Banning). |
0x07 | CANCELLED | Stop-sending/reset: the request is no longer wanted. |
0x08 | REFUSED | Stream reset: the responder declines to serve this request. |
0x09 | INTERNAL_ERROR | The sender encountered an internal error. |
Where this document states that an event is a connection error of a given type, a node detecting that event MUST close the connection with the corresponding error code.
A node receiving an error code it does not recognize MUST treat it as
INTERNAL_ERROR.
Data Types and Encoding
The integer types and byte order, the CompactSize variable-length integer encoding (including its canonicity requirement), and the string encoding are as specified in ZIP 204 21. As there, a node MUST reject non-canonical CompactSize encodings.
Every CompactSize count and length in this protocol is peer-supplied and may be as large as 2^64 − 1 on the wire, whatever bound this document places on its legitimate values. A node MUST check a count or length against its specified bound before using it, and MUST NOT allocate storage in proportion to a declared count before the corresponding data has been received. Where this document requires a bound on a sum of two counts, the node MUST also bound each addend, since a sum of two unbounded values can wrap and satisfy the bound on the sum. Likewise, a node computing an offset or a remaining length from peer-supplied values MUST perform the arithmetic in a way that cannot overflow or underflow.
Serialized Blocks
The Zcash Protocol Specification defines the encodings of block headers and
transactions, but leaves the encoding of a whole block to the peer-to-peer
protocol 12. A serialized block is encoded as follows,
unchanged from the payload of the legacy block message:
| Size | Field | Description |
|---|---|---|
| varies | header | The serialized block header 12 (including the Equihash solution). |
| varies | tx_count | Number of transactions in the block (CompactSize). |
| varies | txns | tx_count serialized transactions 11, in block order. |
Network Address Record
Peer addresses use the addrv2 address record specified in ZIP 155
14; it is the only address encoding in this protocol. The record
fields (time, services, networkID, sizeAddr, addr, port), the
network IDs (IPV4, IPV6, TORV3, I2P, CJDNS), the 512-byte addr
limit, the per-network-ID length and encoding rules, and the prohibition on
gossiping addresses with unrecognized network IDs are all as specified in
ZIP 155.
The port field is interpreted per transport: for QUIC endpoints it is the
UDP port, and for TORV3 addresses it is the onion service virtual port (see
Tor Transport). It MUST be 0 if not relevant for the
network.
The time field of a relayed record is attacker-suppliable and
unauthenticated: it is the relaying peer's claim about when the address was
last seen, and nothing binds it to reality. Because it is the freshness
signal an address manager ranks on, an unvalidated time set far in the
future makes an attacker's addresses permanently the freshest entries of
whatever buckets they occupy, and so a preferred choice at every outbound
peer selection — an eclipse vector 22 that operates from inside the
buckets, where the bucketing of
Address Book Management affords no protection. A
node therefore:
- MUST clamp the
timeof a relayed record before storing it: atimemore than 10 minutes in the future, or equal to 0, is replaced by the current time less 5 days. - SHOULD NOT relay, and SHOULD eventually evict, a record whose clamped
timeis more than 30 days in the past. - SHOULD NOT make the relayed
timethe primary key of outbound peer selection, and SHOULD prefer its own record of successful connections to an address over any peer's claim about it.
These are the protections legacy implementations applied in code; they are stated here because the field is load-bearing for peer selection.
Note: Each network ID implies the transport by which the address is
reachable: IPV4, IPV6, and CJDNS addresses identify QUIC endpoints, and
TORV3 addresses identify onion services of the Tor transport. I2P
addresses are not reachable via either transport defined in this ZIP; they
remain defined for gossip so that nodes can learn them ahead of a future
revision that defines an I2P-capable transport.
Transaction References
Transactions are identified in announcements and requests by transaction
references. Per ZIP 239 23, transactions with version ≥ 5 are relayed
by wtxid (the txid followed by the authorizing data commitment auth_digest),
and transactions with version ≤ 4 by txid. A transaction reference is encoded
as a 1-byte type followed by a type-dependent identifier:
| Type | Name | Total Size | Description |
|---|---|---|---|
0x01 | TXID | 33 bytes | Transaction with version ≤ 4, identified by its 32-byte txid. |
0x02 | WTXID | 65 bytes | Transaction with version ≥ 5, identified by its 64-byte wtxid (txid followed by auth_digest). See ZIP 239 23. |
0x03 | SHORTID | 39 bytes | Transaction within a specific block, identified by a 32-byte block hash followed by a 6-byte short transaction ID (see Short Transaction IDs). Only valid in get-tx requests. |
A transaction reference with an unrecognized type is a connection error of
type PROTOCOL_ERROR. Using the wrong reference type for a transaction's
version is subject to a misbehavior penalty only where the sender of the
reference necessarily holds the transaction and so knows its version:
in announcement stream records and get-mempool response records (see
Misbehavior and Banning). A get-tx requester,
by contrast, may not hold the transaction it is requesting, and can have
derived the reference type from data supplied by another peer (see
Requesting Missing Transactions); a
responder receiving a get-tx reference whose type does not match the
referenced transaction's version MUST answer that reference with a
not-found result, and MUST NOT assign a misbehavior penalty for it.
Service Flags
Service flags are advertised in the services field of init records and
network address records. Bit numbering is as in ZIP 204
| Name | Bit | Description |
|---|---|---|
NODE_NETWORK | 0 | The node is capable of serving the complete block chain. |
NODE_TREE_ROOTS | 3 | The node maintains a per-block note commitment tree root index and serves get-tree-roots requests. |
NODE_SYNC_ARTIFACTS | 4 | The node serves content-addressed synchronization artifacts via get-object requests. |
NODE_NETWORK_LIMITED | 10 | The node is capable of serving at least the most recent 2,304 blocks (approximately two days). |
A node that does not hold the complete block chain — because it synchronized
from a state snapshot and has not finished backfilling, or because it prunes
historical blocks — MUST NOT advertise NODE_NETWORK; it SHOULD advertise
NODE_NETWORK_LIMITED if it can serve at least the most recent 2,304 blocks
of its best chain. A node selecting peers to request historical blocks from
uses these flags (see 13).
Bits 24–31 remain reserved for temporary experiments, as in ZIP 204 24.
A node MUST ignore service bits that it does not recognize, and SHOULD preserve them when relaying address records (see Address Relay).
The legacy NODE_BLOOM flag (bit 2) is retired along with BIP 37 Bloom
filtering, and MUST NOT be advertised.
Connection Handshake
Peers perform an application handshake immediately after the transport connection is established.
Handshake Sequence
- The initiator opens a bidirectional stream with stream type
0x00(the handshake stream) and sends aninitrecord. - The responder sends its own
initrecord on the same stream. - The handshake is complete for a peer once it has both sent and received an
initrecord. The negotiated protocol version ismin(local_version, remote_version).
The two init records are independent: the responder MAY send its init
record as soon as it observes the handshake stream's type byte, without
waiting for the initiator's init record to arrive.
Only the initiator may open the handshake stream, and a connection has exactly
one: a handshake stream opened by the responder, or a second handshake stream,
is a connection error of type PROTOCOL_ERROR.
A node MUST NOT open any other stream before it has sent its init record,
and MUST NOT send announcement records or requests before the handshake is
complete. A node MAY buffer streams received from a peer before the handshake
completes, or MAY refuse them with REFUSED (see
Stream Types); it MUST NOT process them before the handshake
completes.
The handshake stream remains open for the life of the connection. Future
record kinds on the handshake stream may be defined by later revisions; a node
MUST ignore handshake-stream records whose kind it does not recognize.
Finishing or resetting the handshake stream signals intent to disconnect; a
node observing this SHOULD close the connection with NO_ERROR.
Records on the handshake stream use the encoding of Records. The
record payload begins with a 1-byte record kind; kind 0x00 is the init
record.
Init Record
The init record payload (following the kind byte) has the following format:
| Size | Field | Description |
|---|---|---|
| 4 | version | The sender's advertised protocol version (uint32). |
| varies | services | Service flags (CompactSize-encoded uint64). |
| 8 | nonce | Random nonce for self-connection detection (uint64). |
| varies | user_agent | User agent string (CompactSize-prefixed, max 256 bytes). |
| 4 | start_height | Best block height known to the sender (uint32). |
| 1 | relay | Whether the sender wants transaction relay (uint8; 0 or 1). |
| 1 | announce | Whether the sender requests high-bandwidth compact block announcements (uint8; 0 or 1). See Compact Block Relay. |
| 1 | full_ids | Whether the sender requests full transaction IDs in compact block announcements (uint8; 0 or 1). See Full Transaction IDs. |
All fields are mandatory. The legacy timestamp, addr_recv, and addr_from
fields are removed.
The nonce field is used for self-connection detection. If a node receives an
init record containing a nonce it recently sent in its own init records,
it MUST close the connection with the SELF_CONNECTION error code.
If relay is 0, the peer MUST NOT open a transaction announcement stream to
the sender, and SHOULD NOT announce transactions to it by any other means.
There is no mechanism for changing the value of the relay, announce, or
full_ids fields during the life of a connection; a node that wants to
change them disconnects and performs a new handshake.
Handshake Validation
A receiving node MUST validate the init record as follows:
- The
versionfield MUST be at least the minimum protocol version of this ZIP (see Protocol Versioning), and at least the protocol version associated with the current network epoch (see Network Upgrade Epoch Enforcement). On failure, the node MUST close the connection with theOBSOLETEerror code. - The
nonceis checked for self-connection as specified in Init Record. - The
user_agentstring MUST NOT exceed 256 bytes. - The
relay,announce, andfull_idsfields MUST each be 0 or 1. - A peer MUST NOT send more than one
initrecord per connection.
Except where another error code is specified above, a node MUST close the
connection with the PROTOCOL_ERROR error code if any of these checks fail.
Handshake Example (non-normative)
Connection establishment over the QUIC transport:
Initiator Responder
═══ QUIC handshake (ALPN "zcash/main") ═══
── open stream 0x00: type byte, init ────▶
◀──────── same stream: init ──────────────
(handshake is complete for each peer once it
has both sent and received an init record)
── open announcement streams 0x10..0x12 ─▶
◀─ open announcement streams 0x10..0x12 ──
...requests and announcements flow in both directions...
Protocol Versioning
Protocol versions are 32-bit integers, advertised in the version field of
the init record. The negotiated protocol version of a connection is the
minimum of the two peers' advertised versions. The negotiated protocol version
determines whether the peer meets the minimum version requirements for the
current network epoch (see
Network Upgrade Epoch Enforcement), and
gates any features that later revisions of this protocol introduce under new
protocol versions.
Protocol versions inhabit a single numbering space shared with the legacy
protocol: a protocol version is assigned to a (network, network upgrade) pair,
not to a P2P protocol revision, and denotes the same version whether it is
advertised in a legacy version message or in an init record. The
association of protocol versions with network upgrades, and the procedure by
which future network upgrades are assigned protocol versions, are specified in
ZIP 204 25 and are not duplicated here.
At the time of writing, the current protocol version — advertised by nodes
implementing the legacy protocol — is 170160 (PROTOCOL_VERSION). The
protocol version from which the protocol specified by this ZIP is deployed has
not yet been assigned; it will be the protocol version that ZIP 204's
assignment procedure 25 assigns to the network upgrade
that deploys this protocol (see Deployment). That version is
the minimum peer protocol version of this protocol: every node implementing
this ZIP necessarily advertises at least that version, and version-gated
features of the legacy protocol (such as MSG_WTX relay and addrv2 support)
are unconditionally in effect.
Network Upgrade Epoch Enforcement
Each network upgrade defines a minimum protocol version. When a network
upgrade activates (as defined in ZIP 200 3), a node MUST disconnect
any peer whose negotiated protocol version is less than the protocol version
associated with the current epoch, using the OBSOLETE error code.
The protocol versions associated with network upgrades on each network are tabulated in ZIP 204 26.
In the preference window before an upgrade's activation, a node SHOULD preferentially connect to peers advertising the upcoming epoch's protocol version, as specified in ZIP 201 27.
Request Stream Types
This section defines each request stream type. For each type, the request and response formats and their semantics are specified. All requests and responses follow the lifecycle of Request Streams.
get-headers
Stream type: 0x01
Request:
| Size | Field | Description |
|---|---|---|
| varies | locator_count | Number of block locator hashes (CompactSize). |
| varies | locator_hashes | Block locator hashes (each 32 bytes), from highest to lowest height. |
| 32 | hash_stop | Hash of the last desired header, or all zeros to request as many as possible. |
| 1 | tx_ids | Whether each returned header should be accompanied by the block's coinbase transaction and transaction IDs (uint8; 0 or 1). |
Response:
| Size | Field | Description |
|---|---|---|
| varies | count | Number of entries (CompactSize). |
| varies | entries | count entries, each encoded as follows. |
Each entry:
| Size | Field | Description |
|---|---|---|
| varies | header | The block header, encoded as a CompactSize length prefix followed by the serialized header 12 (including the Equihash solution). |
| 1 | has_txs | Present only if the request had tx_ids = 1. 0x01: the coinbase transaction and transaction IDs follow. 0x00: nothing further follows for this entry. |
| varies | coinbase | Present only if has_txs is 0x01: the block's coinbase transaction, encoded as a CompactSize length prefix followed by the serialized transaction 11. |
| varies | ids_count | Present only if has_txs is 0x01: the number of transaction IDs (CompactSize). |
| varies | ids | Present only if has_txs is 0x01: the full transaction IDs (64 bytes each; see Full Transaction IDs) of the block's transactions other than the coinbase transaction, in block order. |
Requests block headers starting after the first block locator hash found in
the responder's best chain, up to and including hash_stop or 160 headers
(MAX_HEADERS_RESULTS), whichever comes first. The locator_count MUST NOT
exceed 101, and tx_ids MUST be 0 or 1.
If none of the locator hashes are found in the responder's best chain, headers
start at height 1 (the block after the genesis block). If locator_count is
0, the response consists solely of the entry for the header whose hash is
hash_stop, if the responder has it. A response with count = 0 is valid and
indicates that the responder has no headers to return.
If tx_ids is 1, each entry additionally identifies the block's transactions:
the coinbase transaction in full (it is never otherwise relayed), and every
other transaction by its full transaction ID, letting the requester fetch only
the transactions it is missing via get-tx (see
Relay Protocol). A responder that cannot supply a returned
header's transactions sets has_txs to 0x00 for that entry, and the
requester falls back to get-blocks.
A node performing bulk synchronization SHOULD set tx_ids to 0: the
transaction-ID form pays off only near the chain tip, where the requester
already holds most of a block's transactions. Because the ids array is not
individually length-prefixed, the record payload limit of
Records does not bound it, and a tx_ids = 1 request over large
historical blocks would otherwise compel a response of hundreds of megabytes
from a request of a few dozen bytes. Both directions are therefore bounded:
- An entry's
ids_countMUST NOT exceed 65,536, the greatest number of transactions a block can contain (see Compact Block Encoding). A larger value is a connection error of typeFLOOD. - A responder SHOULD bound the total serialized size of its response, and MAY
set
has_txsto0x00for any entry, or return fewer entries, to stay within that bound; the requester falls back toget-blocksfor those entries.
The response count MUST NOT exceed 160, and MUST NOT exceed the number of
headers the request could have selected. The headers MUST form a contiguous
chain (each header's hashPrevBlock must match the hash of the preceding
header). A node receiving a response with more than 160 headers, or with
non-contiguous headers, SHOULD discard the response and assign a misbehavior
penalty (see Misbehavior and Banning); this is the
handling of such responses, in place of the connection error that
Request Streams would otherwise prescribe.
The legacy always-zero transaction count that followed each header in
headers messages is removed.
get-blocks
Stream type: 0x02
Request:
| Size | Field | Description |
|---|---|---|
| varies | count | Number of block hashes (CompactSize). |
| varies | hashes | count block hashes (each 32 bytes). |
Response: For each requested hash, in request order:
| Size | Field | Description |
|---|---|---|
| 1 | result | 0x00: a full block follows. 0x02: not found (nothing follows for this entry). |
| varies | object | If result is 0x00: a CompactSize length prefix followed by the serialized block (see Serialized Blocks). |
Requests full blocks by hash; the hashes to request are learned from headers,
announcements, or get-hashes responses. The count MUST NOT exceed 128.
Compact blocks cannot be requested (see
Compact Block Relay).
Each delivered block MUST be the block that was requested: the header of the
block delivered for an entry MUST hash to that entry's requested hash. A
delivered block that does not is a connection error of type PROTOCOL_ERROR
— the check is by hashing alone, so a violation is never attributable to a
different chain view. Without this rule a peer could answer every entry with
some other valid block, choosing which of two competing blocks the requester
sees while remaining syntactically conformant.
A count of 128 bounds the number of blocks but not their size, and blocks
differ in size by three orders of magnitude across the chain's history. A
responder SHOULD therefore also apply a byte budget, and MAY finish its
sending direction after any complete entry, having answered fewer entries
than were requested; the requester re-requests the remainder from any peer.
For bulk synchronization, get-block-range — which is byte-bounded by
construction — is the appropriate primitive (see 13).
get-tx
Stream type: 0x03
Request:
| Size | Field | Description |
|---|---|---|
| varies | count | Number of transaction references (CompactSize). |
| varies | txrefs | count transaction references (see Transaction References). All three reference types are permitted. |
Response: For each requested reference, in request order:
| Size | Field | Description |
|---|---|---|
| 1 | result | 0x00: a transaction follows. 0x02: not found (nothing follows for this entry). |
| varies | tx | If result is 0x00: a CompactSize length prefix followed by the serialized transaction 11. |
Requests transactions by reference. The count MUST NOT exceed 50,000.
Each delivered transaction MUST be the transaction that was requested: it
MUST match the corresponding entry's reference, under the identifier that
reference names (txid, wtxid, or the short transaction ID relative to the
named block). A delivered transaction that does not is a connection error of
type PROTOCOL_ERROR.
As with get-blocks, the count bound does not bound the response in bytes:
50,000 references occupy a request of a few megabytes but could name
transactions totalling far more, an amplification the count limit alone does
not address. A responder SHOULD apply a byte budget to the response, and MAY
finish its sending direction after any complete entry; the requester
re-requests what it still needs. A requester SHOULD size a request to the
bytes it is prepared to receive rather than to the count limit.
SHORTID references identify a transaction within a block relayed by a
compact block; their handling is specified in
Requesting Missing Transactions.
get-addr
Stream type: 0x04
Request: Empty (the stream is finished immediately after the stream type byte).
Response:
| Size | Field | Description |
|---|---|---|
| varies | count | Number of address records (CompactSize). |
| varies | addresses | count network address records (see Network Address Record). |
Requests peer addresses from the remote node. The response count MUST NOT
exceed 1000; a node receiving a larger response SHOULD discard the response
and assign a misbehavior penalty (see
Misbehavior and Banning); this is the handling of
such responses, in place of the connection error that
Request Streams would otherwise prescribe.
Each returned record carries the address's last-known service flags in its
services field (see Service Flags and
Network Address Record). A responder SHOULD set
that field to the services it last observed the address advertise in an
init record, or the services the record was relayed to it with if it has
never connected to the address. The requester MAY use the field to select
candidate peers offering the services it needs (for example,
NODE_SYNC_ARTIFACTS or NODE_TREE_ROOTS), but relayed service flags are
an unauthenticated claim: the peer's actual services are established only by
the services field of its own init record upon connecting, and a node
MUST NOT treat an address book entry's flags as more than a selection hint.
To impede address-based fingerprinting attacks, a node SHOULD send get-addr
only on outbound connections, at most once per connection, and SHOULD only
answer get-addr requests on inbound connections (resetting its sending
direction of the stream with REFUSED otherwise).
get-mempool
Stream type: 0x05
Request: Empty (the stream is finished immediately after the stream type byte).
Response: Open-ended. The response is a sequence of records (see
Records), each a single transaction reference (see
Transaction References) of type TXID or WTXID;
each field of a reference is fixed in size for its type. SHORTID references
MUST NOT be used.
Subscribes to the contents of the peer's transaction memory pool. The
responder first sends a record for each transaction currently in its mempool
(the snapshot; no snapshot records indicate an empty mempool). It then keeps
its sending direction open, and sends further records referencing
transactions as they are accepted into its mempool, until the stream or
connection ends. The requester fetches transactions of interest with
get-tx.
This is the one stream type whose response is open-ended (see
Request Streams): the responder finishing its sending
direction ends the subscription (for example, on shutdown), and a requester
that no longer wants the subscription cancels the responder's sending
direction with CANCELLED. A node MUST NOT open more than one concurrent
get-mempool stream to the same peer; a second concurrent subscription is a
connection error of type PROTOCOL_ERROR.
Records after the snapshot SHOULD be subject to the same trickling delay as transaction announcements (see Trickling). The responder MAY omit references it has already sent to the same peer — in the snapshot, in an earlier record, or on a transaction announcement stream — and the requester MUST tolerate duplicate references.
Serving the snapshot costs the responder a full serialization of its mempool
for a request of one byte, so a node SHOULD rate-limit get-mempool
per peer — a peer that repeatedly cancels and re-opens the stream is
requesting that work again each time — and SHOULD decline the request from a
peer for which it has set relay = 0 in its init record, since such a peer
has declined transaction relay in the other direction. A node concerned about
topology inference SHOULD apply the trickling delay of
Trickling to the post-snapshot records, as above; without it the
subscription reports each transaction's arrival at the responder without
delay, which is exactly what trickling exists to obscure.
get-hashes
Stream type: 0x06
Request:
| Size | Field | Description |
|---|---|---|
| 4 | start_height | Height of the first requested block hash (uint32, little-endian). |
| 4 | count | Maximum number of block hashes requested (uint32, little-endian). |
Response:
| Size | Field | Description |
|---|---|---|
| varies | count | Number of entries (CompactSize). |
| varies | entries | count entries, each encoded as follows. |
Entry k, for k from 0 to count − 1, describes the block at height
start_height + k of the responder's best chain:
| Size | Field | Description |
|---|---|---|
| 32 | hash | Hash of the block at height start_height + k. |
| varies | size | The block's serialized size in bytes (see Serialized Blocks) (CompactSize). |
| varies | txs | Number of transactions in the block, including the coinbase transaction (CompactSize). |
| varies | txouts | Number of transparent outputs created by the block's transactions, including coinbase outputs (CompactSize). |
| varies | notes | Number of note commitments added by the block: two per JoinSplit description, plus one per Sapling Output description, plus one per Orchard-protocol Action description (in the Orchard or, from NU6.3, the Ironwood pool) (CompactSize). |
Requests the hashes of count consecutive blocks of the responder's best
chain starting at start_height, together with per-block sync-cost
metadata. A requester that needs coarser granularity (for example, the
spacing of its checkpoints; see 13) aggregates the per-block
values itself.
The size, txs, txouts, and notes fields are scheduling hints:
they let a
synchronizing node estimate, before downloading, the download volume and
the validation and note-commitment-tree work in each range of the chain,
and so divide download work across peers and interleave transfer with
computation to reach the tip fastest (see 13). Cumulative
txouts values additionally locate each block's bits within the
spentness-hint bitmap of 13. They are
deterministic functions of the block — for a given hash, every honest
responder returns identical values. A node MUST NOT rely on them for any
consensus or validity purpose.
The requested count MUST NOT exceed 50,000, and the greatest requested
height (start_height + count − 1) MUST NOT exceed 0xFFFFFFFF.
The response count MUST NOT exceed the requested count, MAY be less, and
MAY be 0: a node omits requested heights above its chain tip, and SHOULD NOT
include entries for blocks within 100 blocks of its chain tip (which could
still be affected by a chain reorganization; the same margin as the
reorganization rule of
Checkpointed Synchronization). Truncation
MUST be a prefix:
if fewer entries are returned than were requested, the returned entries MUST
correspond to the lowest requested heights, so that the requester can
re-request the missing tail.
Responses are not self-authenticating: a node MUST NOT rely on the returned
hashes for any security-relevant purpose until they are verified, per the
untrusted-inputs rule of
Checkpointed Synchronization. The
metadata, by contrast, is
checkable after the fact: once a node has downloaded an entry's block, it
SHOULD verify any hints it relied upon, and SHOULD assign a
misbehavior penalty (see Misbehavior and Banning)
if txs or notes do not match the downloaded block. Those two
fields are determined by the block hash alone, so a mismatch is not
attributable to a different chain view.
size is not. A block's serialized size depends on its authorizing
data — notably scriptSig lengths — which the txid, and hence the block
hash, does not commit to (see get-block-range and
ZIP 244 28). A node that obtained the hints from one peer and the
blocks from another therefore MUST NOT penalize a size mismatch
unless it has verified the delivered blocks' authorizing data commitment;
otherwise a peer that pads the blocks it serves would cause the penalty to
fall on the honest peer that served the hints, which is an eviction
primitive rather than a defense. A size mismatch on unverified
blocks is instead grounds to re-fetch the blocks.
get-block-range
Stream type: 0x07
Request:
| Size | Field | Description |
|---|---|---|
| 32 | final_hash | Hash of the highest block in the requested range. |
| 4 | count | Maximum number of blocks requested (uint32, little-endian). MUST NOT exceed 65,536. |
| 4 | max_bytes | Maximum total serialized size of the delivered blocks, in bytes (uint32, little-endian). MUST NOT exceed 67,108,864 (64 MiB). |
Response:
| Size | Field | Description |
|---|---|---|
| 1 | result | 0x00: blocks follow. 0x02: not found (the responder does not have the block with hash final_hash; nothing follows). |
| varies | blocks | A sequence of serialized blocks, each encoded as a CompactSize length prefix followed by the serialized block (see Serialized Blocks), in descending height order: the first block is the block whose hash is final_hash, and each subsequent block is the parent of the previous one. |
get-block-range is the bulk block download primitive: where get-blocks
requests individual blocks by hash, get-block-range streams a contiguous
chain of blocks identified by a single anchor — typically a hash the
requester has verified against a trusted commitment, a validated header, or
an already-verified block (see 13).
The responder streams blocks until it has delivered count blocks, or
until delivering the next block would bring the total serialized size of
the delivered blocks above max_bytes, whichever comes first — except
that it MUST be willing to deliver the first block regardless of
max_bytes, so that a maximum-size block is always retrievable. A
response that exceeds either bound — more than count blocks, or any
block after the first whose delivery brings the total serialized size
above max_bytes — is a connection error of type FLOOD; both bounds
are exact, so an honest responder never exceeds them. Because
block sizes vary by three orders of magnitude across the chain's history,
max_bytes — not count — is what bounds the work a stream commits one
peer to; scheduling recommendations are given in 13.
Delivery is in descending height order so that every block is verifiable
on arrival: the first delivered block's header MUST hash to
final_hash, each subsequent delivered block's header MUST hash to the
hashPrevBlock of the previously delivered block, and each delivered
block's transactions MUST match its header's transaction merkle root, and
each delivered block's serialized size MUST NOT exceed the maximum block
size permitted by the consensus rules. A delivered block that violates
these rules is a connection error of type PROTOCOL_ERROR — the rules are
checkable by hashing alone, so a violation is never attributable to a
different chain view. A requester with a trusted anchor therefore needs no
download handles for the range's interior blocks and can assign blame
exactly.
These checks bind each delivered block to the committed chain, but they do
not bind all of its bytes. The transaction merkle root commits to txids,
and from NU5 a v5 transaction's txid excludes its authorizing data —
scriptSigs, proofs, and signatures — which is committed separately by
hashAuthDataRoot within the header's hashBlockCommitments (see ZIP 244
28). Recovering the authorizing data commitment from the header
requires the chain history root at the parent block, which arrival-time
checking does not have, so this check is necessarily deferred to
validation time. A responder can consequently deliver blocks that satisfy
every rule above while carrying arbitrary authorizing data, padded up to
the element size limit.
A requester therefore MUST NOT store a delivered block, serve it to
another peer, or use it to regenerate a synchronization artifact until its
authorizing data commitment has been checked against its header. A block
whose authorizing data commitment fails that check SHOULD incur a
misbehavior penalty of 100 points against the peer that delivered it: the
delivered block matches the committed chain by txid, so the discrepancy is
attributable to that peer and not to a different chain view. A requester
SHOULD also bound the bytes it accepts on a stream by max_bytes,
independently of the block count, since a block may be padded well beyond
the size its hints predicted.
The responder serves the ancestor chain of the block with hash
final_hash, whether or not that block is in its best chain. It MAY also
finish its sending direction early, before either request bound is reached
(for example, to bound the resources committed to one stream). Truncation
for any reason is resumable, since the requester knows the next expected
hash (the hashPrevBlock of the last delivered block) and MAY re-request
the remainder — from the same or a different peer — using it as
final_hash; a stream truncated by max_bytes thereby self-chunks a long
span into byte-bounded, individually verified units even when the
requester has no per-block size information. A requester that no longer
wants the remainder cancels the responder's sending direction with
CANCELLED; delivered blocks remain verified and usable. Transport flow
control bounds the buffering on both sides.
get-tree-roots
Stream type: 0x08
Request:
| Size | Field | Description |
|---|---|---|
| 4 | start_height | Height of the first requested entry (uint32, little-endian). |
| 32 | final_hash | Hash of the block at the highest requested height, start_height + count − 1 (see below). |
| 4 | count | Maximum number of entries requested (uint32, little-endian). MUST NOT exceed 4,000. |
Response:
| Size | Field | Description |
|---|---|---|
| varies | count | Number of entries (CompactSize). |
| varies | entries | count entries, each encoded as follows. |
Entry k, for k from 0 to count − 1, describes the block at height
start_height + k of the chain identified by final_hash — the block
with hash final_hash and its ancestors:
| Size | Field | Description |
|---|---|---|
| 32 | sapling_root | Root of the Sapling note commitment tree after this block. |
| 32 | orchard_root | Root of the Orchard note commitment tree after this block. |
| 32 | ironwood_root | Root of the Ironwood note commitment tree after this block. |
| varies | sapling_txs | Number of transactions in this block with Sapling components, as counted by the block's chain history tree leaf (CompactSize; see ZIP 221 29). |
| varies | orchard_txs | Number of transactions in this block with Orchard-pool components (CompactSize). |
| varies | ironwood_txs | Number of transactions in this block with Ironwood-pool components (CompactSize). |
| 32 | auth_data_root | The block's authorizing data commitment hashAuthDataRoot (see ZIP 244 28). |
For a pool that is not active at the entry's height, the corresponding root field is 32 zero bytes and the corresponding count is 0.
Requests the per-block data from which a synchronizing node can reproduce each block's note commitment tree roots and chain history tree without recomputing the trees from the blocks' note commitments — the dominant CPU cost of validating historical blocks under checkpoint-based synchronization.
The final_hash field anchors the request to a specific chain: the
requester sets it to the hash — taken from headers it has already
authenticated (see 13) — of the block at the highest requested
height, and the requested entries describe that block and its ancestors,
which final_hash identifies uniquely. A responder whose best chain does
not contain the block with hash final_hash at height
start_height + count − 1 MUST refuse the stream with REFUSED rather
than serve entries for different blocks. An honest peer with a divergent
chain view therefore refuses rather than serving mismatching entries, so
an entry that fails verification is never attributable to a different
chain view (cf. the tolerance rule of
Checkpointed Synchronization, which this
anchoring makes unnecessary here).
The entries are not self-authenticating: a node MUST NOT
rely on any part of an entry for any purpose until it has verified that part
against the chain's header commitments, as specified in 13.
Which commitment applies depends on the height, and changes at Heartwood
rather than at NU5: between Sapling and Heartwood activation the header
field hashFinalSaplingRoot is the block's Sapling root and is compared
directly; from Heartwood activation ZIP 221 29 repurposes that
field to carry the chain history root (hashChainHistoryRoot, and from NU5
hashBlockCommitments, which binds it together with the authorizing data
commitment of ZIP 244 28), and entries are verified by
reconstructing the chain history tree.
Not every field of an entry is authenticated at every height. The chain
history tree leaf defines no Ironwood field, so no header commits to an
ironwood_root; and reconstruction verifies an entry only once the
following block's header has been checked against it, so the highest
entry of a response is not yet verified when the response completes. A node
MUST NOT rely on an unverified root, and recomputes the corresponding tree
instead.
An entry that fails verification against an authenticated header SHOULD
incur a misbehavior penalty (see
Misbehavior and Banning). A node MUST NOT assign
that penalty on the basis of a check that does not apply at the entry's
height: in particular, comparing a supplied Sapling root against
hashFinalSaplingRoot above Heartwood activation compares it against an
unrelated commitment and would penalize every honest responder.
Serving get-tree-roots requires a per-block root index that not every
node maintains (in particular, a node that itself synchronized without
computing historical trees may not have one). A node that maintains the
index advertises the NODE_TREE_ROOTS service flag (see
Service Flags). The response MAY contain fewer entries
than requested, and MAY be empty; truncation MUST be a prefix (the returned
entries MUST correspond to the lowest requested heights). A node without
the index refuses the stream with REFUSED. The response count MUST NOT
exceed the requested count.
get-object
Stream type: 0x09
Request:
| Size | Field | Description |
|---|---|---|
| 32 | hash | SHA-256 hash of the requested object. |
| 8 | offset | Byte offset into the object at which to start (uint64, little-endian). MUST NOT exceed 2^48 − 1. |
| 4 | length | Maximum number of bytes requested (uint32, little-endian). MUST NOT exceed 33,554,432 (32 MiB). |
Response:
| Size | Field | Description |
|---|---|---|
| 1 | result | 0x00: the object is available; its size and data follow. 0x02: not found (nothing follows). |
| varies | size | Total size of the object in bytes (CompactSize). |
| varies | data | The object's bytes from offset, up to length bytes or the end of the object, whichever comes first. |
Requests a byte range of a synchronization artifact: an immutable,
content-addressed object identified by the SHA-256 hash of its contents.
The protocol does not interpret artifact contents; artifacts are named by
trusted commitments and synchronization metadata (for example, known-hash
chunk files and state snapshot pieces; see 13), and a node
serves whichever artifacts it holds or can reproduce deterministically from
its chain state. A node that serves artifacts advertises the
NODE_SYNC_ARTIFACTS service flag (see Service Flags).
The responder MAY finish its sending direction early, having delivered
fewer than the requested bytes; the requester detects the shortfall from
size and MAY re-request the remainder from any peer, since object bytes
are position-addressed and identical everywhere. An object is verified by
hashing its complete contents; piece sizing conventions that keep each
piece independently fetchable and verifiable are specified in
13.
The number of bytes returned is determined as follows, and a responder MUST evaluate the two cases in this order:
- If
offsetis greater than or equal to the object'ssize, the response isresult = 0x00, the object'ssize, and no data. - Otherwise the responder returns at most
min(length, size − offset)bytes, starting atoffset.
Evaluating the bound as min(offset + length, size) is not equivalent: the
sum can wrap, yielding an end position below offset and a byte count that
underflows to an enormous value. offset is bounded above for the same
reason, and a responder MUST reject a request whose offset exceeds that
bound as a connection error of type PROTOCOL_ERROR.
A response carrying more than length bytes of data, or data extending
beyond the end of the object as given by its own size field, is a
connection error of type FLOOD.
The same discipline applies to the requester. A response's size is
peer-supplied and unauthenticated until the assembled object is hashed: a
requester MUST NOT compute a remaining length as size − received without
first checking that size is at least received, and a peer reporting a
size inconsistent with a previous response for the same hash, or below
the bytes it has already delivered, is a connection error of type
PROTOCOL_ERROR.
Object data is verified only by hashing the complete object, so
misdelivery is attributable only when a single peer delivered every byte:
a node that has fetched all of an object's bytes from one peer, and finds
that they do not hash to the requested hash, SHOULD assign that peer a
misbehavior penalty (see
Misbehavior and Banning), so that a peer answering
with well-formed garbage is deselected rather than reassigned the same work
indefinitely. A node that assembled an object from
ranges delivered by more than one peer MUST NOT assign a misbehavior
penalty when the assembled object fails verification — any of the
contributing peers could be at fault — and SHOULD instead discard the
data and re-fetch the object with each candidate peer serving the whole
object, which isolates a misbehaving peer. Disagreement between peers
about an object's size is resolved by the hash check, not penalized.
Announcement Stream Types
Block Announcements
Stream type: 0x10
Each record payload on a block announcement stream has the following format:
| Size | Field | Description |
|---|---|---|
| 1 | kind | 0x00: header announcement. 0x01: compact block announcement. |
| varies | payload | For kind = 0x00: a serialized block header 12. For kind = 0x01: a compact block (see Compact Block Encoding). |
Blocks are announced immediately; they are not subject to the trickling delay applied to transactions.
A node announces a new block to a given peer in one of two ways:
- If the peer requested high-bandwidth compact block announcements (the
announcefield of itsinitrecord was 1), by sending a compact block announcement (kind = 0x01). - Otherwise, by sending header announcements (
kind = 0x00) for the new block and for any intermediate blocks the peer has not yet been sent, up to a small implementation-defined limit. This follows the design of BIP 130 30.
A node MUST NOT send compact block announcements to a peer whose init
record had announce = 0. Header announcements MAY be sent to any peer,
regardless of its announce preference; in particular, if a block's compact
block encoding would exceed the maximum record payload length (see
Records), the sender announces that block with a header
announcement instead.
A node SHOULD NOT send a header announcement for a block it has not fully
validated, except as the oversize substitute above for a compact block
announcement to a peer that requested high-bandwidth announcements.
Correspondingly, the pre-validation penalty exemption of
Relay Protocol applies to announcements of either kind
received on a connection on which the receiving node requested
high-bandwidth announcements (its init record had announce = 1); a
header announcement received where the receiver's announce was 0 carries
no such exemption, and an announced block that fails full validation is
subject to the rules of
Misbehavior and Banning.
An announcing node SHOULD only announce a block if it expects the peer to be
able to connect the announced header to the peer's known chain (for example,
because the parent block was previously announced to or by that peer). A node
receiving an announcement whose header does not connect to its known chain
SHOULD recover by sending a get-headers request, and MUST NOT assign a
misbehavior penalty solely because a header does not connect.
Transaction Announcements
Stream type: 0x11
Each record payload on a transaction announcement stream is a single
transaction reference (see Transaction References)
of type TXID or WTXID. SHORTID references MUST NOT be used in
announcements.
A node MUST NOT open a transaction announcement stream to a peer whose init
record had relay = 0.
Announcements are subject to the trickling delay of Trickling.
Interested peers fetch announced transactions with get-tx requests.
Address Announcements
Stream type: 0x12
Each record payload on an address announcement stream is a single network address record (see Network Address Record).
Address announcements are subject to the rate limits and address book management practices of Address Relay.
Block Relay
Divided Block Relay
This protocol divides block headers and block transactions, rather than
transferring monolithic block messages as the legacy protocol did:
- Block headers are gossiped directly on block announcement streams, as in BIP 130 30.
- Block transactions are relayed separately from headers using compact block
relay (adapted from BIP 152 31): a block is transferred as its
header plus identifiers of its transactions, and only the transactions the
receiver is missing are requested and transferred, using
get-txrequests.
Both mechanisms are unconditional; the per-connection preferences are carried
in the announce and full_ids fields of the init record (see
Init Record).
Compact Block Relay
Compact block relay is adapted from BIP 152 31. This section specifies the protocol flows and the Zcash-specific differences from BIP 152; BIP 152 provides the design rationale.
The differences from BIP 152 are:
- There is no
sendcmpctmessage. Compact block relay is unconditional, and the high-bandwidth announcement preference is carried in theannouncefield of theinitrecord (see Init Record). (A future change to the compact block encoding would be deployed under a new protocol version.) - A compact block cannot be requested: BIP 152's
getdatawithMSG_CMPCT_BLOCKhas no equivalent, and compact blocks occur only as high-bandwidth announcements. A peer announced to in low-bandwidth mode instead requests the block's coinbase transaction and transaction IDs viaget-headerswithtx_ids = 1(see Relay Protocol). - A compact block can identify the block's transactions either by short
transaction IDs or by full transaction IDs, at the receiver's option (the
full_idsfield of theinitrecord; see Full Transaction IDs). BIP 152 supports only short transaction IDs. - There are no
getblocktxnandblocktxnmessages. Missing transactions are requested withget-txrequests (usingSHORTID,TXID, orWTXIDreferences) and delivered in the corresponding responses; see Requesting Missing Transactions. - The
headerfield of a compact block uses the Zcash block header encoding 12, including the Equihash solution, rather than the 80-byte Bitcoin header. - The input to the short transaction ID computation is based on the wtxid as defined in ZIP 239 23 for version 5 transactions, rather than on either the txid (BIP 152 version 1) or the BIP 141 wtxid (BIP 152 version 2); see Short Transaction IDs.
Compact Block Encoding
A compact block relays a block as its header plus a compact representation of its transactions:
| Size | Field | Description |
|---|---|---|
| varies | header | The block header, encoded as a CompactSize length prefix followed by the serialized header 12 (including the Equihash solution). |
| 8 | nonce | Nonce for short transaction ID computation (uint64, little-endian). If ids_kind is 1, this field SHOULD be 0 and MUST be ignored. |
| 1 | ids_kind | 0 if ids contains short transaction IDs; 1 if it contains full transaction IDs (uint8). |
| varies | ids_count | Number of transaction IDs (CompactSize). |
| varies | ids | If ids_kind is 0: short transaction IDs, 6 bytes each, little-endian (see Short Transaction IDs). If ids_kind is 1: full transaction IDs, 64 bytes each (see Full Transaction IDs). |
| varies | prefilled_count | Number of prefilled transactions (CompactSize). |
| varies | prefilled_txns | Prefilled transactions (see below). |
Each transaction in the block, in block order, is represented either by a
transaction ID in ids (short or full, according to ids_kind) or by a full
serialized transaction in prefilled_txns; the total number of transactions
in the block is ids_count + prefilled_count. Each of ids_count and
prefilled_count MUST NOT exceed 65,536, and their sum MUST NOT exceed
65,536; a compact block violating any of these is a connection error of type
FLOOD. Bounding the two counts individually is required as well as
bounding the sum: each is an independent CompactSize that may be as large as
2^64 − 1 on the wire, and a sum of two such values can wrap to a small
number that satisfies a bound the addends do not.
A node MUST set ids_kind to 1 in compact blocks sent to a peer that set
full_ids = 1 in its init record, and SHOULD set ids_kind to 0 otherwise.
A compact block with an ids_kind value other than 0 or 1 is a
nonconforming record (see Announcement Streams).
Each entry of prefilled_txns has the following format:
| Size | Field | Description |
|---|---|---|
| varies | index | Differentially encoded index of this transaction within the block (CompactSize; see below). |
| varies | tx | A full serialized transaction, encoded as a CompactSize length prefix followed by the serialized transaction 11. |
Prefilled transaction indexes are differentially encoded as in BIP 152
31. A compact block in which any absolute index is
greater than or equal to ids_count + prefilled_count, or in which indexes
overflow or are not strictly increasing, is a nonconforming record (see
Announcement Streams). The bound is the block's own
transaction count, not the protocol maximum: an index below 65,536 can
still lie outside a small block, and a reconstructor sized to the block's
actual transaction count would then write out of bounds.
The coinbase transaction MUST be prefilled. A sender SHOULD additionally prefill any transaction that it predicts the receiver does not have.
Short Transaction IDs
The short transaction ID of a transaction, relative to a given compact block, is computed as specified in BIP 152 31, with two substitutions:
- The input to SipHash-2-4 is the transaction identifier used for relay of
the transaction: for a transaction with version ≥ 5, its 64-byte wtxid as
defined in ZIP 239 23 (the txid followed by the
auth_digest); for a transaction with version ≤ 4, its 32-byte txid. - The header hashed together with the
noncefield to derive the SipHash keys is the serialized Zcash block header 12 (as it appears in theheaderfield, without the length prefix).
The nonce SHOULD be chosen uniformly at random by the sender of a compact
block, so that short ID collisions between blocks and senders are independent.
Full Transaction IDs
The full transaction ID of a transaction is the 64-byte value consisting of
the transaction's txid followed by its auth_digest. For a transaction with
version ≥ 5, this is the wtxid used for relay, as defined in ZIP 239
23. For a transaction with version ≤ 4, the auth_digest is the
placeholder value consisting of 32 bytes of 0xFF defined in ZIP 244
28.
Matching against full transaction IDs is exact — they are not subject to
collisions and involve no nonce. A node unable to do short-ID matching (for
example, a pruned node) requests full transaction IDs by setting
full_ids = 1 in its init record, at the cost of larger compact blocks.
Requesting Missing Transactions
Transactions that could not be matched while reconstructing a block from a
compact block are requested with get-tx requests, rather than with dedicated
messages as in BIP 152 (getblocktxn and blocktxn, which identify
transactions by index within the block):
- If the compact block had
ids_kind = 0, each unmatched short transaction ID is requested as aSHORTIDtransaction reference containing the block hash and the short transaction ID copied from the compact block. Because short transaction IDs depend on thenonceof the compact block they appeared in, the responding node interprets them using thenonceof the compact block it most recently sent to the requesting peer for the identified block. A node receiving aSHORTIDreference for a block for which it has not recently sent that peer a compact block, or whose short transaction ID matches no transaction — or more than one transaction — in the identified block, SHOULD answer that reference with a not-found result. - If the compact block had
ids_kind = 1— and likewise for each unmatched full transaction ID from aget-headersresponse withtx_ids = 1(seeget-headers) — each unmatched full transaction ID is requested as an ordinary transaction reference: aTXIDreference containing the txid if theauth_digesthalf of the full transaction ID is the 32-byte0xFFplaceholder (indicating a pre-v5 transaction), or aWTXIDreference containing the full transaction ID otherwise.
A node that has sent a peer a compact block, or a get-headers entry carrying
transaction IDs, MUST be able, for a reasonable implementation-defined time
thereafter, to serve that block's transactions to the same peer via get-tx —
including by SHORTID references in the compact block case, and irrespective
of whether those transactions remain in its mempool.
Relay Protocol
A node follows the BIP 152 31 protocol flows:
- High-bandwidth mode (the receiver's
initrecord hadannounce = 1): the sender announces a new block by sending a compact block announcement directly. To minimize propagation latency, the sender MAY do so as soon as it has validated the block header (checking proof of work and difficulty), before fully validating the block. Because a high-bandwidth announcement may legitimately precede full validation by the announcing peer, a node MUST NOT assign a misbehavior penalty for a high-bandwidth announcement — a compact block announcement, or a header announcement substituted for one (see Block Announcements) — of a block that fails full validation, provided the block's header is valid (including its proof of work); the same exemption covers the block's transactions served under Requesting Missing Transactions. A node SHOULD request high-bandwidth announcements from at most 3 peers, preferring the peers that most recently announced blocks to it first; changing the selection requires a new connection (see Init Record). - Low-bandwidth mode (the receiver's
initrecord hadannounce = 0): blocks are announced to the receiver with header announcements (see Block Announcements). The receiver MAY then request the block's coinbase transaction and transaction IDs — aget-headersrequest withtx_ids = 1whosehash_stopis the announced block hash — and reconstruct the block, fetching the transactions it is missing withget-tx; or it MAY request the full block viaget-blocks. The receiver SHOULD use the transaction-ID form only for blocks close to its chain tip (BIP 152 31 recommends within 5 blocks), and request full blocks otherwise.
Upon receiving a compact block — or the equivalent header, coinbase
transaction, and transaction IDs from a get-headers response with
tx_ids = 1 — a node attempts to reconstruct the block by matching each
transaction ID against the transactions it already holds (in its mempool or
otherwise) — computing short transaction IDs for the held transactions if the
IDs are short (ids_kind = 0), or comparing full transaction IDs directly
otherwise:
- If all transactions are available, the node reconstructs and validates the block as if it had been received as a full block.
- If one or more transactions are missing, or (for short transaction IDs) if
two or more held transactions collide on the same short ID, the node
requests the unresolved transactions with a
get-txrequest (see Requesting Missing Transactions), and completes reconstruction as the response arrives. - If any requested transaction is answered with a not-found result, or if
reconstruction fails after the responses arrive (for example, because a
short ID collision caused a wrong match and the reconstructed block is
invalid), the node SHOULD fall back to requesting the full block via
get-blocks, and MUST NOT assign a misbehavior penalty solely because reconstruction failed.
An incorrectly reconstructed block fails validation of the block's merkle root and authorizing data commitment, so short ID matching does not weaken consensus enforcement.
Compact Block Relay Example (non-normative)
A new block reaches peer B (high-bandwidth mode) and peer C (low-bandwidth mode) from peer A:
A ── block announcements ────▶ B : compact block (header, short
IDs, prefilled coinbase)
B matches the short IDs against transactions it
holds; two are unknown
B ── get-tx ─────────────────▶ A : [SHORTID, SHORTID]
A ── response ───────────────▶ B : the two transactions
B reconstructs the block and validates it
A ── block announcements ────▶ C : header announcement
C ── get-headers ────────────▶ A : locator, hash_stop = block
hash, tx_ids = 1
A ── response ───────────────▶ C : header, coinbase, full
transaction IDs
C matches the IDs, fetches missing transactions
via get-tx, reconstructs, and validates
Synchronization
Headers-First Synchronization
Nodes synchronize the block chain using a headers-first approach; it is the baseline synchronization method of this protocol, which every node supports. It is the full-validation method: it assumes no trusted data beyond the consensus rules and the genesis block. (An alternative method for nodes with trusted checkpoint data is specified in Checkpointed Synchronization; recommended concrete synchronization strategies are specified in 13.)
- The synchronizing node sends a
get-headersrequest with a block locator (typically withtx_ids = 0; seeget-headers). - The remote peer responds with up to 160 headers.
- The synchronizing node validates the headers and requests full blocks via
get-blocksrequests. - Steps 1–3 repeat until the node is synchronized with the network.
The following rules apply to headers-first synchronization:
- A node MUST validate each received header — including its proof of work and its difficulty adjustment — before requesting the corresponding block and before extending its header chain with it.
- Received headers, even with valid proof of work, establish only that work was expended: a node MUST NOT treat a block as part of its best chain until the block itself has been validated under the consensus rules. Until then, headers serve to select and schedule block downloads.
- A node MUST NOT assign a misbehavior penalty solely because a peer's headers do not connect to its known chain or reflect a different best chain (see Block Announcements); penalties apply to provably invalid headers and blocks, per Misbehavior and Banning.
Checkpointed Synchronization
A node MAY synchronize spans of the historical chain against trusted
commitments: checkpoint data — bindings of block heights to block hashes —
obtained through a channel the node already trusts, such as its own binary
or local configuration. The bulk primitives of this protocol —
get-hashes, get-block-range (whose descending delivery authenticates
every block against a committed anchor on arrival), get-tree-roots, and
get-object — exist to serve such procedures; a recommended concrete
strategy is specified in 13. Any checkpoint-based
synchronization procedure MUST obey the following rules.
- Validated advancement. A node MUST NOT advance its validated chain except through blocks that it has either validated under the consensus rules, or authenticated by an unbroken hash chain terminating in a checkpoint bound by a trusted commitment. The extent to which consensus checks may be abbreviated for checkpoint-authenticated blocks is a matter of local policy.
- Untrusted inputs. Peer-supplied data — hashes, headers, blocks — MUST NOT be relied upon for any security-relevant purpose until verified under the previous rule; until then it MAY be used only to select and schedule downloads. Data received from peers MUST NOT be incorporated into a node's trusted commitments.
- Tolerance of divergent views. A node MUST NOT assign a misbehavior
penalty to a peer solely because the peer's
get-hashesresponses fail verification against the node's commitments, or reflect a different best chain; such responses are discarded and MAY be retried with other peers. - Reorganization margin. Commitment-based authentication MUST NOT be
applied within 100 blocks of the node's view of the network chain tip; the
chain near the tip is synchronized headers-first. The responder-side
margin of
get-hashesis the same depth, measured the same way, so that a responder's hints and a requester's authentication agree about which blocks are near enough to the tip to be unsettled. - Headers-first fallback. A node MUST be capable of completing
synchronization using headers-first synchronization alone, and MUST fall
back to it where its commitments end or where too few peers serve
get-hashes.
The commitment scheme itself — checkpoint spacing, any chunking, and the hash or signature scheme binding it — is local to the node and out of scope for this protocol.
Block Download Parameters
For headers-first synchronization and near-tip block fetching via
get-blocks, the block download parameters of ZIP 204
32 — the download window, the per-peer in-transit
limit, and the stalling timeout — apply unchanged. On a stall, the node MAY
re-request the block from an alternative peer, cancelling the original
request stream with CANCELLED.
Bulk transfer via get-block-range and get-object is not governed by
those parameters: in-flight volume is bounded in bytes by transport flow
control and by the synchronizing node's scheduling policy, for which
13 gives recommendations (work unit sizing, per-peer budgets,
and stall detection).
Transaction Relay
Announcement-Based Relay
Transaction relay follows an announcement-based protocol:
- A node with a new transaction sends a transaction reference on its transaction announcement stream to each peer (subject to trickling).
- Peers that want the transaction request it with a
get-txrequest. - The originating node serves the transaction in the
get-txresponse.
Announcements use TXID and WTXID references according to transaction
version, as specified in
Transaction References.
Trickling
To impede network topology inference, transaction announcements SHOULD NOT be sent immediately but SHOULD instead be "trickled" at random intervals. The specific parameters are implementation-defined; for the reference values used by legacy implementations, see ZIP 204 33.
Transaction Expiry
The avoidance of relaying transactions that are about to expire
(TX_EXPIRING_SOON_THRESHOLD) is unchanged from the legacy protocol; see
ZIP 204 33.
Mempool Policy
Orphan transaction handling and the minimum relay fee are local mempool policy, unchanged from the legacy protocol; see ZIP 204 33.
Address Relay
Address relay allows nodes to discover peers by propagating network address
records through the network, via address announcement streams and get-addr
requests.
Rate Limiting
Address records SHOULD be subject to rate limiting to prevent address flooding. The specific mechanism is implementation-defined; for the token-bucket reference values used by zcashd, see ZIP 204 34.
A node MAY additionally apply backpressure on the peer's address announcement stream to bound the rate at which it receives address records.
Address Book Management
Relayed addresses are unauthenticated, attacker-suppliable data; an address book that one source can fill leads to eclipse 22. A node SHOULD segment its address book by IP range, in the manner of the Bitcoin Core address manager design:
- Define an address's group as an IP-range prefix of its address — for reference, Bitcoin Core and zcashd use the /16 prefix for IPv4 and the /32 prefix for IPv6 — and, for a relayed address, its source group as the group of the peer that relayed it.
- Partition the address book into a bounded number of bounded-size buckets, and assign each address to a bucket determined by its group and its source group (keyed with a node-local secret), so that addresses from any one source group can occupy only a bounded fraction of the buckets. When a bucket is full, evict within the bucket; do not let insertions grow the book or displace addresses in other buckets.
- Keep addresses learned from independently trusted mechanisms (DNS seeding, local configuration) distinguishable from relayed addresses, and do not let relayed addresses evict them.
A node SHOULD also diversify its outbound connections across groups: for reference, Bitcoin Core and zcashd make at most one outbound connection per group. Outbound peer selection SHOULD NOT be biased toward the addresses most recently received, since those are the easiest for an attacker to have planted.
Addresses of overlay networks (TORV3, I2P) have no IP-range structure and
are free to generate, so bucketing cannot bound an attacker's share of them.
A node SHOULD treat each overlay network as a separate bounded segment of its
address book, and a node supporting both IP-based and overlay transports
SHOULD retain a minimum number of outbound connections on IP-based
transports.
Address Broadcasting
The specific broadcast intervals are implementation-defined; for the reference values used by zcashd, see ZIP 204 34.
How a node determines its own externally reachable addresses is out of scope
for this ZIP; the legacy addr_recv-based self-discovery is removed (see
Init Record).
Misbehavior and Banning
Peer misbehavior is handled by two mechanisms, split by severity. Unambiguous
violations of "MUST"-level requirements — malformed records, invalid stream
usage, exceeded hard limits — are connection errors: the node closes the
connection immediately with the indicated error code (PROTOCOL_ERROR or
FLOOD unless otherwise specified), with no score kept. Violations for which
immediate disconnection would be disproportionate, or which an
honest-but-buggy peer or a peer on a different chain could commit, instead
accumulate a per-peer misbehavior score that leads to disconnection and
banning only when it crosses a threshold.
Every misbehavior penalty — including any implementation-defined penalty assigned under the final row of the table below — is constrained by the following principle: a node MUST NOT assign a misbehavior penalty unless the violation is provable from the received data together with data the node has independently authenticated, does not depend on the node's own chain view, mempool contents, clock, or local policy, and could not be committed by a conformant peer acting on honest but divergent state. Behavior that fails this test — however suspicious — is instead handled by discarding the data, refusing or cancelling streams, rate limits and backpressure, or disconnection without a ban. Penalties assigned on weaker evidence are worse than none: they let an attacker who can arrange the appearance of misbehavior induce honest nodes to ban one another, narrowing each victim's peer set toward the attacker's own nodes.
The misbehavior score works as follows:
- A node SHOULD maintain a misbehavior score for each peer. Scores SHOULD be keyed by network address and persist across connections for a period on the order of the ban duration: several penalties in this protocol are detected only after a delay (sync-cost metadata is checked once the corresponding block has been downloaded; tree-root entries verify one block behind), so a purely per-connection score would let a peer shed its score — or escape a deferred penalty entirely — by reconnecting. A penalty detected after the offending connection has closed SHOULD be applied to the address's score.
- When the node detects one of the violations listed below, it adds the listed number of points to the peer's score:
| Points | Violation |
|---|---|
| 20 | get-headers response with more than 160 headers. |
| 20 | Non-contiguous headers in a get-headers response. |
| 20 | get-addr response with more than 1000 address records. |
| 20 | get-hashes metadata (txs or notes) that does not match the downloaded block (see get-hashes). |
| 100 | A get-tree-roots entry that fails verification against authenticated header commitments (see get-tree-roots). |
| 100 | A block delivered by get-block-range whose authorizing data commitment fails verification against its header (see get-block-range). |
| 100 | A get-object object, delivered entirely by the peer, that fails verification against the requested content hash (see get-object). |
| 100 | Announcing a v5 transaction with a TXID reference, or a v4-or-earlier transaction with a WTXID reference, in an announcement stream record or a get-mempool response record (see Transaction References and ZIP 239 23). Wrong-typed references in get-tx requests are exempt. |
| varies | Transaction, block, or header validation failure that is provable under the principle above: an invalid signature or proof, malformed structure, invalid proof of work, or a commitment mismatch, established with full context. The penalty is determined by the severity of the validation error, subject to the exemptions below. |
Validation-failure penalties apply only where the invalidity proves that this peer misbehaved. In particular, a node MUST NOT assign a misbehavior penalty for:
- Expiry. A transaction that is expired, or expires too soon, relative to the receiver's chain tip: relay and trickling delays make expiry races possible between honest peers (see Transaction Expiry).
- Local policy. Rejection under local mempool policy — fee requirements, orphan handling, conflicts with mempool contents, or resource limits (see Mempool Policy).
- Missing or moved state. A transaction whose inputs are unknown to the receiver or already spent in its current view, or any object the receiver lacks the context to validate.
- Content of requested objects. Consensus invalidity of a block,
transaction, or artifact that the node itself requested by hash or
reference (
get-blocks,get-block-range,get-tx,get-object): the responder served exactly the bytes the identifier names, and blame for the object's existence lies with the peer that announced it, under the rules above. (Delivered data that does not match the requested identifier is covered by each stream type's own rules.) - Block reconstruction service. Transactions served in fulfilment of the serving obligation of Requesting Missing Transactions (see the exemption in Relay Protocol).
Score thresholds and banning work as follows:
- When a peer's score reaches or exceeds the node's ban threshold, the node
SHOULD close the connection with the
MISBEHAVIORerror code and ban the peer's network address. - While an address is banned, the node SHOULD NOT initiate connections to it,
SHOULD refuse inbound connections from it, and SHOULD NOT include it in
get-addrresponses or address announcements. - The ban threshold and ban duration are implementation-defined; for the reference values used by legacy implementations, see ZIP 204 35.
- Whitelisted peers accumulate misbehavior scores but are exempt from banning and from the threshold-triggered disconnection.
Bans are keyed by network address, and are only as strong as the cost of acquiring a new address. Banning is a meaningful deterrent for IP-based transports; for overlay networks whose addresses are free to generate (such as Tor onion services), it excludes only the banned address, and inbound connection limits — not ban lists — bound the node's exposure (see Address Book Management).
Address granularity cuts both ways even on IP transports. A single IPv6 operator typically controls at least a /64, so per-address IPv6 bans are nearly free to evade; a node MAY key IPv6 bans and scores by prefix (a /64 is a reasonable default). Conversely, a single IPv4 address behind carrier-grade NAT may be shared by many independent users, so a ban can exclude bystanders — a reason to keep ban durations bounded rather than to widen their scope.
Security and Privacy Considerations
Scope of transport encryption. Transport encryption protects the confidentiality and integrity of each connection against passive network observers. Peers are deliberately not authenticated (see Certificates), so an active attacker in a position to intercept a connection can terminate the encryption with its own certificate and read or modify that connection's traffic. The structural defense against an active attacker seeking to control a node's view of the network is redundancy: connections to multiple, independently discovered peers.
Denial of service. QUIC's address validation and amplification limits,
including Retry packets, bound the cost that spoofed-source floods can impose
(see QUIC Transport). At the application layer, resource
consumption is bounded by the record payload limit (see Records),
the stream concurrency limits (see
Transport Requirements), the per-request count
limits of each request stream type, and address rate limiting (see
Address Relay); exceeding a hard size or rate limit is a
connection error of type FLOOD.
A count limit is not by itself a bound on work, because the per-item size
varies by orders of magnitude: get-block-range and get-object are
therefore bounded in bytes as well (max_bytes and length), and for
get-blocks, get-tx, and get-headers the responder applies its own byte
budget and may finish early, with the requester re-requesting the remainder.
The general shape of the defense is that a responder is never obliged to
spend unbounded bandwidth, disk reads, or memory on one cheap request, and
that every peer-supplied count or length is checked against its bound before
it is used to size an allocation (see Data Types and Encoding).
Fingerprinting and linkability. A persistent TLS key would allow a node
to be recognized across connections and network locations; a node concerned
about this uses per-connection ephemeral keys (see
Certificates). Connection migration links a node's traffic
across network paths (see QUIC Transport). The user_agent
field of the init record is a fingerprinting vector; a node MAY advertise a
generic user agent string. The get-addr policy (see get-addr)
impedes fingerprinting of a node's address book, and trickling (see
Trickling) impedes inference of the network topology and of the
origin of a transaction from announcement timing.
Traffic analysis. QUIC encrypts payloads but conceals neither packet timing and volume nor the IP addresses of the communicating peers. In particular, an adversary observing a node's traffic may still be able to identify it as the originator of a transaction. The Tor transport (see Tor Transport) defends against this adversary when it is local — an observer of the node's network, or the remote peer itself, learns neither the node's IP address nor which peers it communicates with — but not against a global adversary correlating traffic timing across the Tor network. The anticipated Nym mixnet transport (see Deployment) is directed at that stronger adversary.
Length leakage. Encryption conceals the content of application data but
not its length: neither TLS over TCP nor QUIC pads application data by
default, so encrypted packet sizes leak plaintext lengths. (QUIC defines
PADDING frames 6, but an implementation must use them explicitly.)
The Tor transport is coarser — Tor pads relay cells to a 509-byte body —
so leakage there is granular rather than byte-exact. To remove the
sharpest instance of this channel, every request format in this protocol
whose fields are position-fixed (get-hashes, get-block-range,
get-tree-roots, get-object, and the empty get-addr and get-mempool
requests) uses fixed-length integer encodings: every request of a given
type has a single wire length, smaller than a Tor cell body, so its
encrypted size reveals at most the stream type and never the field values,
and there is no way to misimplement the encoding so that it leaks lengths
but still interoperates. Requests that carry variable-length lists
(get-headers, get-blocks, get-tx) and responses necessarily reveal
their gross size; a node MAY additionally use QUIC PADDING frames to
obscure those lengths, at a bandwidth cost this specification does not
mandate.
Tor. On a Tor transport connection the initiator is anonymous — it
reveals no network address to the responder or to network observers — so a
node SHOULD prefer the Tor transport (where available) for the traffic most
sensitive to linkage, above all announcing transactions it originated. The
responder's onion address is a stable, linkable identifier, and onion
addresses are free to generate, which weakens address-based banning and
address book protections for TORV3 addresses (see
Misbehavior and Banning and
Address Book Management).
Mixnets. The two kinds of traffic most sensitive to linkage are mempool
transaction propagation — announcing a transaction links the announcing
node to it, and a get-mempool subscription reveals interest in the
mempool's contents — and address book queries, whose get-addr
request/response pattern is a fingerprinting and mapping primitive. A node
implementer SHOULD carry both over a mixnet transport once one is
available (the anticipated Nym transport; see Deployment),
and in the interim SHOULD carry them over the Tor transport where
available, per the preference above. Mixnet delay and cover traffic
address the timing correlation that onion routing alone does not.
Synchronization. Headers-first synchronization trusts proof of work: forging a history requires outspending the honest chain's accumulated work over the forged span, which an eclipsing attacker with sufficient hash power can in principle do. Checkpointed synchronization trusts the node's commitment, which shares a trust base with its binary: below its checkpoints, an attacker who cannot break the block hash function cannot cause acceptance of a false history. Under either method, an attacker controlling a node's connections can withhold data and stall synchronization; the mitigations are redundancy and re-requesting from alternative peers (see Block Download Parameters).
Eclipse attacks. An attacker that controls all of a node's connections controls its view of the block chain and mempool. The structural defenses are segmentation of the address book by IP range and diversity in outbound peer selection (see Address Book Management and 22), persisted addresses (see Peer Discovery), and rate-limited, validated address relay (see Address Relay). Overlay addresses such as onion services deserve particular caution here, since an attacker can generate them without limit (see Address Book Management).
Deployment
This ZIP replaces the legacy protocol without a compatibility bridge: nodes implementing this protocol do not interoperate with nodes implementing the legacy protocol. Deployment is coordinated through the network upgrade mechanism 3: the protocol version from which this protocol is in effect will be assigned to a network upgrade, and the epoch enforcement of ZIP 201 27 retires legacy-protocol peers at activation, as with any other network upgrade.
Because there is no compatibility bridge, an implementation deploying this protocol is expected to also implement the legacy protocol during the transition: before activation, a node participates in the network over the legacy protocol while also listening on the QUIC transport, so that DNS seeders and upgraded peers can discover and exercise QUIC endpoints ahead of activation. The QUIC transport uses UDP where the legacy transport uses TCP, so both can be served concurrently on the same port number and address. At activation, epoch enforcement retires legacy-protocol connections, and the node continues on this protocol alone.
DNS seeders are expected to probe and serve the QUIC endpoints of nodes implementing this ZIP.
The protocol semantics are specified against the abstract stream layer of Stream Layer; QUIC and Tor are the transports defined by this ZIP. Future revisions are expected to define additional transports to be used simultaneously with them; in particular, a transport carrying the stream layer over the Nym mixnet 36 is anticipated, providing network-level metadata protection (resistance to traffic analysis of which peers are communicating) stronger than onion routing provides.
Note: The transports are complementary rather than alternatives: a node is expected to use QUIC for latency-critical bulk relay and synchronization, and Tor (eventually Nym) where anonymity matters more than latency, selected per connection according to the traffic it will carry.
Formal Model
A TLA+ formal specification of the legacy protocol's connection lifecycle is available at 37; it has not yet been updated for the protocol specified here.
References
Footnotes
-
Information on BCP 14 — "RFC 2119: Key words for use in RFCs to Indicate Requirement Levels" and "RFC 8174: Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words" ↩
-
Zcash Protocol Specification, Version 2026.8.0 [NU6.3]. Section 3.12: Mainnet and Testnet ↩
-
Zcash Protocol Specification, Version 2026.8.0 [NU6.3]. Section 3.3: The Block Chain ↩
-
RFC 9000: QUIC: A UDP-Based Multiplexed and Secure Transport ↩ ↩2 ↩3 ↩4 ↩5 ↩6
-
Tor Rendezvous Specification — Version 3 Onion Services ↩ ↩2 ↩3
-
RFC 8446: The Transport Layer Security (TLS) Protocol Version 1.3 ↩ ↩2
-
Zcash Protocol Specification, Version 2026.8.0 [NU6.3]. Section 7.1: Transaction Encoding and Consensus ↩ ↩2 ↩3 ↩4 ↩5
-
Zcash Protocol Specification, Version 2026.8.0 [NU6.3]. Section 7.6: Block Header Encoding and Consensus ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9
-
Draft ZIP: Block Chain Synchronization ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9 ↩10 ↩11 ↩12 ↩13 ↩14 ↩15 ↩16 ↩17
-
RFC 7301: Transport Layer Security (TLS) Application-Layer Protocol Negotiation Extension ↩
-
RFC 7250: Using Raw Public Keys in Transport Layer Security (TLS) and Datagram Transport Layer Security (DTLS) ↩
-
ZIP 204: Zcash P2P Network Protocol — Data Types and Encoding ↩
-
Ethan Heilman, Alison Kendler, Aviv Zohar, Sharon Goldberg. Eclipse Attacks on Bitcoin's Peer-to-Peer Network. 24th USENIX Security Symposium, 2015. ↩ ↩2 ↩3
-
bit is the bit with numeric weight . ↩
-
ZIP 204: Zcash P2P Network Protocol — Assigning Protocol Versions to Network Upgrades ↩ ↩2
-
ZIP 204: Zcash P2P Network Protocol — Network Upgrade Epoch Enforcement ↩
-
ZIP 244: Transaction Identifier Non-Malleability ↩ ↩2 ↩3 ↩4 ↩5
-
ZIP 204: Zcash P2P Network Protocol — Block Download Parameters ↩
-
ZIP 204: Zcash P2P Network Protocol — Transaction Relay ↩ ↩2 ↩3
-
ZIP 204: Zcash P2P Network Protocol — Misbehavior and Banning ↩