Danny Willems -- Work In Progress

A mathematician dreaming about describing the Universe with equations and symbols.

How Zcash mining fits together: the node, Stratum, and the mining stack

This post explains how a Zcash miner connects to the rest of the system: what a full node like Zebra actually provides, what the Stratum protocol does, and what software production miners run. It grew out of a question I had while working on the Zcash stack at ZODL: if I want to mine on testnet, what command do I run? The short answer is that there is no single command, because a node and a miner are different programs with different jobs. Along the way it traces the relevant paths in Zebra’s source, with links pinned to the v4.5.1 release commit (76c440e6) so the line numbers stay valid.

This article was written with the help of an AI assistant, working from my own questions, the Zebra source code, and the web sources linked at the end. Treat the profitability and market figures as the claims of their (often commercial) sources, not as my recommendations. Numbers in this space change quickly.

a node is not a miner

Zebra is a Zcash validator node. It validates blocks and transactions, follows the chain, and talks to peers. By default it does not mine, and there is no zebrad mine subcommand: in a normal build, producing blocks is the job of separate miner software. (Zebra does ship an experimental, opt-in internal miner behind a build-time feature flag, which I cover near the end; the default binary does not include it.)

What Zebra provides to miners is a JSON-RPC method called getblocktemplate. This method hands back everything needed to assemble a candidate block: the previous block hash, the set of transactions to include, the coinbase transaction, the current target, and the valid time range. A miner takes that template, searches for a proof of work that meets the target, and submits the completed block back to the node, which then broadcasts it.

So the division of labour is:

  • the node (zebrad) decides what a valid block looks like and serves templates,
  • the miner does the proof-of-work search,
  • something in between distributes work to many miners and aggregates their results.

That “something in between” is a mining pool, and the protocol it speaks to miners is Stratum.

what Stratum is

Stratum is the network protocol between mining hardware and a mining pool. It exists because the node’s getblocktemplate RPC is too heavy for thousands of miners to poll directly, and because a single miner rarely finds a full block on its own. Stratum lets a pool hand out small, measurable units of work and pay miners for the work they demonstrably did.

The original protocol (sometimes called Stratum V1) is line-based JSON-RPC over a plain TCP socket. That is the stratum+tcp://host:port address you configure on a miner. A typical session looks like this:

  1. The miner opens a long-lived TCP connection and subscribes (mining.subscribe), then authorizes a worker (mining.authorize, usually as address.workername).
  2. The pool pushes work to the miner (mining.notify): the header fields to hash and a target.
  3. The pool sets a share difficulty that is much lower than the real network difficulty (mining.set_target). This is the key idea: it lets the pool measure each miner’s contribution continuously, instead of waiting for the rare event of a full block.
  4. The miner searches for solutions and submits any that meet the share difficulty (mining.submit). Most submitted shares are only proof that work was done. Occasionally a share also meets the real network target, and that one is the actual block the pool broadcasts.
  5. The pool credits shares and pays out according to its scheme (PPS, PPS+, or PPLNS).

Stratum V1 was never standardized in a single RFC; the de facto reference is the Bitcoin Wiki’s Stratum mining protocol page, which documents mining.subscribe, mining.notify, mining.submit, and the rest. Zcash does not use Bitcoin’s Stratum verbatim. The original protocol bakes in Bitcoin’s block-header layout and nonce space, and Zcash’s header format and Equihash proof of work break those assumptions. Zcash therefore defines its own variant in ZIP 301: Zcash Stratum Protocol, where, for instance, mining.submit carries the worker name, job id, time, nonce_2, and the Equihash solution rather than Bitcoin’s fields. ZIP 301 grew out of the earlier slushpool Zcash Stratum protocol changes.

Two properties matter. Stratum is push-based, so the pool notifies miners of new work rather than miners polling for it. And the split between share difficulty and network difficulty is what makes pooled mining low-variance and measurable.

Putting the pieces in order:

ASIC / GPU  --Stratum/TCP-->  pool stratum server  --getblocktemplate RPC-->  zebrad

