Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Zebra network architecture

The zebra-network crate translates the bidirectional Zcash wire protocol into Tower services accepting Request values and returning Response values. Each peer has a Connection task and Client handle. The application supplies an inbound service for remote requests; a NetworkService handle routes the application’s outbound requests.

Requests and connections

Solid arrows carry requests or wire messages. Dotted arrows carry metadata or peer-set updates. Response futures return to the requesting caller.

flowchart TD
    service["NetworkService"] --> buffer["Buffer"]
    buffer --> set["PeerSet"]
    set --> load["LoadTrackedClient / PeakEwma"]
    load --> client["Client"]
    client --> connection["Connection"]
    connection <--> framed["Framed / Codec"]
    framed <--> tcp["TcpStream"]
    connection --> inbound["Inbound through LoadShed / Buffer / Timeout"]

    inventory["InventoryRegistry"] -.-> set
    client -.->|missing inventory| inventory
    framed -.->|incoming messages| tracking["register_inventory_status"]
    tracking -.->|inventory advertisements| inventory
    set -.->|connected-peer channels| advert["BlockAdvertiser"]
    advert -->|BlockAdvert| connection
    bans["BanList"] -.-> set
    version["MinimumPeerVersion"] -.-> set
    stalls["FindResponseStallTracker"] -.-> set

The handshake constructs the framed transport, client channels, and connection task. Its incoming-message pipeline records inventory advertisements and transport errors before delivering messages to the connection. A separate, rate-limited heartbeat task updates address liveness after successful ping responses. The client also records missing-inventory responses. The load wrapper uses Tower’s PeakEwma estimator, which tracks recent response latency, for load-sensitive selection.

The peer set routes requests by class:

RequestRouting
FindBlocks, FindHeadersFair rotation across ready connections, preferring serving peers for up to 1.5 seconds. Fast empty replies do not gain a latency-based routing advantage.
BlockDownload, single-item BlocksByHash, single-item TransactionsByIdInventory-aware selection prefers a peer that advertised the item and excludes peers recorded as missing it. Block requests can wait for a suitable busy peer; transaction requests do not use that wait.
AdvertiseTransactionIdsBroadcast to a randomly selected subset of ready peers, rather than selecting by latency.
PushTransactionBroadcast with additional handling for configured zcashd-compat sidecar peers.
Other ordinary requestsPower-of-two-choices selection: sample two ready peers and choose the less loaded one.
AdvertiseBlock, AdvertiseBlockToAllRejected on the ordinary network service; callers must use BlockAdvertiser.

The dedicated block advertiser has its own concurrency bound and per-connection channels. A busy ordinary request does not prevent a connection from receiving a block advertisement. Successful advertisement completion means the selected connections completed their local transport writes, not that remote peers received or accepted the block.

The inbound stack bounds remote requests with load shedding, a buffer, and a response timeout. The connection task interprets incoming messages as responses to outstanding requests or inbound requests.

Discovery and address state

The initializer adds peer clients from accepted inbound connections, initial seed/cache connections, and the crawler.

flowchart TD
    initial["add_initial_peers"] --> connector["peer::Connector"]
    crawler["crawl_and_dial"] --> connector
    listener["accept_inbound_connections"] --> handshake["peer::Handshake"]
    connector --> handshake
    initial -->|DiscoveredPeer| discovery["peerset_tx / peerset_rx"]
    crawler -->|DiscoveredPeer| discovery
    listener -->|DiscoveredPeer| discovery
    discovery --> set["PeerSet"]
    set -->|MorePeers| crawler

    crawler --> next["NextPeerService"]
    crawler --> crawl["CrawlService"]
    crawl -->|Peers request| set
    next --> bookservice["AddressBookService"]
    crawl --> bookservice
    bookservice --> updater["AddressBookUpdater"]
    handshake --> updater
    initial --> updater
    updater --> book["AddressBook"]
    updater --> bans["BanList"]
    cache["peer_cache_updater"] --> bookservice

The initial-peer task resolves configured seed addresses and reads the peer cache. The candidate services select outbound candidates from the address book and store addresses learned from peer crawls. Candidate selection and crawling have separate rate limits. The crawl-and-dial loop crawls on its timer or when the peer set requests more peers and no candidates are available. Dialing respects the configured connection limits.

The address-book updater owns address mutations and publishes ban updates. The peer-set maintenance path removes failed, banned, or protocol-ineligible connections and handles queued requests when suitable peers become available. The minimum protocol version follows the local chain tip; network-upgrade behavior is specified by ZIP 201.

Ownership and shutdown

A NetworkService handle owns the network’s worker and background tasks. Its clones and unfinished response futures keep those tasks alive. Releasing the last owner stops the tasks and releases the listener and peer connections.

Services retained by the network, including the inbound handler’s downloaders and mempool, use NetworkService::background_service(). That handle shares the existing buffer without owning network lifetime, avoiding a reference cycle. A BlockAdvertiser handle also requires an external owning network handle to remain alive.