A self-hosted Bitcoin solo hunter node for small LAN fleets. Zero fee — 100% of every block reward goes directly to the miner's own address.
License: non-commercial use only. See LICENSE and NOTICE.md for provenance.
| Component | Technology | Port |
|---|---|---|
| Stratum server | Rust + Tokio | 3333 |
| REST API | Axum | 8080 (standalone) / 8081 (bridge) |
| Dashboard | React + Nginx | 3334 (host) |
| Bitcoin node | Bitcoin Core via RPC + ZMQ | (external) |
| Database | SQLite | (Docker volume) |
git clone https://github.com/RulezZzOr/BlockFinderPool.git
cd BlockFinderPool
bash setup-blackhole.shThe installer auto-detects your Bitcoin Core container, reads RPC credentials
and ZMQ settings, writes env/.env, builds, and starts everything.
git clone https://github.com/RulezZzOr/BlockFinderPool.git
cd BlockFinderPool
bash setup-blackhole.shIf Bitcoin Core is running locally (native or Docker) the installer finds it automatically. For a remote node it will ask for the IP and credentials.
bash setup-blackhole.sh --unattended
# Then edit env/.env and run: bash run.sh| Step | Action |
|---|---|
| 1 | Checks Docker + Docker Compose |
| 2 | Detects local/standalone deployment mode |
| 3 | Finds Bitcoin Core (Docker container or native) |
| 4 | Reads RPC credentials from bitcoin.conf or node config |
| 5 | Tests RPC connectivity |
| 6 | Optionally adds ZMQ lines to bitcoin.conf and restarts Core |
| 7 | Asks for your payout Bitcoin address |
| 8 | Writes env/.env with all discovered values |
| 9 | Selects the correct docker-compose overlay for your platform |
| 10 | Builds pool + dashboard with git provenance |
| 11 | Starts services |
| 12 | Verifies health and prints a status report |
git clone https://github.com/RulezZzOr/BlockFinderPool.git
cd BlockFinderPoolcp env/.env.example env/.env
# Edit env/.env — at minimum set RPC_URL, RPC_USER, RPC_PASS, PAYOUT_ADDRESSAutomatic setup:
bash run.shStandalone:
docker compose -f docker-compose.yml -f docker-compose.standalone.yml up -dSolo hunter profile:
docker compose -f docker-compose.solo.yml up -d --build
# Optional dashboard:
docker compose -f docker-compose.solo.yml --profile dashboard up -d --buildCPU pinning for a 4-core host:
export POOL_CPUSET=2-3
docker compose -f docker-compose.solo.yml up -d --buildIf your Bitcoin Core runs as a container, pin it to the first two cores:
docker update --cpuset-cpus="0,1" bitcoindIf Bitcoin Core runs as a native system service, pin it with taskset:
taskset -cp 0,1 $(pidof bitcoind)This keeps Bitcoin Core on 0-1 and BlockFinder on 2-3, which is usually the cleanest split for a 4-thread box.
docker compose logs -f blackhole-poolYou should see:
INFO stratum listening on 0.0.0.0:3333
INFO ZMQ block connected: tcp://...
INFO new template: height=... txs=... ...
Configure your miner with:
| Field | Value |
|---|---|
| URL | stratum+tcp://YOUR_POOL_HOST:3333 |
| Username | YOUR_BITCOIN_ADDRESS (e.g. bc1q...) |
| Password | (anything — leave blank or type x) |
Important: Set your Bitcoin address as the username. The pool automatically uses it as the coinbase payout address, so the block reward goes directly to your wallet. If the username is not a valid Bitcoin address, the pool falls back to the
PAYOUT_ADDRESSset inenv/.env.
All settings live in env/.env. Copy env/.env.example to env/.env as shown above.
- Open your node's Bitcoin Core settings or node dashboard
- Find the RPC credentials and ZMQ settings
- Or read them directly from the host:
# SSH into your Bitcoin node host, then:
grep -E "rpc|zmq" /path/to/bitcoin.conf.env variable |
Where to find it |
|---|---|
RPC_URL |
http://<node-ip>:8332 |
RPC_USER |
The RPC user from bitcoin.conf |
RPC_PASS |
The long base64 string in bitcoin.conf under rpcpassword= |
ZMQ_BLOCKS |
tcp://<node-ip>:28334 |
ZMQ_TXS |
tcp://<node-ip>:28336 |
ZMQ is required for low-latency block notifications (reduces stale shares).
Add to your node's bitcoin.conf:
zmqpubhashblock=tcp://0.0.0.0:28334
zmqpubhashtx=tcp://0.0.0.0:28336Then restart Bitcoin Core.
If your Bitcoin Core node is on the same host or LAN, set RPC_URL and ZMQ_*
to the node's actual IP or 127.0.0.1 when using host networking.
The recommended approach is to set the miner's username to a Bitcoin address.
The pool then uses that address automatically — no .env change needed per miner.
The PAYOUT_ADDRESS in .env is the fallback used when the miner's username
is not a recognisable Bitcoin address (e.g. a plain worker name like rig1).
Supported address formats:
- Bech32 (P2WPKH / P2WSH):
bc1q.../bc1p... - P2SH:
3... - P2PKH (legacy):
1...
Open http://YOUR_POOL_HOST:3334 in a browser to see:
- Live hashrate chart
- Connected miners table
- Shares accepted / rejected
- Found blocks log
- Network difficulty & estimated time to block
BlockFinder includes a solo-hunter path tuned for small LAN fleets and block discovery:
mining.submitupdates raw best-share counters immediately after the hash is known- block candidates are detected on the fast path before slow persistence work
- candidate data is written to the
block_candidatesSQLite table even whenPERSIST_SHARES=false - the dashboard shows raw best, accepted best, current-block best, previous-block best, and a recent candidate log
Recent Block Windows show the best share seen by BlockFinder during each Bitcoin network block interval. These are not found blocks unless the share reaches network difficulty and appears in Block Candidates / Blocks Found.
This mode is intended for home users with 1–5 miners on the same LAN. It favors low-latency block discovery and honest reporting over public-pool accounting features.
When a share reaches or exceeds the Bitcoin network target, BlockFinder:
- updates raw best-share metrics immediately
- spawns the high-priority
submitblockpath immediately - persists a forensic row into
block_candidates - updates the final
submitblock_resultand RPC latency after the call returns
The candidate record stores the submit-time forensic details needed for post-mortem analysis: worker, payout address, session id, job id, height, prevhash, ntime, nonce, version, version-rolling mask, extranonce values, merkle root, coinbase hex, block header hex, full block hex when available, block hash, submitted difficulty, network difficulty, share difficulty, submitblock result, RPC latency, and RPC error.
This happens independently of PERSIST_SHARES=false, so a solo hunter can keep the block-candidate audit trail without retaining every accepted share.
Donation address:
bc1pzvqagy932kmts9rluzpq39upk0hnttz22gdyeslf8lpc4aepyrqslfds96
Feel free to use it. We are all one family.
The REST API is available on port 8080 in standalone mode, or 8081 when
running through the Docker bridge setup:
| Endpoint | Description |
|---|---|
GET /health |
Health check |
GET /pool |
Pool stats (fee, connected miners, hashrate) |
GET /miners |
Per-worker stats |
GET /hashrate |
Hashrate history |
GET /blocks |
Found blocks |
GET /block-candidates |
Recent block candidates and submitblock results |
GET /network |
Network difficulty, block height |
GET /metrics |
Internal counters (ZMQ, jobs, stales…) |
Example:
curl http://localhost:8080/pool | python3 -m json.toolTo wipe all share/block data and restart clean:
bash reset-pool.shRun this from the
BlockFinderdirectory. You may needsudodepending on Docker volume permissions.
Bitcoin Core is not reachable. Check:
RPC_URL,RPC_USER,RPC_PASSinenv/.env- Bitcoin Core is fully synced
- Firewall allows port 8332 from the pool container
- Verify ZMQ is enabled and
ZMQ_BLOCKSpoints to the correct IP/port docker compose logs pool | grep ZMQshould show "ZMQ block connected"
- Check that
mining.authorizesucceeds in the pool logs - Verify the Stratum port (3333) is reachable from the miner
- Check
docker compose logs pool | grep SUBMITfor the rejection reason - Common causes: stale block (ZMQ latency), nTime out of range, witness commitment mismatch
BlockFinder/
├── docker-compose.yml — Compose service definitions
├── env/
│ ├── .env — Your config (gitignored)
│ └── .env.example — Template with instructions
├── pool/ — Rust backend (Stratum + API)
│ ├── Dockerfile
│ └── src/
│ ├── main.rs
│ ├── config.rs — Environment variable parsing
│ ├── stratum/mod.rs — Stratum v1 server
│ ├── template/mod.rs — GBT fetching + coinbase building
│ ├── share/mod.rs — Share validation
│ ├── api/mod.rs — REST API (Axum)
│ ├── metrics.rs — In-memory stats
│ ├── vardiff.rs — Auto difficulty adjustment
│ ├── rpc.rs — Bitcoin Core RPC client
│ └── storage/ — SQLite + Redis (optional)
├── dashboard/ — React frontend
│ ├── Dockerfile
│ ├── nginx.conf
│ └── src/
└── reset-pool.sh — Wipe DB and restart
AUTH_TOKENis empty by default — any miner that can reach port 3333 can connect. Set it inenv/.envif you want password-protected access.- Set
REQUIRE_AUTH_TOKEN=trueto make the pool refuse to start ifAUTH_TOKENis not set — useful as a safety net in network-facing deployments. - The pool is designed for a personal home node. Do not expose port 3333 to the internet unless you understand the implications.
- Never commit
env/.envto version control — it is listed in.gitignore.
Recommended: v30.2.0 or later.
Measured GBT tail latency (p95):
- v30.0.0: ~80ms (occasionally spikes to 565ms)
- v30.2.0: ~50ms (more consistent)
Upgrade your node using its normal update path.
| Value | Behaviour | When to use |
|---|---|---|
false (default) |
No share rows written; worker_best still persisted |
High hashrate, NVMe not a concern |
true |
Every accepted share stored in SQLite | Forensic audit trail, historical analysis |
Disk budget: ~200 bytes/share → ~300 MB per million shares (~69 h at 240 TH/s).
For a 1-5 miner LAN node, the recommended defaults are:
SOLO_MODE=true
PERSIST_SHARES=false
PERSIST_BLOCKS=true
PERSIST_BEST=true
BEST_PERSIST_INTERVAL_SECS=10
TARGET_SHARE_TIME_SECS=15
VARDIFF_RETARGET_SECS=30
MIN_DIFFICULTY=8192
MAX_DIFFICULTY=16777216
STRATUM_START_DIFFICULTY=32768
RECONNECT_RECENT_SECS=15
EXTRANONCE1_SIZE=4
EXTRANONCE2_SIZE=8Controls how often ZMQ TX events trigger a getblocktemplate call.
The dedup layer absorbs ~86% of responses as identical, so miner job rate
is largely independent of this value. The main effect is Bitcoin Core CPU load.
| Value | GBT calls/min | Core data | Use case |
|---|---|---|---|
| 1000 | ~66 | ~3.2 MB/s | Maximum fee freshness |
| 1500 | ~44 | ~2.1 MB/s | Balanced (recommended) |
| 3000 | ~22 | ~1.1 MB/s | Reduce Core CPU, minimal freshness cost |
After a new block, ZMQ TX events are suppressed for this many ms to avoid hammering Bitcoin Core while it processes the new mempool burst. Wire-measured: mempool often stabilises 10–12 s after a block, but solo hunting should not suppress TX refreshes too long after a new block. Default: 8000 ms. Use 5000 ms only on a strong local Bitcoin Core/NVMe host; increase to 12000-20000 if Core CPU spikes after blocks.
For a perfectly coordinated job pipeline, keep these equal:
NOTIFY_BUCKET_REFILL_MS = ZMQ_DEBOUNCE_MS
This ensures every new template from Bitcoin Core finds a token ready in the per-miner bucket — no artificial delays, no wasted refills.
The REST API is available on port 8080 in standalone/solo mode, or 8081 when routed through the Docker bridge setup.
| Endpoint | Description |
|---|---|
GET /health |
Health check {"ok":true} |
GET /pool |
Full pool stats (hashrate, miners, blocks, counters) |
GET /miners |
Per-worker stats (hashrate, shares, best diff, RTT) |
GET /blockfinder/miners |
Enriched miner list with firmware, session info |
GET /blockfinder/connection-status |
RPC + ZMQ + Stratum live connectivity |
GET /blockfinder/template-info |
Current block template (height, txs, target) |
GET /blockfinder/mempool |
Live mempool info from Bitcoin Core |
GET /blocks |
Found blocks history |
GET /build-info |
Build provenance (git SHA, timestamp, image ID) |
Quick check:
curl http://localhost:8081/pool | python3 -m json.tool
curl http://localhost:8081/blockfinder/connection-status | python3 -m json.tool# Live pool logs
docker compose logs -f blackhole-pool
# Container health
docker ps --filter "name=blackhole"
# Pool stats snapshot
curl -s http://localhost:8081/pool | python3 -m json.tool
# Miner list
curl -s http://localhost:8081/blockfinder/miners | python3 -m json.tool
# Connection status (RPC + ZMQ + Stratum)
curl -s http://localhost:8081/blockfinder/connection-status | python3 -m json.tool
# Check for errors in logs
docker logs blackhole-blackhole-pool-1 2>&1 | grep -E "ERROR|WARN|BLOCK"