dvb-WarpPool — open-source solo mining pool written in Rust (Umbrel / macOS / Windows / Linux)

A certificate renewed itself and nobody noticed — which is the whole point

Quick intro for anyone new: dvb-WarpPool is a from-scratch Rust solo Bitcoin mining pool you
host yourself. The block reward goes straight to your wallet through the coinbase — the pool never
holds, forwards or settles anything. Run publicly, every visiting miner mines to their own
address (0 % fee, non-custodial).

v1.22.0 is a polish release. Two small things, both from actually running the thing.

Certificates now renew quietly in the background. If you serve your stratum port with a real
certificate — because some miner firmware only trusts public CAs — you used to have an invisible
expiry date in your calendar. The daemon read the certificate and key exactly once at startup, so
after the next Let’s Encrypt renewal it kept serving the old one, and when that finally expired,
TLS broke for every miner at once. The workaround was a restart, and our own guide carried it as a
reminder in the cheat sheet.

That is gone. The pool now checks whether the two files changed and puts a renewed certificate into
service on its own. Connections that are already open keep the certificate they negotiated, so a
renewal costs nobody a reconnect
. And if the reload catches a half-written file mid-renewal, the
old certificate stays in service and the next check tries again, rather than dropping the listener.

The donation card was rewritten too. It used to open with “Support the pool” — a request made
before the reader had decided they liked anything. It now leads with a question and then gives the
reason: the pool takes no fee, so donations are what cover the server.

Going through it turned up something mildly embarrassing: four of the eight languages had been
asking for a tip in the waiter sense for months — propina, pourboire, mancia, 打赏. All
four came from the English word “tip” in the original copy. Chinese was the worst of them: 打赏
is a gratuity to a person, while the admin side of the same application already said 捐赠, so
the public page and the admin UI were using different words for the same thing. This time each
language was written on its own terms instead of translated.

Being straight about it: the pool runs publicly on mainnet but has not found a block yet. The
full block path is proven end-to-end against a real Bitcoin Core on regtest and signet. 1174 tests
gate every release, and builds are signed and reproducible.

Repo: dvb-projekt/dvb-WarpPool: Bitcoin solo/pool mining server — Stratum V1 + V2 - Forgejo: Beyond coding. We forge.
Compared with ckpool / public-pool / mkpool: Comparison (ckpool / public-pool / mkpool) - dvb-WarpPool

The pool that tells you the truth about your odds — and celebrates the moments

A quick intro to dvb-WarpPool for anyone new: a Bitcoin solo mining pool written from scratch in
Rust that you host yourself. The block reward goes straight to your wallet through the coinbase — the
pool holds, routes and settles nothing. In public mode every visiting miner mines to their own
address (0 % fee, non-custodial).

v1.23.0 ships two things you feel immediately as a miner.

Honest block odds instead of a number that reads as “never”. Each miner’s public page
(/users/<address>) used to show a single “estimated time to block” tile. For a home rig it said
“~12,000 years” — the mean of an exponential distribution dressed up as a forecast. It reads like
“forget it” and hides the one property that makes solo mining make sense at all: the process is
memoryless. Every second is a fresh draw; there is no queue, no progress bar, nothing getting
closer.

In its place is the real picture: your chance in the next 24 hours and within one year (long
odds as “1 in N”, e.g. “1 in 6.3 million”; percentages rounded down so they never overstate), the
50/50 point (the median — when it becomes more likely than not, not the average), and your luck
so far
: how many blocks your accepted work “should” have produced (almost always well under 1), how
many you actually found, and — if none yet — what percentage of miners with exactly your history would
also still be empty-handed. Under it, the honest core in one line: no amount of waiting makes a block
more likely; only more hashrate or a lower difficulty. Everything is computed server-side and also
exposed as an odds object on /api/users/<address>, so third-party tools get identical numbers.

And the pool now celebrates the moments. Solo mining lives on three of them: your new personal-best
share, entering the pool’s all-time top 100, and the block. Until now the pool pushed only the block.
If you have verified your payout address and set a notification target (ntfy or webhook), you now also
get a ping when you set a new personal-best share (across all your rigs, not per device) and when
you first enter the all-time top 100 — a genuine entry, not merely climbing higher. Each kind has
its own cooldown so a good run doesn’t become a stream of messages, and a found block sends a single
headline rather than three overlapping alerts. No new configuration — it rides the existing,
signature-verified notification target.

Honest stays honest: the pool runs publicly on mainnet but has not yet found a block — the full
block path is proven end-to-end on regtest and signet against a real Bitcoin Core. Bitcoin mining is
inherently probabilistic; nothing here promises a reward.

Repo: dvb-projekt/dvb-WarpPool: Bitcoin solo/pool mining server — Stratum V1 + V2 - Forgejo: Beyond coding. We forge. · Docs: https://docs.warppool.org ·
Comparison: Comparison (ckpool / public-pool / mkpool) - dvb-WarpPool

Taproot wallets can now prove the address is theirs

