Stratum V1 · Stratum V1 over TLS · native Stratum V2 (Noise-encrypted) · multiple coin families · one codebase
Live pool · Benchmark report · Quick start · Contributing · Security policy
English | 简体中文 | Русский | Español | Português (Brasil) | Deutsch | Français | 日本語 | 한국어 | Türkçe
mkpool is a high-performance, multi-threaded solo mining pool engine. It speaks Stratum V1, Stratum V1 over TLS, and native Stratum V2 (Noise-encrypted), and runs multiple coin families out of a single codebase. It is live on mainnet today, powering mkpool.com; this README reflects the deployed state.
On an eight-core box it sustains over 330,000 fully validated shares per second at sub-millisecond latency and absorbs large reconnect storms, all measured by a fully reproducible open source benchmark (details below).
- Why mkpool?
- Benchmarks
- Supported coins
- Features
- Quick start
- Runtime control (
mkpool-ctl) - Scale-out roles
- Testing and hardening
- Architecture
- Project scope
- Contributing
- Support the project
- Acknowledgments
- Attribution and license
mkpool fully validates roughly 330,000 shares per second on an eight-core box, keeps submit-to-ack under a millisecond at every percentile, and absorbs the reconnect storms that rental marketplaces such as NiceHash and MiningRigRentals generate, on the order of 6,400 full connect cycles per second. Every share is validated end to end (coinbase rebuild, merkle root, 80-byte header, double SHA-256) before the pool answers.
TLS (stratum+ssl://) and native Stratum V2 with a Noise NX handshake and signed authority certificates are all handled in-process, with no stunnel and no external proxy. mkpool also carries a bidirectional Stratum V2 to V1 translator: point a Noise-encrypted SV2 ASIC such as a Bitaxe at any plain V1 pool, or point plain V1 rigs at an SV2-only pool. Because most mining hardware still speaks V1, this bridges an entire fleet in either direction, verified end to end on real hardware. (The SV2 miner-facing side supports standard channels for now.)
Every supported coin runs from the same codebase, each one a config file away, whether it uses SHA-256d, Scrypt, or Equihash. Mining is genuinely solo. A miner's username is their payout address, and the coinbase is rebuilt per session so a found block pays straight to the finder's wallet.
The engine is built to survive hostile traffic, with token-bucket rate limiting, automatic bans on invalid-share floods, an in-memory blacklist, stale-by-block and duplicate-share rejection, and a published fuzzing harness that hammers the Stratum parser. For day-to-day operation, RPC failover across multiple nodes with a primary-recovery watchdog, block-submit retry, and a JSON control socket (mkpool-ctl) let you watch who is mining and steer them live (client.reconnect, drop, log level) without a database round-trip or a restart, and SO_REUSEPORT keeps restarts nearly downtime-free. On Bitcoin, an optional build (MKPOOL_ENABLE_IPC, off by default) sources templates directly from Bitcoin Core over Cap'n Proto and relays found blocks through its low-latency submitSolution interface, with the standard ZMQ/GBT path as fallback.
The build includes unit tests, ASan/TSan/UBSan sanitizer runs, and a built-in Prometheus metrics endpoint, and uses CMake and Ninja. Every feature in this repository runs on mainnet across every supported chain, under real production load including rented hashrate.
mkpool ships with a fully reproducible Stratum benchmark: two identical 8-core boxes (Azure Standard_D8lds_v7), a single bitcoind regtest node, one load generator, and fixed difficulty 1. Every submitted share is fully validated (coinbase rebuild, merkle root, 80-byte header, double SHA-256) before the pool answers, so the figures below reflect real validated work rather than raw socket throughput.
| Scenario | Result |
|---|---|
| Sustained validated shares/s (128 to 2,048 connections) | ~315k to 337k |
| Median submit-to-ack latency (100 connections, light load) | 116 µs |
| 99th percentile latency | 602 µs |
| Reconnect cycles/s (200 parallel connect-subscribe-authorize-submit-close loops) | ~6,391 (4 errors) |
| Resident memory at 2k / 4k / 8k idle connections | 66 / 108 / 197 MiB |
These ratios hold their shape as load rises. Full methodology, charts, and the self-contained report are in the benchmark write-up and the raw HTML report, and the reproducible benchmark kit (load generator, orchestrator, configs) lets you run it yourself.
Tip when benchmarking mkpool yourself: use a real, valid payout address as the Stratum username. mkpool validates addresses locally at authorize time and early-rejects invalid usernames, which would unfairly skip the work being measured.
| Coin | Ticker | Algorithm | Notes |
|---|---|---|---|
| Bitcoin | BTC | SHA-256d | V1, TLS, SV2 |
| Bitcoin Cash | BCH | SHA-256d | CashAddr, V1/TLS/SV2 |
| BitcoinII | BC2 | SHA-256d | V1/TLS/SV2 |
| Bitcoin Cash II | BCH2 | SHA-256d | CashAddr, V1/TLS/SV2 |
| eCash | XEC | SHA-256d | Avalanche pre-consensus, SV2 |
| DigiByte | DGB | SHA-256d | V1/TLS/SV2 |
| Litecoin | LTC | Scrypt | merge-mines DOGE |
| Dogecoin | DOGE | Scrypt (AuxPoW) | merge-mined on LTC |
| Zcash | ZEC | Equihash 200,9 | mining.set_target, Blossom subsidy |
| Capability | Supported |
|---|---|
Stratum V1 (mining.*) |
✅ |
Stratum V1 over TLS (stratum+ssl://) |
✅ in-binary any_stream variant, SIGHUP cert reload |
Stratum V2 native (Noise NX handshake, encrypted) |
✅ full-block mode, collects fees |
| SV2 secret-authority key / signed certs | ✅ |
SV2 empty-block vs full-block toggle (v2EmptyBlocks) |
✅ |
BIP310 mining.configure (version-rolling negotiation) |
✅ |
ASICBoost / version-mask (version_mask) |
✅ validated (BIP310) |
subscribe-extranonce extension |
✅ |
Suggested difficulty (mining.suggest_difficulty, d= in password) |
✅ clamped per coin |
| Capability | Supported |
|---|---|
| Bitcoin (BTC, SHA-256d) | ✅ |
| Bitcoin Cash (BCH, SHA-256d, CashAddr) | ✅ |
| BitcoinII (BC2, SHA-256d) | ✅ |
| Bitcoin Cash II (BCH2, SHA-256d, CashAddr) | ✅ |
| eCash (XEC, SHA-256d + Avalanche pre-consensus) | ✅ |
| DigiByte (DGB, SHA-256d) | ✅ |
| Litecoin (LTC, Scrypt) | ✅ |
| Dogecoin merge-mined on LTC (AuxPoW) | ✅ parent + aux blocks |
Zcash (ZEC, Equihash 200,9, mining.set_target) |
✅ |
| Single codebase, per-coin config | ✅ one binary, every supported coin |
| Equihash share validation (in-process) | ✅ equihash.hpp + unit test |
| Blossom-aware subsidy / halving (ZEC) | ✅ |
| Capability | Supported |
|---|---|
| Language / standard | C++23 |
| Concurrency model | Single-process, async io_context worker pool (std::jthread) |
| Networking | Boost.Asio / Beast, per-session strand |
| Session map | Sharded (default 64 shards), low-contention broadcast |
| Per-session write path | Strand-bound WriteQueue + 1 MiB watermark (no async_write races) |
| Job/work window | JobWindow rolling buffer (default 32 jobs) keyed by job_id |
| New work on block change | ✅ full tx set, ZMQ-driven, no transactionless work |
| Periodic job rebroadcast (keepalive for strict clients) | ✅ 30s, resets on real blocks |
| ZMQ block-hash notification | ✅ edge-trigger bug fixed |
bitcoind failover (multiple local or remote nodes) |
✅ ordered rpcFallbacks + 30s primary-recovery watchdog |
| Block-submission retry on transport failure | ✅ retries only when the node returned no answer (never on a real result) |
| Redundant block propagation (extra submit nodes) | ✅ additionalSubmitEndpoints, fire-and-forget, never gates the primary submit |
| Bitcoin Core mining IPC (BTC) | ✅ optional (MKPOOL_ENABLE_IPC): Cap'n Proto template source, node-precomputed merkle branch, low-latency submitSolution block relay; ZMQ/GBT fallback |
| Solo coinbase (miner address = username) | ✅ per-session coinbase2 rebuild |
| Operator fee / donation from coinbase | ✅ configurable %, incl. aux/DOGE split |
| Custom coinbase signature | ✅ configurable |
| Proxy mode | ✅ TLS uplink; multi-upstream hot-standby + active/active |
| Passthrough / node / redirector modes | ✅ all three, over an mkpool-native TLS cluster protocol (trunk multiplexing + loopback-bridge origin ingest; node adds local block submission; weighted, health/latency-aware redirector) |
| Graceful low-downtime restart | ✅ zero-downtime 2-slot swap (SO_REUSEPORT + staggered client.reconnect) |
| Capability | Supported |
|---|---|
| Vardiff (EMA / decaying-average) | ✅ |
| Per-coin vardiff ranges | ✅ (e.g. BTC/BCH/BC2/BCH2/DGB/XEC [1024, 1M], ZEC [8192, 524288]) |
| Fixed-difficulty tiers (one TCP port each) | ✅ e.g. 10M / 50M / 100M ports |
Custom d= clamp (1024-10M) |
✅ |
| Stale-by-block share rejection | ✅ prevhash checked against current tip |
| Duplicate-share rejection | ✅ in-memory dedupe set, cleared per block |
| ntime validation (BIP113-compatible) | ✅ utils::valid_ntime |
int64_t coinbase value (overflow-safe) |
✅ end-to-end |
| Local address validation (no RPC per authorize) | ✅ BIP173/BIP350/base58/CashAddr decoders |
| Capability | Supported |
|---|---|
| Token-bucket per-IP rate limiting | ✅ |
| Auto-ban on excessive invalid shares | ✅ |
| In-memory IP blacklist | ✅ |
| Connection-drop observability (per-disconnect logs) | ✅ reason/worker/lifetime/shares |
| Runtime control / admin socket | ✅ mkpool-ctl (21 JSON commands) |
client.reconnect (move miners without an operator-side drop) |
✅ broadcast or per-client, via the control socket |
| Live in-process stats via socket (hashrate 1m/5m, best-share-of-round, idle secs) | ✅ per miner / worker / user / pool, computed on demand from vardiff (no DB hit) |
| Idle / dead-worker detection + optional reap | ✅ opt-in idleDropSeconds |
| Database resilience (auto-reconnect + zero-loss requeue) | ✅ |
| Prometheus metrics endpoint | ✅ optional (MKPOOL_ENABLE_METRICS) |
| Sanitizer builds (ASan / TSan / UBSan) | ✅ CMake options + scripts/run_sanitizers.sh |
| Unit tests (Catch2 / Catch-style) | ✅ merkle, vardiff, address, SV2 noise, etc. |
| Stratum fuzzing harness | ✅ scripts/fuzz_*.sh (7 abuse categories, daemon-survival assertions) |
| Build system | CMake + Ninja |
| Platform | Linux (Ubuntu 24.04+) |
| External dependencies | Boost, OpenSSL, libpq/pqxx, libzmq, libsodium |
# system deps
sudo apt update
sudo apt install -y build-essential cmake ninja-build pkg-config git \
libboost-system-dev libboost-thread-dev libboost-program-options-dev \
libssl-dev libpq-dev libpqxx-dev libzmq3-dev cppzmq-dev libsodium-dev libsecp256k1-dev
# clone + configure + build (C++23)
git clone https://github.com/Mecanik/mkpool.git && cd mkpool
cmake -S . -B build -GNinja -DCMAKE_BUILD_TYPE=RelWithDebInfo
cmake --build build -jCMake options
| Option | Default | Description |
|---|---|---|
MKPOOL_BUILD_TESTS |
ON |
Catch2 unit tests |
MKPOOL_ENABLE_LTO |
ON |
Link-time optimization |
MKPOOL_ENABLE_TLS |
ON |
OpenSSL TLS context support |
MKPOOL_ENABLE_METRICS |
ON |
Prometheus exposer |
MKPOOL_ENABLE_IPC |
OFF |
Bitcoin Core mining IPC (Cap'n Proto), BTC-only |
MKPOOL_ENABLE_ASAN |
OFF |
AddressSanitizer |
MKPOOL_ENABLE_TSAN |
OFF |
ThreadSanitizer |
MKPOOL_ENABLE_UBSAN |
OFF |
UndefinedBehaviorSanitizer |
MKPOOL_ENABLE_NATIVE |
OFF |
-march=native |
You need a synced coin node (with ZMQ block notifications enabled) and a reachable PostgreSQL instance.
cp config.json.example config.json
# then edit config.json:
# - RPC host/credentials for your coin daemon(s)
# - PostgreSQL credentials
# - YOUR donation/payout addresses (never keep the placeholders)
# - stratum tiers/ports, vardiff ranges, optional TLS cert paths, optional SV2 portconfig.json.example documents every field the loader understands, including fixed-difficulty tiers, TLS tiers ("tls": true), Stratum V2 settings, and LTC+DOGE merge mining (aux block).
./build/mkpool --config config.jsonDepending on config, a running pool exposes Stratum V1 on vardiff and fixed-difficulty tiers (one port each, for example 3331 for vardiff and 3335 for fixed-10M); Stratum over TLS on any tier marked "tls": true, speaking stratum+ssl:// on its port; native Stratum V2 (Noise) on stratumV2Port (for example BTC 3340); and, when built with metrics, a Prometheus endpoint on metricsListenPort (default 9090). Miners connect with their payout address as username, and the block reward goes straight to that address.
Each instance opens a private Unix control socket (default /run/mkpool/<instance>.sock; set controlSocket to override, or "off" to disable). Query and steer a live pool with the bundled scripts/mkpool-ctl.py (shown below as mkpool-ctl), no restart, no database round-trip:
mkpool-ctl -i btc-mainnet stats # uptime, connections, pool hashrate, per-coin template + best share
mkpool-ctl -i btc-mainnet clients # every connection: ip, worker, diff, hashrate, idle secs
mkpool-ctl -i btc-mainnet workers # aggregated per address.worker
mkpool-ctl -i btc-mainnet users # aggregated per payout address
mkpool-ctl -i btc-mainnet getclient 42 # one connection in detail
mkpool-ctl -i btc-mainnet reconnect # client.reconnect to every miner (e.g. before maintenance)
mkpool-ctl -i btc-mainnet dropclient 42 # disconnect one miner
mkpool-ctl -i btc-mainnet loglevel debug # change log level live
mkpool-ctl -i btc-mainnet healthcheck # template freshness per coin
mkpool-ctl -i btc-mainnet help # full command listFull command set: ping, help, version, uptime, stats, clients, workers, users, getclient, getuser, getworker, userclients, workerclients, loglevel, reconnect, reconnclient, dropclient, dropall, resetshares, blacklistreload, healthcheck. Every reply is JSON. Hashrate, best-share-of-round and idle time are maintained in-process (derived from the share-rate vardiff already tracks) and read on demand, so listing 50k workers costs nothing until you ask. The socket is created 0600 (owner-only); with one process per coin under systemd, each coin gets its own socket.
Beyond the default solo pool engine, mkpool can run as one of four distribution roles, selected with global.role. Each has a fully commented example config in the repo root, and every role other than pool is stateless (no database or blacklist).
| Role | What it does | Example |
|---|---|---|
proxy |
Terminates downstream miners and relays their work to one or more upstream pools, subdividing the upstream extranonce per miner and forwarding qualifying shares. Multi-upstream hot-standby (failover) or weighted activeactive; the upstream owns payout and block submission. Also translates between Stratum V2 and V1 in either direction (see below). |
config.proxy.json.example |
redirector |
Accepts a miner, immediately client.reconnects it onto one of several backends, then closes. Round-robin / random / latency steering with active health probing. |
config.redirector.json.example |
passthrough |
Edge aggregator: terminates miners regionally and multiplexes all of them over one TLS trunk to an mkpool origin, which keeps full per-miner accounting. Collapses many public miner connections into a handful of trunks. | config.passthrough.json.example |
node |
A passthrough that also runs a local bitcoind: the origin streams found blocks down the trunk and the node submits them locally for geographically redundant / faster propagation. |
config.node.json.example |
The proxy translates both directions between Stratum V2 and Stratum V1, so mixed-protocol fleets and pools interoperate transparently. Verified end-to-end on real hardware (a Bitaxe over encrypted Noise) and against mkpool's own SV2 pool.
| Direction | What it enables | Example |
|---|---|---|
| SV2 miners → V1 pool | Point a modern, Noise-encrypted Stratum V2 ASIC at any plain Stratum V1 pool. The proxy runs the SV2 Noise handshake + certificate, serves a standard mining channel, and translates jobs/shares to V1. | config.sv2-to-v1.json.example |
| V1 miners → SV2 pool | Point plain Stratum V1 rigs (which is most hardware) at an SV2-only pool. The proxy opens an SV2 extended channel upstream, subdivides its extranonce across your V1 miners, and translates shares to SubmitSharesExtended. |
config.v1-to-sv2.json.example |
The SV2 miner-facing side (SV2 → V1) currently supports standard mining channels; the SV2 pool-facing side (V1 → SV2) uses extended channels. The upstream-SV2 transport is plaintext SV2 for now (a Noise-encrypted upstream is a follow-up).
Passthrough and node speak a versioned, TLS-capable trunk protocol of mkpool's own. The origin is an ordinary pool that opts in with a per-coin cluster.ingestPort; it bridges each multiplexed miner to its own local stratum port, so clustered miners are served exactly like direct ones with no change to the solo path. The trunk hello carries a shared cluster.token that must match on both ends; set cluster.rawblockZmq on the origin (pointed at its node's rawblock feed) to enable node block relay. See config.cluster-origin.json.example for the origin side.
# origin: an ordinary pool with cluster ingest enabled (keeps the database + accounting)
mkpool -c config.cluster-origin.json.example
# edge: aggregates miners and trunks them to the origin's ingest port
mkpool -c config.passthrough.json.exampleProxy and redirector are single-hop and need no origin; passthrough and node require an origin running the cluster ingest.
cd build
ctest --output-on-failure -jscripts/run_sanitizers.sh builds the unit tests under AddressSanitizer, UndefinedBehaviorSanitizer, and ThreadSanitizer in a throwaway .san/ directory (your normal build/ is left untouched) and reports any findings.
./scripts/run_sanitizers.sh # asan+ubsan and tsan
./scripts/run_sanitizers.sh asan # a single flavor
./scripts/run_sanitizers.sh --fuzz # also fuzz a sanitized instancescripts/fuzz_*.sh throw malformed and abusive Stratum traffic at a running pool and assert it survives (same PID before and after) with no handler exceptions. Point them at a local instance:
# quick malformed-frame battery
HOST=127.0.0.1 PORT=3331 ./scripts/fuzz_stratum.sh
# full suite: malformed JSON, protocol abuse, share spam, auth abuse,
# slowloris, version-rolling abuse, binary noise
HOST=127.0.0.1 PORT=3331 ./scripts/fuzz_suite.shflowchart LR
N["bitcoind / node<br/>(RPC + ZMQ)"] -- "ZMQ hashblock + GBT" --> G[Generator]
G -- JobPtr --> S[Stratifier]
S --> PM["PoolManager<br/>(sharded session map)"]
PM -- "notifyNewJob<br/>(per-session strand)" --> CS["ClientSession × N<br/>V1 / TLS / SV2 Noise"]
RL[RateLimiter] --- CS
CS -- "accepted shares" --> DB["DB worker<br/>(PostgreSQL)"]
M[Miners] <--> CS
IoPool runs N worker io_contexts (default hardware_concurrency()). Each ClientSession lives on one of those workers behind an Asio strand, its socket type (plain, TLS, or SV2 Noise) abstracted behind any_stream and every write routed through a strand-bound WriteQueue. On each JobPtr, PoolManager walks the shards and dispatches notifyNewJob to every session's strand. The Generator rebroadcasts the current job every 30 seconds as a keepalive (with clean_jobs=false, so no work is discarded), which stops strict clients such as rental-marketplace proxies and farm controllers from idle-disconnecting between blocks.
This repository is the pool engine, published for transparency. The operational stack that surrounds it in production (the database/analytics service, the public REST API, and the website) is not part of this open release.
mkpool is an original codebase. The async C++ engine, multi-coin support, Stratum V2 (Noise) and TLS stack, per-miner solo coinbase construction, and security tooling were all written from scratch. The one component that intentionally borrows from ckpool (Con Kolivas' GPLv3 C pool) is the variable-difficulty retarget math, a small, attributed re-implementation of a well-proven algorithm (see Attribution and license).
Contributions are welcome: bug reports, protocol edge cases, new coin families, performance work, documentation, and translations of this README. Please read CONTRIBUTING.md before opening a pull request, and for anything security-related follow SECURITY.md rather than opening a public issue. If mkpool is useful to you, starring the repo genuinely helps other people find it. ⭐
mkpool is free and open source. There is no fee to use the code and no built-in donation skim. If the project has been useful to you and you would like to chip in toward its development, you can send a tip here. It is entirely optional and very much appreciated.
BTC: bc1qlugz6as6x3n03c6x8zddpnmypsaucdmh3lc5z0
mkpool is built on top of a lot of excellent open source work. A sincere thank you to the maintainers and contributors of every project below. The pool would not exist without them.
| Library | License | Used for |
|---|---|---|
| Boost (Asio / Beast) | BSL-1.0 | Async networking, strands, HTTP RPC client |
| OpenSSL | Apache-2.0 | TLS, SHA-256 |
| fmt | MIT | Hot-path Stratum formatting |
| spdlog | MIT | Logging |
| nlohmann/json | MIT | Config and RPC JSON |
| cxxopts | MIT | Command-line parsing |
| libpqxx / libpq | BSD-3-Clause / PostgreSQL | Database access |
| ZeroMQ (libzmq + cppzmq binding) | MPL-2.0 / MIT | Block-hash notifications |
| libsodium | ISC | Stratum V2 Noise crypto |
| libsecp256k1 | MIT | EC keys / signatures (SV2) |
| Catch2 | BSL-1.0 | Unit tests |
| prometheus-cpp | MIT | Optional metrics endpoint |
All of these are under GPLv3-compatible licenses. mkpool does not vendor (copy) their source; they are linked from your system package manager or fetched by CMake at build time. If you distribute a compiled mkpool binary, ship a THIRD-PARTY-NOTICES file reproducing these projects' copyright and license texts alongside it.
mkpool is original software, © 2025-2026 Mecanik1337 (contact@mecanik.dev), licensed under the GNU General Public License v3.0 (GPL-3.0). Every source file carries the full GPLv3 header.
Almost all of the codebase (the async engine, multi-coin support, Stratum V2 (Noise) and TLS, solo coinbase construction, and security tooling) is written from scratch and owes nothing to ckpool beyond being the same kind of program.
The single exception, disclosed for honesty and license compliance: the variable-difficulty retarget math in vardiff.cpp / vardiff.hpp re-implements ckpool's decay_time() (src/libckpool.c) and time_bias() / add_submit() (src/stratifier.c) by Con Kolivas (also GPLv3). That is the only part adapted from ckpool; no ckpool C source files are vendored or copied verbatim, and a few Stratum field conventions (e.g. 4-byte extranonce1) simply follow common practice. The runtime control socket's command names (stats, clients, workers, reconnect, …) mirror ckpool's for operator familiarity, but the command dispatch, JSON wire format, and implementation are entirely original. These are attributed inline. Because mkpool is GPLv3, this re-use is fully permitted; if you redistribute mkpool, keep it under GPLv3, retain these attributions, and ship the full license text (COPYING).
ckpool: https://bitbucket.org/ckolivas/ckpool, © 2014-2026 Con Kolivas.
If you run mkpool, find a block with it, or just like the engineering, a star is the easiest way to support the project.