# ZIP 204: Zcash P2P Network Protocol

Status: Draft

Owners: Daira-Emma Hopwood, Kris Nuttycombe

Snapshot: a34cfc091434275120ff017c92c3b50d3cef623a

Source date: 2026-10-16T02:56:04+01:00

[Source revision](https://github.com/zcash/zips/commit/a34cfc091434275120ff017c92c3b50d3cef623a)

[Official source](https://zips.z.cash/zip-0204) · [Source file](https://github.com/zcash/zips/blob/a34cfc091434275120ff017c92c3b50d3cef623a/zips/zip-0204.rst) · [Discussion](https://github.com/zcash/zips/issues/352)

## Document parts

[Part 1](https://zip-tools-web.harpocrates.workers.dev/zip/204) · [Part 2](https://zip-tools-web.harpocrates.workers.dev/zip/204?part=2)

Reading part 1 of 2. Follow all parts for the complete proposal.

Preamble

**ZIP**: 204

**Title**: Zcash P2P Network Protocol

**Owners**: 

- [Daira-Emma Hopwood](mailto:daira@jacaranda.org)
- [Kris Nuttycombe](mailto:kris@nutty.land)

**Status**: Draft

**Category**: Network

**Created**: 2026-02-05

**License**: MIT

**Discussions-To**: [https://github.com/zcash/zips/issues/352](https://github.com/zcash/zips/issues/352)

<a id="terminology"></a>

## Terminology

The key words "MUST", "MUST NOT", "SHOULD", "SHOULD NOT", and "RECOMMENDED" in this document are to be interpreted as described in BCP 14<a id="fnref1"></a>
[1](/zip/204?part=2#fn1) 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.<a id="fnref2"></a>
[2](/zip/204?part=2#fn2)

The term "network upgrade" is to be interpreted as described in ZIP 200.<a id="fnref3"></a>
[3](/zip/204?part=2#fn3)

The term "block chain" in this document is to be interpreted as described in section 3.3 of the Zcash Protocol Specification.<a id="fnref4"></a>
[4](/zip/204?part=2#fn4)

**peer**: 

A network node that participates in the Zcash P2P protocol by maintaining one or more TCP connections to other peers and exchanging protocol messages.

**connection**: 

A TCP session between two peers over which P2P messages are exchanged.

**inbound connection**: 

A connection that was initiated by the remote peer.

**outbound connection**: 

A connection that was initiated by the local node.

**protocol version**: 

A 32-bit integer identifying the set of protocol features supported by a node. Higher values indicate support for more recent features.

<a id="abstract"></a>

## Abstract

This ZIP specifies the Zcash peer-to-peer network protocol, including connection setup, message framing, message types, and relay behavior. It is intended to be sufficient for an implementor to build a conformant Zcash network peer.

<a id="motivation"></a>

## Motivation

There is currently no single document that specifies the full Zcash P2P network protocol. While Bitcoin protocol documentation exists, the Zcash protocol has diverged in several significant ways: network magic bytes differ, protocol version numbers follow an independent numbering scheme, inventory types include `MSG_WTX`<a id="fnref5"></a>
[5](/zip/204?part=2#fn5), and network upgrade activation drives protocol versioning and peer management <a id="fnref6"></a>
[6](/zip/204?part=2#fn6)<a id="fnref7"></a>
[7](/zip/204?part=2#fn7).

This ZIP consolidates the protocol specification into a single normative reference, enabling independent implementations and protocol analysis.

This ZIP does not specify the encoding of transactions and blocks; for those, refer to the Zcash Protocol Specification<a id="fnref8"></a>
[8](/zip/204?part=2#fn8)<a id="fnref9"></a>
[9](/zip/204?part=2#fn9).

<a id="specification"></a>

## Specification

<a id="network-parameters"></a>

### Network Parameters

<a id="magic-bytes"></a>

#### Magic Bytes

Each Zcash network is identified by a 4-byte magic value that appears at the start of every protocol message. A node MUST reject messages whose magic bytes do not match the expected network.

| Network | Magic Bytes (hex) |
| --- | --- |
| Mainnet | `24 e9 27 64` |
| Testnet | `fa 1a f9 bf` |
| Regtest | `aa e8 3f 5f` |

<a id="default-ports"></a>

#### Default Ports

| Network | Default Port |
| --- | --- |
| Mainnet | 8233 |
| Testnet | 18233 |
| Regtest | 18344 |

<a id="dns-seeds"></a>

#### DNS Seeds

The following DNS seed hostnames are used for initial peer discovery.

**Mainnet:**

- `dnsseed.z.cash`

- `dnsseed.str4d.xyz`

- `mainnet.seeder.zfnd.org`

- `mainnet.is.yolo.money`

**Testnet:**

- `dnsseed.testnet.z.cash`

- `testnet.seeder.zfnd.org`

- `testnet.is.yolo.money`

Regtest does not use DNS seeds or hardcoded seed nodes.

<a id="peer-discovery"></a>

### Peer Discovery

A node discovers peers through the following mechanisms:

1. **DNS seeding.** On startup, a node queries the DNS seed hostnames listed above for A and AAAA records. The zcashd implementation does this only if the address manager is empty or if fewer than 2 outbound connections have been established within 11 seconds; the Zebra implementation always performs DNS lookups on startup (alongside consulting a persistent address cache).

2. **Hardcoded seed nodes.** If DNS seeding fails or is insufficient, the node MAY fall back to a compiled-in list of seed node addresses. In the zcashd implementation these hardcoded addresses may be found in the chain parameters for the relevant network. DNS seeding is preferred over hardcoded seed nodes.

3. **Address relay.** Connected peers exchange address information using `addr` and `getaddr` messages (see [addr](#addr) and [getaddr](#getaddr)). This mechanism operates independently of the above.

<a id="connection-handling"></a>

### Connection Handling

<a id="transport"></a>

#### Transport

All connections use unencrypted TCP. The protocol does not provide transport-layer encryption or authentication.

<a id="connection-limits"></a>

#### Connection Limits

The specific connection limits are implementation-defined. The zcashd implementation defaults to a maximum of 125 peer connections (`DEFAULT_MAX_PEER_CONNECTIONS`), of which a maximum of 8 are outbound connections (`MAX_OUTBOUND_CONNECTIONS`). The Zebra implementation computes its limits from a configurable initial target size (default 25), applying multipliers to derive an outbound limit of 75 and an inbound limit of 125.

<a id="timeouts"></a>

#### Timeouts

A node SHOULD disconnect a peer if no message has been received from that peer within a reasonable timeout period. The zcashd implementation uses 1200 seconds (20 minutes, `TIMEOUT_INTERVAL`) for this purpose; the Zebra implementation uses a 20-second request timeout (`REQUEST_TIMEOUT`). Automatic `ping` messages (see [ping](#ping)) are sent periodically to keep connections alive and measure latency.

Additional timeout rules:

- A node SHOULD close the connection if a peer has not sent a `version` message within a reasonable time after connection establishment. The zcashd implementation uses 60 seconds; the Zebra implementation uses 3 seconds (`HANDSHAKE_TIMEOUT`).

- A node SHOULD close the connection if a peer has not responded to a `ping` with a corresponding `pong` within a reasonable time. The zcashd implementation uses 1200 seconds (20 minutes); the Zebra implementation uses 20 seconds (`REQUEST_TIMEOUT`).

The specific timeout values are implementation-defined.

<a id="message-framing"></a>

### Message Framing

Every protocol message consists of a 24-byte header followed by a variable-length payload.

<a id="message-header"></a>

#### Message Header

| Offset | Size | Field | Description |
| --- | --- | --- | --- |
| 0 | 4 | `magic` | Network magic bytes (see [Magic Bytes](#magic-bytes)). |
| 4 | 12 | `command` | ASCII command string, NUL-padded to 12 bytes. |
| 16 | 4 | `length` | Payload size in bytes (`uint32`, little-endian). |
| 20 | 4 | `checksum` | First 4 bytes of `SHA-256d(payload)`. |

The total header size is 24 bytes.

<a id="command-string-validation"></a>

#### Command String Validation

The `command` field MUST contain only printable ASCII characters (bytes in the range `0x20` to `0x7E` inclusive) up to the first NUL byte (`0x00`). After the first NUL byte, all remaining bytes MUST be `0x00`. A message that violates these constraints MUST be ignored.

The following command strings are currently used on the Zcash network:

`version`, `verack`, `ping`, `pong`, `reject`, `addr`, `addrv2`, `getaddr`, `inv`, `getdata`, `notfound`, `getblocks`, `getheaders`, `headers`, `block`, `tx`, `mempool`, `filterload`, `filteradd`, `filterclear`, `alert`.

<a id="maximum-message-size"></a>

#### Maximum Message Size

The maximum payload size is 2,097,152 bytes (2 MiB). A node MUST reject any message whose `length` field exceeds this limit.

<a id="checksum-verification"></a>

#### Checksum Verification

The `checksum` field contains the first 4 bytes of the double-SHA-256 hash of the payload:

> `checksum = SHA-256(SHA-256(payload))[0..4]`

A node MUST verify the checksum after receiving the full payload and MUST reject the message if the checksum does not match.

<a id="data-types-and-encoding"></a>

### Data Types and Encoding

All multi-byte integer types are encoded in little-endian byte order unless otherwise specified. The integer types used in this specification (`uint8`, `uint16`, `int32`, `uint32`, `int64`, `uint64`) have their conventional meanings.

<a id="compactsize"></a>

#### CompactSize

Note

The CompactSize encoding is used in the Zcash Protocol Specification (section 7) but is not formally defined there. It is defined here for completeness.

A variable-length unsigned integer encoding used for lengths and counts:

| Value Range | Encoding Size | Format |
| --- | --- | --- |
| 0 to 252 | 1 byte | Single byte with the value directly. |
| 253 to 0xFFFF | 3 bytes | `0xFD` followed by the value as `uint16`. |
| 0x10000 to 0xFFFFFFFF | 5 bytes | `0xFE` followed by the value as `uint32`. |
| 0x100000000 to 0xFFFFFFFFFFFFFFFF | 9 bytes | `0xFF` followed by the value as `uint64`. |

Encodings MUST be canonical: the shortest possible encoding MUST be used for any given value. A node MUST reject messages containing non-canonical CompactSize encodings.

<a id="strings"></a>

#### Strings

Character strings are encoded as a CompactSize length prefix followed by that many bytes of string data. There is no NUL terminator.

<a id="network-address-cservice"></a>

#### Network Address (`CService`)

A network address without associated metadata, as inherited from the Bitcoin protocol:

| Size | Field | Description |
| --- | --- | --- |
| 16 | `ip` | IPv6 address (or IPv4-mapped IPv6 address<a id="fnref10"></a> [10](/zip/204?part=2#fn10) for IPv4 peers). |
| 2 | `port` | TCP port number (big-endian, i.e. network byte order). |

IPv4 addresses are represented as IPv4-mapped IPv6 addresses as defined in RFC 4291 section 2.5.5.2<a id="fnref11"></a>
[11](/zip/204?part=2#fn11): the first 12 bytes are `00 00 00 00 00 00 00 00 00 00 FF FF` followed by the 4-byte IPv4 address.

<a id="peer-address-caddress"></a>

#### Peer Address (`CAddress`)

A network address with associated metadata, used in `version`, `addr`, and other messages:

| Size | Field | Description |
| --- | --- | --- |
| 8 | `services` | Bitfield of service flags (`uint64`, little-endian). |
| 16 | `ip` | IPv6 address (or IPv4-mapped IPv6). |
| 2 | `port` | TCP port number (big-endian). |

When the negotiated protocol version (see [Protocol Version Negotiation](#protocol-version-negotiation)) is ≥ 31402 (`CADDR_TIME_VERSION`) and the address appears in a context other than a `version` message, the encoding is preceded by a `uint32` timestamp field:

| Size | Field | Description |
| --- | --- | --- |
| 4 | `time` | Unix-epoch UTC time in seconds when the node was last seen (`uint32`, little-endian). |
| 8 | `services` | Bitfield of service flags (`uint64`, little-endian). |
| 16 | `ip` | IPv6 address (or IPv4-mapped IPv6). |
| 2 | `port` | TCP port number (big-endian). |

<a id="service-flags"></a>

### Service Flags

Service flags are advertised in the `services` field of `version` messages and `CAddress` structures. In the table below, bit $k$ refers to the bit with numeric weight $2^k$; that is, the `services` field has the corresponding flag set if and only if `services & (1 << k) != 0`.

| Name | Bit | Description |
| --- | --- | --- |
| `NODE_NETWORK` | 0 | The node is capable of serving the complete block chain. |
| `NODE_BLOOM` | 2 | The node supports Bloom-filtered connections (BIP 37<a id="fnref12"></a> [12](/zip/204?part=2#fn12)). Zcash nodes used to support this by default without advertising the bit, but no longer do as of protocol version 170004 (`NO_BLOOM_VERSION`). |

Bits 24–31 are reserved for temporary experiments.

<a id="inventory-vectors"></a>

### Inventory Vectors

Inventory vectors identify objects (transactions and blocks) for relay. The format depends on the type:

| Type | Code | Entry Size | Description |
| --- | --- | --- | --- |
| `MSG_TX` | 1 | 36 bytes | Transaction identified by txid. 4-byte type code + 32-byte txid. |
| `MSG_BLOCK` | 2 | 36 bytes | Block identified by block hash. 4-byte type code + 32-byte block hash. |
| `MSG_FILTERED_BLOCK` | 3 | 36 bytes | Filtered block (`getdata` only). 4-byte type code + 32-byte block hash. |
| `MSG_WTX` | 5 | 68 bytes | Transaction identified by wtxid. 4-byte type code + 32-byte txid + 32-byte authorizing data commitment (`auth_digest`). Requires negotiated protocol version ≥ 170014 (`CINV_WTX_VERSION`). See ZIP 239<a id="fnref13"></a> [13](/zip/204?part=2#fn13). |

Because `MSG_TX` and `MSG_WTX` entries have different sizes (36 and 68 bytes respectively), they may be mixed in a single `inv` or `getdata` message. The total message size is therefore not determined solely by the entry count and MUST be determined by parsing each entry individually.

A node MUST reject inventory vectors with unrecognized type codes. A node MUST reject `MSG_WTX` inventory vectors if the negotiated protocol version is less than 170014.

<a id="connection-handshake"></a>

### Connection Handshake

Peers perform a version handshake immediately upon establishing a TCP connection.

<a id="handshake-sequence"></a>

#### Handshake Sequence

1. The initiating (outbound) peer sends a `version` message.

2. The receiving (inbound) peer sends its own `version` message.

3. Each peer, upon receiving a valid `version` message, responds with a `verack` message.

4. The negotiated protocol version is `min(local_version, remote_version)`.

A peer MUST NOT send any message other than `version` before receiving the remote peer's `version` message. A peer MUST NOT send any message other than `verack` after sending its `version` and before receiving the remote peer's `verack`.

If validation of the `version` message received from a peer fails, the node MUST disconnect the peer. See (see [Version Validation](#version-validation)) below for additional details.

<a id="protocol-version-negotiation"></a>

#### Protocol Version Negotiation

Each peer advertises its own protocol version in its `version` message. The *negotiated protocol version* for the connection is defined as:

> `negotiated_version = min(local_version, remote_version)`

Before the handshake completes, messages are serialized using the initial protocol version 209 (`INIT_PROTO_VERSION`). Once both `version` and `verack` messages have been exchanged, the negotiated protocol version is used for all subsequent interpretation of protocol features and message semantics on that connection. In particular, the negotiated protocol version determines:

- Whether `CAddress` structures include a `time` field (version ≥ 31402).

- Whether `MSG_WTX` inventory vectors are permitted (version ≥ 170014).

- Whether `addrv2` messages are supported (see [addrv2](#addrv2)).

- Whether the peer meets the minimum version requirements for the current network epoch (see [Network Upgrade Epoch Enforcement](#network-upgrade-epoch-enforcement)).

Note that "the protocol version" as used in this document refers to the negotiated protocol version unless otherwise specified. A node's *advertised* protocol version is the version it includes in its `version` message.

<a id="version-message"></a>

#### Version Message

The `version` message payload has the following format:

| Offset | Size | Field | Description |
| --- | --- | --- | --- |
| 0 | 4 | `version` | Protocol version (`int32`). |
| 4 | 8 | `services` | Bitfield of service flags (`uint64`). |
| 12 | 8 | `timestamp` | Unix-epoch UTC time in seconds (`int64`). |
| 20 | 26 | `addr_recv` | Address of the receiving node (`CAddress` without `time` field). |
| 46 | 26 | `addr_from` | Address of the sending node (`CAddress` without `time` field). |
| 72 | 8 | `nonce` | Random nonce for self-connection detection (`uint64`). |
| 80 | varies | `user_agent` | User agent string (CompactSize-prefixed, max 256 bytes). |
| varies | 4 | `start_height` | Best block height known to the sender (`int32`). |
| varies | 1 | `relay` (optional) | Whether the sender wants transaction relay (`uint8`; 0 for false, nonzero for true). |

The `addr_recv` and `addr_from` fields use the `CAddress` encoding *without* the `time` field, regardless of the protocol version being negotiated.

The `nonce` field is used for self-connection detection. If a node receives a `version` message containing its own nonce, it MUST close the connection.

The `user_agent` string MUST NOT exceed 256 bytes. A node SHOULD disconnect peers that send a longer user agent.

The `relay` field is optional; if not present, its value MUST be interpreted as true. A node SHOULD reject `version` messages in which the `relay` field is present with a value other than 0 or 1. Nodes SHOULD create version messages with this field present.

<a id="version-validation"></a>

#### Version Validation

A receiving node MUST validate the `version` message as follows:

- The `version` field MUST be at least 170002 (`MIN_PEER_PROTO_VERSION`).

- On Testnet, the `version` field MUST be at least 170040 (`MIN_TESTNET_PEER_PROTO_VERSION`).

- The `version` field MUST be at least the protocol version associated with the current network epoch (see [Network Upgrade Peer Management](#network-upgrade-peer-management)).

- The `nonce` MUST NOT match the local node's nonce (self-connection detection).

- A peer MUST NOT send more than one `version` message per connection. Duplicate `version` messages incur a misbehavior penalty.

If any of these checks fail, the node MUST disconnect the peer. A `reject` message with code `REJECT_OBSOLETE` (`0x11`) SHOULD be sent before disconnecting for version-related failures.

<a id="protocol-versioning"></a>

### Protocol Versioning

Protocol versions are 32-bit integers. The following versions are significant:

| Version | Constant | Significance |
| --- | --- | --- |
| 209 | `INIT_PROTO_VERSION` | Initial protocol version. Used as the serialization version before the `version`/`verack` handshake completes. |
| 31402 | `CADDR_TIME_VERSION` | Adds the `time` field to `CAddress` in `addr` messages. Also affects encoding of the `addr_from` field in `version` messages. |
| 60000 | `BIP0031_VERSION` | `ping` messages include a `uint64` nonce and expect a `pong` response. For protocol versions ≤ 60000, `ping` has an empty payload. |
| 170002 | `MIN_PEER_PROTO_VERSION` | Minimum protocol version for Mainnet peers. |
| 170004 | `NO_BLOOM_VERSION` | Bloom filter commands (`filterload`, `filteradd`, `filterclear`) are disabled unless the node advertises `NODE_BLOOM`. |
| 170014 | `CINV_WTX_VERSION` | Adds `MSG_WTX` inventory type for v5 transaction relay. See ZIP 239 <a id="fnref14"></a> [14](/zip/204?part=2#fn14). |
| 170040 | `MIN_TESTNET_PEER_PROTO_VERSION` | Minimum protocol version for Testnet peers. |
| 170160 | `PROTOCOL_VERSION` | Current protocol version. |

<a id="network-upgrade-epoch-enforcement"></a>

#### Network Upgrade Epoch Enforcement

Each network upgrade defines a minimum protocol version. When a network upgrade activates (as defined in ZIP 200<a id="fnref15"></a>
[15](/zip/204?part=2#fn15)), a node MUST disconnect any peer whose negotiated protocol version is less than the protocol version associated with the current epoch. The node SHOULD send a `reject` message with code `REJECT_OBSOLETE` (`0x11`) before disconnecting.

The following protocol versions are associated with network upgrades on Mainnet:

| Network Upgrade | Protocol Version | Activation Height |
| --- | --- | --- |
| Sprout | 170002 | (always active) |
| Overwinter | 170005 | 347,500 |
| Sapling | 170007 | 419,200 |
| Blossom | 170009 | 653,600 |
| Heartwood | 170011 | 903,000 |
| Canopy | 170013 | 1,046,400 |
| NU5 | 170100 | 1,687,104 |
| NU6 | 170120 | 2,726,400 |
| NU6.1 | 170140 | 3,146,400 |
| NU6.2 | 170150 | 3,364,600 |
| NU6.3 | 170160 | 3,428,143 |

The following protocol versions are associated with network upgrades on Testnet:

| Network Upgrade | Protocol Version | Activation Height |
| --- | --- | --- |
| Sprout | 170002 | (always active) |
| Overwinter | 170003 | 207,500 |
| Sapling | 170007 | 280,000 |
| Blossom | 170008 | 584,000 |
| Heartwood | 170010 | 903,800 |
| Canopy | 170012 | 1,028,500 |
| NU5 | 170050 | 1,842,420 |
| NU6 | 170110 | 2,976,000 |
| NU6.1 | 170130 | 3,536,500 |
| NU6.2 | 170150 | 4,052,000 |
| NU6.3 | 170160 | 4,134,000 |

Note that Testnet protocol versions differ from Mainnet for several upgrades (Overwinter, Blossom, Heartwood, Canopy, NU5, NU6, and NU6.1), but coincide for Sapling, NU6.2, and NU6.3. The coincidences for NU6.2 and NU6.3 are a consequence of the ad hoc assignment used through NU6.3; see [Assigning Protocol Versions to Network Upgrades](#assigning-protocol-versions-to-network-upgrades) for the rule adopted to prevent such coincidences from NU7 onward.

<a id="assigning-protocol-versions-to-network-upgrades"></a>

#### Assigning Protocol Versions to Network Upgrades

When a new network upgrade is defined, it is assigned a protocol version for each network on which it will activate. In the tables above, the *Mainnet protocol version* and *Testnet protocol version* of an upgrade are the values in the Mainnet and Testnet tables respectively; Regtest uses the Testnet protocol version. These assignments MUST satisfy the following invariants:

1. **Zcash range.** Every assigned protocol version is of the form `170000 + n` with `0 ≤ n ≤ 999`. The leading `170` distinguishes Zcash protocol versions from those of Bitcoin Core, from which the `version` message format is inherited. When this range nears exhaustion, a successor scheme will need to be specified.

2. **Per-network monotonicity.** For a given network, the protocol version assigned to an upgrade MUST be strictly greater than any preceding protocol version assigned to that network. This is what allows a node to reject peers that have not upgraded (see [Network Upgrade Epoch Enforcement](#network-upgrade-epoch-enforcement)).

Through NU6.3, protocol versions were assigned by hand and do not follow a single rule; in particular the Testnet and Mainnet protocol versions of NU6.2 and NU6.3 are equal (`170150` and `170160` respectively), so those values do not by themselves identify a network. From NU7 onward, protocol versions MUST be assigned by the following procedure, which preserves the invariants above and additionally guarantees that no protocol version is ever assigned to more than one (network, upgrade) pair:

> Let `M` be the Mainnet protocol version of the immediately preceding upgrade, and let `base` be the least value of the form `170000 + 20·k` that is strictly greater than `M`. Then the Testnet protocol version of the new upgrade is `base`, and its Mainnet protocol version is `base + 10`.

Equivalently, from NU7 onward Testnet protocol versions are congruent to 0 modulo 20 and Mainnet protocol versions are congruent to 10 modulo 20, so the two never coincide.

For example, the preceding Mainnet protocol version at NU7 is NU6.3's `170160`; the least `170000 + 20·k` strictly greater than `170160` is `170180`, so NU7 is assigned Testnet protocol version `170180` and Mainnet protocol version `170190`. The upgrade following NU7 would then be assigned `170200` (Testnet) and `170210` (Mainnet).

<a id="message-types"></a>

### Message Types

This section defines each protocol message. For each message, the command string, direction, payload format, and semantics are specified.

<a id="control-messages"></a>

#### Control Messages

<a id="version"></a>

##### `version`

**Command**: 

`version`

**Direction**: 

Both (sent by each side during handshake)

**Payload**: 

See [Version Message](#version-message).

Initiates the connection handshake. The details of this message are specified in [Connection Handshake](#connection-handshake).

<a id="verack"></a>

##### `verack`

**Command**: 

`verack`

**Direction**: 

Both

**Payload**: 

Empty (0 bytes).

Acknowledges receipt of a `version` message. After exchanging `version` and `verack` messages, the connection is considered established.

<a id="ping"></a>

##### `ping`

**Command**: 

`ping`

**Direction**: 

Both

**Payload**:

> | Size | Field | Description |
> | --- | --- | --- |
> | 8 | `nonce` | Random value (`uint64`). |

Sent periodically to measure latency and detect dead connections. The zcashd implementation sends `ping` messages every 120 seconds (`PING_INTERVAL`); the Zebra implementation uses a 59-second heartbeat interval (`HEARTBEAT_INTERVAL`). The receiving node MUST respond with a `pong` message containing the same nonce.

Note

Historically (BIP 31<a id="fnref16"></a>
[16](/zip/204?part=2#fn16)), `ping` messages had an empty payload for protocol versions ≤ 60000 (`BIP0031_VERSION`). Since the minimum Zcash peer protocol version is 170002 (`MIN_PEER_PROTO_VERSION`), well above this threshold, all conformant Zcash peers use the nonce-bearing format. The empty-payload variant will never be encountered on the Zcash network.

<a id="pong"></a>

##### `pong`

**Command**: 

`pong`

**Direction**: 

Both

**Payload**:

> | Size | Field | Description |
> | --- | --- | --- |
> | 8 | `nonce` | The nonce from the corresponding `ping` message (`uint64`). |

Sent in response to a `ping`. The `nonce` MUST match the `nonce` from the `ping` that is being responded to.

<a id="reject"></a>

##### `reject`

**Command**: 

`reject`

**Direction**: 

Both

**Payload**:

> | Size | Field | Description |
> | --- | --- | --- |
> | varies | `message` | The command string of the rejected message (CompactSize-prefixed string). |
> | 1 | `ccode` | Reject code (`uint8`). See [Reject Codes](#reject-codes). |
> | varies | `reason` | Human-readable rejection reason (CompactSize-prefixed string). |
> | 0 or 32 | `data` | Optional. For `tx` and `block` rejections, the 32-byte hash of the rejected object. |

Notifies the peer of a rejected message. This message is informational; receipt of a `reject` message does not require any specific action by the receiving node.

<a id="alert"></a>

##### `alert`

**Command**: 

`alert`

**Direction**: 

Both

**Payload**:

> | Size | Field | Description |
> | --- | --- | --- |
> | varies | `payload` | Serialized alert data (CompactSize-prefixed byte vector). |
> | varies | `signature` | ECDSA signature over the payload (CompactSize-prefixed byte vector). |
> 
> 
> 
> 
> The serialized alert data contains:
> 
> 
> 
> 
> | Size | Field | Description |
> | --- | --- | --- |
> | 4 | `version` | Alert format version (`int32`). |
> | 8 | `relay_until` | Timestamp after which nodes should stop relaying (`int64`). |
> | 8 | `expiration` | Timestamp after which the alert is no longer in effect (`int64`). |
> | 4 | `id` | Unique alert identifier (`int32`). |
> | 4 | `cancel` | ID of a previous alert to cancel (`int32`). |
> | varies | `set_cancel` | Set of alert IDs to cancel (CompactSize-prefixed vector of `int32`). |
> | 4 | `min_ver` | Minimum applicable client version (`int32`). |
> | 4 | `max_ver` | Maximum applicable client version (`int32`). |
> | varies | `set_sub_ver` | Set of applicable user agent substrings (CompactSize-prefixed vector of strings). |
> | 4 | `priority` | Alert priority (`int32`). |
> | varies | `comment` | Comment for operators (CompactSize-prefixed string, max 65536 bytes). |
> | varies | `status_bar` | Status bar message (CompactSize-prefixed string, max 256 bytes). |
> | varies | `rpc_error` | RPC error message (CompactSize-prefixed string, max 256 bytes). |

The `alert` message is deprecated. It is documented here for completeness, as it remains part of the zcashd implementation but has been proposed for removal <a id="fnref17"></a>
[17](/zip/204?part=2#fn17), and implementations MAY choose not to support it. Nodes that do support `alert` messages SHOULD validate the alert signature against the network's alert public key before processing or relaying alerts.

<a id="address-messages"></a>

#### Address Messages

<a id="addr"></a>

##### `addr`

**Command**: 

`addr`

**Direction**: 

Both

**Payload**:

> | Size | Field | Description |
> | --- | --- | --- |
> | varies | `count` | Number of addresses (CompactSize). |
> | varies | `addr_list` | List of `CAddress` entries (with `time` field). |

The `count` MUST NOT exceed 1000. A node receiving an `addr` message with more than 1000 entries SHOULD assign a misbehavior penalty of 20 points to the sending peer.

<a id="getaddr"></a>

##### `getaddr`

**Command**: 

`getaddr`

**Direction**: 

Both (but typically only sent to inbound peers)

**Payload**: 

Empty (0 bytes).

Requests peer addresses from the remote node. The remote node responds by sending one or more `addr` messages. A node SHOULD only send `getaddr` once per connection, and SHOULD only process `getaddr` from inbound peers to prevent address-based fingerprinting attacks.

<a id="addrv2"></a>

##### `addrv2`

**Command**: 

`addrv2`

**Direction**: 

Both

**Payload**:

> | Size | Field | Description |
> | --- | --- | --- |
> | varies | `count` | Number of addresses (CompactSize). |
> | varies | `addr_list` | List of address entries in `addrv2` format (see below). |

Each address entry has the following format:

> | Size | Field | Description |
> | --- | --- | --- |
> | 4 | `time` | Unix-epoch UTC time in seconds when the node was last seen (`uint32`, little-endian). |
> | varies | `services` | Service bits (CompactSize-encoded `uint64`). |
> | 1 | `networkID` | Network identifier (`uint8`). |
> | varies | `sizeAddr` | Length of `addr` in bytes (CompactSize). |
> | varies | `addr` | Network address (`sizeAddr` bytes). Interpretation depends on `networkID`. |
> | 2 | `port` | Network port (`uint16`, big-endian). MUST be 0 if not relevant for the network. |

The `count` MUST NOT exceed 1000. A node MUST reject messages with more than 1000 addresses.

The `addr` field MUST NOT exceed 512 bytes. A node MUST reject messages with a longer `addr` field, irrespective of the network ID.

The `services` field is encoded as a CompactSize, unlike the fixed 8-byte `uint64` encoding used in `addr` messages. This makes the common case (few service bits) more compact.

The following network IDs are defined:

> | Network ID | Name | Address Size | Description |
> | --- | --- | --- | --- |
> | `0x01` | `IPV4` | 4 bytes | IPv4 address. |
> | `0x02` | `IPV6` | 16 bytes | IPv6 address. |
> | `0x04` | `TORV3` | 32 bytes | Tor v3 onion service address (Ed25519 public key). |
> | `0x05` | `I2P` | 32 bytes | I2P overlay network address (SHA-256 hash). |
> | `0x06` | `CJDNS` | 16 bytes | Cjdns overlay network address (IPv6 in `fc00::/8`). |

Network ID `0x03` is reserved (it was assigned to Tor v2 in BIP 155<a id="fnref18"></a>
[18](/zip/204?part=2#fn18), but Tor v2 addresses are no longer supported and MUST NOT be used).

A node MUST reject addresses whose length does not match the expected length for the given network ID. A node MUST NOT gossip addresses with unrecognized network IDs.

`IPV4` and `IPV6` addresses are encoded in network byte order (big-endian). `TORV3` addresses consist of the 32-byte Ed25519 master public key of the onion service. `I2P` addresses consist of the decoded 32-byte SHA-256 hash. `CJDNS` addresses are 16-byte IPv6 addresses in the `fc00::/8` range, encoded in network byte order.

**Deployment:** Unlike BIP 155<a id="fnref19"></a>
[19](/zip/204?part=2#fn19), which uses a `sendaddrv2` handshake message, Zcash signals `addrv2` support through protocol version negotiation, as specified in ZIP 155<a id="fnref20"></a>
[20](/zip/204?part=2#fn20). The deployment version has not yet been assigned; see ZIP 155 for the status of this assignment. A node MUST NOT send `addrv2` messages on connections where the negotiated protocol version is below the deployment threshold. A node that supports `addrv2` MAY still send `addr` messages on any connection.

The full specification of the `addrv2` message, including network address encoding details, is given in ZIP 155<a id="fnref21"></a>
[21](/zip/204?part=2#fn21).

<a id="inventory-messages"></a>

#### Inventory Messages

<a id="inv"></a>

##### `inv`

**Command**: 

`inv`

**Direction**: 

Both

**Payload**:

> | Size | Field | Description |
> | --- | --- | --- |
> | varies | `count` | Number of inventory vectors (CompactSize). |
> | varies | `inventory` | List of inventory vectors (see [Inventory Vectors](#inventory-vectors)). |

Announces one or more objects (transactions or blocks) that the sender has available. The `count` MUST NOT exceed 50,000. A node receiving an `inv` message with more than 50,000 entries SHOULD assign a misbehavior penalty of 20 points.

<a id="getdata"></a>

##### `getdata`

**Command**: 

`getdata`

**Direction**: 

Both

**Payload**:

> | Size | Field | Description |
> | --- | --- | --- |
> | varies | `count` | Number of inventory vectors (CompactSize). |
> | varies | `inventory` | List of inventory vectors (see [Inventory Vectors](#inventory-vectors)). |

Requests one or more objects from the remote peer. The `count` MUST NOT exceed 50,000. A node receiving a `getdata` message with more than 50,000 entries MUST assign a misbehavior penalty of 20 points.

If the requested object is not available, the receiving node SHOULD respond with a `notfound` message.

<a id="notfound"></a>

##### `notfound`

**Command**: 

`notfound`

**Direction**: 

Both

**Payload**:

> | Size | Field | Description |
> | --- | --- | --- |
> | varies | `count` | Number of inventory vectors (CompactSize). |
> | varies | `inventory` | List of inventory vectors identifying objects not found. |

Sent in response to a `getdata` message for objects that the node does not have.

<a id="block-messages"></a>

#### Block Messages

<a id="getblocks"></a>

##### `getblocks`

**Command**: 

`getblocks`

**Direction**: 

Both

**Payload**:

> | Size | Field | Description |
> | --- | --- | --- |
> | 4 | `version` | Protocol version (`uint32`). |
> | varies | `block_locator_count` | Number of block locator hashes (CompactSize). |
> | varies | `block_locator_hashes` | Block locator hashes (each 32 bytes), from highest to lowest height. |
> | 32 | `hash_stop` | Hash of the last desired block, or all zeros to request as many as possible. |

Requests an `inv` message containing block hashes starting after the first block locator hash found in the responder's best chain, up to and including `hash_stop` or 500 blocks, whichever comes first. A node MUST NOT return more than 500 blocks.

<a id="getheaders"></a>

##### `getheaders`

**Command**: 

`getheaders`

**Direction**: 

Both

**Payload**:

> | Size | Field | Description |
> | --- | --- | --- |
> | 4 | `version` | Protocol version (`uint32`). |
> | varies | `block_locator_count` | Number of block locator hashes (CompactSize). |
> | varies | `block_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. |

Requests a `headers` message containing 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, whichever comes first. A node MUST NOT return more than 160 headers (`MAX_HEADERS_RESULTS`).

<a id="headers"></a>

##### `headers`

**Command**: 

`headers`

**Direction**: 

Both

**Payload**:

> | Size | Field | Description |
> | --- | --- | --- |
> | varies | `count` | Number of headers (CompactSize). |
> | varies | `headers` | Block headers. Each header is followed by a transaction count field (CompactSize, always 0 in `headers` messages). |

The `count` MUST NOT exceed 160. The headers MUST form a contiguous chain (each header's `hashPrevBlock` must match the hash of the preceding header). A node receiving non-contiguous headers SHOULD assign a misbehavior penalty of 20 points.

For the encoding of block headers, see the Zcash Protocol Specification <a id="fnref22"></a>
[22](/zip/204?part=2#fn22).

<a id="block"></a>

##### `block`

**Command**: 

`block`

**Direction**: 

Both

**Payload**: 

A single serialized block.

Sent in response to a `getdata` request for a `MSG_BLOCK` inventory entry. For the encoding of blocks, see the Zcash Protocol Specification<a id="fnref23"></a>
[23](/zip/204?part=2#fn23).

<a id="transaction-messages"></a>

#### Transaction Messages

<a id="tx"></a>

##### `tx`

**Command**: 

`tx`

**Direction**: 

Both

**Payload**: 

A single serialized transaction.

Sent in response to a `getdata` request for a `MSG_TX` or `MSG_WTX` inventory entry. For the encoding of transactions, see the Zcash Protocol Specification <a id="fnref24"></a>
[24](/zip/204?part=2#fn24).

<a id="bloom-filter-messages"></a>

#### Bloom Filter Messages

These messages implement BIP 37<a id="fnref25"></a>
[25](/zip/204?part=2#fn25) Bloom filtering. As of protocol version 170004, a node MUST NOT send Bloom filter commands to a peer that does not advertise `NODE_BLOOM` in its service flags. Sending Bloom filter commands to a peer that does not advertise `NODE_BLOOM` SHOULD incur a misbehavior penalty of 100 points (immediate ban).

<a id="filterload"></a>

##### `filterload`

**Command**: 

`filterload`

**Direction**: 

Both

**Payload**: 

A serialized Bloom filter, as defined in BIP 37<a id="fnref26"></a>
[26](/zip/204?part=2#fn26).

Sets a Bloom filter on the connection. After a filter is set, the peer will only relay transactions that match the filter.

<a id="filteradd"></a>

##### `filteradd`

**Command**: 

`filteradd`

**Direction**: 

Both

**Payload**:

> | Size | Field | Description |
> | --- | --- | --- |
> | varies | `data` | Element to add to the Bloom filter (CompactSize-prefixed, max 520 bytes). |

Adds an element to the existing Bloom filter. The data MUST NOT exceed 520 bytes. A node receiving data exceeding 520 bytes SHOULD assign a misbehavior penalty of 100 points.

<a id="filterclear"></a>

##### `filterclear`

**Command**: 

`filterclear`

**Direction**: 

Both

**Payload**: 

Empty (0 bytes).

Removes the Bloom filter from the connection. Transaction relay resumes normally.

<a id="memory-pool"></a>

#### Memory Pool

<a id="mempool"></a>

##### `mempool`

**Command**: 

`mempool`

**Direction**: 

Both

**Payload**: 

Empty (0 bytes).

Requests the contents of the peer's transaction memory pool. The peer responds by sending one or more `inv` messages listing the transactions in its mempool.

<a id="block-relay"></a>

### Block Relay

<a id="headers-first-synchronization"></a>

#### Headers-First Synchronization

Nodes MAY synchronize the block chain using a headers-first approach. The Zebra implementation uses headers-first synchronization; zcashd does not<a id="fnref27"></a>
[27](/zip/204?part=2#fn27).

1. The synchronizing node sends a `getheaders` message with a block locator.

2. The remote peer responds with a `headers` message containing up to 160 headers.

3. The synchronizing node validates the headers and requests full blocks via `getdata` with `MSG_BLOCK` inventory entries.

4. Steps 1–3 repeat until the node is synchronized with the network.

<a id="block-download-parameters"></a>

#### Block Download Parameters

- **Download window:** A node SHOULD NOT request blocks more than 1024 blocks ahead of its current validated tip (`BLOCK_DOWNLOAD_WINDOW`).

- **Per-peer limit:** A node SHOULD NOT have more than 16 blocks in transit from a single peer simultaneously (`MAX_BLOCKS_IN_TRANSIT_PER_PEER`).

- **Stalling timeout:** If a peer has not delivered a requested block within 2 seconds (`BLOCK_STALLING_TIMEOUT`) and there are other peers available with the same block, the node MAY request the block from an alternative peer.

- **Maximum headers per response:** 160 (`MAX_HEADERS_RESULTS`).

<a id="block-announcement"></a>

#### Block Announcement

Blocks are announced immediately via `inv` messages; they are not subject to the trickling delay applied to transactions.

<a id="transaction-relay"></a>

### Transaction Relay

<a id="inventory-based-relay"></a>

#### Inventory-Based Relay

Transaction relay follows an inventory-based protocol:

1. A node with a new transaction sends an `inv` message to its peers.

2. Peers that want the transaction respond with a `getdata` message.

3. The originating node sends the transaction in a `tx` message.

Transactions with version ≤ 4 MUST be announced using `MSG_TX` inventory vectors. Transactions with version ≥ 5 MUST be announced using `MSG_WTX` inventory vectors. Using the wrong inventory type for a transaction version SHOULD incur a misbehavior penalty. See ZIP 239<a id="fnref28"></a>
[28](/zip/204?part=2#fn28).

<a id="trickling"></a>

#### Trickling

To impede network topology inference, transaction inventory SHOULD NOT be sent immediately but SHOULD instead be "trickled" at random intervals. The specific trickling parameters are implementation-defined. The zcashd implementation uses the following values:

- The average broadcast interval is 5 seconds (`INVENTORY_BROADCAST_INTERVAL`).

- For outbound peers, the effective interval in seconds is halved rounding down (2 seconds on average).

- At most 35 inventory entries (`INVENTORY_BROADCAST_MAX`) are sent per trickle interval.

- Broadcast times follow a Poisson distribution centered on the broadcast interval.

The Zebra implementation uses a 7-second gossip delay (`PEER_GOSSIP_DELAY`).

<a id="transaction-expiry"></a>

#### Transaction Expiry

A node SHOULD NOT relay a transaction that will expire within 3 blocks of its view of the current chain tip (`TX_EXPIRING_SOON_THRESHOLD`).

<a id="orphan-transactions"></a>

#### Orphan Transactions

An orphan transaction is one that references inputs from unknown parent transactions.

- A node SHOULD store at most 100 orphan transactions (`DEFAULT_MAX_ORPHAN_TRANSACTIONS`).

- Orphan transactions expire and are removed after 1200 seconds (20 minutes, `ORPHAN_TX_EXPIRE_TIME`).

- Only fully transparent transactions are eligible for orphan storage.

- When the orphan pool is full, the oldest orphan transactions are evicted.

<a id="relay-fee"></a>

#### Relay Fee

Transactions are subject to a minimum relay fee. Both the zcashd and Zebra implementations use a base rate of 100 zatoshis per 1000 bytes (`DEFAULT_MIN_RELAY_TX_FEE`), though the zcashd implementation applies additional logic for low-fee and free transactions<a id="fnref29"></a>
[29](/zip/204?part=2#fn29). A node SHOULD NOT relay transactions with fees below its minimum relay fee threshold.

<a id="address-relay"></a>

### Address Relay

Address relay allows nodes to discover peers by propagating `CAddress` records through the network.

<a id="rate-limiting"></a>

#### Rate Limiting

Address records SHOULD be subject to rate limiting to prevent address flooding. The specific rate-limiting mechanism is implementation-defined. The zcashd-specific rate-limiting mechanism uses a token-bucket algorithm:

- Rate: 0.1 address records per second per peer (`MAX_ADDR_RATE_PER_SECOND`).

- Bucket capacity: 1000 (`MAX_ADDR_PROCESSING_TOKEN_BUCKET`), replenished at 0.1 tokens per second.

- Upon receiving a `getaddr` response, 1000 tokens (`MAX_ADDR_TO_SEND`) are added to the bucket, exempt from the soft limit.

- Whitelisted peers bypass address rate limiting.

<a id="address-broadcasting"></a>

#### Address Broadcasting

The specific broadcast intervals are implementation-defined. The zcashd implementation uses the following values:

- Addresses are broadcast to peers at an average interval of 30 seconds (`AVG_ADDRESS_BROADCAST_INTERVAL`).

- A node's own address is broadcast approximately every 9.6 hours (`AVG_LOCAL_ADDRESS_BROADCAST_INTERVAL = 24 * 24 * 60 = 34560` seconds).

<a id="misbehavior-and-banning"></a>

### Misbehavior and Banning

Nodes SHOULD track misbehavior scores for each connected peer. The zcashd implementation bans a peer when its cumulative score reaches or exceeds 100 (`DEFAULT_BANSCORE_THRESHOLD`), with bans lasting 24 hours (`DEFAULT_MISBEHAVING_BANTIME`). The Zebra implementation also uses a threshold of 100 (`MAX_PEER_MISBEHAVIOR_SCORE`) but bans persist indefinitely. The ban threshold and duration are implementation-defined.

Whitelisted peers accumulate misbehavior scores but are exempt from banning.

The following table lists the misbehavior penalties assigned for specific protocol violations:

| Points | Violation |
| --- | --- |
| 1 | Duplicate `version` message. |
| 1 | Message received before `version` handshake. |
| 1 | Duplicate `verack` message. |
| 10 | Alert processing failure. |
| 20 | `addr` message with more than 1000 entries. |
| 20 | `inv` message with more than 50,000 entries. |
| 20 | `getdata` message with more than 50,000 entries. |
| 20 | `headers` message with more than 160 entries. |
| 20 | Non-contiguous headers sequence. |
| 50 | Causing send buffer overflow. |
| 100 | Bloom filter commands without `NODE_BLOOM` support. |
| 100 | Invalid Bloom filter parameters. |
| 100 | `filteradd` data exceeding 520 bytes. |
| 100 | Using `MSG_TX` to announce a v5 transaction, or `MSG_WTX` to announce a v4-or-earlier transaction (see ZIP 239 <a id="fnref30"></a> [30](/zip/204?part=2#fn30)). |
| varies | Transaction, block, or header validation failure. The penalty is determined by the severity of the validation error. |

<a id="network-upgrade-peer-management"></a>

### Network Upgrade Peer Management

Network upgrade peer management is specified in ZIP 201<a id="fnref31"></a>
[31](/zip/204?part=2#fn31). This section summarizes the key behaviors.

<a id="pre-activation"></a>

#### Pre-Activation

In the 1728-block window (`NETWORK_UPGRADE_PEER_PREFERENCE_BLOCK_PERIOD`) before a network upgrade activation height, a node SHOULD preferentially connect to peers that advertise a protocol version at or above the version required by the upcoming upgrade. This period corresponds to approximately 1.5 days at the post-Blossom block interval.

<a id="post-activation"></a>

#### Post-Activation

After a network upgrade activates, a node MUST disconnect any peer whose negotiated protocol version is below the version required by the active epoch. The node SHOULD send a `reject` message with code `REJECT_OBSOLETE` (`0x11`) before disconnecting.

<a id="reject-codes"></a>

## Reject Codes

| Code | Constant | Description |
| --- | --- | --- |
| `0x01` | `REJECT_MALFORMED` | The message could not be decoded. |
| `0x10` | `REJECT_INVALID` | The message was invalid (e.g., a block or transaction that fails validation). |
| `0x11` | `REJECT_OBSOLETE` | The message uses an obsolete protocol version. |
| `0x12` | `REJECT_DUPLICATE` | The message is a duplicate of one already received. |
| `0x40` | `REJECT_NONSTANDARD` | The transaction is valid but not standard. |
| `0x41` | `REJECT_DUST` | One or more transaction outputs are below the dust threshold. |
| `0x42` | `REJECT_INSUFFICIENTFEE` | The transaction fee is insufficient. |
| `0x43` | `REJECT_CHECKPOINT` | The block conflicts with a checkpoint. |

<a id="formal-model"></a>

## Formal Model

A TLA+ formal specification of the connection lifecycle described in this ZIP is available at<a id="fnref32"></a>
[32](/zip/204?part=2#fn32). The model covers the handshake, keepalive, block synchronization, and disconnection phases, verifying liveness (all peers eventually reach the same block height) and safety (message size bounds hold across all reachable states).

<a id="references"></a>

## References

---

## Citation relationships

Cites: [ZIP 155](https://zip-tools-web.harpocrates.workers.dev/zip/155), [ZIP 200](https://zip-tools-web.harpocrates.workers.dev/zip/200), [ZIP 201](https://zip-tools-web.harpocrates.workers.dev/zip/201), [ZIP 225](https://zip-tools-web.harpocrates.workers.dev/zip/225), [ZIP 239](https://zip-tools-web.harpocrates.workers.dev/zip/239), [ZIP 244](https://zip-tools-web.harpocrates.workers.dev/zip/244)

Cited by: [ZIP 258](https://zip-tools-web.harpocrates.workers.dev/zip/258)