Skip to content

LayerTwo-Labs/simplepool

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

6 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

simplepool

A small, single-binary solo-mining stratum server in pure C11. It accepts miner connections on TCP :3334, builds block templates via bitcoind's getblocktemplate, submits found blocks via submitblock, and records every accepted share into a local SQLite database. A separate Node.js dashboard reads a snapshot of that file for stats.

It is a solo pool with direct payouts: every coinbase has two outputs — the miner who found the block gets the reward (minus a small operator fee), and the configured operator_address gets the rest (default 1% = 100 basis points, configurable via fee_bps). Each connected miner gets its own coinbase rendered against the miner's own address; the merkle branches, prev-hash, ntime, etc. are shared.

There is no PPS, no inter-miner reward sharing, and no difficulty-weighted accounting. If your miner finds the block, your address gets ~99% of the subsidy + fees on-chain in the same coinbase transaction; if it doesn't, nobody on this proxy gets anything for that height. The shares and workers tables exist purely so the dashboard can show a leaderboard, per-worker drilldown, and historical "blocks found by the pool" view.

Stratum username convention

The mining.authorize username must start with the miner's Bitcoin address. Format:

<bitcoin_address>[.<rig_label>]
  • bitcoin_address is required and must be a valid bech32 (P2WPKH) or base58check (P2PKH / P2SH) address. It is parsed at authorize time and an invalid address is rejected with a clear error and logged in the rejects table.
  • rig_label is optional and lets a single miner (same address) have multiple rigs in the leaderboard as separate rows. Use anything alphanumeric plus _ -.
  • The password is discarded entirely. There are no accounts and no auth.

Examples: bc1qabc…, bc1qabc….basement-rig, bcrt1q…test.alice.

This is a sibling project to the Rust mining pool that lives elsewhere in this same monorepo. The two share nothing in code or goals: the Rust pool is a production-style PPS pool with payouts; simplepool is intentionally minimal and exists for solo mining + observability only.

Status: wired. The main binary loads config, connects to bitcoind, opens the SQLite store, builds an initial job from getblocktemplate, serves stratum on the configured port, and watches for new tips on a background thread.

Build

Dependencies: sqlite3, libcurl, pthread, plus a C11 compiler.

macOS:

brew install sqlite curl
make

Debian / Ubuntu:

sudo apt install build-essential libsqlite3-dev libcurl4-openssl-dev
make

The binary lands at build/simplepool.

Run

cp proxy.conf.example proxy.conf
# edit proxy.conf
./build/simplepool proxy.conf

Initialise the SQLite database from the shipped schema:

mkdir -p data
sqlite3 data/shares.db < schema.sql

Database & dashboard snapshot

The proxy is the only writer. The database lives at data/shares.db and runs in WAL mode, so a read-only consumer cannot block writes or corrupt the file.

For the dashboard we still recommend pointing it at a separate snapshot file rather than the live DB. This isolates the dashboard's query load from the proxy's writer and means a future code change on the dashboard side can never accidentally open the live file read-write.

Use SQLite's online backup — it is atomic and safe to run while the proxy is writing. A plain cp of a WAL'd database is not safe; always use .backup:

# one-shot
sqlite3 data/shares.db ".backup data/shares.snapshot.db"

Run it on a timer (cron / systemd-timer / launchd), e.g. every minute:

* * * * * sqlite3 /path/to/data/shares.db ".backup /path/to/data/shares.snapshot.db"

The dashboard reads data/shares.snapshot.db by default — see dashboard/README.md.

Config keys

operator_address = bc1q...   # required: recipient of the fee_bps cut
fee_bps          = 100       # 100 = 1%; valid range 0..1000 (max 10%)
coinbase_tag     = /simplepool/ # short string baked into the coinbase scriptSig

fee_bps = 0 disables the fee output (single-payout coinbase, all to the miner). If the computed fee would be below the relay dust threshold (~546 sats) the operator output is dropped automatically and the miner gets the full reward.

How shares are credited

One accepted share = one row in the shares table, tagged with the worker_id resolved from the (sanitized) stratum username — which now encodes the miner's payout address. The workers row stores payout_address separately so the dashboard can also roll up by address across multiple rigs.

If a share also satisfies the network target, it is additionally recorded in blocks_found with height, hash, finder_id, finder_address, reward_sats (paid to the miner), and fee_sats (paid to operator_address). The matching shares row has is_block = 1 and the block hash.

Run against local regtest

The repo ships a best-effort integration test that exercises the proxy end-to-end against a regtest bitcoind:

# bitcoind must already be running with -regtest, RPC on 127.0.0.1:18443,
# user/password "drivepool"/"drivepool" (or override with env vars).
chmod +x tests/test_integration.sh
./tests/test_integration.sh

The script:

  1. Skips with exit 0 if bitcoin-cli, nc, or sqlite3 are missing, or if no regtest node is reachable.
  2. Mines 101 blocks if needed so templates are non-empty.
  3. Writes tests/integration.proxy.conf and starts ./build/simplepool on 127.0.0.1:13334 with the DB at /tmp/simplepool-int.db.
  4. Sends a tiny mining.subscribe / mining.authorize / stale mining.submit sequence over nc, then SIGINTs the proxy.
  5. Asserts that workers has at least one row, workers.payout_address is populated, and rejects has at least one row.

For the broader stack flow (Docker compose, Rust pool, dashboard) see ../docs/TESTING.md.

Layout

Makefile             # build / clean / test / format / install
schema.sql           # SQLite schema (WAL, 4 tables — workers, shares,
                     # rejects, blocks_found)
proxy.conf.example   # key = value config
src/
  main.c             # entry point: config + bitcoind + store + stratum + tip watcher
  config.{c,h}       # tiny key=value config parser
  coinbase.{c,h}     # BIP34 coinbase tx builder; bech32 + base58check decoders
  log.{c,h}          # tiny pthread-safe stderr logger
  share.{c,h}        # share-validation math
  sha256.{c,h}       # vendored SHA-256
  stratum.{c,h}      # stratum v1 server
  store.{c,h}        # SQLite writer with batching
  bitcoind.{c,h}     # libcurl-based JSON-RPC client
  cjson/             # vendored cJSON (MIT) — see src/cjson/README.md
include/             # public headers (empty for now)
tests/               # unit tests + integration shell script

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages

  • C 89.3%
  • EJS 3.9%
  • JavaScript 3.4%
  • Makefile 1.3%
  • Shell 1.3%
  • CSS 0.8%