Quick intro for newcomers: dvb-WarpPool is a self-hosted Bitcoin solo mining pool written in Rust.
The block reward goes straight to your wallet through the coinbase — no middleman. In public mode every
visiting miner mines to their own address (0 % fee, non-custodial).

Each miner’s public page (/users/<address>) has an owner area: prove an address is yours — by signing
a short challenge in your wallet, no account, no funds moved — and you unlock privacy (hide the
address from the public list) and notifications (ntfy/webhook: block, a new personal-best share,
entering the all-time top 100, a rig going offline).

That ownership proof only had a format for legacy and SegWit addresses (BIP-137). Taproot (bc1p)
had no signed-message format — so it stayed locked out, even though Taproot addresses have always been
able to mine.
v1.24.0 fixes that: Taproot now verifies via BIP-322 (the format Sparrow
produces for Taproot). It feels identical to you — open the wallet, “Sign message”, paste, sign, done;
the wallet picks the right format itself.

For context, source-verified (I read the others’ code before writing sentences like this): of ckpool,
public-pool and mkpool, none ties privacy or notifications to any proof that the address is yours —
this “prove the address is yours, and the control over it is yours too” exists only on dvb-WarpPool as
of today. Other pools do things we don’t; this is one we’re alone on.

Honest framing: the pool runs publicly on mainnet but has not yet found a block (the full block
path is proven end-to-end on regtest and signet against a real Bitcoin Core). Bitcoin mining is
inherently probabilistic; nothing here promises a reward.

Repo: dvb-projekt/dvb-WarpPool: Bitcoin solo/pool mining server — Stratum V1 + V2 - Forgejo: Beyond coding. We forge. · Docs: https://docs.warppool.org ·
Comparison: Comparison (ckpool / public-pool / mkpool) - dvb-WarpPool

dvb-WarpPool v1.25.1 — when two Sv2 miners don’t give you twice the coverage

Briefly, what dvb-WarpPool is: a solo mining pool written in Rust that you run on your
own hardware next to your Bitcoin node. The block reward goes straight to the finder’s
address through the coinbase — the pool never holds coins, no accounts, no payouts.
Stratum V1 and V2, web interface in 8 languages, reproducible signed builds. AGPLv3.

The bug. A user (John11) reported that several of his Sv2 miners kept showing the
same best share, and that the colliding pair changed over time — most recently two
devices with a 3.5× difference in hash rate. His suspicion: they are being given
identical work.

He was right. The extranonce prefix counter was a field on the channel registry, and
that registry is rebuilt per connection. So the first channel of every connection got
00 00 00 00, and all directly connected Sv2 miners searched the same extranonce space.
Stratum V1 was never affected — there the counter is an AtomicU64 on the shared server
state. That contrast was the evidence.

What it means in practice: no shares were lost and nothing was unsafe, the work was
real. But two miners were not giving you twice the coverage. This affects every operator
running more than one directly connected Sv2 miner, silently.

The fix allocates prefixes process-wide, with the mode explicit in the type. The
decisive new test deliberately sits on the listener’s live constructors rather than on
the data structure — the old test opened two channels in one registry and could not see
the failure axis at all. Verified by removing the fix: red; putting it back: green.

Second change: standard or extended channel? That was the most common follow-up
question about Sv2, and the connect dialog — the one place you look while setting a
miner up — said nothing. It now recommends extended when you connect to a pool you
do not run yourself. On an extended channel the pool sends the coinbase halves and the
merkle path, so your miner can check for itself what it is hashing and that the reward
output is the one you configured. On a standard channel it receives a finished merkle
root and has to take the pool’s word for it. AxeOS gives the same advice in its own
tooltip. The honest counter-argument is in there too: extended costs bigger jobs and a
little merkle work on the device.

From v1.25.0 comes the way back out of the interface to the project site — “Powered
by dvb-WarpPool” in the footer plus one line in the connect dialog for newcomers.

Status: 1206 tests + 4 ignored, clippy clean, svelte-check with no errors.

Staying honest: this pool has not found a mainnet block yet. The consensus path
is verified end to end on regtest and signet, but the first mainnet block is still
outstanding. Nothing here is “battle-tested”.

v1.25.2, half a day later: the connect dialog never had a scroll area of its own. On
a phone it therefore ran off the top and the bottom at once — you saw the middle and
could not reach the username or the password. Fixed; nothing changes on a desktop.

Source: dvb-projekt/dvb-WarpPool: Bitcoin solo/pool mining server — Stratum V1 + V2 - Forgejo: Beyond coding. We forge.
Comparison with ckpool / public-pool / mkpool: Comparison (ckpool / public-pool / mkpool) - dvb-WarpPool

Why my Sv2 miners sat at 2–4% rejects — a bug hunt in three acts

dvb-WarpPool is a solo mining pool written in Rust that you run yourself: on an Umbrel box,
a server, a Raspberry Pi or on macOS. It speaks Stratum V1 and V2, has a PWA interface, and
sends the full block reward straight to your own wallet through the coinbase — the pool
never holds coins, there are no accounts and nothing to pay out.

Act 1: the find nobody was looking for (v1.25.3/v1.25.4)

