Skip to content
LogoLogo

12. P2P protocol

12.1 Transport

TCP, then a hybrid key exchange:

initiator → responder:  magic(4) | 0x01 | x25519_pub(32) | ML-KEM-768 encapsulation key(1184)
responder → initiator:  magic(4) | 0x01 | x25519_pub(32) | ML-KEM-768 ciphertext(1088)
okm = HKDF-SHA3-256(salt=magic, ikm = x25519_ss ‖ mlkem_ss,
                    info = "ARRK p2p v1" ‖ SHA3-256(hello ‖ reply), L = 96)
k_i→r = okm[0:32]   k_r→i = okm[32:64]   session_id = okm[64:96]
frame = u32 ciphertext_len ‖ ChaCha20-Poly1305(k, nonce = 96-bit LE counter, plaintext)
  • Counters start at 0 per direction and never repeat. Any authentication failure closes the connection.
  • The maximum frame is 33 MiB.
  • Peers are not authenticated; consensus validation protects the node.

12.2 Messages

payload = u8 type ‖ body. Protocol version 3 (VERSION.protocol): headers travel as full headers with their AuxPoW. Peers announcing a lower version are disconnected.

TypeNameBody
1VERSIONprotocol u32, services u64, timestamp u64, nonce u64, user_agent varstr(≤256), best_height u32, listen_port u16, genesis 32
2VERACK—
3 / 4PING / PONGnonce u64
5GETBLOCKSvarint n (≤101) × hash32, stop hash32 (served for compatibility; v2 nodes sync with GETHEADERS)
6 / 7 / 10INV / GETDATA / NOTFOUNDvarint n (≤50,000) × [type u8 (1 tx, 2 block), hash32]
8 / 9BLOCK / TXserialized object
11 / 12GETADDR / ADDR— / varint n (≤1000) × [host varstr(≤255), port u16]
13GETHEADERSvarint n (≤101) × hash32 (locator), stop hash32
14HEADERSvarint n (≤2000) × full header (§9); replies stop at a 16 MiB byte budget
15SENDCMPCTmode u8 (0 = announce blocks with INV, 1 = push CMPCTBLOCK)
16CMPCTBLOCKfull header ‖ nonce u64 ‖ varint s (≤100,000) × shortid (6) ‖ varint p (≤100,000) × [index u32 ‖ varbytes tx]
17GETBLOCKTXNblock_hash 32 ‖ varint n (≤100,000) × index u32
18BLOCKTXNblock_hash 32 ‖ varint n (≤100,000) × varbytes tx
23CHECKPOINTa (partial) checkpoint (§9.8); relayed when it adds signatures; final checkpoints and every hand-over are sent after VERACK
24PACKAGEvarint n (2–25) × varbytes tx: parent and child for fee bumping (§13.2)

Service bits (VERSION services):

BitNameMeaning
0NODE_NETWORKserves every block
2NODE_FILTERSserves compact block filters (§12.5)
10NODE_NETWORK_LIMITEDa pruned node: serves at least the last 288 blocks below its best height (as BIP159)

A node MUST NOT ask a peer without NODE_NETWORK for a block 288 or more below that peer's best height. A peer asked for a block it does not keep answers NOTFOUND.

Handshake rules:

  • A different genesis or a self-connect nonce closes the connection.
  • After VERACK each side sends SENDCMPCT(1). If the peer's best_height is above our best known header, we start headers-first sync.

Misbehaviour:

  • Undecodable messages, consensus-invalid blocks or transactions, and invalid headers score 100 → 24 h ban.
  • Policy rejections (fees, mempool conflicts) and time-too-new MUST NOT be penalised.

