Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Architecture

dvb-WarpPool is a Rust workspace with 18 library crates + 6 binaries. A clear layering — from pure schema crates without async at the bottom up to the async-orchestrated binaries on top — keeps the system testable and lets sub-crates run standalone (e.g. the translator as a sidecar without the pool daemon).

Crate Overview

Schema + foundation (no async)

CrateWhat
warppool-profilesadmin profiles — capacity hints + defaults. TOML values klein / mittel / gross / enterprise (Small ≤5 / Medium ≤20 / Large ≤100 / Enterprise >100 miners). These are recommendations for tuning threads, RAM target, retention and poll intervals — not hard caps; all four are hot-switchable at runtime
warppool-configTOML schema (PoolConfig + sub-configs + secrets)
warppool-hwdetecthardware detection via sysinfo → profile recommendation

Core mining layer

CrateWhat
warppool-bitcoin-rpcJSON-RPC + ZMQ subscribe for Bitcoin Core
warppool-bitcoin-ipcopt-in Cap’n Proto mining client for Bitcoin Core >= 31 (low-latency template source; TemplateSource trait shared with the GBT path). Selected via [node].template_source = "ipc"; GBT stays the default, and Core’s own IPC mining interface is still experimental
warppool-job-buildercoinbase tx (BIP34 height, SegWit commitment, BIP54-forward-compatible locktime) + merkle tree + Stratum job construction
warppool-share-validatorshare PoW check + block-found detection
warppool-stratum-v1TCP listener, session state machine, VarDiff
warppool-stratum-v2NOISE handshake, binary framing, mining subprotocol
warppool-translatorV1↔V2 sidecar (a pool can offer Sv2 only; V1 miners connect via the translator)

Storage + API

CrateWhat
warppool-storageSQLite via sqlx, all tables + migrations
warppool-apiaxum HTTP API (REST + SSE stream + static UI serving)

Operational subsystems

CrateWhat
warppool-healthBitcoin Core multi-RPC health + bitcoin.conf parser + snippet generator
warppool-autoupdateversion parser + forge release client + atomic_swap + cosign hook
warppool-notifierpush sinks (ntfy/Telegram/Discord/Slack/email) + counter metrics
warppool-telemetryvendor API probes (AxeOS, NerdNOS, Avalon, …) + mDNS discovery + HTTP sweep + PoolMetrics
warppool-uninstallcross-platform uninstall plans (per-OS path tables, marker-gated Bitcoin Core targets)

Tools

CrateWhat
warppool-simulatorsim miners (vendor personas) + sim node + scenarios

Binaries

BinaryPurpose
dvb-warppool-daemonthe pool — orchestrates all subsystems
dvb-warppool-clioperator tools (hash-password, token-create, set-profile, check-update, …)
dvb-warppool-setupfirst-run wizard (axum, embedded HTML)
dvb-warppool-translatorV1→V2 sidecar (clap CLI, can run as a systemd service)
dvb-warppool-simsimulation runner (scenario list / run)
dvb-warppool-mac-launchermacOS app-bundle launcher (caffeinate wrapper, daemon supervision)

Data Flow (happy path)

                            +---------------------+
                            | Bitcoin Core        |
                            | (mainnet / regtest) |
                            +---------------------+
                              | RPC          | ZMQ pub
                              v              v
                            +---------------------+
                            | bitcoin-rpc         |
                            |  - getblocktemplate  |
                            |  - submitblock       |
                            |  - hashblock watch   |
                            +---------------------+
                                   |
                                   v
                            +---------------------+
                            | job-builder         |
                            |  - coinbase splits  |
                            |  - merkle tree      |
                            |  - StratumJob       |
                            +---------------------+
                                   |
                       +-----------+-----------+
                       v                       v
              +------------------+   +------------------+
              | stratum-v1       |   | stratum-v2       |
              |  TCP / TCP+TLS   |   |  TCP+NOISE       |
              +------------------+   +------------------+
                       |                       |
                       v                       v
               V1 miners (Bitaxe,       V2 miners / translator
               NerdNOS, AntminerS23)
                       |                       |
                       v                       v
                      submit (extranonce, nonce, ntime)
                                   |
                                   v
                            +---------------------+
                            | share-validator     |
                            |  - PoW check        |
                            |  - block detection  |
                            +---------------------+
                                   |
                       +-----------+-----------+
                       v                       v
              +------------------+   +------------------+
              | storage          |   | block-submit-loop|
              |  - record_share  |   |  - assemble block|
              |  - vardiff_state |   |  - submitblock   |
              +------------------+   +------------------+
                                              |
                                              v on success
                            +---------------------+
                            | notifier            |
                            |  fire BlockFound    |
                            +---------------------+
                              |    |    |    |    |
                              v    v    v    v    v
                            ntfy tg disc slack email