The starting point was the question of why my Stratum V2 miners lost 2–4% of their shares as
“low difficulty” while V1 devices with the same chip sat at 0.2%. While I was measuring,
something else surfaced first: when a V2 miner connects, it negotiates how many bytes of the coinbase
it may vary itself. The pool took the requested size at face value — for a NerdQAxe that
stuffed ten bytes into an eight-byte gap. Harmless for shares, since miner and pool compute
identically. But the coinbase came out two bytes longer than its own length field allows:
Bitcoin Core’s decoderawtransaction rejects those exact bytes. A found block would have been
lost. It had been there since v1.0.0.

The first fix went wrong: v1.25.3 widened the miner’s part for both protocols — but V1
firmware generates its part itself and ignores what the pool announces. The result: every V1
share discarded as malformed. Twenty minutes after it hit my pool, the release was withdrawn
(marked as a pre-release, store rolled back). v1.25.4 splits the gap per protocol: V1 gets
exactly the four bytes its firmware sends anyway; the pool’s own part absorbs the growth.
The lesson became a rule: wire changes are now measured against real miners before tagging —
all 1215 tests were green even on the broken version, because every one of them used the one
size that happened to fit.

Act 2: the actual answer (v1.25.5)

With better diagnostics, the pattern emerged: every reject sat within 90 seconds of a
block change — 60 out of 60 in the measurement window, median 16 seconds, zero in between.
The cause was a subtlety of the V2 spec: a job with min_ntime set means “mine immediately” —
against the prev hash the miner knows at that moment. On a block change, that is the old
one. The pool sent every job that way, so spec-compliant firmware hashed against the wrong
chain after every block until the next update arrived. V1 does not have this problem,
because mining.notify carries the prev hash inside the job.

Now a block change sends a future job that SetNewPrevHash activates; a template refresh
sends an immediately active job with no SetNewPrevHash. During the rework it turned out the
built-in V1<->V2 translator relied on the same wrong assumption — caught in analysis this
time, not in production.

And a second find: V2 connections kept dropping with an encryption error at irregular
intervals — anywhere from minutes to hours apart. That was a concurrency bug in the pool —
the frame read could be interrupted by other events and lost cipher state when it was. The
reader is now cancellation-safe.

The measurement

Before: 9.6 lost shares per block change on average (36 out of 36 changes affected), five
connection drops in 68 minutes. After, on the same miners: 26 consecutive block changes with
zero rejects, and over three hours without a single drop. Only after that measurement was
the release tagged.

Who should update: everyone running Stratum V2 miners; if you grabbed v1.25.3,
definitely. Nothing to configure. To be straight about it: this pool has not found a block
on mainnet yet — the full block path is verified end to end on regtest and signet, but “not
yet” is “not yet”.

Repo: dvb-projekt/dvb-WarpPool: Bitcoin solo/pool mining server — Stratum V1 + V2 - Forgejo: Beyond coding. We forge.
Comparison with other solo pools: Comparison (ckpool / public-pool / mkpool) - dvb-WarpPool

Our pool dashboard shows fewer workers as of today — and that’s the actual update.

Since dvb-WarpPool got listed on a public pool-comparison site, its measurement probes have been connecting to our Stratum port around the clock — each check with a freshly generated throwaway address that never submits a single share. Perfectly legitimate (that’s how you measure pools independently), but our dashboard dutifully counted every one of those addresses as a “worker”. Result: 203 workers on display, 31 of them actually mining.

With v1.25.6 the public worker and wallet totals only count miners that submitted at least one accepted share. A new retention rule additionally clears rows that never submitted any share from the database after 7 days (configurable; a real miner stops qualifying within seconds, and a misconfigured miner whose shares get rejected stays visible for debugging).

Also in v1.25.6: if you run your pool behind a CA-signed TLS certificate (e.g. Let’s Encrypt), the connect dialog now says so — firmware verifies it against its built-in CA store, no extra file needed. Previously the dialog always warned about “self-signed” and effectively pointed miners at the unencrypted port. Self-signed setups (the usual case on a home network) keep the familiar warning and certificate download.

A small release, but one we care about: a solo pool lives on its numbers being true — even when the honest number is smaller than the impressive one. As always: non-custodial, 0% fee, every miner mines to their own address. The pool has not found a mainnet block yet — solo mining remains a lottery ticket.

Repo: dvb-projekt/dvb-WarpPool: Bitcoin solo/pool mining server — Stratum V1 + V2 - Forgejo: Beyond coding. We forge.
Comparison with other pools: Comparison (ckpool / public-pool / mkpool) - dvb-WarpPool

Someone read our source and found a real weakness — v1.25.7 closes it. Thank you, Distortions.

This is open source working as intended: the operator of StratumStats/M45Core read our code and reported that a hostile client could overwhelm the pool just by submitting invalid shares — every rejected share spawned its own unbounded background write to the database, and share submission had no rate limit. He was right.

