zebrad and the Tower Pattern
Why This Chapter Exists
zebrad is the binary. It does almost no logic itself; it wires Tower services into a graph. Until you can read a tower::Service and know what poll_ready returning Pending means in this graph, the rest of the codebase reads as magic.
Tower in Three Sentences
A tower::Service<Request> is an asynchronous function from
Request to Result<Response, Error> with two extras: a
poll_ready method that signals backpressure ("not ready yet, do not
call me"), and a call method that returns a future. Tower lets you
compose services by wrapping them in middleware: rate limiting,
timeouts, batching, retries, load balancing, all become layers around
an inner service. Zebra is built on Tower throughout, so reading
Zebra without internalizing Tower is going to feel arbitrary.
The bounds Zebra puts on every service in AGENTS.md:
S: Service<Req, Response = Resp, Error = BoxError> + Send + Clone + 'static,
S::Future: Send + 'static,
Two non-obvious rules from AGENTS.md:
poll_readymust check all inner services, not just the one Tower thinks it should check.- clone services before moving into async blocks. Cloning a Tower
service is cheap (it usually contains channels and
Arcs); moving the original into a task makes the service unusable from the caller's site.
Tower-batch-control
A middleware crate. Lives at tower-batch-control/. Used to batch
many requests of one type so an inner service can verify them in one
call.
The shape:
- a
Batch<Service, Request>wraps a service that accepts batches. - callers send individual requests; they are buffered up to a maximum count or maximum wait time.
- when either threshold trips, the batch is dispatched and the results are sent back to the individual callers' futures.
Used in zebra-consensus/src/primitives/ for every batched
verifier: Groth16, Halo2, Ed25519, RedJubjub, RedPallas.
Files to read:
tower-batch-control/src/service.rs: the batched service itself.tower-batch-control/src/worker.rs: the worker task that drains the batch.tower-batch-control/src/layer.rs: the TowerLayeradapter.
Tower-fallback
Pairs naturally with tower-batch-control. When a batch fails as a
whole (one bad item invalidates the whole batch), the fallback
service re-runs each item individually so the offending one can be
identified and reported. Lives at tower-fallback/.
Read tower-fallback/src/service.rs to see the simple "try
primary, then fallback" semantics.
zebrad
The binary. Built on abscissa, a small CLI/application framework.
Entry Points
zebrad/src/bin/ holds the binary entry point. zebrad/src/ application.rs wires the application: command parsing, config
loading, component startup. zebrad/src/commands/ lists each
subcommand: start, generate (config), copy_state,
tip_height, plus entry_point.rs and tests.rs.
start.rs is the meat. It launches the components.
Components
tokio.rs: builds the Tokio runtime with appropriate worker counts.tracing/: configurestracing-subscriberand the various sinks (stdout, file, journald, sentry, flamegraph, console-subscriber).metrics.rs: optional Prometheus exporter.sync/: the chain syncer. Drives initial block download and ongoing tip following. SendsRequest::BlocksByHash/Request::BlocksByRangeto the peer set, hands blocks to the consensus router, then to the state writer.inbound/: the inbound service handed tozebra-network::init. Receives requests from peers (GetBlocks,GetData,Inv,Tx, etc.) and dispatches them to the state, mempool, or consensus services.mempool/: the mempool implementation. Readbook/src/dev/ mempool-specification.mdfirst.miner.rs: optional in-process miner (testnet only with theinternal-minerfeature; the documented production setup uses an external pool talking togetblocktemplate).health/: HTTP health endpoints for Kubernetes-style liveness and readiness checks. Seebook/src/user/health.md.
The orchestration pattern in start.rs is worth memorizing: every
component is a tower::Service or task that gets started, connected
to its peers and downstreams via channels or service clones, and
shutdown is propagated through oneshot channels and graceful drop
order.
Config
zebrad/src/config.rs defines the top-level ZebradConfig. Sub-
configs live in the relevant crates: zebra-network::Config,
zebra-state::Config, zebra-consensus::Config,
zebra-rpc::config::Config, etc. The full TOML is documented in
book/src/user/run.md and generated by zebrad generate.
A pattern called out in AGENTS.md: every config struct uses
#[serde(deny_unknown_fields, default)] so unrecognized fields are
errors but old configs remain valid as defaults fill in new fields.
The Parallel Verification RFC
The single most important document for understanding the
shape of Zebra's runtime is RFC 0002, "Parallel Verification", at
book/src/dev/rfcs/0002-parallel-verification.md. Read it before
or alongside this file.
The idea: blocks can be verified in arbitrary order as long as their state dependencies (UTXOs, anchors, nullifier sets) are resolved by the state service. The state service queues requests until their dependencies are met. Combined with batched cryptography, this lets Zebra verify many blocks in flight.
Suggested Exercises
- read RFC 0002 and 0004 in
book/src/dev/rfcs/. Then openzebrad/src/components/sync/and find the place that turns "I got a block from a peer" into "the block is innon_finalized_state". - find the place where the inbound service is constructed and
passed to
zebra_network::init. WhatRequestvariants does it handle? - open any verifier in
zebra-consensus/src/primitives/and trace howtower-batch-controlandtower-fallbackare layered together. - read
zebrad/src/components/mempool/alongside the mempool spec. What is the difference between the "transaction queue" and the mempool?
Spec Pointers
- Tower documentation.
tower-batch-controlandtower-fallbackin this workspace: the project-specific Tower glue.
Exercises
- Find the entry point of
zebrad startand list every service it constructs in order. - Pick one service and trace where
poll_readyis implemented. What backpressure does it expose? - Add a metric (
zebrad.startup.duration_msor similar) that fires once at startup. Confirm it appears in/metrics.