12.3 Headers-first sync

  • A block locator lists up to 10 hashes from our best header back through the header tree, then the active chain's locator (dense for 10 blocks, then exponentially sparse, ending at genesis).

  • GETHEADERS: the responder finds the first locator hash on its active chain and answers with HEADERS for up to 2,000 following blocks (stopping at stop if non-zero).

  • Accepting headers. Headers in one message MUST be continuous (prev_hash = previous header's hash), otherwise the peer scores 20. Each new header MUST:

    • extend a known header or block (an unconnecting first header triggers a GETHEADERS with our locator; 10 unconnecting messages score 20);
    • have version 1, a target ≤ the pow limit, and valid proof of work;
    • pass the contextual header rules of §9.2: height, expected bits, and timestamp > median time past.

    A failure scores 100. A header more than max_future_drift ahead of local time stops processing without penalty.

  • Header tree. Accepted headers whose blocks we don't have go into a header tree that records cumulative work. The best header is the tree entry or active tip with the most work.

  • More headers: a full HEADERS message (2,000) triggers the next GETHEADERS, from its last header.

  • Downloading bodies. While the best header has more work than the active tip, the node downloads the blocks on its path:

    • at most the first 128 missing blocks past the fork point;
    • at most 16 requests in flight per peer, spread over peers whose best height covers the block;
    • requests unanswered within 30 s are reassigned without penalty.

    Blocks may arrive in any order. They wait in the orphan pool until their parent connects. A block that fails validation is removed from the header tree together with its descendants.

12.4 Compact block relay

  • When a block becomes the new active tip, it is pushed as CMPCTBLOCK to peers in mode 1, and announced with INV to the others. Several blocks connected at once are announced with INV.

  • Short IDs: shortid(tx) = TH("cmpct/shortid", block_hash ‖ u64 nonce ‖ wtxid)[0:6], with a fresh random nonce per message. Using the wtxid means a transaction with different witness data cannot be substituted.

  • Prefilled transactions: the coinbase (index 0) is always prefilled. Prefilled indices are strictly increasing, and s + p is the block's transaction count; short IDs fill the remaining positions in order.

  • Receiving a CMPCTBLOCK:

    1. The header goes through the §12.3 header rules first.
    2. If its parent is not an active-chain block, the header is treated as an announcement and the body is downloaded normally.
    3. Otherwise the receiver fills the slots from its mempool.
    4. It requests the remaining indices with GETBLOCKTXN (strictly increasing, below the count).
    5. The responder answers BLOCKTXN with exactly those transactions, in order.

    If two candidates match one short ID, a short ID repeats, the transaction counts are inconsistent, or the reconstructed block fails its merkle or witness root, the receiver falls back to GETDATA for the full block without penalising the sender (a short-ID collision is not misbehaviour). A BLOCKTXN with the wrong number of transactions scores 10; GETBLOCKTXN indices out of range score 10.

12.5 Light-client messages

TypeNameBody
19GETCFILTERSstart_height u32 ‖ stop_hash 32 (≤ 1,000 blocks)
20CFILTERblock_hash 32 ‖ varbytes filter (one per block, in height order)
21GETCFHEADERSstart_height u32 ‖ stop_hash 32 (≤ 2,000 blocks)
22CFHEADERSstop_hash 32 ‖ prev_header 32 ‖ varint n × filter_hash 32

Nodes that serve filters set services bit 2 (NODE_FILTERS) in VERSION. A light client:

  1. syncs headers (§12.3);
  2. fetches filter headers from several peers and compares them;
  3. downloads filters and checks each against its header;
  4. fetches the full blocks that match its locks or assets.

It can verify any filter header against proof of work through the coinbase commitment (§9.2 rule 6), using the coinbase and its merkle branch (REST /v1/blocks/{id}/filter).

12.6 Hardening (policy)

These follow Bitcoin Core:

  • Inbound eviction: when inbound slots are full, the node protects 4 peers by lowest latency, 4 that most recently relayed a new block and 4 longest connected. It then evicts the youngest peer of the most represented network group (/16 IPv4, /32 IPv6). If every peer is protected, the new connection is refused.
  • ADDR rate limit: a token bucket per peer, 0.1 address per second, burst 1000. Addresses beyond it are ignored.
  • Anchors: up to 2 outbound peers are saved to anchors.json and reconnected first after a restart.