As of v1.25.7 a rejected share costs two in-memory counter bumps; a single loop flushes the coalesced totals to the database every two seconds — on both Stratum V1 and V2. Junk traffic has nowhere left to pile up. Also fixed: a timing clash between the pool and Bitcoin Core that wrote hundreds of harmless but noisy warnings per day into the log.

The report is credited by name in the changelog. If you run the pool yourself: update recommended — this is hardening, not a feature release. As always: non-custodial, 0% fee, every miner mines to their own address, and we still honestly have “not yet” found a mainnet block.

Repo: dvb-projekt/dvb-WarpPool: Bitcoin solo/pool mining server — Stratum V1 + V2 - Forgejo: Beyond coding. We forge.
Comparison: Comparison (ckpool / public-pool / mkpool) - dvb-WarpPool

v1.26.0: We measured every millisecond between “new block” and “new job” — and removed three of them.

How fast a solo pool hands its miners fresh work after a new block is decided almost entirely by the Bitcoin node behind it, not by the pool software. We took the chain apart with microsecond timestamps on our reference box and on an Umbrel Home: hearing the block, reconstructing the compact block, validating, building the template, pushing the job. The result is now a handbook page, “Node Tuning for Block Latency” — numbers, not opinions.

What v1.26.0 does with it:

• Latency as a metric. Every installation now sees its own chain: three new Prometheus gauges, fields in the API, and one “tip → job” log line per new block.
• A warning when ZMQ goes silent. A node with ZMQ configured but not delivering used to drop the pool silently onto the poll tick — new blocks arrived up to 90 seconds late. The pool now counts those and warns in the dashboard.
• A node recommendation from the measurement. The setup wizard suggests blockreconstructionextratxn=1000: with it, one block in three needed no round trip to a peer on our box, and reconstruction fell from 90–150 ms to a 60 ms median.
• IPC template source, automatically. template_source = "auto" probes the Bitcoin Core 31 socket at start and takes the fast path when it is there — on Umbrel via an environment variable, no config editing.
• Clean shutdown on SIGTERM. docker stop used to kill the daemon outright. Share buffers are now flushed and sessions closed properly.
• Restart without a gap, optional. With reuse_port a new daemon can take over the ports while the old one still serves.
• For developers: the Sv2 handshake logs every stage with concrete hints (someone got their own pool talking to ours with them last week), Sv2 shares the rate limiter with V1, and both protocol parsers now run against fuzz tests in the regular test suite.

And a small invitation: our node accepts peering from other pools (91.98.76.244:8333). One manual peer to a node on a fast relay took us from hearing blocks 12 ms after to 17 ms before a relay-connected pool’s job — trustless, one line in bitcoin.conf. The peering page in the docs shows how to measure it.

As always: non-custodial, 0% fee, every miner mines to their own address, and we still honestly have “not yet” found a mainnet block.

Repo: dvb-projekt/dvb-WarpPool: Bitcoin solo/pool mining server — Stratum V1 + V2 - Forgejo: Beyond coding. We forge.
Comparison: Comparison (ckpool / public-pool / mkpool) - dvb-WarpPool
Node tuning: Node Tuning for Block Latency - dvb-WarpPool

dvb-WarpPool v1.27.0 — a small one, all about keeping the node behind the pool in view:

• Opt-in Bitcoin Core release check: once a day the pool fetches the Core release list and warns in the dashboard when a newer release exists on your node’s own line (31.1 → 31.2 style bug-fix/security releases). Newer major lines are only shown, never nagged about. Off by default; the pool never updates Core itself.
• Admin → Health now starts with a node card: Core version + newest release, chain, height, peers, pruned/txindex, ZMQ, template source.
• Fix for an Umbrel race: after a Bitcoin app update both apps restart together, and the pool’s template_source = "auto" probe ran before bitcoind bound its IPC socket, so the pool silently stayed on GBT. It now keeps probing and tells you to restart once the socket answers.
• Quieter V1 log on port-scanner hangups, and a noise_probe example for anyone writing an Sv2 client against us (found the swapped-cipher bug in an external client in minutes last week).

Changelog: v1.27.0 - dvb-projekt/dvb-WarpPool - Forgejo: Beyond coding. We forge.

v1.30.0: the pool now knows whether your mining power came off the roof, out of the house battery, or from the grid — and deliberately does not flatter itself.

dvb-WarpPool is my non-custodial Bitcoin solo pool: 0 % fee, every miner mines straight to its own address, Stratum V1 and V2, an interface in eight languages, as an Umbrel app or native.

This one started in my own basement. The pool used to show two sources for the kilowatt-hours it consumed: solar or grid. In the evening it said grid. Except that wasn’t true — in the evening the house battery carries the house here, and what’s in it is solar from midday. The meter shows nothing of that; it simply shows no import. What booked it as grid was the old check, which only compared PV production against house load: sun gone, production zero, therefore grid.

As of v1.30.0 there are three shares: direct solar, battery, grid. Battery means solar stored during the day and consumed later. It counts as self-supplied and costs accordingly nothing — only what actually came from the grid is priced.