Daemon Task Topology

dvb-warppool-daemon is a Tokio multi-reactor with roughly a dozen long-lived tasks plus per-connection tasks. All tasks share one CancellationToken for graceful shutdown.

                  +------- main() async -------+
                  |                            |
                  +- spawn ---------+----------+
                          |         |
                  +-------+-------+ +------------------+
                  | Stratum-V1     | Stratum-V2        |
                  | accept loop    | accept loop       |
                  | (+ TLS)        | (+ NOISE)         |
                  +----------------+ +-----------------+
                          |               |
                  per connection: session task
                          |               |
                          v               v
                  +---------------------------------+
                  | block_found_tx broadcast        |
                  +---------------------------------+
                          |
                  +-------+--------+
                  | block_submit_  |
                  | loop           |
                  +----------------+
                          |
                  Bitcoin Core RPC submitblock
                          |
                  +-------+--------+
                  | notifier.notify(BlockFound)     |

                  +- spawn ---------+----------+
                  | job_refresh_loop                |
                  |  - poll getblocktemplate (60s)  |
                  |  - ZMQ hashblock fast path      |
                  |  - drain & build job            |
                  |  - push to stratum handles      |
                  +---------------------------------+

                  +- spawn ---------+----------+
                  | aggregate_loop (60s tick)       |
                  |  - storage.aggregate_5min       |
                  |  - SharesAccepted SSE event     |
                  +---------------------------------+

                  +- spawn ---------+----------+
                  | health_check_loop (60s tick)    |
                  |  - check_bitcoin_health         |
                  |  - SSE HealthSnapshot           |
                  |  - RpcDown/Recovered notify     |
                  +---------------------------------+

                  +- spawn ---------+----------+
                  | periodic_update_check (24h)     |
                  |  - Forge Releases API           |
                  |  - SSE UpdateAvailable          |
                  +---------------------------------+

                  +- spawn ---------+----------+
                  | miner_poll_loop (vendor probes) |
                  +---------------------------------+

                  +- spawn ---------+----------+
                  | HTTP API (axum on :18334)       |
                  |  - REST endpoints               |
                  |  - SSE /api/events              |
                  |  - static UI from --ui-dir      |
                  +---------------------------------+

(Later phases added further opt-in loops on the same pattern: UPnP lease renewal, the Anker-bridge supervisor, and the daily energy aggregation.)

Shared State

Tokio tasks share Arc<...> handles instead of global statics:

HandleTypeUsed by
notifier: Arc<Notifier>shared via cloneblock_submit_loop, health_check_loop, periodic_update_check, NotifierConnectionSink, API
pool_metrics: Arc<PoolMetrics>atomic countersNotifierConnectionSink, BitcoinRpc, API /metrics handler
event_bus: Arc<PoolEventBus>broadcast::Senderall subsystems publish; API → subscribers (SSE)
storage: Arc<Storage>sqlx poolshare recording, audit log, vardiff state, settings
snapshot: Arc<RwLock<NetworkSnapshot>>RwLock snapshotjob_refresh_loop writes; API reads
profile_kind: Arc<RwLock<ProfileKind>>hot-switchableAPI admin route + display
cancel: CancellationTokenpropagationall tasks (graceful shutdown)

Storage Schema

Core tables from phases 1-15; the schema is created programmatically from a bundled SCHEMA_SQL constant (CREATE TABLE IF NOT EXISTS …, run by init_schema() in crates/storage/src/lib.rs) — there are no sqlx file migrations. Later phases added the device tables (miners, miner-telemetry raw/5min), the per-day energy history (daily_aggregates) and goPool-import bookkeeping on the same pattern.