There is also a newer redesign, Stratum V2, which is binary, encrypted, and adds a mode that lets miners choose their own transaction set rather than accepting the pool’s; its specification is maintained in the sv2-spec repository. Its production use so far has been mostly in Bitcoin. I did not find evidence of meaningful Stratum V2 adoption in Zcash mining, so for Zcash the practical protocol today is still V1.

how a block is created and received, in Zebra’s code

The conceptual flow above maps onto concrete code. This section traces it in Zebra, with each reference pinned to the v4.5.1 commit so the line numbers do not drift. Two RPC methods bracket the creation of a block, and a separate network path handles receiving one from peers.

creating a block: getblocktemplate and submitblock

A pool asks the node for work through the getblocktemplate RPC, implemented at zebra-rpc/src/methods.rs:2217. The method reads chain context from the state service (the tip hash, height, expected difficulty, and the valid time range), fetches candidate transactions from the mempool, selects them using the ZIP-317 fee-weighted algorithm, constructs the coinbase transaction, computes the Merkle and authorizing-data roots, and returns a template.

What the template leaves blank is the proof of work. The block header carries a nonce field at zebra-chain/src/block/header.rs:82 and an Equihash solution at header.rs:85. The miner searches for a nonce and solution that make the block hash meet the target. That search is the work; everything else in the header is fixed by the template.

When a miner finds a solution, the pool returns the completed block through the submitblock RPC at methods.rs:2540. It deserializes the hex into a Block (methods.rs:2547) and hands it to the consensus block verifier as Request::Commit(Arc::new(block)) (methods.rs:2568). That request type is defined at zebra-consensus/src/block/request.rs:9: Commit runs full semantic validation (including the Equihash proof of work) and then contextual validation, and commits the block. A sibling variant, CheckProposal, skips the proof-of-work check and does not commit; it backs the proposal mode of getblocktemplate at get_block_template.rs:724, which lets a pool validate a template’s structure before any work is done. If the submitted block is accepted, the node advertises it to peers via advertise_mined_block (methods.rs:2582).

receiving a block: gossip, download, verify, commit

A node usually learns of a block not from a miner but from a peer. The peer sends an inventory message advertising hashes it has. The wire messages are defined in zebra-network/src/protocol/external/message.rs: Inv (line 146) advertises inventory, GetData (line 196) requests it, and Block (line 201) carries the block itself. When a connection decodes an Inv containing a single block hash, it turns it into the internal Request::AdvertiseBlock at peer/connection.rs:1282.

The inbound service routes that advertisement to the block downloader at zebrad/src/components/inbound.rs:554, calling download_and_verify. The downloader fetches the block body with a BlocksByHash request (inbound/downloads.rs:298), enforces per-IP and concurrency limits to bound abuse, and then submits the block to the consensus verifier with the same Request::Commit(block) that submitblock uses (downloads.rs:395). A locally mined block and a gossiped block converge on the same verification entry point.

After semantic checks pass, the verifier commits the block to the state service with Request::CommitSemanticallyVerifiedBlock at zebra-consensus/src/block.rs:376, and on success the state replies Response::Committed (block.rs:379). The state request type is defined at zebra-state/src/request.rs:787. Bulk historical sync uses a separate, lighter path, CommitCheckpointVerifiedBlock, for blocks below the last checkpoint.

re-gossiping to peers

Once a freshly committed block becomes the new best tip, the node advertises it onward. The gossip task at zebrad/src/components/sync/gossip.rs chooses how widely to announce: a locally submitted (mined) block goes to all ready peers via AdvertiseBlockToAll (line 132), while a relayed block goes to a fraction of peers via AdvertiseBlock (line 134). On the wire that becomes an Inv message again, sent at peer/connection.rs:1160. That closes the loop: a block enters as an Inv hash, is fetched, verified, and committed, then leaves as an Inv hash to the node’s own peers, which is how a mined block propagates across the network one hop at a time.

mining on testnet with Zebra