The more interesting part is how you attribute that, and there I went with the less flattering option. The obvious approach is the house average: if the house runs 75 % on battery, credit the miners with 75 % battery. That reads nicely and is wrong. An example: in the evening, 200 W from the grid and 600 W from the battery, 300 W of which is miners. The average would credit the miners with 75 % battery. But unplug the miners and the grid draw drops to zero — so those 200 W were on their account, not the house’s.

So the pool attributes marginally: the miners are the last consumer switched on, and their load is charged against the sources in the order those would fall away if you unplugged them — grid import first, then battery, then direct PV. In the example that’s 200 W grid plus 100 W battery, two thirds to one third. With the grid at zero it’s a full 100 % battery, which is the case this whole thing exists for. It stays proportional rather than all-or-nothing: a minute whose marginal load spans two sources is booked exactly that way.

Where the data comes from: if you use the Anker bridge, there is nothing to do — it reports charge, discharge, grid import and feed-in, and it always had those values, the pool simply never read them. For Home Assistant there are five optional sensors, entered in the setup wizard or the admin area. One thing matters there: positive watts, one sensor per direction. Many HA integrations expose one signed value for charging and discharging; that does not work here and has to be split into two template sensors first.

Without a battery nothing changes: two shares as before, and the third bar only appears once your system has actually contributed a battery kilowatt-hour. Fixed along the way: the week, month and year view had priced every kilowatt-hour at the grid tariff, self-generated ones included — the energy card had long had this right, the overview didn’t follow.

All open source, AGPL, in the same repo as the pool.

Repo: dvb-projekt/dvb-WarpPool: Rust Bitcoin solo mining pool — Stratum V1 + V2, PWA operator UI, signed reproducible builds - Forgejo: Beyond coding. We forge.
Comparison: Comparison (ckpool / public-pool / mkpool) - dvb-WarpPool

dvb-WarpPool v1.30.1 is a security patch for Umbrel installs from our community store. If you run 1.28.0, 1.29.0 or 1.30.0 there, please update.

What was wrong: 1.28.0 added a second host port (18335) so a LAN sidecar could read /api/workers and /api/energy without the app login. That port was mapped straight onto the pool’s main API port, which also carries the first-run setup wizard and the initial admin setup. So between installing the app and finishing the wizard, anyone on your LAN could have reached both without the Umbrel login. Once the wizard and the admin account were done, the window was closed; the port then only served the same anonymous reads the public pool serves.

The fix: the port now maps to a separate read-only listener inside the daemon ([server].api_readonly_listen). It answers GETs, accepts the token-guarded WarpTune snapshot push, and refuses everything under /api/auth and /api/admin outright. The setup wizard never binds it. Verified over real sockets before tagging.

Not affected: native installs (.deb/.rpm/AppImage/.dmg) and Docker setups without that port mapping, and the official-store package (which never had the port). Nothing in the pool’s own login, tokens or Stratum paths changed.

Changelog: v1.30.1 - dvb-projekt/dvb-WarpPool - Forgejo: Beyond coding. We forge.

dvb-WarpPool v1.31.0 — mostly dvb-WarpTune, the auto-clock sidecar:

• Avalon Q over the cgminer API: the three work modes are the ladder, ascset workmode is the only write, steps judged on the 15-minute hashrate (the since-boot average lags for hours after a mode switch).
• Any ASIC the NerdOS firmware knows: ladder from the board’s own option list, expected hashrate from the reported small-core count. BM1373 boards should work without a code change; first unit test pending.
• Fan-target clamp, learned the hard way: a target below the firmware’s fan target + 2 °C made the fan PID slow the fan on every step down, so the chip never cooled and the regulator walked a Gamma 640 → 500 MHz in 13 minutes (every write verified, restore clean). Now clamped in every mode instead of only warned.
• Point changes made outside the regulator (AxeOS UI, Avalon mode switch) restart settle/judge instead of being taken back.
• The sidecar binary ships in all release formats now (was source-only through 1.30.0).
• Pool: Admin → Health header count and IBD flag were read once at start and drifted; fixed. rustls bump for RUSTSEC-2026-0285.

Changelog: v1.31.0 - dvb-projekt/dvb-WarpPool - Forgejo: Beyond coding. We forge.

1 Like

v1.28.0 → v1.31.0: An auto-clock for Bitaxe and friends that respects your hand tuning — as an open sidecar next to the pool.

dvb-WarpPool is my non-custodial Bitcoin solo pool: 0 % fee, every miner mines straight to its own address, Stratum V1 and V2, a UI in eight languages, as an Umbrel app or native. v1.28.0 adds something I have wanted for a long time and no firmware ships: dvb-WarpTune, an auto-clock for AxeOS miners with guardrails.

What it does: WarpTune holds each miner at a temperature target by stepping frequency and core voltage along the board’s own ladder. NerdOS devices bring their ladder with them; Bitaxe and BitForge Nano get the vendor table. One decision every 30 seconds, 25 MHz steps, hysteresis, settle time after each step.

What it does not do: wreck your tuning. The point a miner runs at when WarpTune starts is the ceiling for its voltage. If you set your Gamma to 640 MHz at 1140 mV, WarpTune will never hand it 1200 mV, table or no table. On shutdown, or after the miner reboots, it puts exactly that point back and reads it back to prove it.

