drg-xmrig: fork of xmrig-hac with unified pow-hash share model
Port miner113's RX_DRAGONX mining model into the RX_HUSH path so DragonX mining is identical in solo and pool mode: - CpuWorker: filter EVERY hash on SHA256D(header + RandomX solution) (the block-bearing pow-hash) instead of the RandomX hash; submit the full 32-byte nonce + rx_hash. Removes the fragile pool-mode dual-check that was dropping ~half of block candidates. - Job: 32-byte nonce for RX_HUSH in pool mode too (was solo-only). - JobResult: populate nonceBytes() on the standard 4-byte path. - Client: submit a variable-width nonce (32-byte for DragonX) with a dynamically laid-out temp buffer. Effect: shares and blocks use one metric, so the pool receives every block candidate (no under-submission gap) and the hashrate is block-relevant. Rebrand to drg-xmrig (version.h, build.sh, package.json, README) + add PROTOCOL.md wire spec. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
291
PROTOCOL.md
Normal file
291
PROTOCOL.md
Normal file
@@ -0,0 +1,291 @@
|
||||
# RX_DRAGONX Wire Protocol & Nonce Handling Spec (miner113 xmrig fork)
|
||||
|
||||
Source tree analyzed: `/home/dev/xmrig` (C++ xmrig fork, "miner113"/"Duke Leto" DragonX additions).
|
||||
Scope: ONLY the `RX_DRAGONX` (DragonX / Hush Smart Chain) algorithm path.
|
||||
All line references are to files under `/home/dev/xmrig/`.
|
||||
|
||||
> TL;DR for the pool implementer:
|
||||
> - Stratum `algo` string is exactly **`rx/dragonx`**.
|
||||
> - DragonX header (pre-nonce) is **108 bytes**; the full hashed blob is **140 bytes** (108 header + 32-byte nonce). Pool may send the blob either as a 140-byte hex `blob` (standard `job` notify) or as a Zcash-style `mining.notify` params array.
|
||||
> - Nonce is a **32-byte field at offset 108** (`nonceOffset()=108`, `nonceSize()=32`).
|
||||
> - In **pool/stratum** mode only the **uint32 at bytes [108:112]** of the nonce varies, starting from **0** with **no randomization**, driven by the global atomic counter. Bytes **[112:140] stay exactly as the pool sent them in the blob** (normally all zero). There is **no pool-assigned extra_nonce** applied to the DragonX nonce. => Two miners on the same job WILL scan identical nonce values unless the pool differentiates the blob per-connection. See §3 "BOTTOM LINE".
|
||||
> - Submit JSON: `{id, job_id, nonce(64 hex = 32 bytes), result(64 hex = 32-byte RandomX hash), algo:"rx/dragonx"}`.
|
||||
> - Share filter = `double_sha256(140-byte header || 0x20 || rx_hash)`; submit if the **last 8 bytes (little-endian uint64 at offset 24)** `< job.target()`. Same metric for shares and blocks (no separate block-target check).
|
||||
|
||||
---
|
||||
|
||||
## 1. ALGORITHM IDENTITY
|
||||
|
||||
### Enum / id
|
||||
- `Algorithm::RX_DRAGONX = 0x72151264` — `src/base/crypto/Algorithm.h:83`.
|
||||
- Family is computed as `id & 0xff000000` for non-CN algos: `0x72151264 & 0xff000000 = 0x72000000 = RANDOM_X` (`src/base/crypto/Algorithm.h:98` `RANDOM_X = 0x72000000`, `:180` `family()`). So `RX_DRAGONX.family() == Algorithm::RANDOM_X`. This matters: all the `family()==RANDOM_X` branches in the miner apply to DragonX.
|
||||
- `l2()`/`l3()` derive from the id bytes like other RandomX algos (`Algorithm.h:178`); DragonX uses the standard RandomX scratchpad size (2 MiB) via its config (see below).
|
||||
|
||||
### Stratum algo string (the exact "algo" JSON value)
|
||||
- Canonical name string: **`"rx/dragonx"`** — `src/base/crypto/Algorithm.cpp:87` (`kRX_DRAGONX = "rx/dragonx"`), registered in `kAlgorithmNames` at `:154` via `ALGO_NAME(RX_DRAGONX)`. `Algorithm::name()` returns this string, and it is what gets put into the submit `algo` field (§4).
|
||||
- Accepted aliases (case-insensitive, `kAlgorithmAliases`, `src/base/crypto/Algorithm.cpp:270-272`):
|
||||
- `"rx/dragonx"` (auto, from name)
|
||||
- `"randomx/dragonx"`
|
||||
- `"randomdragonx"`
|
||||
- `"dragonx"`
|
||||
- The pool may name the algo via the pool config `"algo": "rx/dragonx"` and/or `"coin": "dragonx"` (see `config-dragonx.json` lines 8-9). When a `job` carries an `"algo"` string it is parsed by `job.setAlgorithm(algo)` (`Client.cpp:382`); otherwise the coin's algorithm is inferred from the blob version byte (`Client.cpp:384-390`). Note: `RX_DRAGONX` is **not** present in `Coin.cpp`'s coin table, so coin-based inference does not map to DragonX; the `"algo"` string (or pool `algorithm()` default) is the reliable identity. The Zcash-style `mining.notify` path keys off `m_pool.algorithm() == RX_DRAGONX` (`Client.cpp:815`).
|
||||
|
||||
### RandomX parameters / seed handling (customized)
|
||||
- Config selected in `RxAlgo::base()`: `case Algorithm::RX_DRAGONX: return &RandomX_DragonXConfig;` — `src/crypto/rx/RxAlgo.cpp:52-53`.
|
||||
- DragonX RandomX configuration — `src/crypto/randomx/randomx.cpp:94-101`:
|
||||
```
|
||||
ArgonIterations = 5
|
||||
ArgonSalt = "RandomXHUSH\x03"
|
||||
ProgramSize = 512
|
||||
ProgramIterations = 4096
|
||||
ProgramCount = 16
|
||||
```
|
||||
(All other parameters inherit `RandomX_ConfigurationBase`: `ArgonLanes=1`, `SuperscalarLatency=170`, etc. — `randomx.cpp:123+`.)
|
||||
- RandomX program version: `RxAlgo::version()` returns `104` for everything except RX_WOW (`RxAlgo.cpp:63-66`), so DragonX = 104.
|
||||
- Seed: standard RandomX `seed_hash` (32 bytes). Delivered over stratum in the `seed_hash` field and stored via `Job::setSeedHash()` which requires exactly 64 hex chars (`Job.cpp:193-206`). In solo mode the seed comes from the daemon's `randomxseedhash` getblocktemplate field (`JunoRpcClient.cpp:539`). No DragonX-specific seed transformation — the seed bytes are used directly as the RandomX cache key.
|
||||
|
||||
---
|
||||
|
||||
## 2. JOB FORMAT (pool -> miner)
|
||||
|
||||
There are **two** delivery formats handled by `Client.cpp`:
|
||||
|
||||
### 2a. Standard JSON `job` notify (method `"job"` / login result) — `Client.cpp:365 parseJob`
|
||||
JSON fields consumed (object `params`):
|
||||
| field | required | code | how consumed |
|
||||
|-------|----------|------|--------------|
|
||||
| `job_id` | yes | 3 | `job.setId()` (`Client.cpp:374`) |
|
||||
| `algo` | optional | — | `job.setAlgorithm(algo)` (`Client.cpp:379-382`); should be `"rx/dragonx"` |
|
||||
| `blob` | yes | 4 | `job.setBlob(blobData)` (`Client.cpp:405`) — full 140-byte hex blob |
|
||||
| `target` | yes | 5 | `job.setTarget()` (`Client.cpp:411`) |
|
||||
| `height` | optional | — | `job.setHeight(Json::getUint64(...))` (`Client.cpp:416`) |
|
||||
| `seed_hash` | yes (RANDOM_X) | 7 | `job.setSeedHash()` (`Client.cpp:423`) — DragonX is RANDOM_X family, so required |
|
||||
| `sig_key` | optional | — | `job.setSigKey()` (`Client.cpp:428`) — 128 hex (64-byte) miner-signature key; normally absent for DragonX |
|
||||
| `extra_nonce`, `pool_wallet` | only MODE_SELF_SELECT | 4 | `Client.cpp:393-401`; **not used in normal DragonX pool mode** |
|
||||
|
||||
In this format the pool supplies the entire blob (header + nonce placeholder) as hex.
|
||||
|
||||
### 2b. Zcash-style `mining.notify` (DragonX-specific) — `Client.cpp:814-873`
|
||||
Only taken when `method == "mining.notify"` AND `m_pool.algorithm() == RX_DRAGONX`. `params` is an **array** with >= 9 elements (`Client.cpp:816`):
|
||||
```
|
||||
[ job_id, version, prevhash, merkleroot, blockcommitments, time, bits, clean_jobs, seed_hash ]
|
||||
[0] [1] [2] [3] [4] [5] [6] [7] [8]
|
||||
```
|
||||
- `version`,`time`,`bits` are hex strings parsed with `strtoul(...,16)` (`Client.cpp:838` for time; version/bits parsed inside `setZcashJob`).
|
||||
- `clean_jobs` is a bool, currently **ignored** (`Client.cpp:836` `(void)cleanJobs`).
|
||||
- Job built via `job.setZcashJob(version, prevHash, merkleRoot, blockCommitments, time, bits)` (`Client.cpp:846`).
|
||||
- `seed_hash` via `job.setSeedHash()` (`Client.cpp:852`).
|
||||
- Target: this path sets a **placeholder** target of all-`f`s (`Client.cpp:860`) or default diff 1 (`:861`). Real target is expected from `mining.set_difficulty`. NOTE: **there is currently NO handler for `mining.set_difficulty`** in `Client.cpp` (grep shows only the comment) — i.e. in pure Zcash-notify mode the effective target stays at the placeholder unless the pool also sends a standard `job`. The reliable, fully-wired path is **2a** (standard `job` with a real `target`).
|
||||
|
||||
### The blob: size and layout
|
||||
|
||||
**Pool sends 140 bytes** (108-byte header + 32-byte nonce placeholder). Evidence:
|
||||
- `setBlob` minimum-size check: `minSize = nonceOffset() + nonceSize()` = `108 + 32 = 140` (`Job.cpp:74`), and it rejects `size < minSize` (`Job.cpp:75`). So a blob shorter than 140 bytes (e.g. a 108-byte header-only blob) is **rejected** in path 2a.
|
||||
- `setZcashJob` builds the 108-byte header then sets `m_size = 140` reserving the 32-byte nonce slot (`Job.cpp:180`).
|
||||
- `setJunoHeader` (solo) copies 108 bytes and sets `m_size = 140` (`Job.cpp:185-191`).
|
||||
|
||||
So: the on-wire `blob` for path 2a should be **140 bytes = 280 hex chars**, with bytes [108:140] (the nonce) normally zero.
|
||||
|
||||
`setBlob` details (`Job.cpp:61-94`):
|
||||
- hex string length must be even (`:68`), `size = len/2`.
|
||||
- `minSize = 140`, must satisfy `140 <= size < sizeof(m_blob)=408` (`:74-77`, `kMaxBlobSize=408` `Job.h:50`).
|
||||
- `Cvt::fromHex` into `m_blob` (`:79`).
|
||||
- If the existing nonce dword (`readUnaligned(nonce())`, i.e. uint32 at offset 108) is non-zero, `m_nicehash` is force-enabled (`:83-85`). For DragonX the placeholder is zero so this stays false.
|
||||
- stores `m_size = size` (`:92`).
|
||||
|
||||
### Header layout (the 108-byte DragonX header) — `setZcashJob` `Job.cpp:96-183`
|
||||
| offset | size | field | byte order in blob |
|
||||
|-------:|-----:|-------|--------------------|
|
||||
| 0 | 4 | version (nVersion) | little-endian (raw `strtoul` value memcpy'd) `Job.cpp:115-117` |
|
||||
| 4 | 32 | prevHash | **byte-reversed** display->internal `Job.cpp:119-133` |
|
||||
| 36 | 32 | merkleRoot | **byte-reversed** display->internal `Job.cpp:135-149` |
|
||||
| 68 | 32 | blockCommitments | **byte-reversed** display->internal `Job.cpp:151-165` |
|
||||
| 100 | 4 | time (nTime) | little-endian `Job.cpp:167-169` |
|
||||
| 104 | 4 | bits (nBits compact) | little-endian `Job.cpp:171-174` |
|
||||
| 108 | 32 | nonce | (mining field; see §3) |
|
||||
|
||||
Byte-order rule: the three 32-byte hashes arrive from getblocktemplate / pool in **display (big-endian) order** and are reversed to **internal (little-endian) order** for hashing (each `m_blob[pos+i] = temp[31-i]`). version/time/bits are stored little-endian directly. The solo `JunoRpcClient` builds the identical 108-byte layout manually (`JunoRpcClient.cpp:548-583`) — note it copies the already-internal-order hashes (`m_headerPrevHash` etc.) straight in without re-reversing, because those buffers were stored in internal order when parsed.
|
||||
|
||||
`setJunoHeader(const uint8_t* header108)` (`Job.cpp:185-191`): bypasses hex/reverse logic, memcpy's a ready-made 108-byte header, sets `m_size=140`. Used only by solo `JunoRpcClient` (`JunoRpcClient.cpp:587`).
|
||||
|
||||
> There is no function named `setDragonxHeader`. The DragonX header is built either by `setZcashJob` (stratum Zcash-notify) or `setJunoHeader` (solo), or delivered pre-built as the 140-byte `blob` (standard `job`).
|
||||
|
||||
### Target parsing & diff conversion — `Job::setTarget` `Job.cpp:209-250`
|
||||
- For DragonX (not RX_YADA), `target` hex is decoded by `Cvt::fromHex` and interpreted by length:
|
||||
- **4 bytes**: `m_target = 0xFFFFFFFFFFFFFFFF / (0xFFFFFFFF / u32)` (`Job.cpp:219-220`) — "compact" 32-bit difficulty-style target.
|
||||
- **8 bytes**: `m_target = u64` read directly (little-endian) (`Job.cpp:222-223`).
|
||||
- other lengths => 0 => rejected.
|
||||
- After parsing, `m_diff = toDiff(m_target)`.
|
||||
- `toDiff(target) = target ? (0xFFFFFFFFFFFFFFFF / target) : 0` — `Job.h:121`.
|
||||
- `setTarget64(u64)` is the direct setter used by solo: `m_target = u64; m_diff = toDiff(u64)` (`Job.h:107`). Solo computes `target64` from the compact `bits` field (`JunoRpcClient.cpp:604-635`), extracting the 64-bit window at byte offset 24 of the 256-bit target.
|
||||
- The mining comparison is always `pow_value(uint64) < m_target` (see §5). Larger `m_target` = easier.
|
||||
|
||||
---
|
||||
|
||||
## 3. NONCE HANDLING (most important)
|
||||
|
||||
### nonceOffset / nonceSize
|
||||
- `nonceOffset()` for RX_DRAGONX = **108** — `Job.cpp:271-273`.
|
||||
- `nonceSize()` for RX_DRAGONX = **32** — `Job.h:84-86` (`if (algorithm()==RX_DRAGONX) return 32;`).
|
||||
- The DragonX nonce field therefore occupies blob bytes **[108:140]** (32 bytes).
|
||||
|
||||
### Two distinct nonce regimes
|
||||
|
||||
DragonX uses a different nonce strategy depending on mining mode (`Job::isSoloMining()` / `WorkerJob::isSoloMining()`):
|
||||
|
||||
#### (A) POOL / STRATUM mode (`isSoloMining()==false`) — the standard incrementing 32-bit counter
|
||||
- `WorkerJob::nonce()` for DragonX returns a `uint32_t*` pointing at blob offset 108 (`WorkerJob.h:46`, and the `N==1` specialization `:150-153`). It only ever treats the nonce as a **uint32**.
|
||||
- Starting value & seeding: the global atomic counter `Nonce::m_nonces[index]` starts at **0** (`Nonce.cpp:27` `m_nonces[2] = {0,0}`) and is **reset to 0** on every job change (`Miner.cpp:124` `Nonce::reset(job.index())`, which sets `m_nonces[index]=0`, `Nonce.h:44`). There is **no randomization** of the starting nonce in pool mode.
|
||||
- `Nonce::next()` (`Nonce.cpp:33-65`): `fetch_add(reserveCount)` on the shared counter, then writes `(*nonce & ~mask) | counter` into the **uint32 at offset 108** (`:57`). The `nonce+1` (offset 112) write at `:59-61` only happens when `mask > 0xFFFFFFFF`. For DragonX `nonceMask()` = `0xFFFFFFFF` (because `nonceSize()=32 != sizeof(uint64_t)=8` and not nicehash → the `0xFFFFFFFFULL` branch of `Job.h:93`), so **only bytes [108:112] are ever written**; bytes [112:139] are untouched by the nonce machinery.
|
||||
- Per-round increment: `WorkerJob::nextRound` adds `roundSize` (=1) to the uint32 at 108 each hash, and every `kReserveCount`(=32768) hashes grabs a fresh range via `Nonce::next` (`WorkerJob.h:69-87`, specialization `:156-176`; `CpuWorker::nextRound` `CpuWorker.cpp:480-500`, `kReserveCount` `CpuWorker.cpp:82`).
|
||||
- WHICH bytes vary: **only the 4 bytes [108:112]** (a little-endian uint32) increment. Bytes **[112:139] stay exactly as they were in the blob the pool sent** (all zero for a normal placeholder).
|
||||
- The full 32-byte nonce that gets hashed and submitted is read from the blob at offset 108 each round and saved before increment: `CpuWorker.cpp:342-344` (`memcpy(current_solo_nonces + i*32, m_job.blob() + nonceOffset() + i*job.size(), 32)`). So the submitted nonce = `[ varying uint32 LE | 28 bytes copied verbatim from the blob (normally zero) ]`.
|
||||
|
||||
#### (B) SOLO mode (`isSoloMining()==true`) — random 256-bit nonce per thread
|
||||
- Solo nonces are managed by `SoloNonce` and `WorkerJob`'s solo path, NOT by the global `Nonce` counter.
|
||||
- `WorkerJob::initSoloNonces()` (`WorkerJob.h:99-105`) calls `SoloNonce::initialize()` per thread then copies the 32 bytes into the blob at offset 108.
|
||||
- `SoloNonce::initialize` (`SoloNonce.cpp:39-61`): fills all 32 bytes with **cryptographically secure random** (`/dev/urandom`, or `BCryptGenRandom` on Windows; fallback `mt19937_64`), then **zeros bytes [0:2]** (low 16 bits, increment space) and **zeros bytes [30:32]** (top 16 bits, safety margin). Net: ~224 random bits per thread.
|
||||
- `WorkerJob::nextRoundSolo` (`WorkerJob.h:107-114`) does a full little-endian 256-bit `SoloNonce::increment` (`SoloNonce.cpp:64-72`) each round and re-copies to the blob — never exhausts.
|
||||
- So in solo every thread/instance gets a distinct random high-entropy nonce, and the whole 32-byte field is meaningful and submitted (`result.soloNonce()`).
|
||||
|
||||
### extra_nonce application
|
||||
- DragonX standard stratum path does **NOT** apply any `extra_nonce` to the nonce field. `Job::setExtraNonce`/`extraNonce()` (`Job.h:103`,`:79`) are only set in `MODE_SELF_SELECT` (`Client.cpp:394`) and consumed only by `SelfSelectClient` (`SelfSelectClient.cpp:172`) — a different code path that does not run for DragonX pool mining. The DragonX worker code (`CpuWorker.cpp`) never reads `extraNonce()`.
|
||||
- `nonceMask()` does reference `extraNonce().size()` but only for the `nonceSize()==sizeof(uint64_t)` (KawPow) case (`Job.h:93`); for DragonX (`nonceSize()==32`) the mask is the constant `0xFFFFFFFF`, independent of extra_nonce.
|
||||
|
||||
### BOTTOM LINE (miner-to-miner / thread-to-thread overlap)
|
||||
- **Across threads of one miner instance:** no overlap. The shared atomic `Nonce::m_nonces[index]` hands out disjoint `reserveCount`(32768)-sized ranges to each thread via `Nonce::next` (`Nonce.cpp:40`). Threads scan different uint32 ranges of [108:112].
|
||||
- **Across two separate miners (or one miner reconnecting) on the SAME job from a pool, in stratum/pool mode:** they **WILL scan the exact same nonce values**. Both start their counter at 0 (reset on job change, `Miner.cpp:124`; initial `m_nonces={0,0}`), there is **no per-connection randomization**, and bytes [112:139] are identical (the zero placeholder from the blob). Nothing in the miner prevents the overlap.
|
||||
- => To avoid duplicate-share work, the **pool MUST differentiate the job per connection**, e.g. by writing a unique value into the nonce placeholder bytes **[112:139]** of the per-miner `blob` (those bytes are preserved verbatim by the miner and included in the hash + submitted nonce), and/or into other header fields. There is no `extra_nonce` JSON mechanism wired for DragonX, so per-miner uniqueness has to be baked into the `blob` (path 2a) the pool sends.
|
||||
- **Solo mode** is the only mode with built-in cross-instance collision avoidance, via the 224-bit random `SoloNonce` (§3B). This randomization does **not** apply to pool mode.
|
||||
|
||||
---
|
||||
|
||||
## 4. SUBMIT FORMAT (miner -> pool)
|
||||
|
||||
### Stratum submit (`Client::submit`, `Client.cpp:181-251`)
|
||||
JSON-RPC `"submit"` with `params` object:
|
||||
| field | value | reference |
|
||||
|-------|-------|-----------|
|
||||
| `id` | `m_rpcId` (login id string) | `Client.cpp:223` |
|
||||
| `job_id` | `result.jobId` | `Client.cpp:224` |
|
||||
| `nonce` | hex of `result.nonceBytes()`, length `nonceSize()` bytes | `Client.cpp:211`,`:225` |
|
||||
| `result` | hex of the 32-byte hash `result.result()` | `Client.cpp:212`,`:226` |
|
||||
| `sig` | hex of 64-byte miner signature, **only if present** | `Client.cpp:214-216`,`:229-231` (absent for normal DragonX) |
|
||||
| `algo` | `result.algorithm.name()` = `"rx/dragonx"`, only if `EXT_ALGO` negotiated and algo valid | `Client.cpp:238-240` |
|
||||
|
||||
### `nonce` field for DragonX = 32 bytes => 64 hex chars. Confirmed.
|
||||
- `nonceHexSize = result.nonceSize()*2 + 1` (`Client.cpp:203`); for DragonX `nonceSize()==32` → 64 hex chars (+NUL).
|
||||
- `Cvt::toHex(nonce, nonceHexSize, result.nonceBytes(), result.nonceSize())` writes all 32 bytes (`Client.cpp:211`).
|
||||
- Contents = the **full 32-byte nonce field** from the blob as hashed: in pool mode `[ uint32 LE counter | 28 zero bytes ]`; in solo `[ random 256-bit nonce ]`. (`result.nonceBytes()`/`m_nonceBytes`, populated per constructor — see below.)
|
||||
- `m_tempBuf` was enlarged to 512 bytes specifically to hold the 64-char nonce + result + sig (`Client.cpp:86`, comment "Increased for RX_DRAGONX 32-byte nonce support").
|
||||
|
||||
### `result` field = the 32-byte RandomX hash (PoW solution) => 64 hex chars. Confirmed.
|
||||
- `Cvt::toHex(data, 65, result.result(), 32)` (`Client.cpp:212`). `result.result()` = `m_result[32]`, the RandomX hash `m_hash` for that nonce (passed into the JobResult ctor in `CpuWorker.cpp:449`). It is **not** the double-SHA256 pow_hash; it is the raw RandomX output (so the pool can re-verify `randomx(blob,seed)==result` and recompute `double_sha256` itself).
|
||||
|
||||
### JobResult constructors used — `src/net/JobResult.h`
|
||||
- DragonX uses the **32-byte-nonce / solo-style constructor**: `JobResult(const Job&, const uint8_t* nonce32, const uint8_t* result)` (`JobResult.h:94-107`). It copies `m_result=result`, `m_nonceBytes=nonce32` (full 32 bytes), sets `m_nonceSize=32`, and `m_isSoloResult=true`.
|
||||
- This is what `CpuWorker` calls for DragonX: `JobResults::submit(JobResult(job, current_solo_nonces + i*32, m_hash + i*32))` (`CpuWorker.cpp:449`). Same call shape is used for solo (`CpuWorker.cpp:457`).
|
||||
- The uint64-nonce constructor `JobResult(job, uint64 nonce, result, ...)` (`JobResult.h:46-80`) is the **standard XMRig path** for non-DragonX algos (`CpuWorker.cpp:459`). NOTE: that constructor *does* contain a DragonX special-case (`JobResult.h:70-79`: for RX_DRAGONX set `m_nonceSize=32`, zero 32 bytes, write only first 8 from the uint64), but **the DragonX worker branch never uses it** — it always uses the 32-byte ctor. So the effective DragonX submit nonce always carries the true 32-byte field from the blob (low 4 bytes vary, rest as-sent), not a truncated 8-byte value.
|
||||
|
||||
### Solo submit (`JunoRpcClient`)
|
||||
Solo does NOT use the stratum `submit` JSON. `JunoRpcClient::submit` → `submitBlock` (`JunoRpcClient.cpp:141-149`, `:358-443`) assembles a full raw block hex and calls daemon `submitblock` (JSON-RPC 1.0). The block = 108-byte header + `result.soloNonce()` (32 bytes, `:394-395`) + `0x20` solution-length varint + `result.result()` (32-byte rx hash, `:399-400`) + tx count + coinbase + txs. The nonce/result semantics match the stratum submit (32-byte nonce, 32-byte rx hash).
|
||||
|
||||
---
|
||||
|
||||
## 5. POW HASH / SHARE FILTER (`src/backend/cpu/CpuWorker.cpp`)
|
||||
|
||||
### `dragonx_pow_hash` — `CpuWorker.cpp:106-117`
|
||||
```c
|
||||
static inline void dragonx_pow_hash(const uint8_t* blob, const uint8_t* rx_hash, uint8_t* out) {
|
||||
uint8_t full_header[173];
|
||||
memcpy(full_header, blob, 140); // header(108) + nonce(32)
|
||||
full_header[140] = 0x20; // compact_size = 32 (solution length)
|
||||
memcpy(full_header + 141, rx_hash, 32); // RandomX hash = PoW solution
|
||||
uint8_t tmp[32];
|
||||
SHA256(full_header, 173, tmp);
|
||||
SHA256(tmp, 32, out); // double SHA256
|
||||
}
|
||||
```
|
||||
Exact 173-byte layout:
|
||||
| offset | size | content |
|
||||
|-------:|-----:|---------|
|
||||
| 0 | 108 | header base (version, prevHash, merkleRoot, blockCommitments, time, bits) |
|
||||
| 108 | 32 | 32-byte nonce (this round's nonce) |
|
||||
| 140 | 1 | `0x20` (CompactSize = 32, the solution length) |
|
||||
| 141 | 32 | RandomX hash result (`m_hash` for this nonce) |
|
||||
|
||||
Output = `SHA256(SHA256(full_header[173]))`. `SHA256` is OpenSSL when TLS is built, else sph_sha256 (Linux) / BCrypt (Windows) (`CpuWorker.cpp:41-67`). Confirmed byte layout matches the daemon's `CBlockHeader::GetHash()`.
|
||||
|
||||
The `blob_for_header[140]` fed to `dragonx_pow_hash` is reconstructed as `m_job.blob()[0:108]` (header, unchanged by nextRound) + `current_solo_nonces[i][0:32]` (the saved 32-byte nonce *before* increment) — `CpuWorker.cpp:435-441`. This guarantees the hashed nonce == the submitted nonce.
|
||||
|
||||
### Submit condition for RX_DRAGONX — `CpuWorker.cpp:425-450`
|
||||
```c
|
||||
alignas(8) uint8_t pow_hash[32];
|
||||
dragonx_pow_hash(blob_for_header, m_hash + i*32, pow_hash);
|
||||
const uint64_t pow_value = *reinterpret_cast<uint64_t*>(pow_hash + 24); // last 8 bytes, LE
|
||||
if (pow_value < job.target()) {
|
||||
JobResults::submit(JobResult(job, current_solo_nonces + i*32, m_hash + i*32));
|
||||
}
|
||||
```
|
||||
- `pow_value` = little-endian uint64 read from `pow_hash[24..31]` (the last 8 bytes) — `CpuWorker.cpp:444`.
|
||||
- Submit iff `pow_value < job.target()` — `CpuWorker.cpp:445`.
|
||||
- This is **uniform**: there is exactly one comparison, identical in solo and pool mode. No `m_job.isSoloMining()` branch inside the DragonX block (contrast the standard path at `CpuWorker.cpp:454-460`, which does branch solo vs pool only to pick the JobResult constructor — DragonX skips that and always uses the 32-byte ctor).
|
||||
|
||||
### No separate block_target check. Confirmed.
|
||||
- The only target compared anywhere on the DragonX path is `job.target()` against `pow_value`. There is **no** second comparison against a distinct network/block target in `CpuWorker.cpp` (grep: the DragonX block has a single `< job.target()` test at `:445`). Shares and blocks use the **same** double-SHA256 metric; whether a submitted share is also a block is determined by the pool/daemon comparing the same `double_sha256` against the real network target (the comments at `CpuWorker.cpp:446-448` state the pool does the block-vs-share distinction). The miner submits every share whose `double_sha256` beats the pool-set `job.target()`.
|
||||
|
||||
---
|
||||
|
||||
## 6. SOLO vs POOL behavior
|
||||
|
||||
| aspect | POOL / stratum (`Client`) | SOLO (`JunoRpcClient` / daemon) |
|
||||
|--------|---------------------------|---------------------------------|
|
||||
| job source | `parseJob` (140-byte `blob`) or Zcash `mining.notify` (`setZcashJob`) | getblocktemplate → `setJunoHeader` (`JunoRpcClient.cpp:587`) |
|
||||
| `isSoloMining()` | false | true (`JunoRpcClient.cpp:640`) |
|
||||
| nonce field | uint32 counter at [108:112], rest = blob bytes (zero); starts 0, no randomization | random 224-bit `SoloNonce`, per-thread, 256-bit increment |
|
||||
| nonce bytes scanned | only [108:112] vary | full 32 bytes meaningful |
|
||||
| RandomX hashing | `randomx_calculate_hash_first/next`, `family()==RANDOM_X` branch (`CpuWorker.cpp:366-384`) — **identical** | same |
|
||||
| pow hash + filter | `dragonx_pow_hash`, `pow_value < job.target()` (`CpuWorker.cpp:425-450`) — **identical** | **identical** (same code, no branch) |
|
||||
| JobResult ctor | 32-byte ctor `JobResult(job, nonce32, result)` (`CpuWorker.cpp:449`) | same 32-byte ctor (`CpuWorker.cpp:449`) |
|
||||
| target derivation | from pool `target` hex (`Job::setTarget`) | from compact `bits` → `setTarget64` (`JunoRpcClient.cpp:604-635`) |
|
||||
| submit transport | JSON-RPC `submit` {id,job_id,nonce,result,algo} (`Client::submit`) | daemon `submitblock` with full serialized block (`JunoRpcClient::submitBlock`) |
|
||||
|
||||
**Key point:** the **CpuWorker hashing and the share/PoW filter are byte-for-byte identical** in solo and pool for DragonX (same `randomx_calculate_hash_*`, same `dragonx_pow_hash`, same `pow_value < job.target()`, same 32-byte JobResult). The differences are entirely in (a) **nonce seeding** (random 256-bit solo vs zero-start uint32 pool) and (b) **submission transport** (raw `submitblock` vs stratum `submit`). The `m_isSoloMining` flag only switches nonce management (`CpuWorker.cpp:337-344`, `nextRound`→`nextRoundSolo` `:483-484`) and submit transport — not the hash math or the difficulty test.
|
||||
|
||||
---
|
||||
|
||||
## Quick reference — exact JSON shapes
|
||||
|
||||
### Pool -> miner (standard `job`)
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"method": "job",
|
||||
"params": {
|
||||
"job_id": "<id>",
|
||||
"algo": "rx/dragonx",
|
||||
"blob": "<280 hex chars = 140 bytes: 108-byte header + 32-byte nonce placeholder>",
|
||||
"target": "<8 or 16 hex chars (4 or 8 LE bytes)>",
|
||||
"height": <int>,
|
||||
"seed_hash":"<64 hex chars = 32-byte RandomX seed>"
|
||||
}
|
||||
}
|
||||
```
|
||||
(For per-miner nonce-space separation, set distinct values in blob bytes [112:140], i.e. hex chars 224..280.)
|
||||
|
||||
### Miner -> pool (`submit`)
|
||||
```json
|
||||
{
|
||||
"id": <seq>,
|
||||
"jsonrpc": "2.0",
|
||||
"method": "submit",
|
||||
"params": {
|
||||
"id": "<rpcId from login>",
|
||||
"job_id": "<id>",
|
||||
"nonce": "<64 hex chars = full 32-byte nonce field>",
|
||||
"result": "<64 hex chars = 32-byte RandomX hash>",
|
||||
"algo": "rx/dragonx"
|
||||
}
|
||||
}
|
||||
```
|
||||
Pool verification (per the miner's contract): recompute `rx = randomx(seed_hash, header[0:108] || nonce)` and require `rx == result`; then compute `double_sha256(header[0:108] || nonce || 0x20 || rx)` and compare its last 8 bytes (LE uint64 at offset 24) against the share target (and against the network target for block detection).
|
||||
61
README.md
61
README.md
@@ -1,35 +1,50 @@
|
||||
# XMRig-HAC
|
||||
# DRG-XMRig
|
||||
|
||||
XMRig-HAC is a fork of [XMRig](https://github.com/xmrig/xmrig) with added support for **DragonX** coin mining using the **rx/hush** (RandomX-HUSH) algorithm.
|
||||
**DRG-XMRig** is the official [DragonX](https://dragonx.is) CPU miner — a fork of
|
||||
[XMRig](https://github.com/xmrig/xmrig) that mines DragonX's dual-hash
|
||||
(RandomX + double-SHA256) proof of work.
|
||||
|
||||
**Repository:** [git.hush.is/dragonx/xmrig-hac](https://git.hush.is/dragonx/xmrig-hac)
|
||||
**Repository:** https://git.dragonx.is/DragonX/drg-xmrig
|
||||
|
||||
## Fork Changes
|
||||
## What's different
|
||||
|
||||
This fork adds:
|
||||
- **DragonX** coin support
|
||||
- **rx/hush** algorithm (RandomX-HUSH variant)
|
||||
- Customized RandomX configuration for HUSH/DragonX
|
||||
DragonX block validity is `SHA256D(header + RandomX(header)) <= target`: RandomX
|
||||
produces the solution, and the **double-SHA256** of `header + solution` is the
|
||||
difficulty-bearing hash.
|
||||
|
||||
Based on XMRig v6.25.0.
|
||||
DRG-XMRig filters **every** hash on that double-SHA256 pow-hash (not on the RandomX
|
||||
hash), uniformly in both solo and pool mode. A block is therefore simply a share
|
||||
that clears a harder target, which means:
|
||||
|
||||
- the pool receives **every** block candidate — no under-submission gap, and
|
||||
- the reported hashrate is block-relevant (it matches the network metric).
|
||||
|
||||
It mines the `rx/dragonx` algorithm (aliases: `rx/hush`, `dragonx`) using DragonX's
|
||||
customized RandomX parameters.
|
||||
|
||||
The exact wire protocol (job format, 32-byte nonce / extranonce handling, submit
|
||||
format, pow-hash construction) is documented in [PROTOCOL.md](PROTOCOL.md). A
|
||||
DragonX pool that scores shares on the double-SHA256 pow-hash is required.
|
||||
|
||||
Based on XMRig v6.25.
|
||||
|
||||
## Mining backends
|
||||
- **CPU** (x86/x64/ARMv7/ARMv8/RISC-V)
|
||||
- **OpenCL** for AMD GPUs.
|
||||
- **CUDA** for NVIDIA GPUs via external [CUDA plugin](https://github.com/xmrig/xmrig-cuda).
|
||||
- **CPU** (x86/x64, ARMv7/ARMv8, RISC-V)
|
||||
|
||||
## Download
|
||||
* **[Binary releases](https://git.hush.is/dragonx/xmrig-hac/releases)**
|
||||
* **[Build from source](https://xmrig.com/docs/miner/build)**
|
||||
## Build
|
||||
```
|
||||
./build.sh --linux-release # Linux x86_64 static release
|
||||
./build.sh --win-release # Windows x86_64 (MinGW cross-compile)
|
||||
```
|
||||
Dependencies are built by `scripts/build_deps.sh` on first run.
|
||||
|
||||
## Usage
|
||||
The preferred way to configure the miner is the [JSON config file](https://xmrig.com/docs/miner/config) as it is more flexible and human friendly. The [command line interface](https://xmrig.com/docs/miner/command-line-options) does not cover all features, such as mining profiles for different algorithms. Important options can be changed during runtime without miner restart by editing the config file or executing [API](https://xmrig.com/docs/miner/api) calls.
|
||||
|
||||
* **[Wizard](https://xmrig.com/wizard)** helps you create initial configuration for the miner.
|
||||
* **[Workers](http://workers.xmrig.info)** helps manage your miners via HTTP API.
|
||||
```
|
||||
./xmrig -o <pool-host>:<port> -u <YOUR_DRGX_ADDRESS> -a rx/dragonx
|
||||
```
|
||||
The preferred way to configure the miner is the JSON config file (`config.json`).
|
||||
|
||||
## Credits
|
||||
|
||||
This fork is based on [XMRig](https://github.com/xmrig/xmrig) by:
|
||||
* **[xmrig](https://github.com/xmrig)**
|
||||
* **[sech1](https://github.com/SChernykh)**
|
||||
Based on [XMRig](https://github.com/xmrig/xmrig) by
|
||||
[xmrig](https://github.com/xmrig) and [sech1](https://github.com/SChernykh)).
|
||||
DragonX dual-hash mining model.
|
||||
|
||||
8
build.sh
8
build.sh
@@ -1,5 +1,5 @@
|
||||
#!/bin/bash
|
||||
# XMRig-HAC Release Build Script
|
||||
# DRG-XMRig (DragonX miner) Release Build Script
|
||||
# Usage: ./build.sh [--linux-release] [--win-release]
|
||||
|
||||
set -e
|
||||
@@ -31,7 +31,7 @@ if ! $BUILD_LINUX && ! $BUILD_WIN; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "=== XMRig-HAC Release Builder v${VERSION} ==="
|
||||
echo "=== DRG-XMRig Release Builder v${VERSION} ==="
|
||||
mkdir -p "$RELEASE_DIR"
|
||||
|
||||
build_linux() {
|
||||
@@ -65,7 +65,7 @@ build_linux() {
|
||||
strip xmrig
|
||||
|
||||
# Package
|
||||
local PKG_NAME="xmrig-hac-${VERSION}-linux-x64"
|
||||
local PKG_NAME="drg-xmrig-${VERSION}-linux-x64"
|
||||
local PKG_DIR="$RELEASE_DIR/$PKG_NAME"
|
||||
rm -rf "$PKG_DIR"
|
||||
mkdir -p "$PKG_DIR"
|
||||
@@ -95,7 +95,7 @@ build_windows() {
|
||||
local BUILD_DIR="$ROOT_DIR/build-windows"
|
||||
|
||||
# Package
|
||||
local PKG_NAME="xmrig-hac-${VERSION}-win-x64"
|
||||
local PKG_NAME="drg-xmrig-${VERSION}-win-x64"
|
||||
local PKG_DIR="$RELEASE_DIR/$PKG_NAME"
|
||||
rm -rf "$PKG_DIR"
|
||||
mkdir -p "$PKG_DIR"
|
||||
|
||||
41
doc/build/CMAKE_OPTIONS.md
vendored
41
doc/build/CMAKE_OPTIONS.md
vendored
@@ -1,41 +0,0 @@
|
||||
# CMake options
|
||||
**Recent version of this document: https://xmrig.com/docs/miner/cmake-options**
|
||||
|
||||
## Algorithms
|
||||
|
||||
* **`-DWITH_CN_LITE=OFF`** disable all CryptoNight-Lite algorithms (`cn-lite/0`, `cn-lite/1`).
|
||||
* **`-DWITH_CN_HEAVY=OFF`** disable all CryptoNight-Heavy algorithms (`cn-heavy/0`, `cn-heavy/xhv`, `cn-heavy/tube`).
|
||||
* **`-DWITH_CN_PICO=OFF`** disable CryptoNight-Pico algorithm (`cn-pico`).
|
||||
* **`-DWITH_RANDOMX=OFF`** disable RandomX algorithms (`rx/loki`, `rx/wow`).
|
||||
* **`-DWITH_ARGON2=OFF`** disable Argon2 algorithms (`argon2/chukwa`, `argon2/wrkz`).
|
||||
|
||||
## Features
|
||||
|
||||
* **`-DWITH_HWLOC=OFF`**
|
||||
disable [hwloc](https://github.com/xmrig/xmrig/issues/1077) support.
|
||||
Disabling this feature is not recommended in most cases.
|
||||
This feature add external dependency to libhwloc (1.10.0+) (except MSVC builds).
|
||||
* **`-DWITH_LIBCPUID=OFF`** disable built in libcpuid support, this feature always disabled if hwloc enabled, if both hwloc and libcpuid disabled auto configuration for CPU will very limited.
|
||||
* **`-DWITH_HTTP=OFF`** disable built in HTTP support, this feature used for HTTP API and daemon (solo mining) support.
|
||||
* **`-DWITH_TLS=OFF`** disable SSL/TLS support (secure connections to pool). This feature add external dependency to OpenSSL.
|
||||
* **`-DWITH_ASM=OFF`** disable assembly optimizations for modern CryptoNight algorithms.
|
||||
* **`-DWITH_EMBEDDED_CONFIG=ON`** Enable [embedded](https://github.com/xmrig/xmrig/issues/957) config support.
|
||||
* **`-DWITH_OPENCL=OFF`** Disable OpenCL backend.
|
||||
* **`-DWITH_CUDA=OFF`** Disable CUDA backend.
|
||||
* **`-DWITH_SSE4_1=OFF`** Disable SSE 4.1 for Blake2 (useful for arm builds).
|
||||
|
||||
## Debug options
|
||||
|
||||
* **`-DWITH_DEBUG_LOG=ON`** enable debug log (mostly network requests).
|
||||
* **`-DHWLOC_DEBUG=ON`** enable some debug log for hwloc.
|
||||
* **`-DCMAKE_BUILD_TYPE=Debug`** enable debug build, only useful for investigate crashes, this option slow down miner.
|
||||
|
||||
## Special build options
|
||||
|
||||
* **`-DXMRIG_DEPS=<path>`** path to precompiled dependencies https://github.com/xmrig/xmrig-deps
|
||||
* **`-DARM_TARGET=<number>`** override ARM target, possible values `7` (ARMv7) and `8` (ARMv8).
|
||||
* **`-DUV_INCLUDE_DIR=<path>`** custom path to libuv headers.
|
||||
* **`-DUV_LIBRARY=<path>`** custom path to libuv library.
|
||||
* **`-DHWLOC_INCLUDE_DIR=<path>`** custom path to hwloc headers.
|
||||
* **`-DHWLOC_LIBRARY=<path>`** custom path to hwloc library.
|
||||
* **`-DOPENSSL_ROOT_DIR=<path>`** custom path to OpenSSL.
|
||||
12
package.json
12
package.json
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "xmrig",
|
||||
"version": "3.0.0",
|
||||
"description": "RandomX, CryptoNight and Argon2 miner",
|
||||
"name": "drg-xmrig",
|
||||
"version": "6.25.1",
|
||||
"description": "DragonX RandomX (rx/dragonx) CPU miner",
|
||||
"main": "index.js",
|
||||
"directories": {
|
||||
"doc": "doc"
|
||||
@@ -11,13 +11,13 @@
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://git.hush.is/dragonx/xmrig-hac.git"
|
||||
"url": "git+https://git.dragonx.is/DragonX/drg-xmrig.git"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "GPLv3",
|
||||
"bugs": {
|
||||
"url": "https://git.hush.is/dragonx/xmrig-hac/issues"
|
||||
"url": "https://git.dragonx.is/DragonX/drg-xmrig/issues"
|
||||
},
|
||||
"homepage": "https://git.hush.is/dragonx/xmrig-hac#readme"
|
||||
"homepage": "https://git.dragonx.is/DragonX/drg-xmrig#readme"
|
||||
}
|
||||
|
||||
@@ -330,10 +330,16 @@ void xmrig::CpuWorker<N>::start()
|
||||
alignas(8) uint8_t current_solo_nonces[N * 32];
|
||||
for (size_t i = 0; i < N; ++i) {
|
||||
current_job_nonces[i] = readUnaligned(m_job.nonce(i));
|
||||
// Save solo nonces BEFORE they get incremented by nextRound()
|
||||
// Save the 32-byte nonce BEFORE nextRound() increments the counter.
|
||||
if (m_job.isSoloMining()) {
|
||||
memcpy(current_solo_nonces + i * 32, m_job.soloNonce(i), 32);
|
||||
}
|
||||
// RX_HUSH/DragonX pool mining: the 32-byte nonce lives in the blob at
|
||||
// [108:140] (4-byte counter + 28 bytes of pool-assigned extraNonce). Save it
|
||||
// verbatim so the submitted nonce matches the header that was hashed.
|
||||
else if (job.algorithm() == Algorithm::RX_HUSH) {
|
||||
memcpy(current_solo_nonces + i * 32, m_job.blob() + m_job.nonceOffset() + i * job.size(), 32);
|
||||
}
|
||||
}
|
||||
|
||||
# ifdef XMRIG_FEATURE_BENCHMARK
|
||||
@@ -412,78 +418,34 @@ void xmrig::CpuWorker<N>::start()
|
||||
}
|
||||
# endif
|
||||
|
||||
if (job.algorithm() == Algorithm::RX_HUSH && m_job.isSoloMining()) {
|
||||
// ── DRAGONX/HUSH solo mining: use double_sha256(173-byte header) for difficulty ──
|
||||
if (job.algorithm() == Algorithm::RX_HUSH) {
|
||||
// ── DRAGONX/HUSH dual-hash PoW (unified solo + pool) ──
|
||||
//
|
||||
// The daemon checks GetHash() = double_sha256(173 bytes) < target,
|
||||
// NOT the RandomX hash directly. We must filter shares the same way
|
||||
// so that every submitted share is a genuine block candidate.
|
||||
// The block/share metric is double_sha256(header + RandomX solution),
|
||||
// NOT the RandomX hash. Filter EVERY hash on this pow-hash so that shares
|
||||
// and blocks use the same metric: a block is simply a share that clears a
|
||||
// harder target, so the pool receives every block candidate (no gap).
|
||||
//
|
||||
// Reconstruct the 140-byte blob that was ACTUALLY hashed this round:
|
||||
// - bytes [0:108] are unchanged by nextRound() (only nonce changes)
|
||||
// - bytes [108:140] = saved nonce (current_solo_nonces, before nextRound)
|
||||
// Reconstruct the 140-byte header that was actually hashed this round:
|
||||
// - bytes [0:108] header base (unchanged by nextRound)
|
||||
// - bytes [108:140] the 32-byte nonce saved before nextRound:
|
||||
// solo: a random 32-byte nonce;
|
||||
// pool: a 4-byte counter + 28 bytes of pool-assigned extraNonce,
|
||||
// preserved verbatim from the blob.
|
||||
uint8_t blob_for_header[140];
|
||||
memcpy(blob_for_header, m_job.blob(), 108); // header base (unchanged)
|
||||
memcpy(blob_for_header + 108, current_solo_nonces + i * 32, 32); // saved 32-byte nonce
|
||||
memcpy(blob_for_header, m_job.blob(), 108);
|
||||
memcpy(blob_for_header + 108, current_solo_nonces + i * 32, 32);
|
||||
|
||||
// Compute PoW hash: double_sha256(blob[140] + 0x20 + rx_hash[32])
|
||||
// PoW hash = double_sha256(blob[140] + 0x20 + rx_hash[32])
|
||||
alignas(8) uint8_t pow_hash[32];
|
||||
dragonx_pow_hash(blob_for_header, m_hash + (i * 32), pow_hash);
|
||||
|
||||
// Compare last 8 bytes of pow_hash (same field as XMRig's standard check)
|
||||
// Compare last 8 bytes (same field as XMRig's standard difficulty check).
|
||||
const uint64_t pow_value = *reinterpret_cast<uint64_t*>(pow_hash + 24);
|
||||
if (pow_value < job.target()) {
|
||||
// Submit full 32-byte nonce + rx_hash as result
|
||||
// Submit the full 32-byte nonce + the RandomX hash as the result.
|
||||
JobResults::submit(JobResult(job, current_solo_nonces + i * 32, m_hash + (i * 32)));
|
||||
}
|
||||
} else if (job.algorithm() == Algorithm::RX_HUSH && !m_job.isSoloMining()) {
|
||||
// ── DRAGONX/HUSH pool mining: dual check ──
|
||||
//
|
||||
// DragonX uses dual PoW: the block hash is SHA256D(header + RandomX solution),
|
||||
// NOT the RandomX hash itself. We must check SHA256D for EVERY hash to detect
|
||||
// blocks, and also check the RandomX hash for share difficulty.
|
||||
//
|
||||
// Reconstruct the 140-byte header with the 4-byte nonce at offset 108.
|
||||
// Bytes 112-139 come from the pool blob and may contain a per-client
|
||||
// extraNonce1 (pool embeds it at offset 112 for nonce-space partitioning).
|
||||
// We must preserve those bytes so pool-side re-verification matches.
|
||||
uint8_t blob_for_header[140];
|
||||
memcpy(blob_for_header, m_job.blob(), 140); // full blob incl. extraNonce
|
||||
memcpy(blob_for_header + 108, ¤t_job_nonces[i], 4); // overwrite only bytes 108-111
|
||||
|
||||
bool submitted = false;
|
||||
|
||||
// Check SHA256D for block detection if pool sent block_target
|
||||
if (job.hasBlockTarget()) {
|
||||
alignas(8) uint8_t pow_hash[32];
|
||||
dragonx_pow_hash(blob_for_header, m_hash + (i * 32), pow_hash);
|
||||
|
||||
// Compare pow_hash <= block_target (both in uint256 internal byte order)
|
||||
// byte[31] is MSB (most significant in arith terms), compare from there down
|
||||
bool isBlock = true;
|
||||
for (int b = 31; b >= 0; --b) {
|
||||
if (pow_hash[b] < job.blockTarget()[b]) {
|
||||
break; // pow_hash < target → is a block
|
||||
} else if (pow_hash[b] > job.blockTarget()[b]) {
|
||||
isBlock = false;
|
||||
break; // pow_hash > target → not a block
|
||||
}
|
||||
}
|
||||
|
||||
if (isBlock) {
|
||||
// SHA256D meets block target — submit immediately
|
||||
JobResults::submit(job, current_job_nonces[i], m_hash + (i * 32), nullptr);
|
||||
submitted = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Also check RandomX hash for normal share difficulty (if not already submitted)
|
||||
if (!submitted) {
|
||||
const uint64_t value = *reinterpret_cast<uint64_t*>(m_hash + (i * 32) + 24);
|
||||
if (value < job.target()) {
|
||||
JobResults::submit(job, current_job_nonces[i], m_hash + (i * 32), nullptr);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// ── Standard XMRig path (Monero, CryptoNight, etc.) ──
|
||||
const uint64_t value = *reinterpret_cast<uint64_t*>(m_hash + (i * 32) + 24);
|
||||
|
||||
@@ -81,7 +81,7 @@ xmrig::Client::Client(int id, const char *agent, IClientListener *listener) :
|
||||
BaseClient(id, listener),
|
||||
m_agent(agent),
|
||||
m_sendBuf(1024),
|
||||
m_tempBuf(256)
|
||||
m_tempBuf(512) // sized for RX_HUSH/DragonX 32-byte (64 hex) nonce
|
||||
{
|
||||
m_reader.setListener(this);
|
||||
m_key = m_storage.add(this);
|
||||
@@ -196,11 +196,18 @@ int64_t xmrig::Client::submit(const JobResult &result)
|
||||
const char *nonce = result.nonce;
|
||||
const char *data = result.result;
|
||||
# else
|
||||
char *nonce = m_tempBuf.data();
|
||||
char *data = m_tempBuf.data() + 16;
|
||||
char *signature = m_tempBuf.data() + 88;
|
||||
// Nonce width is algorithm-dependent: RX_HUSH/DragonX uses a 32-byte nonce (64 hex),
|
||||
// everything else 4 bytes (8 hex). Lay the temp buffer out dynamically so the larger
|
||||
// nonce can't overrun the result/signature fields.
|
||||
const size_t nonceHexSize = result.nonceSize() * 2 + 1;
|
||||
const size_t dataOffset = (nonceHexSize + 7) & ~static_cast<size_t>(7); // align to 8
|
||||
const size_t sigOffset = dataOffset + 72;
|
||||
|
||||
Cvt::toHex(nonce, sizeof(uint32_t) * 2 + 1, reinterpret_cast<const uint8_t *>(&result.nonce), sizeof(uint32_t));
|
||||
char *nonce = m_tempBuf.data();
|
||||
char *data = m_tempBuf.data() + dataOffset;
|
||||
char *signature = m_tempBuf.data() + sigOffset;
|
||||
|
||||
Cvt::toHex(nonce, nonceHexSize, result.nonceBytes(), result.nonceSize());
|
||||
Cvt::toHex(data, 65, result.result(), 32);
|
||||
|
||||
if (result.minerSignature()) {
|
||||
|
||||
@@ -77,7 +77,7 @@ public:
|
||||
inline const uint32_t *nonce() const { return reinterpret_cast<const uint32_t*>(m_blob + nonceOffset()); }
|
||||
inline const uint8_t *blob() const { return m_blob; }
|
||||
inline size_t nonceSize() const {
|
||||
if (algorithm() == Algorithm::RX_HUSH && m_isSoloMining) return 32;
|
||||
if (algorithm() == Algorithm::RX_HUSH) return 32;
|
||||
return (algorithm().family() == Algorithm::KAWPOW) ? 8 : 4;
|
||||
}
|
||||
inline size_t size() const { return m_size; }
|
||||
|
||||
@@ -54,6 +54,11 @@ public:
|
||||
{
|
||||
memcpy(m_result, result, sizeof(m_result));
|
||||
|
||||
// Populate nonceBytes so the stratum submit (which reads nonceBytes()/nonceSize())
|
||||
// sends the correct nonce on the standard 4-byte path too.
|
||||
memcpy(m_nonceBytes, &nonce, sizeof(uint32_t));
|
||||
m_nonceSize = 4;
|
||||
|
||||
if (header_hash) {
|
||||
memcpy(m_headerHash, header_hash, sizeof(m_headerHash));
|
||||
}
|
||||
|
||||
@@ -8,10 +8,10 @@
|
||||
#ifndef XMRIG_VERSION_H
|
||||
#define XMRIG_VERSION_H
|
||||
|
||||
#define APP_ID "xmrig-hac"
|
||||
#define APP_NAME "XMRig-HAC"
|
||||
#define APP_DESC "XMRig miner"
|
||||
#define APP_VERSION "6.25.1-hac"
|
||||
#define APP_ID "drg-xmrig"
|
||||
#define APP_NAME "DRG-XMRig"
|
||||
#define APP_DESC "DragonX RandomX miner (rx/dragonx)"
|
||||
#define APP_VERSION "6.25.1-drg1"
|
||||
#define APP_DOMAIN "dragonx.is"
|
||||
#define APP_SITE "www.dragonx.is"
|
||||
#define APP_COPYRIGHT "Copyright (C) 2026 dragonx.is"
|
||||
|
||||
Reference in New Issue
Block a user