For testing the path end to end, the Zebra book documents a setup using s-nomp (ZcashFoundation zebra-mining fork) as the pool/stratum server and nheqminer (also the zebra-mining fork) as the proof-of-work miner. This is explicitly a testing configuration. The s-nomp fork predates network upgrades from NU5 onward, and the tromp CPU solver in nheqminer produces only a few solutions per second. It is useful for exercising the RPC, not for competing for real blocks. The Zcash Foundation has described mining testnet blocks against Zebra using exactly this s-nomp fork in Experimental Mining Support in Zebra.

The rough shape is:

  • configure zebrad.toml with network = "Testnet", a transparent miner_address, and an rpc.listen_addr (testnet RPC conventionally uses port 18232),
  • sync zebrad to the testnet tip,
  • run s-nomp pointed at Zebra’s RPC port,
  • point nheqminer at the s-nomp Stratum port.

One detail worth noting: when using s-nomp, you set rpc.enable_cookie_auth = false in zebrad.toml, because that fork connects without the cookie authentication Zebra enables by default. The full walkthrough is in the Zebra book under the mining section.

other pool and miner software

The s-nomp + nheqminer pair is the example Zebra’s own documentation walks through, not the only option. Zebra exposes a generic getblocktemplate and submitblock interface, so in principle any pool-server software that speaks those RPCs can drive it. Other pool/stratum servers in the Zcash and Equihash-coin ecosystem include:

  • z-nomp, a NOMP fork aimed at Zcash and Zclassic,
  • NOMP (Node Open Mining Portal), the ancestor of the *-nomp family,
  • YIIMP, a PHP-based multi-algorithm pool,
  • Miningcore, a C#/.NET multi-coin engine with Equihash support.

There is an important caveat: availability is not the same as a working integration. Only the zebra-mining fork of s-nomp is documented as tested against Zebra, and s-nomp, z-nomp, NOMP, and Miningcore are each archived or unmaintained upstream (the linked commits are their last states, not active development). Any of these would need verifying against current Zebra and current Equihash (200, 9) before relying on it. Large commercial pools run proprietary stacks that are not public.

mining inside Zebra: the internal miner

There is a detail I glossed over when I said a node “is not a miner”: Zebra ships an experimental internal miner, so for solo or testnet use you can skip the external pool and miner entirely. The key is that the Equihash algorithm has two sides, and Zebra has access to both through the equihash crate published from librustzcash:

  • the verifier, equihash::is_valid_solution, which every node uses to check a block’s proof of work (zebra-chain/src/work/equihash.rs:89),
  • the solver, equihash::tromp::solve_200_9, a binding to Tromp’s optimized Equihash (200, 9) solver, which searches for a valid solution (equihash.rs:155).

The solver is gated behind a Cargo feature so it is not compiled into the default node: zebra-chain/Cargo.toml:34 reads internal-miner = ["equihash/solver"], and zebrad re-exports the same feature at zebrad/Cargo.toml:66. With that feature, Solution::solve (equihash.rs:134) drives the solver over a nonce range.

The component that ties it together is zebrad/src/components/miner.rs. It runs, in-process, the exact loop from the earlier code section: it calls get_block_template (miner.rs:282), runs Solution::solve on a blocking thread (miner.rs:578), and submits the solved block through submit_block (miner.rs:500). That is getblocktemplate -> solve -> submitblock with no s-nomp and no nheqminer. To use it, build with the feature and set a miner address:

cargo build --release --features internal-miner --bin zebrad

So why does the rest of this post still point at external miners? Two reasons, both visible in the code. First, the feature is labelled experimental (zebrad/Cargo.toml:65). Second, it is CPU-only and single-threaded per solver: the doc comment on solve notes it “uses 144 MB of RAM and one CPU core” and “can run for minutes or hours if the network difficulty is high” (equihash.rs:130). That is fine for a private chain, regtest, or testnet experiments, but it is not competitive with the GPU and ASIC solvers on mainnet. The solver it binds, Tromp’s, is the same family as nheqminer’s tromp CPU solver, so the performance ceiling is similar.

In other words, the choice is not “node or miner” but which solver and how much hardware: librustzcash’s equihash solver is enough to mine, and Zebra already wires it up, but production mining uses faster solvers and a pool layer to aggregate many of them.