The guardrails the firmware lacks:
• A power budget per board from the measured cooling resistance, so a step up is computed before the temperature has to stop it.
• A reject guard that knows reasons: stale and low-difficulty do not count as instability. The counters come from the pool, not the firmware, because ESP-Miner resets them on every reconnect.
• Step memory: a step that produced rejects is off limits for an hour.
• Every write is verified by reading it back. Twelve writes per hour is the cap, three failures in a row switch live control off, and the regulator keeps going as a proposal engine.
• Limits for VR temperature, input voltage and current, all taken from what the firmware reports about itself.

Then the pool coupling: WarpTune reads the electricity picture from the pool, tariff and solar surplus, and in energy mode switches miners between Eco, Balanced and Max with a minimum hold against cloud gaps. The other way round, the Devices tab shows a badge per miner: mode, frequency, state, last decision. The pool only displays; it never writes to a miner.

Two switches for live: one on the command line, one per miner in the config. Without both it is a dry run, and dry run is the default. On my side five devices on three firmwares ran 48 hours dry; all stayed on their hand-tuned points and the regulator agreed with every one. The Gamma then ran live for a week against acceptance criteria written down beforehand; the numbers are below.

Open source, AGPL, in the same repo as the pool, installed on macOS as a launchd service with one script. Umbrel users get a second API port without the login with this update, so a sidecar on the LAN can read along.

Addendum v1.29.0: Two things that were obviously missing the moment it was built. First, a switch in the admin area so a home pool turns the coupling on without editing a file, and it applies at once, no restart. Second, the public pool: your miners sit in your home networks, which the pool cannot reach. So the sidecar sends its state itself, with a token you create in the owner area of your wallet page after proving ownership with a signature. The badge then shows per rig on your own wallet page, to visitors only if you want it. The pool accepts only states that belong to your wallet, at most every ten seconds, and never writes anything back to a miner. The live switch stays where it belongs: with you, on the machine the sidecar runs on.

One week live on the Gamma: 7 days live on the Gamma (2026-09-11 → 09-18, 991 samples): 0 interventions, hashrate avg 1306.8 GH/s against the 1305.6 GH/s baseline, 60.0–62.5 °C, 22.8 W, 17.5 J/TH, 4 stale shares in total, no reboot (15.8 days uptime). The only writes were a supervised thermal test on day 5, which is where the fan-target clamp in v1.31.0 comes from.

v1.31.0 (Avalon Q over the cgminer API, any ASIC NerdOS knows, the fan-target clamp, manual changes respected) is one post up in this thread.

Energy mode is in its first dry run on my Octaxe and Avalon Q this week. Day one already taught me something: the pool’s surplus flag flips on the first watt, so the governor needs a margin before it lifts a miner that would draw 850 W more — that lands in v1.32.0.

Repo: dvb-projekt/dvb-WarpPool: Rust Bitcoin solo mining pool — Stratum V1 + V2, PWA operator UI, signed reproducible builds - Forgejo: Beyond coding. We forge.
Comparison: Comparison (ckpool / public-pool / mkpool) - dvb-WarpPool

v1.32.0 — a field note on solar surplus

Short one. Yesterday was the first day without the EV charging, so I could finally look at the energy mode properly, and the numbers showed a mistake on the pool side rather than in the tuner.

The pool’s surplus flag required PV to exceed the house load by the miners’ power plus a buffer. That rule is right for a sub-meter that sits in front of the miners only. My Anker measures the whole house, miners included, so they were counted twice. Miners on PV for about four hours, flag on for six minutes, and dvb-WarpTune saw only two short Max windows.

v1.32.0 adds consumption_includes_pool (default true, because that is what a house meter reports) and keeps the old rule behind false. On the tuner side the energy mode now has a surplus margin per miner (the extra watts that Max draws over Eco, 900 W on an Avalon Q, 30 W on an Octaxe) and an exit debounce, so a cloud passing over does not flip the mode every hold period.

Still dry-run on the Octaxe and Avalon here; the Gamma stays live. The BM1373 board arrives Monday, that will be v1.33.0.

Repo: dvb-projekt/dvb-WarpPool: Rust Bitcoin solo mining pool — Stratum V1 + V2, PWA operator UI, signed reproducible builds - Forgejo: Beyond coding. We forge. · Changelog in the repo.

v1.33.0 — the first day with a NerdAxe Gaia, and what it taught the tuner

The BM1373 board arrived Monday. It is a NerdAxe Gaia on NerdOS, one chip, 6860 small cores, factory point 350 MHz at 940 mV. dvb-WarpPool knows it as its own vendor now, and the AxeOS probe takes the board name from the firmware, so a board that takes over another one’s address shows up as what it is (mine sat on the old Gamma’s IP and was labelled Gamma for an hour).