TablePhaseWhat
workers1worker list (user, last_seen_at, shares_accepted/rejected, blocks_found)
shares_raw1last hour of raw shares — basis for hashrate computation
shares_agg_5min1aggregated 5-minute buckets — hashrate chart data
blocks_found1block history (worker, height, hash, accepted, reject_reason)
wallet_lifetime8per-wallet lifetime accumulator (sum_difficulty, expected_blocks, since) backing the odds panel’s expected_blocks/p_zero; written batched from the aggregate loop, watermarked by pool_settings.wallet_lifetime_last_rowid
pool_settings2.5generic KV store (active_profile_kind, donation addresses, the v1.21.0 marquee banner text + expiry, etc.)
vardiff_state2.5per-worker VarDiff snapshots (current_diff, ema, last_share_unix)
audit_log3admin actions (actor, action, target, peer_ip, ok, details)
api_tokens3.2persistent bearer tokens (token_hash, name, scope, last_used_at, revoked_at)
admin_2fa3.3TOTP secrets per user (secret_base32, enabled)
push_subscriptions1web-push subscriptions (PWA)

Timestamp convention. DATETIME columns are written by SQLite itself (CURRENT_TIMESTAMPYYYY-MM-DD HH:MM:SS, space separator, UTC); pool_settings string values are written by Rust as RFC3339 (T separator). The two are not lexically comparable ('T' > ' '), so cutoffs against DATETIME columns must be computed inside SQL (datetime('now', '-N seconds')), never bound as Rust-formatted strings — a v1.0.16 bug filtered out every active worker exactly this way. The full rules live in the warppool-storage module docs.

The Sv2 Stack in Detail

Stratum V2 is a binary-framed protocol with a NOISE handshake in front. Two subprotocols matter in the pool context:

  1. Mining subprotocol (implemented) — channel-based, extranonce-aware, version rolling per BIP-320, per-channel set-target.
  2. Template Distribution Protocol (TDP; foundation in phase 7.6a, wiring in 7.6b deferred) — replaces getblocktemplate with push-driven template updates straight from the Bitcoin node.

Standard vs. extended channels. Both are supported; pick by role. Standard is header-only mining for end devices: the pool computes the merkle root and ships ready-to-hash work — smaller frames, no merkle work on the device. Extended hands the miner coinbase_prefix/suffix plus the merkle path and full extranonce rolling, which is what proxies, translators and farm aggregators need to manage their own search space (our own translator opens an extended channel upstream for exactly that reason). A single Bitaxe-class device gains nothing from extended — nonce + version rolling is ample search space for its hash rate — and pays with bigger jobs and per-job merkle work on the ESP32. Recommendation: standard channel for solo devices, extended for proxy/aggregation setups. VarDiff converges per connection either way; a difficulty difference between the two modes is just convergence state, not quality.

V1 miner ----TCP plain---->  stratum-v1 server  ----------+
                                                          |
V1 miner ----TCP+TLS------>  stratum-v1 TLS server -------+
                                                          |
                                                          v
                                              +------------------+
                                              |  job-builder     |
                                              |  share-validator |
                                              +------------------+
                                                          ^
                                                          |
V2 miner ----TCP+NOISE----> stratum-v2 server (sv2_listen,  +
                                       reference deployment :3336)
                                                          |
V1 miner ----TCP--+                                       |
                  |                                        |
                  v                                        |
     dvb-warppool-translator (sidecar) --TCP+NOISE--------+
       - SetupConnection
       - OpenExtendedMiningChannel with user_identity
       - SubmitSharesExtended with miner extranonce
       - receives NewExtendedMiningJob + SetNewPrevHash
       - maps to V1 mining.notify + slushpool prev_hash + BIP-320 version rolling

The concrete wire format lives in crates/stratum-v2/src/messages.rs with roundtrip tests. The NOISE handshake uses the SRI noise_sv2 stack (secp256k1 + EllSwift + Schnorr-signed certificates, spec-conformant since the Sv2 modernisation) — pure Rust, no OpenSSL.

Connection Lifecycle Hooks

Both Stratum servers expose an optional ConnectionSink trait. For authenticated workers (V1: mining.authorize, V2: OpenChannel with user_identity) on_authorized fires; on connection end on_disconnect.

The daemon implements a NotifierConnectionSink struct that implements both traits. A per-worker debounce (default 30s) keeps flapping miners from firing a notification on every reconnect. The same hooks feed the live worker sets (TLS/Sv2 badges) and the peer-IP refcount map used by the energy accounting (“Disconnected” devices are excluded from watts/kWh).

   v1.Session::run
         |
   handle_authorize → connection_sink.on_authorized
         |
   ... shares ...
         |
   loop exit → connection_sink.on_disconnect

   v2.handle_connection
         |
   process_frame → channels().iter() → new user_identity?
         |                                    → on_authorized
   ... shares ...
         |
   loop exit → for each notified_user → on_disconnect

