Skip to content

mkpool-mining/mkpool

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

28 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

mkpool

Modern multi-coin solo mining pool engine, written in C++23

Stratum V1 · Stratum V1 over TLS · native Stratum V2 (Noise-encrypted) · multiple coin families · one codebase

CI Fuzz CodeQL Release

License: GPLv3 C++23 Platform: Linux Stratum Benchmark GitHub stars

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).

Table of contents

Why mkpool?

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.

Benchmarks

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.

Supported coins

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

Features

Protocols and encryption

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

Coins, algorithms and merge mining

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)

Pool engine and architecture

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)

Difficulty and share handling

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

Security and operations

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

Quick start

1. Build (Ubuntu 24.04+)

# 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 -j
CMake 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

2. Configure

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 port

config.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).

3. Run

./build/mkpool --config config.json

Depending 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.

Runtime control (mkpool-ctl)

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 list

Full 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.

Scale-out roles

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

Bidirectional Stratum V2 ↔ V1 translator

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).

Cluster protocol (passthrough / node)

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.example

Proxy and redirector are single-hop and need no origin; passthrough and node require an origin running the cluster ingest.

Testing and hardening

Unit tests

cd build
ctest --output-on-failure -j

Sanitizer sweeps

scripts/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 instance

Fuzz the Stratum parser

scripts/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.sh

Architecture

flowchart 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
Loading

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.

Project scope

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).

Contributing

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. ⭐

Support the project

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

Acknowledgments

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.

Attribution and license

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.


⬆ back to top

If you run mkpool, find a block with it, or just like the engineering, a star is the easiest way to support the project.

Releases

Sponsor this project

Packages

Used by

Contributors

Languages