I let dvb-WarpTune regulate it from the first minute, with the 300–440 MHz and 900–1040 mV window the firmware offers and no vendor table. Ten minutes later it stood at 440 MHz with a core voltage of 0 mV, 5 W on the meter, no fault flag, and the API still reported the nominal hashrate. The regulator sat in its judge window and waited, because everything it looked at said “fine”. I wrote the factory point by hand.

Three things came out of that day. A step the tuner has never seen hashing in full is judged for ten minutes before the next one is taken, so the first climb on a new board is slow on purpose. A chip that reports 0 mV or 0 hashrate for three decisions, or a draw far below what the point should pull, goes back to the last point that ran and the board is rebooted. And the regulator’s memory is bound to the device, not to the address: the Gaia had inherited the Gamma’s 640 MHz voltage entry, a point that does not exist in its window.

Smaller: an automatic fan with reserve keeps the thermal budget (an Octaxe spent an afternoon of surplus at 650 MHz because the fixed-fan rule saw 61 °C, not 61 % fan), Eco gets its own ladder top, the voltage trial takes the pool’s share counter as a second vote against a firmware reading that drifts ±5 %, and the surplus margin of the energy mode is an entry condition only.

On the pool side a fix I owe to a six-minute link outage: shares after a VarDiff retarget were booked at the new difficulty while the miner still worked the old job, and the reconnect burst drew a 65 TH/s peak into a 17 TH/s pool. V1 now books at the difficulty its job went out with, Sv2 at the target the share actually met.

The Gaia runs at 400 MHz today, on the tuner, live. 420 is a question for the data, not for me. Still no mainnet block.

Repo: dvb-projekt/dvb-WarpPool: Rust Bitcoin solo mining pool — Stratum V1 + V2, PWA operator UI, signed reproducible builds - Forgejo: Beyond coding. We forge. · Changelog in the repo.

1 Like

keep on mining! Can’t wait for more updates

1 Like

v1.34.0 — shares that arrive after the block, and a tuner that waits for enough of them

Two things this release, both about counting honestly.

When a new block arrives, a miner gets a fresh job with clean_jobs set, but the chips are still finishing the work they hold, and those results reach the pool a few seconds later. dvb-WarpPool used to drop the old jobs at that moment, so each late share came back as “stale share”: one or two per block change, a steady 1–3 % that looked like a network or firmware problem and was neither. Both Stratum V1 and Sv2 now keep the jobs from before the block for 60 seconds and book those shares like any other, at the difficulty their job went out with. A block solution on such a job still goes to the node, which decides between the two blocks. After 60 seconds the job is stale as before. The duplicate check no longer resets on a block change, so a share sent before and after it still counts once.

On the tuner side the voltage trial now waits for 200 accepted pool shares before it calls a voltage good or short. The first version of that rule looked back 30 minutes, and VarDiff keeps every miner near four shares a minute, so the window never held 200 and the trial waited forever. It reaches back until it has them now. My NerdAxe Gaia at 420 MHz, over a day: 960 mV good at 101 % of the expected rate over 200 shares, 950 mV short twice at 82 %, 940 mV short at 53 %. The firmware read 102–105 % the whole time; only the pool saw the difference. There is a new yield command that prints this per operating point from the pool’s shares.

The Avalon Q went live on the energy mode this week and the first real work-mode switch was refused by the firmware: the command needs a set verb that the dry runs never exercised. Fixed, and today it went Eco, Standard, Super on PV surplus and back to Eco on the tariff. Without the pool’s energy feed the mode now holds Eco rather than Balanced, which had let the Q climb to Standard in the three minutes a restart took to get the first reading.

Still no mainnet block.

Repo: dvb-projekt/dvb-WarpPool: Rust Bitcoin solo mining pool — Stratum V1 + V2, PWA operator UI, signed reproducible builds - Forgejo: Beyond coding. We forge. · Changelog in the repo.

v1.36.0 — calibration by button, and what it found on my Octaxe

The headline of this release is a button. For those running dvb-WarpTune next to the pool, every miner under live control now has Start calibration on the devices page. It finds the lowest core voltage each of the miner’s two steps (Eco and Max) still hashes cleanly at, and it judges by the pool’s accepted shares, not by the miner’s own counters: the reference voltage first, then 10 mV less at a time, 200 shares each, until a step falls short. That takes a few hours; the miner keeps mining. At the end the card shows the voltage found per step and asks you to apply or discard it.

Two rules shaped it. It only ever lowers the voltage below your hand-tuned point at the same frequency, so a calibrated miner never draws more power than before. And it errs on the safe side: the result is the lowest voltage that passed above the highest one that failed, plus a 20 mV reserve, and in operation a step gets 10 mV more whenever the shares fall short there later. A 200-share window still varies by several percent, so now and then a good voltage fails — calibration then keeps more voltage, never less.

Testing it on my own NerdOctaxe was instructive. The board’s floor came out at the hand-tuned 1210 mV, no saving. Per-chip counters showed why: one of the eight chips has weak domains that switch off below about 1210 mV and stay off until the voltage comes back up, a quiet 1.5–3 % loss the pool’s shares barely see. The other seven chips hold 1180 mV. Calibration settled where the weakest chip still hashes, which is the honest answer for that board; spotting such a domain directly is next.