Event Bus + SSE

PoolEventBus is a tokio broadcast channel with PoolEvent variants:

EventSource
BlockFoundblock_submit_loop
NewJobjob_refresh_loop
SharesAcceptedaggregate_loop
HealthSnapshothealth_check_loop (phase 13b)
UpdateAvailableperiodic_update_check (phase 8e)
ProfileSwitchedAPI admin profile hot-switch
HeartbeatSSE keepalive with the current subscriber count

The API’s /api/events opens one SSE stream per client with the current subscription set. The UI uses it for HealthBanner + UpdateBanner + toast events.

Configuration Layers

/etc/dvb-warppool/
├── config.toml         # non-sensitive settings
├── secrets.toml        # chmod 600 — admin hash, jwt secret, rpc pass, sv2 key
└── pool.db             # SQLite (path configurable)

Env vars for opt-in subsystems (see the Configuration Reference): WARPPOOL_HEALTH_CHECK_INTERVAL_SECONDS, WARPPOOL_AUTOUPDATE_REPO, WARPPOOL_DISCONNECT_DEBOUNCE_SECS, sink-specific tokens, etc.

Lifecycle (daemon boot)

  1. Parse CLI (clap) + ENV
  2. Load config.toml + (optional) secrets.toml
  3. Validate config (mining.payout_address required, ratelimit constraints, …)
  4. Init tracing subscriber (JSON when logging.json)
  5. Open SQLite + run migrations
  6. Resolve admin profile (persisted value in pool_settings beats the config default)
  7. Construct BitcoinRpc with with_metrics(pool_metrics)
  8. Probe RPC (warning on failure; the daemon still starts)
  9. Construct Notifier from config
  10. Construct PoolMetrics (Arc-shared)
  11. Build the initial job (or skip when RPC failed)
  12. Spawn the Stratum V1 listener (+ TLS listener when cert/key configured)
  13. Spawn the Stratum V2 listener when sv2_listen is configured (the authority key is auto-generated into secrets.toml if missing)
  14. Spawn job_refresh_loop (poll + optional ZMQ)
  15. Spawn block_submit_loop
  16. Spawn aggregate_loop (60s)
  17. Spawn health_check_loop when WARPPOOL_HEALTH_CHECK_INTERVAL_SECONDS > 0
  18. Spawn periodic_update_check when WARPPOOL_AUTOUPDATE_REPO + interval > 0
  19. Spawn the HTTP API (axum on status_listen)
  20. Install signal handlers (SIGTERM → cancel.cancel() → all tasks shut down)
  21. The tokio::main event loop runs until cancel

Crate Dependencies (DAG)

Simplified crate graph (not every edge; no cyclic deps):

profiles --+
hwdetect --+--> config
              |
              +--> storage
              +--> bitcoin-rpc --+
              +--> bitcoin-ipc   |
              +--> job-builder   |
              +--> share-validator
              +--> stratum-v1 -----+
              +--> stratum-v2 -----+
              +--> translator -----+
              +--> telemetry -----+
              +--> notifier ------+
              +--> autoupdate ----+
              +--> health --------+
              +--> uninstall -----+
              +--> api -----------+
              +--> simulator -----+
                                 |
                                 v
                  dvb-warppool-daemon
                  dvb-warppool-cli
                  dvb-warppool-setup
                  dvb-warppool-translator
                  dvb-warppool-sim
                  dvb-warppool-mac-launcher

Crates and their one-directional deps:

  • telemetry ← none (foundation for metrics)
  • bitcoin-rpctelemetry (for the RPC latency histogram, phase 16.3)
  • apinotifier + autoupdate + telemetry + storage + …
  • daemon → all library crates plus runtime deps (tokio, sqlx, …)

Testing Strategy

Three layers, plus one extra operator-driven layer:

LayerWhatTool
Unitpure logic per cratecargo test -p warppool-<crate>
Integrationmultiple crates in-process, axum mock serverstests/ per crate
Simagainst the real daemon process, vendor personasdvb-warppool-sim scenario <name>
Regtest E2Eagainst a real bitcoind regtestscripts/regtest-up.sh + --ignored (4 tests, opt-in)

As of v1.24.0: 1201 tests + 4 ignored, all green. UI side: pnpm svelte-check → 374 files, 0 errors.

See TESTING.md for details + the simulator workflow.