draft-arya-jvff-p2p-quic-transport
Version 2 Zcash P2P Network Protocol
- Draft
| 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.