Networking and JSON-RPC
Why This Chapter Exists
Two surfaces, both attacker-controlled. zebra-network is the gossip layer (P2P, attackers connect to it); zebra-rpc is the local management surface (less hostile, still untrusted in shared environments). If you contribute here, you are in the part of the codebase most likely to ship a CVE.
zebra-network
The module-level doc in zebra-network/src/lib.rs is the best
single document on the Zebra P2P design. Read it end to end before
you read any subdirectory.
The architecture in one paragraph: the Zcash network protocol is
Bitcoin-derived and stateful (any message may be a request or a
response depending on context). Zebra wraps that legacy protocol into
a stateless, request-response oriented protocol defined by
Request and Response enums in protocol::internal. The whole
peer set is exposed as a single Tower service that load-balances
outbound requests over available peers. Inbound requests are dispatched
to a Tower service supplied by the caller.
Protocol/
Two layers:
external/: the wire-format types.message.rsis the canonical list of every Zcash P2P message.codec/is the TokioCodecimplementation that frames messages on a TCP stream.addr/decodes/encodes peer addresses (v1 and v2 variants).inv.rsdefines the inventory item type used ininv/getdata.types.rsdefinesPeerServices,Nonce,Version.internal/: the Zebra-internal request/response abstraction.request.rsandresponse.rsare the Tower interfaces every other crate uses.response_status.rsadds an extra Zebra-side response status type for partial inventory responses.
Peer/
Per-connection state. Each peer has:
- a
Connectiontask (peer/connection/), which is the per-peer state machine. It accepts internalRequests, dispatches them as outbound wire messages, accepts inbound wire messages and routes them either as responses to outstanding requests or as inbound requests to the caller's service. - a
Clientservice (peer/client/), the external handle other code uses to send a request to one specific peer. - a
Handshake(peer/handshake/), which performs the Zcashversion/verackexchange (with Tor-style anonymization toggles, seeisolated/). - a
Connector(peer/connector.rs) that combines TCP dial plus handshake.
Peer_set/
The connection pool.
set/is the load-balanced multi-peer Tower service. It picks a peer per outbound request using inventory-aware routing.inventory_registry/tracks which peer advertised which inventory item, so we route block and tx requests to peers that have them.candidate_set/is the pool of address-book entries that have not yet been tried.initialize/is the startup logic: load seeds, resolve DNS, load cached peers from disk, kick off outbound dial loop, spawn the listener task.limit.rsenforces inbound and outbound connection bounds.unready_service.rsis the small utility for "park a service that is not currently ready for the next request without losing it".stall_tracker/records last-heard timestamps so we can drop stuck peers.
Address_book*
The address book is in address_book.rs and friends. It is the
in-memory list of MetaAddrs (peer address plus attempt metadata).
address_book_updater.rs reconciles updates from many peer tasks
into one canonical view. peer_cache_updater.rs persists the address
book to disk on a timer.
policies.rs
Tower retry policy.
Isolated/
The anonymizing connector. The TCP and Tor (currently disabled, see
the comment in lib.rs) variants. Used to send user-generated
transactions without revealing the sender's IP.
Things to Internalize
- the request/response inversion. From outside, "the network" is a single Tower service. From the inside of a connection, an inbound message can be a response to one of our requests or a request to us. Both cases share the same connection state machine.
- the "drop on overload" pattern. Inbound queues have a finite depth; when they fill, the connection is dropped. This is DoS defense and the right pattern for adversarial networks.
PeerSetandInventorydecouple gossip from peer identity. A request for a block can go to any peer that advertised that block, not a specific one.
zebra-rpc
The module-level doc is sparse; the right entry point is methods/.
Structure
server/: HTTP server usingjsonrpsee. Mounted inzebrad's startup.methods/types/: per-method DTOs. Read this directory directly to see the full RPC surface.methods.rsandmethods/: the handler implementations.queue/: the transaction queue used by the mempool RPC flow.indexer/: a gRPC indexer service. Separate from JSON-RPC.sync.rs: the synchronization helpers used by mempool/template RPC paths.client.rs: a small JSON-RPC client used internally for tests and utilities.
RPC Methods to Know
methods/types/ lists them by name. The ones that exercise the
interesting code paths:
getblockchaininfo,getblockcount,getbestblockhash,getblock,getblockheader: read paths through the state service.sendrawtransaction: full transaction verification path. Touches every cryptographic verifier.getrawmempool,getmempoolinfo: mempool views.getblocktemplate,submitblock: mining flow. The most complex RPCs because they build a candidate block (coinbase, transactions pulled from mempool, default roots, expiry, long-poll support). Readget_block_template/andlong_poll.rs.validateaddress,z_validateaddress: shielded and transparent address parsing.getpeerinfo,getnetworkinfo: introspection intozebra-network.
zcashd Compatibility
Zebra targets the JSON-RPC of zcashd so that mining pools and
existing wallets (lightwalletd) can use Zebra without change. Every
method's wire format and error code is meant to match zcashd.
Compatibility tests exist under
zebra-rpc/src/tests/; integration tests run a real lightwalletd
against a syncing zebrad.
The Indexer
The indexer (gRPC, behind the indexer feature) exposes additional
data not present in the JSON-RPC, including spend lookups and
historical chain data. This is the API that wallet servers like
Zaino consume.
The Mempool
The mempool is split between zebrad/src/components/mempool/ (the
orchestration) and zebra-rpc/src/queue/ (the RPC-side queue). The
mempool spec lives at book/src/dev/mempool-specification.md; read
it before reading the code.
Key consensus rules to know:
- transactions in the mempool must verify against the current tip context. On reorg, mempool entries are re-verified.
- transactions expire on a per-height basis using ZIP-203 expiry.
- a separate "transaction queue" feeds the mempool: transactions
enter via
sendrawtransactionor peer gossip, queue up, then flush into the mempool batch by batch.
Suggested Exercises
- open
zebra-network/src/protocol/external/message.rsand list every Zcash P2P message type. For each, identify whether it is a request, a response, or unsolicited. - follow a
getblocksrequest from a peer. Which task receives it, which service answers it, which response message goes out? - open
zebra-rpc/src/methods/types/get_block_template/and sketch the full lifecycle of agetblocktemplatelong-poll. - find every place where
MAX_TX_INV_IN_SENT_MESSAGEis used. Why is there a limit, and why this number?
Spec Pointers
- Bitcoin/Zcash P2P protocol:
zebra-network/src/protocol/. - zcashd RPC reference for the methods Zebra must emulate.
TrustedPreallocatediscipline: required reading before allocating from any external byte stream.
Exercises
- Find one
TrustedPreallocateimpl and explain in one sentence what bound it enforces. - Identify one RPC method that has no rate limit in Zebra today. Decide whether the omission is safe and explain why.
- Add a debug log to the inbound peer handshake that prints the user agent. Run against testnet and confirm the log fires.