set_extranonce Race Condition — Missed Solo Block
Critical — Immediate action required
Symptoms
- Miner firmware fired a `BLOCK FOUND` / `BLOCK CANDIDATE` / `LUCKY!` notification at a specific timestamp
- Pool dashboard shows no block logged on the worker at that timestamp
- Pool dashboard shows a `bestshare` value at or above current network difficulty for the worker around that minute
- AxeOS / DCENT_OS / serial output contains a `mining.submit` reply of `{"result": false, "error": [21, "Job not found", null]}` or `[23, "Low difficulty share", null]` at the relevant timestamp
- Same log shows a `mining.set_extranonce` push within `< 1 second` of the failed `mining.submit`
- Same log shows a `mining.notify` with `clean_jobs=true` within `< 1 second` of the failed `mining.submit`
- mempool.space at the relevant block height is owned by a different pool (`/ViaBTC/`, `/F2Pool/`, `/Foundry USA/` — not your pool's tag)
- No block at the relevant height matches the operator's coinbase address
- Pool's `blocks-found` page does not list the event
- forkmonitor.info shows no orphan / stale block at that height attributable to the pool either
- Network difficulty was at or just below the `bestshare` value submitted (within a factor of 1-3×)
- Operator was running a pool with frequent `mining.set_extranonce` pushes (load-balanced backend, multi-instance ckpool fork, public stratum proxy)
Step-by-Step Fix
Flash the latest stable AxeOS / ESP-Miner build on every Bitaxe, the latest stable NerdMiner / NerdAxe / NerdQAxe build on those devices, and the latest stable DCENT_OS build on any Antminer in the solo stack. Older builds — especially anything pre-AxeOS 2.5.x or pre-2024 NerdMiner forks — are more likely to include lazy stratum handlers that widen the race window. Modern builds default to polite flush-on-`set_extranonce`. Releases live at github.com/bitaxeorg/ESP-Miner/releases, github.com/BitMaker-hub/NerdMiner_v2, github.com/shufps/ESP-Miner-NerdQAxePlus, and github.com/DCentralTech/DCENT_OS.
Point every solo miner at a session-stable pool. First-tier: `solo.ckpool.org` (Con Kolivas, open-source) and `public-pool.io` (Benjamin Wilson, open-source) — neither rotates `extranonce1` mid-session for load balancing. Second-tier: any pool whose source is publicly auditable so you can confirm session behaviour. Avoid: closed-source pools, load-balanced pools that aggressively shuffle backends, and self-hosted ckpool forks more than a year stale.
Stop using a stratum proxy unless it's a known polite implementation. Some local stratum proxies introduce additional race windows by buffering pool→miner messages or rewriting JSON-RPC IDs. The simplest solo stack is miner → pool, direct LAN/WAN, no proxy. If you need a proxy for monitoring or VPN, run it in pass-through mode and verify it doesn't buffer / batch / rewrite stratum JSON-RPC.
Run on a low-latency network path to the pool. A `solo.ckpool.org` ping from Toronto residential typically lands at ~25 ms RTT; from Saskatchewan ~70 ms; from Yukon ~120 ms+. Higher RTT widens every race window proportionally. Run wired Ethernet to a residential gateway with low buffer-bloat (test at waveform.com/tools/bufferbloat). For multi-miner Canadian setups, a single low-RTT VPS as a stratum forwarder can help — but only if it's a polite forwarder.
Document the firmware version, pool URL, worker address, and a network-path RTT baseline for every solo miner you run. When the next near-miss happens, the post-mortem starts with `what was the configuration`. A simple solo-mining-fleet.md text file with one entry per miner is enough.
Wire up persistent serial logging on every Bitaxe / NerdMiner / NerdAxe. USB-TTL cable (FT232RL, CH340, CP2102) connected to the device's UART pads or USB-C data path. Run `screen /dev/ttyUSB0 115200 -L -Logfile bitaxe-$(hostname).log` or `tio -l -m INLCRNL /dev/ttyUSB0` on a small always-on machine (Raspberry Pi 4). Rotate logs daily with `logrotate`. Storage cost: ~50 MB/month/miner. Forensic value: priceless when a race fires.
Run tcpdump on the LAN to capture stratum traffic. On the Pi: `sudo tcpdump -i eth0 -w /var/log/stratum-$(date +%F).pcap -G 86400 'port 3333 or port 21496 or port 3334'`. (`solo.ckpool.org` uses 3333; `public-pool.io` uses 21496; `mine.ocean.xyz` uses 3334.) Daily rotation, 50-200 MB/day depending on miner count. Wireshark replays the pcap and reads every `mining.notify` / `mining.set_extranonce` / `mining.submit` JSON-RPC line directly.
Set up a webhook-based block-found alert pipeline. AxeOS, NerdMiner, and most modern open-source firmware can fire a webhook on `BLOCK FOUND` events. Point the webhook at Discord / Telegram / a custom URL with a payload containing: timestamp, worker address, submitted nonce, claimed block height, pool URL, and a snapshot of the most recent 200 lines of stratum log. Every near-miss arrives in your pocket with the evidence packet pre-attached.
Run a parallel monitoring Bitcoin Core node on the LAN. A Raspberry Pi 4 with a 1 TB SSD ($120-250 CAD all-in) runs a fully-validating node that logs every block at every height, with `debug=net` in `bitcoin.conf` for peer-connectivity logging. When a `BLOCK FOUND` event fires from your miner, the Bitcoin Core node tells you authoritatively whether a block at that height arrived from anywhere, which peer relayed it first, and how long it took to propagate.
Calibrate the firmware's `BLOCK FOUND` threshold against actual current network difficulty. Query mempool.space's `/api/v1/difficulty-adjustment` endpoint for the current target. If the miner is firing block-found alerts at share difficulties below current network difficulty, you've got more false positives than races, and the firmware threshold needs an update. Bitaxe AxeOS shows current best diff in the dashboard; NerdMiner shows it on the OLED/TFT.
Audit your firmware's `mining.set_extranonce` handler in source. ESP-Miner: clone github.com/bitaxeorg/ESP-Miner, look in `components/stratum/stratum_api.c` for `set_extranonce` / `extranonce_subscribe` handlers. The handler should: (a) atomically update the session's `extranonce1` cache, (b) signal the work-distribution task to flush in-flight nonces, (c) trigger a coinbase / merkle-root rebuild, (d) push fresh work to the chips. If b-d are deferred until the next `mining.notify`, the firmware is `lazy` and the race window is larger than necessary. File a GitHub issue with a clear repro.
For DCENT_OS on Antminer, confirm the `[EXTRANONCE FLUSH]` log marker is present. SSH into the Antminer, `journalctl -u cgminer | grep -i extranonce`. The current DCENT_OS build emits `[EXTRANONCE FLUSH]` immediately on `mining.set_extranonce` reception, with old/new values and the work-flush completion timestamp. If your build doesn't show this marker, you're on a stale DCENT_OS — pull the latest. DCENT_OS is the only Antminer firmware that exposes session-level stratum forensics this richly.
Test your stack against an `extranonce` rotation on demand. The `public-pool` source is runnable locally — spin up an instance, point a development Bitaxe at it, and trigger `mining.set_extranonce` from the pool side mid-hash. Capture the full sequence: miner-side stratum log, pool-side stratum log, and a tcpdump pcap. Confirm the miner flushes work cleanly within < 50 ms of the message arriving on the wire. If it doesn't, you have a measurable race window for a firmware bug report.
Consider Stratum v2 for any new Antminer-class solo deployment. SV2 (Braiins specification, see stratumprotocol.org) eliminates this specific race by structuring extranonce allocation as an explicit session resource with strict message-ordering rules. SV2-capable open-source firmware can talk SV2 to SV2-capable pools (Braiins Pool, some experimental solo SV2 deployments). For solo Bitaxe / NerdMiner, SV2 isn't yet broadly available pool-side; v1 + polite firmware + session-stable pool is the answer for now.
Run a multi-miner lottery with diversified firmware/pool combinations. If you operate three solo miners, point them at three different pools (e.g., `solo.ckpool.org`, `public-pool.io`, `solo.d-central.tech`) and run different firmware versions on different units. The race-window probability is independent across pools and firmware builds, so a near-miss on one is not a near-miss on the others — and a find on any one of them is yours. This is how serious solo operators run their fleets.
Lock down the post-event evidence packet. The instant you suspect a race, screenshot the miner UI, the pool dashboard's recent shares page, and any Discord / Telegram alerts. Note: worker name, claimed block height, timestamp down to the second, pool URL. Do not reboot, change Stratum User, or flash. Pull the captured stratum log and pcap covering 60 seconds before and after the suspected event. Search for `mining.submit` JSON-RPC lines around your timestamp. A reply of `result: false` with `error[0]=21` or `23` is the smoking gun.
Find the adjacent `mining.set_extranonce` or `mining.notify` line within ±2 seconds of the failed `mining.submit`. A `mining.set_extranonce` immediately before a failed submit strongly suggests the race. A `mining.notify` with `clean_jobs=true` immediately before suggests a job-rotation race rather than an extranonce-rotation race — same family, slightly different mitigation. Both are minimized by polite-flush firmware. No housekeeping message in the window means the failure is something other than the race.
Cross-check on chain via mempool.space at the relevant block height. Find every block within ±2 minutes of your event. Read each coinbase. If a block at that height was mined by a different pool with a different coinbase address, the race didn't matter — somebody else found a valid nonce first regardless. forkmonitor.info will tell you whether any orphan candidate exists at the relevant height.
Pool admin contact (only with the evidence packet ready). On open-source solo pools, ckpool admin (Con Kolivas) responds on Libera IRC `#ckpool`; public-pool.io's Benjamin Wilson responds on GitHub issues at the public-pool repo. Send: the failed `mining.submit` JSON, adjacent `mining.set_extranonce` / `mining.notify` JSON, your worker address, the pool URL and timestamp. They cannot recover the block but they can tune their backend to widen the race window in your favour next time.
Open a D-Central support ticket for: (a) confirmed stale-extranonce rejects of network-difficulty shares with full forensic packet, (b) suspected firmware regression after an OTA upgrade, (c) written forensic finding for tax / accounting / insurance purposes, (d) infrastructure review before a serious multi-miner deployment. D-Central will replay the pcap in Wireshark, cross-reference against on-chain blocks, reproduce the race window on a bench Bitaxe with a controlled `public-pool` instance, file upstream bug reports, and provide a written report. For Antminer-class solo on stock Bitmain firmware, the ticket includes a DCENT_OS migration recommendation as a deliverable.
When to Seek Professional Repair
If the steps above do not resolve the issue, or if you are not comfortable performing these repairs yourself, professional service is recommended. Attempting advanced repairs without proper equipment can cause further damage.
Related Error Codes
Still Having Issues?
Our team of Bitcoin Mining Hackers has been repairing ASIC miners since 2016. We have seen it all and fixed it all. Get a professional diagnosis.