what production miners run

Real Zcash mining uses different tools than the testnet setup. Zcash uses the Equihash proof of work with parameters (200, 9), which is memory-hard: for GPUs, VRAM bandwidth tends to matter more than raw compute, and reports suggest NVIDIA/CUDA cards are better optimized for it than AMD.

hardware and miner software

ASICs dominate the economics of Equihash (200, 9). In 2026 the machines most discussed are Bitmain’s Antminer Z15 family. Vendor and comparison sites quote the following (verify against current listings before buying anything):

model hashrate power efficiency
Antminer Z15 Pro ~840 kSol/s ~2780 W ~3.31 J/kSol
Antminer Z15 ~420 kSol/s ~1510 W ~3.6 J/kSol

ASICs run vendor firmware and connect straight to a pool over Stratum, so there is no separate miner program to install on them.

GPU mining is much less competitive against ASICs, but where it is done, the common GPU miners are closed-source and distributed as binaries, each taking a small developer fee skimmed from mining time:

  • lolMiner (around 0.7% dev fee), distributed through a binary-only releases repository,
  • GMiner (around 2% dev fee). Its release repository lists Equihash variants 144_5, 125_4, and 210_9, which are the parameter sets for coins like Bitcoin Gold and Beam, not Zcash’s own 200_9; treat GMiner as an Equihash-family miner and check the current build’s coin list before assuming it mines ZEC,
  • miniZ (around 2%, CUDA-focused). It is closed-source with no public code repository, and its former site miniz.ch no longer resolves to the project at the time of writing, so I am not linking it.

Two older miners, EWBF’s CUDA Zcash miner and Bminer, are largely unmaintained and have no canonical public source to link.

The one genuinely open-source miner in this list is nheqminer. Its source is the NiceHash repository at nicehash/nheqminer (archived in 2021), and the Zebra-compatible variant is the ZcashFoundation/nheqminer zebra-mining fork. Because the GPU miners above are closed-source, the linked repositories hold release binaries and documentation rather than buildable source.

pools

Miners point their Stratum connection at a pool, not at a node directly. Pool listings in 2026 include 2Miners, F2Pool, ViaBTC, AntPool, and Kryptex, with fees typically in the 0 to 1.5% range. As an example of the endpoint format, 2Miners publishes stratum+tcp://zec.2miners.com:1010 with regional variants. Pool availability changes over time, so it is worth confirming an endpoint is live and the pool still supports ZEC before committing hardware.

the wider mining industry, briefly

A few data points give context, with the caveat that the sources diverge and many are commercially motivated:

  • Market-size estimates vary widely. One projection puts the crypto-mining market near $31.76B in 2026, growing to about $62.29B by 2035 (a CAGR around 7.8%); other reports cite figures several times smaller. The spread suggests these are loose estimates.
  • Bitcoin’s hashrate set highs in January 2026, briefly crossing 1 ZH/s (1,000 EH/s), with forecasts pointing toward roughly 1.8 ZH/s by year end.
  • Reporting describes a profit squeeze in early 2026, with mid-generation hardware running below breakeven unless operators have access to power below about 5 cents per kWh, which is driving consolidation.
  • A recurring theme is mining firms repurposing data-center capacity and cheap power toward AI and high-performance computing workloads.

In my view, the directional trends here are more trustworthy than any single dollar figure or ROI claim, because the precise numbers come from sources with an interest in selling either hardware or a market-research report.

summary

Zcash mining is not one program but a chain of them. The node validates and serves block templates through getblocktemplate. The pool turns those templates into a stream of small, measurable work units and distributes them over Stratum. The miner, whether an ASIC running vendor firmware or a GPU running a miner such as lolMiner, does the Equihash search and submits shares. In Zebra’s code, both a locally submitted block and a gossiped one meet at the same Request::Commit verification path, and a committed best-tip block is re-announced to peers as an Inv hash. Knowing where each piece sits makes it clearer why “start a miner” is really several decisions: which node, which pool, and which hardware.

sources