Smaller: the voltage search line on each device now says what it checked and why a miner sits above WarpTune’s table voltage (“needs 960 mV (table 940) · at 950 mV too many chip errors”).

Still no mainnet block.

Repo: dvb-projekt/dvb-WarpPool: Rust Bitcoin solo mining pool — Stratum V1 + V2, PWA operator UI, signed reproducible builds - Forgejo: Beyond coding. We forge. · Changelog in the repo.

v1.37.0 + v1.37.1 — the first real savings from calibration, what the clock search taught me, and Stratum V2 for small miners

Last release added calibration by button; this one makes it worth pressing. On my BitForge Nano the Max step (650 MHz) came down from the hand-tuned 1160 mV to 1050 mV including a 20 mV reserve: 52.1 W → 43.7 W, −16 %, with the pool’s hashrate unchanged. The per-chip error rate stayed flat under 0.5 % down to 1050 mV and jumped to 2.9 % at 1020 — exactly where calibration said no. On a Bitaxe Gamma the Max step (640 MHz) went 1140 → 1050 mV, 23.6 → 20.7 W, −12 %.

New is an opt-in clock search: before each voltage stage it looks for the best clock, judged over 30 minutes on fan, temperatures, core current, power, the draw on the power supply (kept under 80 % of its rating) and the pool’s shares. On the Nano it stopped right at my hand-tuned point: with the lid on and a 60 °C fan target the fan needed 95–100 % at 1160 mV. That is the rule doing its job — at 1050 mV the same chip needs 58 %, so a later version can search the clock at the calibrated voltage (already there as an option).

Running it live taught me three things, all fixed in this release. Chip domains that silently drop out (1–3 % of hashrate, too little for the shares to show) are now caught from the firmware’s per-chip counters, and a short bump to the hand-tuned voltage brings them back. The in-operation raise of a calibrated voltage reacted to share luck; it now needs a clear shortfall or a second sign, at most twice a day, and steps back after six good hours. And a NerdAxe Gaia showed that a power budget isn’t enough: one step up from 380 to 400 MHz drew 32 A against its regulator’s ~33 A limit and tripped it. Every step up and every restore is now held under a per-board share of that limit, predicted from what the miner actually drew there before.

v1.37.1 is a small follow-up for tiny miners on Stratum V2: a recognised NerdMiner-class device now opens its Sv2 channel at the same small difficulty it gets over V1 instead of a fixed 4096. Tested with WarpAxe on a LilyGO T-Display-S3 (~290 kH/s): start 0.0064, 4 of 4 shares valid in 12 minutes. Unrecognised miners keep the 4096 floor.

Still no mainnet block.

Repo: dvb-projekt/dvb-WarpPool: Rust Bitcoin solo mining pool — Stratum V1 + V2, PWA operator UI, signed reproducible builds - Forgejo: Beyond coding. We forge. · Changelog in the repo

v1.38.0 — hashrate per watt instead of the lowest stable voltage, a quick check, and what a chip domain on my Octaxe taught me

Calibration used to take the lowest voltage that passed 200 pool shares. Looking at the journal, that wasn’t always the best choice: on my Bitaxe Gamma at 640 MHz, 1030 mV passed, but with 6.3 % chip errors; 1040 mV had 1.9 %. Since the pool only books what the chips get right, the errors are real loss — a NerdAxe Gaia with ~6 % chip errors showed ~6 % less hashrate at the pool over 24 hours, next to a Gamma at 100 %. So v1.38 picks the voltage with the most hashrate per watt among the ones that pass, and the card says why (“1040 mV instead of the stable 1030 mV — x % more hashrate per watt”).

The same data made a faster calibration possible. Over 88 voltage verdicts from the last month, the chip error rate predicted the 200-share verdict 86–95 % of the time, and unlike the shares it rises smoothly as the voltage drops. The new quick check walks down by the error counters, about 10 minutes per step, and only the final candidate gets the full share verdict, with a 30 mV reserve. It’s meant to take about 1.5–2 hours per stage instead of 6–13; I haven’t run it on all miners yet. A re-check 28 days after you apply runs on its own on Max miners and only ever suggests.

The Octaxe gave me a lesson today. After a firmware update (a warm reboot) one of its 32 chip domains stayed off. WarpTune now switches the chain off and on in that case — confirmed off, 15 seconds, on, ready, at most three an hour and only once the miner is steady — but four tries didn’t bring that domain back. Switching the power supply off for a few seconds did. So WarpTune now gives up on a domain after two failed tries and the card says exactly that: power-cycle it, and if it still drops out, it’s a margin question for you.

Also new: Admin → Workers to remove idle workers (old test names in the leaderboard), and NerdMiner-class miners no longer triple their difficulty after a few lucky shares.

Still no mainnet block.

Repo: dvb-projekt/dvb-WarpPool: Rust Bitcoin solo mining pool — Stratum V1 + V2, PWA operator UI, signed reproducible builds - Forgejo: Beyond coding. We forge. · Changelog in the repo.