Compare commits
6 Commits
9c9b7680e4
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 24232d72c3 | |||
| 7d22bc2bb5 | |||
| 95d3ff2c4a | |||
| a27a1e327b | |||
| c94352d305 | |||
| fc8286800d |
6
.github/ISSUE_TEMPLATE/bug_report.md
vendored
6
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -17,9 +17,9 @@ Steps to reproduce the behavior.
|
|||||||
A clear and concise description of what you expected to happen.
|
A clear and concise description of what you expected to happen.
|
||||||
|
|
||||||
**Required data**
|
**Required data**
|
||||||
- XMRig version
|
- XMRig-HAC version
|
||||||
- Either the exact link to a release you downloaded from https://github.com/xmrig/xmrig/releases
|
- Either the exact link to a release you downloaded from https://git.hush.is/dragonx/xmrig-hac/releases
|
||||||
- Or the exact command lines that you used to build XMRig
|
- Or the exact command lines that you used to build XMRig-HAC
|
||||||
- Miner log as text or screenshot
|
- Miner log as text or screenshot
|
||||||
- Config file or command line (without wallets)
|
- Config file or command line (without wallets)
|
||||||
- OS: [e.g. Windows]
|
- OS: [e.g. Windows]
|
||||||
|
|||||||
37
.gitignore
vendored
37
.gitignore
vendored
@@ -1,8 +1,43 @@
|
|||||||
|
# Build output
|
||||||
/build
|
/build
|
||||||
|
/build-windows
|
||||||
scripts/build
|
scripts/build
|
||||||
|
|
||||||
|
# Dependencies
|
||||||
|
/deps-windows
|
||||||
scripts/deps
|
scripts/deps
|
||||||
|
|
||||||
|
# IDE / editor
|
||||||
/CMakeLists.txt.user
|
/CMakeLists.txt.user
|
||||||
/.idea
|
/.idea
|
||||||
/src/backend/opencl/cl/cn/cryptonight_gen.cl
|
|
||||||
.vscode
|
.vscode
|
||||||
/.qtcreator
|
/.qtcreator
|
||||||
|
|
||||||
|
# Mining config (contains pool/wallet settings — copy config.example.json)
|
||||||
|
src/config.json
|
||||||
|
|
||||||
|
# Build artifacts (safety net)
|
||||||
|
*.o
|
||||||
|
*.a
|
||||||
|
*.so
|
||||||
|
*.dll
|
||||||
|
*.exe
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Dependency tarballs
|
||||||
|
*.tar.gz
|
||||||
|
|
||||||
|
# Generated OpenCL
|
||||||
|
/src/backend/opencl/cl/cn/cryptonight_gen.cl
|
||||||
|
|
||||||
|
# OS files
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
*.swp
|
||||||
|
*~
|
||||||
|
release/
|
||||||
|
build-linux/
|
||||||
|
build-windows/
|
||||||
|
deps-windows/
|
||||||
|
|||||||
@@ -1,3 +1,11 @@
|
|||||||
|
# XMRig-HAC Fork
|
||||||
|
- Added **DragonX** coin support
|
||||||
|
- Added **rx/hush** algorithm (RandomX-HUSH variant for HUSH/DragonX)
|
||||||
|
- Customized RandomX configuration for HUSH/DragonX mining
|
||||||
|
- Based on XMRig v6.25.0
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
# v6.25.0
|
# v6.25.0
|
||||||
- [#3680](https://github.com/xmrig/xmrig/pull/3680) Added `armv8l` to the list of 32-bit ARM targets.
|
- [#3680](https://github.com/xmrig/xmrig/pull/3680) Added `armv8l` to the list of 32-bit ARM targets.
|
||||||
- [#3708](https://github.com/xmrig/xmrig/pull/3708) Minor Aarch64 JIT changes (better instruction selection, don't emit instructions that add 0, etc).
|
- [#3708](https://github.com/xmrig/xmrig/pull/3708) Minor Aarch64 JIT changes (better instruction selection, don't emit instructions that add 0, etc).
|
||||||
|
|||||||
@@ -91,6 +91,7 @@ set(HEADERS_CRYPTO
|
|||||||
src/crypto/common/HugePagesInfo.h
|
src/crypto/common/HugePagesInfo.h
|
||||||
src/crypto/common/MemoryPool.h
|
src/crypto/common/MemoryPool.h
|
||||||
src/crypto/common/Nonce.h
|
src/crypto/common/Nonce.h
|
||||||
|
src/crypto/common/SoloNonce.h
|
||||||
src/crypto/common/portable/mm_malloc.h
|
src/crypto/common/portable/mm_malloc.h
|
||||||
src/crypto/common/VirtualMemory.h
|
src/crypto/common/VirtualMemory.h
|
||||||
)
|
)
|
||||||
@@ -128,6 +129,7 @@ set(SOURCES_CRYPTO
|
|||||||
src/crypto/common/HugePagesInfo.cpp
|
src/crypto/common/HugePagesInfo.cpp
|
||||||
src/crypto/common/MemoryPool.cpp
|
src/crypto/common/MemoryPool.cpp
|
||||||
src/crypto/common/Nonce.cpp
|
src/crypto/common/Nonce.cpp
|
||||||
|
src/crypto/common/SoloNonce.cpp
|
||||||
src/crypto/common/VirtualMemory.cpp
|
src/crypto/common/VirtualMemory.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -162,7 +164,7 @@ if (XMRIG_OS_WIN)
|
|||||||
src/crypto/common/VirtualMemory_win.cpp
|
src/crypto/common/VirtualMemory_win.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
set(EXTRA_LIBS ws2_32 psapi iphlpapi userenv dbghelp)
|
set(EXTRA_LIBS ws2_32 psapi iphlpapi userenv dbghelp bcrypt)
|
||||||
elseif (XMRIG_OS_APPLE)
|
elseif (XMRIG_OS_APPLE)
|
||||||
list(APPEND SOURCES_OS
|
list(APPEND SOURCES_OS
|
||||||
src/App_unix.cpp
|
src/App_unix.cpp
|
||||||
|
|||||||
33
README.md
33
README.md
@@ -1,13 +1,17 @@
|
|||||||
# XMRig
|
# XMRig-HAC
|
||||||
|
|
||||||
[](https://github.com/xmrig/xmrig/releases)
|
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.
|
||||||
[](https://github.com/xmrig/xmrig/releases)
|
|
||||||
[](https://github.com/xmrig/xmrig/releases)
|
|
||||||
[](https://github.com/xmrig/xmrig/blob/master/LICENSE)
|
|
||||||
[](https://github.com/xmrig/xmrig/stargazers)
|
|
||||||
[](https://github.com/xmrig/xmrig/network)
|
|
||||||
|
|
||||||
XMRig is a high performance, open source, cross platform RandomX, KawPow, CryptoNight and [GhostRider](https://github.com/xmrig/xmrig/tree/master/src/crypto/ghostrider#readme) unified CPU/GPU miner and [RandomX benchmark](https://xmrig.com/benchmark). Official binaries are available for Windows, Linux, macOS and FreeBSD.
|
**Repository:** [git.hush.is/dragonx/xmrig-hac](https://git.hush.is/dragonx/xmrig-hac)
|
||||||
|
|
||||||
|
## Fork Changes
|
||||||
|
|
||||||
|
This fork adds:
|
||||||
|
- **DragonX** coin support
|
||||||
|
- **rx/hush** algorithm (RandomX-HUSH variant)
|
||||||
|
- Customized RandomX configuration for HUSH/DragonX
|
||||||
|
|
||||||
|
Based on XMRig v6.25.0.
|
||||||
|
|
||||||
## Mining backends
|
## Mining backends
|
||||||
- **CPU** (x86/x64/ARMv7/ARMv8/RISC-V)
|
- **CPU** (x86/x64/ARMv7/ARMv8/RISC-V)
|
||||||
@@ -15,7 +19,7 @@ XMRig is a high performance, open source, cross platform RandomX, KawPow, Crypto
|
|||||||
- **CUDA** for NVIDIA GPUs via external [CUDA plugin](https://github.com/xmrig/xmrig-cuda).
|
- **CUDA** for NVIDIA GPUs via external [CUDA plugin](https://github.com/xmrig/xmrig-cuda).
|
||||||
|
|
||||||
## Download
|
## Download
|
||||||
* **[Binary releases](https://github.com/xmrig/xmrig/releases)**
|
* **[Binary releases](https://git.hush.is/dragonx/xmrig-hac/releases)**
|
||||||
* **[Build from source](https://xmrig.com/docs/miner/build)**
|
* **[Build from source](https://xmrig.com/docs/miner/build)**
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
@@ -24,15 +28,8 @@ The preferred way to configure the miner is the [JSON config file](https://xmrig
|
|||||||
* **[Wizard](https://xmrig.com/wizard)** helps you create initial configuration for the miner.
|
* **[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.
|
* **[Workers](http://workers.xmrig.info)** helps manage your miners via HTTP API.
|
||||||
|
|
||||||
## Donations
|
## Credits
|
||||||
* Default donation 1% (1 minute in 100 minutes) can be increased via option `donate-level` or disabled in source code.
|
|
||||||
* XMR: `48edfHu7V9Z84YzzMa6fUueoELZ9ZRXq9VetWzYGzKt52XU5xvqgzYnDK9URnRoJMk1j8nLwEVsaSWJ4fhdUyZijBGUicoD`
|
|
||||||
|
|
||||||
## Developers
|
This fork is based on [XMRig](https://github.com/xmrig/xmrig) by:
|
||||||
* **[xmrig](https://github.com/xmrig)**
|
* **[xmrig](https://github.com/xmrig)**
|
||||||
* **[sech1](https://github.com/SChernykh)**
|
* **[sech1](https://github.com/SChernykh)**
|
||||||
|
|
||||||
## Contacts
|
|
||||||
* support@xmrig.com
|
|
||||||
* [reddit](https://www.reddit.com/user/XMRig/)
|
|
||||||
* [twitter](https://twitter.com/xmrig_dev)
|
|
||||||
|
|||||||
124
build.sh
Executable file
124
build.sh
Executable file
@@ -0,0 +1,124 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# XMRig-HAC Release Build Script
|
||||||
|
# Usage: ./build.sh [--linux-release] [--win-release]
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
RELEASE_DIR="$ROOT_DIR/release"
|
||||||
|
VERSION=$(grep '#define APP_VERSION' "$ROOT_DIR/src/version.h" | sed 's/.*"\(.*\)"/\1/')
|
||||||
|
|
||||||
|
BUILD_LINUX=false
|
||||||
|
BUILD_WIN=false
|
||||||
|
|
||||||
|
for arg in "$@"; do
|
||||||
|
case "$arg" in
|
||||||
|
--linux-release) BUILD_LINUX=true ;;
|
||||||
|
--win-release) BUILD_WIN=true ;;
|
||||||
|
*)
|
||||||
|
echo "Usage: $0 [--linux-release] [--win-release]"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if ! $BUILD_LINUX && ! $BUILD_WIN; then
|
||||||
|
echo "Usage: $0 [--linux-release] [--win-release]"
|
||||||
|
echo ""
|
||||||
|
echo "Flags:"
|
||||||
|
echo " --linux-release Build Linux x86_64 release"
|
||||||
|
echo " --win-release Build Windows x86_64 release (cross-compile with MinGW)"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "=== XMRig-HAC Release Builder v${VERSION} ==="
|
||||||
|
mkdir -p "$RELEASE_DIR"
|
||||||
|
|
||||||
|
build_linux() {
|
||||||
|
echo ""
|
||||||
|
echo "========================================="
|
||||||
|
echo " Building Linux x86_64 Release"
|
||||||
|
echo "========================================="
|
||||||
|
|
||||||
|
local BUILD_DIR="$ROOT_DIR/build-linux"
|
||||||
|
local DEPS_DIR="$ROOT_DIR/scripts/deps"
|
||||||
|
|
||||||
|
# Build dependencies if needed
|
||||||
|
if [ ! -f "$DEPS_DIR/lib/libuv.a" ] || [ ! -f "$DEPS_DIR/lib/libssl.a" ] || [ ! -f "$DEPS_DIR/lib/libhwloc.a" ]; then
|
||||||
|
echo "--- Building Linux dependencies ---"
|
||||||
|
cd "$ROOT_DIR/scripts"
|
||||||
|
bash build_deps.sh
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Configure and build
|
||||||
|
mkdir -p "$BUILD_DIR"
|
||||||
|
cd "$BUILD_DIR"
|
||||||
|
|
||||||
|
cmake "$ROOT_DIR" \
|
||||||
|
-DCMAKE_BUILD_TYPE=Release \
|
||||||
|
-DBUILD_STATIC=ON \
|
||||||
|
-DWITH_OPENCL=OFF \
|
||||||
|
-DWITH_CUDA=OFF \
|
||||||
|
-DXMRIG_DEPS="$DEPS_DIR"
|
||||||
|
|
||||||
|
make -j"$(nproc)"
|
||||||
|
strip xmrig
|
||||||
|
|
||||||
|
# Package
|
||||||
|
local PKG_NAME="xmrig-hac-${VERSION}-linux-x64"
|
||||||
|
local PKG_DIR="$RELEASE_DIR/$PKG_NAME"
|
||||||
|
rm -rf "$PKG_DIR"
|
||||||
|
mkdir -p "$PKG_DIR"
|
||||||
|
|
||||||
|
cp "$BUILD_DIR/xmrig" "$PKG_DIR/"
|
||||||
|
cp "$ROOT_DIR/src/config.example.json" "$PKG_DIR/config.json"
|
||||||
|
cp "$ROOT_DIR/README.md" "$PKG_DIR/"
|
||||||
|
|
||||||
|
cd "$RELEASE_DIR"
|
||||||
|
zip -r "${PKG_NAME}.zip" "$PKG_NAME"
|
||||||
|
rm -rf "$PKG_DIR"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Linux release: release/${PKG_NAME}.zip"
|
||||||
|
}
|
||||||
|
|
||||||
|
build_windows() {
|
||||||
|
echo ""
|
||||||
|
echo "========================================="
|
||||||
|
echo " Building Windows x86_64 Release"
|
||||||
|
echo "========================================="
|
||||||
|
|
||||||
|
# Delegate to the existing cross-compilation script
|
||||||
|
cd "$ROOT_DIR/scripts"
|
||||||
|
bash build_windows.sh
|
||||||
|
|
||||||
|
local BUILD_DIR="$ROOT_DIR/build-windows"
|
||||||
|
|
||||||
|
# Package
|
||||||
|
local PKG_NAME="xmrig-hac-${VERSION}-win-x64"
|
||||||
|
local PKG_DIR="$RELEASE_DIR/$PKG_NAME"
|
||||||
|
rm -rf "$PKG_DIR"
|
||||||
|
mkdir -p "$PKG_DIR"
|
||||||
|
|
||||||
|
cp "$BUILD_DIR/xmrig.exe" "$PKG_DIR/"
|
||||||
|
cp "$ROOT_DIR/bin/WinRing0/WinRing0x64.sys" "$PKG_DIR/"
|
||||||
|
cp "$ROOT_DIR/src/config.example.json" "$PKG_DIR/config.json"
|
||||||
|
cp "$ROOT_DIR/README.md" "$PKG_DIR/"
|
||||||
|
|
||||||
|
cd "$RELEASE_DIR"
|
||||||
|
zip -r "${PKG_NAME}.zip" "$PKG_NAME"
|
||||||
|
rm -rf "$PKG_DIR"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Windows release: release/${PKG_NAME}.zip"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Run selected builds
|
||||||
|
$BUILD_LINUX && build_linux
|
||||||
|
$BUILD_WIN && build_windows
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "========================================="
|
||||||
|
echo " Release artifacts in release/"
|
||||||
|
echo "========================================="
|
||||||
|
ls -lh "$RELEASE_DIR"/*.zip 2>/dev/null || true
|
||||||
@@ -14,8 +14,20 @@ if (WITH_TLS)
|
|||||||
set(OPENSSL_USE_STATIC_LIBS TRUE)
|
set(OPENSSL_USE_STATIC_LIBS TRUE)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
# For cross-compilation, allow manual specification of OpenSSL paths
|
||||||
|
if (CMAKE_CROSSCOMPILING AND XMRIG_DEPS)
|
||||||
|
set(OPENSSL_INCLUDE_DIR "${XMRIG_DEPS}/include" CACHE PATH "OpenSSL include dir")
|
||||||
|
set(OPENSSL_CRYPTO_LIBRARY "${XMRIG_DEPS}/lib/libcrypto.a" CACHE FILEPATH "OpenSSL crypto lib")
|
||||||
|
set(OPENSSL_SSL_LIBRARY "${XMRIG_DEPS}/lib/libssl.a" CACHE FILEPATH "OpenSSL ssl lib")
|
||||||
|
if (EXISTS "${OPENSSL_CRYPTO_LIBRARY}" AND EXISTS "${OPENSSL_SSL_LIBRARY}" AND EXISTS "${OPENSSL_INCLUDE_DIR}/openssl/ssl.h")
|
||||||
|
set(OPENSSL_FOUND TRUE)
|
||||||
|
set(OPENSSL_LIBRARIES ${OPENSSL_SSL_LIBRARY} ${OPENSSL_CRYPTO_LIBRARY})
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
find_package(OpenSSL)
|
if (NOT OPENSSL_FOUND)
|
||||||
|
find_package(OpenSSL)
|
||||||
|
endif()
|
||||||
|
|
||||||
if (OPENSSL_FOUND)
|
if (OPENSSL_FOUND)
|
||||||
set(TLS_SOURCES
|
set(TLS_SOURCES
|
||||||
|
|||||||
19
cmake/mingw-w64-toolchain.cmake
Normal file
19
cmake/mingw-w64-toolchain.cmake
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# CMake Toolchain file for cross-compiling to Windows using MinGW-w64
|
||||||
|
set(CMAKE_SYSTEM_NAME Windows)
|
||||||
|
set(CMAKE_SYSTEM_PROCESSOR x86_64)
|
||||||
|
|
||||||
|
# Specify the cross compiler
|
||||||
|
set(CMAKE_C_COMPILER x86_64-w64-mingw32-gcc-posix)
|
||||||
|
set(CMAKE_CXX_COMPILER x86_64-w64-mingw32-g++-posix)
|
||||||
|
set(CMAKE_RC_COMPILER x86_64-w64-mingw32-windres)
|
||||||
|
|
||||||
|
# Target environment on the build host system
|
||||||
|
set(CMAKE_FIND_ROOT_PATH /usr/x86_64-w64-mingw32)
|
||||||
|
|
||||||
|
# Adjust the default behaviour of the FIND_XXX() commands:
|
||||||
|
# search headers and libraries in the target environment
|
||||||
|
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
|
||||||
|
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
|
||||||
|
|
||||||
|
# Search programs in the host environment
|
||||||
|
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
"pools": [
|
"pools": [
|
||||||
{
|
{
|
||||||
"url": "pool.monero.hashvault.pro:3333",
|
"url": "pool.monero.hashvault.pro:3333",
|
||||||
"user": "48edfHu7V9Z84YzzMa6fUueoELZ9ZRXq9VetWzYGzKt52XU5xvqgzYnDK9URnRoJMk1j8nLwEVsaSWJ4fhdUyZijBGUicoD",
|
"user": "YOUR_WALLET_ADDRESS",
|
||||||
"pass": "x",
|
"pass": "x",
|
||||||
"keepalive": false,
|
"keepalive": false,
|
||||||
"nicehash": false,
|
"nicehash": false,
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"url": "pool.supportxmr.com:3333",
|
"url": "pool.supportxmr.com:3333",
|
||||||
"user": "48edfHu7V9Z84YzzMa6fUueoELZ9ZRXq9VetWzYGzKt52XU5xvqgzYnDK9URnRoJMk1j8nLwEVsaSWJ4fhdUyZijBGUicoD",
|
"user": "YOUR_WALLET_ADDRESS",
|
||||||
"pass": "x",
|
"pass": "x",
|
||||||
"keepalive": false,
|
"keepalive": false,
|
||||||
"nicehash": false,
|
"nicehash": false,
|
||||||
|
|||||||
@@ -11,13 +11,13 @@
|
|||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git+https://github.com/xmrig/xmrig.git"
|
"url": "git+https://git.hush.is/dragonx/xmrig-hac.git"
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "GPLv3",
|
"license": "GPLv3",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/xmrig/xmrig/issues"
|
"url": "https://git.hush.is/dragonx/xmrig-hac/issues"
|
||||||
},
|
},
|
||||||
"homepage": "https://github.com/xmrig/xmrig#readme"
|
"homepage": "https://git.hush.is/dragonx/xmrig-hac#readme"
|
||||||
}
|
}
|
||||||
|
|||||||
112
scripts/build_windows.sh
Executable file
112
scripts/build_windows.sh
Executable file
@@ -0,0 +1,112 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# XMRig Windows Cross-Compilation Build Script
|
||||||
|
# Builds xmrig for Windows using MinGW-w64 on Linux
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
XMRIG_DIR="$(dirname "$SCRIPT_DIR")"
|
||||||
|
BUILD_DIR="$XMRIG_DIR/build-windows"
|
||||||
|
DEPS_DIR="$XMRIG_DIR/deps-windows"
|
||||||
|
OPENSSL_VERSION="1.1.1w"
|
||||||
|
|
||||||
|
echo "=== XMRig Windows Build ==="
|
||||||
|
echo "Source: $XMRIG_DIR"
|
||||||
|
echo "Build: $BUILD_DIR"
|
||||||
|
echo "Deps: $DEPS_DIR"
|
||||||
|
|
||||||
|
# Check for MinGW-w64
|
||||||
|
if ! command -v x86_64-w64-mingw32-gcc-posix &> /dev/null; then
|
||||||
|
echo "Error: MinGW-w64 not found. Install with:"
|
||||||
|
echo " sudo apt install mingw-w64"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Create directories
|
||||||
|
mkdir -p "$BUILD_DIR"
|
||||||
|
mkdir -p "$DEPS_DIR/include"
|
||||||
|
mkdir -p "$DEPS_DIR/lib"
|
||||||
|
|
||||||
|
# Build OpenSSL if needed
|
||||||
|
if [ ! -f "$DEPS_DIR/lib/libssl.a" ]; then
|
||||||
|
echo "=== Building OpenSSL $OPENSSL_VERSION ==="
|
||||||
|
cd "$DEPS_DIR"
|
||||||
|
|
||||||
|
if [ ! -f "openssl-${OPENSSL_VERSION}.tar.gz" ]; then
|
||||||
|
wget "https://openssl.org/source/old/1.1.1/openssl-${OPENSSL_VERSION}.tar.gz"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -d "openssl-${OPENSSL_VERSION}" ]; then
|
||||||
|
tar -xzf "openssl-${OPENSSL_VERSION}.tar.gz"
|
||||||
|
fi
|
||||||
|
|
||||||
|
cd "openssl-${OPENSSL_VERSION}"
|
||||||
|
|
||||||
|
# Configure for Windows cross-compilation
|
||||||
|
./Configure mingw64 \
|
||||||
|
--cross-compile-prefix=x86_64-w64-mingw32- \
|
||||||
|
no-shared \
|
||||||
|
no-asm \
|
||||||
|
no-zlib \
|
||||||
|
no-comp \
|
||||||
|
--prefix="$DEPS_DIR"
|
||||||
|
|
||||||
|
make -j$(nproc)
|
||||||
|
make install_sw
|
||||||
|
cd "$DEPS_DIR"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Build libuv if needed
|
||||||
|
if [ ! -f "$DEPS_DIR/lib/libuv.a" ]; then
|
||||||
|
echo "=== Building libuv dependency ==="
|
||||||
|
cd "$DEPS_DIR"
|
||||||
|
|
||||||
|
if [ ! -d "libuv-1.51.0" ]; then
|
||||||
|
if [ ! -f "libuv.tar.gz" ]; then
|
||||||
|
curl -L -o libuv.tar.gz https://github.com/libuv/libuv/archive/refs/tags/v1.51.0.tar.gz
|
||||||
|
fi
|
||||||
|
tar -xzf libuv.tar.gz
|
||||||
|
fi
|
||||||
|
|
||||||
|
cd libuv-1.51.0
|
||||||
|
mkdir -p build && cd build
|
||||||
|
cmake .. \
|
||||||
|
-DCMAKE_TOOLCHAIN_FILE="$XMRIG_DIR/cmake/mingw-w64-toolchain.cmake" \
|
||||||
|
-DCMAKE_INSTALL_PREFIX="$DEPS_DIR" \
|
||||||
|
-DLIBUV_BUILD_SHARED=OFF \
|
||||||
|
-DBUILD_TESTING=OFF
|
||||||
|
make -j$(nproc)
|
||||||
|
make install
|
||||||
|
fi
|
||||||
|
|
||||||
|
cd "$BUILD_DIR"
|
||||||
|
|
||||||
|
echo "=== Configuring XMRig ==="
|
||||||
|
cmake "$XMRIG_DIR" \
|
||||||
|
-DCMAKE_TOOLCHAIN_FILE="$XMRIG_DIR/cmake/mingw-w64-toolchain.cmake" \
|
||||||
|
-DCMAKE_BUILD_TYPE=Release \
|
||||||
|
-DBUILD_STATIC=ON \
|
||||||
|
-DWITH_TLS=ON \
|
||||||
|
-DWITH_HWLOC=OFF \
|
||||||
|
-DWITH_OPENCL=OFF \
|
||||||
|
-DWITH_CUDA=OFF \
|
||||||
|
-DXMRIG_DEPS="$DEPS_DIR" \
|
||||||
|
-DOPENSSL_ROOT_DIR:PATH="$DEPS_DIR" \
|
||||||
|
-DOPENSSL_INCLUDE_DIR:PATH="$DEPS_DIR/include" \
|
||||||
|
-DOPENSSL_CRYPTO_LIBRARY:FILEPATH="$DEPS_DIR/lib/libcrypto.a" \
|
||||||
|
-DOPENSSL_SSL_LIBRARY:FILEPATH="$DEPS_DIR/lib/libssl.a" \
|
||||||
|
-DOPENSSL_LIBRARIES="$DEPS_DIR/lib/libssl.a;$DEPS_DIR/lib/libcrypto.a" \
|
||||||
|
-DUV_INCLUDE_DIR="$DEPS_DIR/include" \
|
||||||
|
-DUV_LIBRARY="$DEPS_DIR/lib/libuv.a"
|
||||||
|
|
||||||
|
echo "=== Building XMRig ==="
|
||||||
|
make -j$(nproc)
|
||||||
|
|
||||||
|
# Strip the executable
|
||||||
|
x86_64-w64-mingw32-strip xmrig.exe 2>/dev/null || true
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== Build Complete ==="
|
||||||
|
ls -la *.exe 2>/dev/null || echo "Executable not found"
|
||||||
|
echo ""
|
||||||
|
echo "Copy the .exe and WinRing0x64.sys to Windows to run"
|
||||||
@@ -16,5 +16,5 @@
|
|||||||
:: Smaller pools also often have smaller fees/payout limits.
|
:: Smaller pools also often have smaller fees/payout limits.
|
||||||
|
|
||||||
cd /d "%~dp0"
|
cd /d "%~dp0"
|
||||||
xmrig.exe -o xmrpool.eu:3333 -u 48edfHu7V9Z84YzzMa6fUueoELZ9ZRXq9VetWzYGzKt52XU5xvqgzYnDK9URnRoJMk1j8nLwEVsaSWJ4fhdUyZijBGUicoD -p x
|
xmrig.exe -o xmrpool.eu:3333 -u YOUR_WALLET_ADDRESS -p x
|
||||||
pause
|
pause
|
||||||
|
|||||||
@@ -12,5 +12,5 @@
|
|||||||
:: But you will only get a payout when you find a block which can take more than a year for a single low-end PC.
|
:: But you will only get a payout when you find a block which can take more than a year for a single low-end PC.
|
||||||
|
|
||||||
cd /d "%~dp0"
|
cd /d "%~dp0"
|
||||||
xmrig.exe -o YOUR_NODE_IP:18081 -a rx/0 -u 48edfHu7V9Z84YzzMa6fUueoELZ9ZRXq9VetWzYGzKt52XU5xvqgzYnDK9URnRoJMk1j8nLwEVsaSWJ4fhdUyZijBGUicoD --daemon
|
xmrig.exe -o YOUR_NODE_IP:18081 -a rx/0 -u YOUR_WALLET_ADDRESS --daemon
|
||||||
pause
|
pause
|
||||||
|
|||||||
@@ -32,6 +32,7 @@
|
|||||||
#include "base/net/stratum/Job.h"
|
#include "base/net/stratum/Job.h"
|
||||||
#include "base/tools/Alignment.h"
|
#include "base/tools/Alignment.h"
|
||||||
#include "crypto/common/Nonce.h"
|
#include "crypto/common/Nonce.h"
|
||||||
|
#include "crypto/common/SoloNonce.h"
|
||||||
|
|
||||||
|
|
||||||
namespace xmrig {
|
namespace xmrig {
|
||||||
@@ -89,6 +90,29 @@ public:
|
|||||||
inline int32_t nonceOffset() const { return currentJob().nonceOffset(); }
|
inline int32_t nonceOffset() const { return currentJob().nonceOffset(); }
|
||||||
inline size_t nonceSize() const { return currentJob().nonceSize(); }
|
inline size_t nonceSize() const { return currentJob().nonceSize(); }
|
||||||
|
|
||||||
|
// Solo mining nonce management
|
||||||
|
inline bool isSoloMining() const { return m_isSoloMining; }
|
||||||
|
inline void setSoloMining(bool solo) { m_isSoloMining = solo; }
|
||||||
|
inline uint8_t* soloNonce(size_t i = 0) { return m_soloNonces[index()] + i * 32; }
|
||||||
|
inline const uint8_t* soloNonce(size_t i = 0) const { return m_soloNonces[index()] + i * 32; }
|
||||||
|
|
||||||
|
inline void initSoloNonces() {
|
||||||
|
for (size_t i = 0; i < N; ++i) {
|
||||||
|
SoloNonce::initialize(soloNonce(i));
|
||||||
|
// Copy to blob at nonce offset
|
||||||
|
SoloNonce::copyToBlob(blob() + (i * currentJob().size()) + nonceOffset(), 0, soloNonce(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool nextRoundSolo() {
|
||||||
|
for (size_t i = 0; i < N; ++i) {
|
||||||
|
SoloNonce::increment(soloNonce(i));
|
||||||
|
// Copy to blob at nonce offset
|
||||||
|
SoloNonce::copyToBlob(blob() + (i * currentJob().size()) + nonceOffset(), 0, soloNonce(i));
|
||||||
|
}
|
||||||
|
return true; // Never exhausted with 256-bit nonces
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
inline uint64_t nonceMask() const { return m_nonce_mask[index()]; }
|
inline uint64_t nonceMask() const { return m_nonce_mask[index()]; }
|
||||||
|
|
||||||
@@ -115,6 +139,10 @@ private:
|
|||||||
uint64_t m_nonce_mask[2] = { 0, 0 };
|
uint64_t m_nonce_mask[2] = { 0, 0 };
|
||||||
uint64_t m_sequence = 0;
|
uint64_t m_sequence = 0;
|
||||||
uint8_t m_index = 0;
|
uint8_t m_index = 0;
|
||||||
|
|
||||||
|
// Solo mining nonces (32 bytes per job slot per N)
|
||||||
|
alignas(8) uint8_t m_soloNonces[2][32 * N]{};
|
||||||
|
bool m_isSoloMining = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -40,6 +40,35 @@
|
|||||||
#include "net/JobResults.h"
|
#include "net/JobResults.h"
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef XMRIG_FEATURE_TLS
|
||||||
|
# include <openssl/sha.h>
|
||||||
|
#else
|
||||||
|
// Standalone SHA-256 for non-TLS builds
|
||||||
|
# ifdef _WIN32
|
||||||
|
# include <windows.h>
|
||||||
|
# include <bcrypt.h>
|
||||||
|
static void SHA256(const uint8_t* data, size_t len, uint8_t* out) {
|
||||||
|
BCRYPT_ALG_HANDLE hAlg = nullptr;
|
||||||
|
BCRYPT_HASH_HANDLE hHash = nullptr;
|
||||||
|
BCryptOpenAlgorithmProvider(&hAlg, BCRYPT_SHA256_ALGORITHM, nullptr, 0);
|
||||||
|
BCryptCreateHash(hAlg, &hHash, nullptr, 0, nullptr, 0, 0);
|
||||||
|
BCryptHashData(hHash, const_cast<PUCHAR>(data), static_cast<ULONG>(len), 0);
|
||||||
|
BCryptFinishHash(hHash, out, 32, 0);
|
||||||
|
BCryptDestroyHash(hHash);
|
||||||
|
BCryptCloseAlgorithmProvider(hAlg, 0);
|
||||||
|
}
|
||||||
|
# else
|
||||||
|
# include "crypto/ghostrider/sph_sha2.h"
|
||||||
|
static void SHA256(const uint8_t* data, size_t len, uint8_t* out) {
|
||||||
|
sph_sha256_context ctx;
|
||||||
|
sph_sha256_init(&ctx);
|
||||||
|
sph_sha256(&ctx, data, len);
|
||||||
|
sph_sha256_close(&ctx, out);
|
||||||
|
}
|
||||||
|
# endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
#ifdef XMRIG_ALGO_RANDOMX
|
#ifdef XMRIG_ALGO_RANDOMX
|
||||||
# include "crypto/randomx/randomx.h"
|
# include "crypto/randomx/randomx.h"
|
||||||
#endif
|
#endif
|
||||||
@@ -55,6 +84,37 @@ namespace xmrig {
|
|||||||
static constexpr uint32_t kReserveCount = 32768;
|
static constexpr uint32_t kReserveCount = 32768;
|
||||||
|
|
||||||
|
|
||||||
|
// ── DRAGONX/HUSH PoW hash ────────────────────────────────────────────────────
|
||||||
|
//
|
||||||
|
// DragonX block difficulty is checked against double_sha256(173-byte header),
|
||||||
|
// NOT the RandomX hash directly (unlike Monero).
|
||||||
|
//
|
||||||
|
// Full 173-byte header structure:
|
||||||
|
// [0:108] header_base = version(4) + prevhash(32) + merkle(32)
|
||||||
|
// + commitments(32) + time(4) + bits(4)
|
||||||
|
// [108:140] nonce = 32-byte miner nonce
|
||||||
|
// [140] 0x20 = compact_size for 32-byte solution
|
||||||
|
// [141:173] rx_solution = RandomX hash result (32 bytes)
|
||||||
|
//
|
||||||
|
// @param blob 140-byte header WITH nonce already at bytes [108:140]
|
||||||
|
// @param rx_hash 32-byte RandomX hash result
|
||||||
|
// @param out 32-byte output buffer for double_sha256
|
||||||
|
|
||||||
|
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 bytes) + nonce (32 bytes)
|
||||||
|
full_header[140] = 0x20; // compact_size = 32 (solution length)
|
||||||
|
memcpy(full_header + 141, rx_hash, 32); // RandomX hash = the PoW solution
|
||||||
|
|
||||||
|
// double SHA256 = SHA256(SHA256(full_header))
|
||||||
|
uint8_t tmp[32];
|
||||||
|
SHA256(full_header, 173, tmp);
|
||||||
|
SHA256(tmp, 32, out);
|
||||||
|
}
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
|
||||||
#ifdef XMRIG_ALGO_CN_HEAVY
|
#ifdef XMRIG_ALGO_CN_HEAVY
|
||||||
static std::mutex cn_heavyZen3MemoryMutex;
|
static std::mutex cn_heavyZen3MemoryMutex;
|
||||||
VirtualMemory* cn_heavyZen3Memory = nullptr;
|
VirtualMemory* cn_heavyZen3Memory = nullptr;
|
||||||
@@ -267,8 +327,13 @@ void xmrig::CpuWorker<N>::start()
|
|||||||
}
|
}
|
||||||
|
|
||||||
uint32_t current_job_nonces[N];
|
uint32_t current_job_nonces[N];
|
||||||
|
alignas(8) uint8_t current_solo_nonces[N * 32];
|
||||||
for (size_t i = 0; i < N; ++i) {
|
for (size_t i = 0; i < N; ++i) {
|
||||||
current_job_nonces[i] = readUnaligned(m_job.nonce(i));
|
current_job_nonces[i] = readUnaligned(m_job.nonce(i));
|
||||||
|
// Save solo nonces BEFORE they get incremented by nextRound()
|
||||||
|
if (m_job.isSoloMining()) {
|
||||||
|
memcpy(current_solo_nonces + i * 32, m_job.soloNonce(i), 32);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# ifdef XMRIG_FEATURE_BENCHMARK
|
# ifdef XMRIG_FEATURE_BENCHMARK
|
||||||
@@ -337,18 +402,97 @@ void xmrig::CpuWorker<N>::start()
|
|||||||
|
|
||||||
if (valid) {
|
if (valid) {
|
||||||
for (size_t i = 0; i < N; ++i) {
|
for (size_t i = 0; i < N; ++i) {
|
||||||
const uint64_t value = *reinterpret_cast<uint64_t*>(m_hash + (i * 32) + 24);
|
|
||||||
|
|
||||||
# ifdef XMRIG_FEATURE_BENCHMARK
|
# ifdef XMRIG_FEATURE_BENCHMARK
|
||||||
if (m_benchSize) {
|
if (m_benchSize) {
|
||||||
|
const uint64_t value = *reinterpret_cast<uint64_t*>(m_hash + (i * 32) + 24);
|
||||||
if (current_job_nonces[i] < m_benchSize) {
|
if (current_job_nonces[i] < m_benchSize) {
|
||||||
BenchState::add(value);
|
BenchState::add(value);
|
||||||
}
|
}
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
# endif
|
# endif
|
||||||
if (value < job.target()) {
|
|
||||||
JobResults::submit(job, current_job_nonces[i], m_hash + (i * 32), job.hasMinerSignature() ? miner_signature_saved : nullptr);
|
if (job.algorithm() == Algorithm::RX_HUSH && m_job.isSoloMining()) {
|
||||||
|
// ── DRAGONX/HUSH solo mining: use double_sha256(173-byte header) for difficulty ──
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
//
|
||||||
|
// 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)
|
||||||
|
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
|
||||||
|
|
||||||
|
// Compute 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)
|
||||||
|
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
|
||||||
|
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.
|
||||||
|
// In pool mode, bytes 112-139 of nNonce are zero (only 4-byte nonce used).
|
||||||
|
uint8_t blob_for_header[140];
|
||||||
|
memcpy(blob_for_header, m_job.blob(), 108); // header base
|
||||||
|
memset(blob_for_header + 108, 0, 32); // clear 32-byte nonce field
|
||||||
|
memcpy(blob_for_header + 108, ¤t_job_nonces[i], 4); // 4-byte nonce at offset 108
|
||||||
|
|
||||||
|
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);
|
||||||
|
if (value < job.target()) {
|
||||||
|
if (m_job.isSoloMining()) {
|
||||||
|
JobResults::submit(JobResult(job, current_solo_nonces + i * 32, m_hash + (i * 32)));
|
||||||
|
} else {
|
||||||
|
JobResults::submit(job, current_job_nonces[i], m_hash + (i * 32), job.hasMinerSignature() ? miner_signature_saved : nullptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
m_count += N;
|
m_count += N;
|
||||||
@@ -369,6 +513,11 @@ void xmrig::CpuWorker<N>::start()
|
|||||||
template<size_t N>
|
template<size_t N>
|
||||||
bool xmrig::CpuWorker<N>::nextRound()
|
bool xmrig::CpuWorker<N>::nextRound()
|
||||||
{
|
{
|
||||||
|
// Solo mining uses its own 256-bit nonce management
|
||||||
|
if (m_job.isSoloMining()) {
|
||||||
|
return m_job.nextRoundSolo();
|
||||||
|
}
|
||||||
|
|
||||||
# ifdef XMRIG_FEATURE_BENCHMARK
|
# ifdef XMRIG_FEATURE_BENCHMARK
|
||||||
const uint32_t count = m_benchSize ? 1U : kReserveCount;
|
const uint32_t count = m_benchSize ? 1U : kReserveCount;
|
||||||
# else
|
# else
|
||||||
@@ -516,6 +665,14 @@ void xmrig::CpuWorker<N>::consumeJob()
|
|||||||
|
|
||||||
m_job.add(job, count, Nonce::CPU);
|
m_job.add(job, count, Nonce::CPU);
|
||||||
|
|
||||||
|
// Handle solo mining nonce initialization
|
||||||
|
if (job.isSoloMining()) {
|
||||||
|
m_job.setSoloMining(true);
|
||||||
|
m_job.initSoloNonces();
|
||||||
|
} else {
|
||||||
|
m_job.setSoloMining(false);
|
||||||
|
}
|
||||||
|
|
||||||
# ifdef XMRIG_ALGO_RANDOMX
|
# ifdef XMRIG_ALGO_RANDOMX
|
||||||
if (m_job.currentJob().algorithm().family() == Algorithm::RANDOM_X) {
|
if (m_job.currentJob().algorithm().family() == Algorithm::RANDOM_X) {
|
||||||
allocateRandomX_VM();
|
allocateRandomX_VM();
|
||||||
|
|||||||
@@ -200,6 +200,7 @@ if (WITH_HTTP)
|
|||||||
src/base/net/http/HttpData.h
|
src/base/net/http/HttpData.h
|
||||||
src/base/net/http/HttpResponse.h
|
src/base/net/http/HttpResponse.h
|
||||||
src/base/net/stratum/DaemonClient.h
|
src/base/net/stratum/DaemonClient.h
|
||||||
|
src/base/net/stratum/HushClient.h
|
||||||
src/base/net/stratum/SelfSelectClient.h
|
src/base/net/stratum/SelfSelectClient.h
|
||||||
src/base/net/tools/TcpServer.h
|
src/base/net/tools/TcpServer.h
|
||||||
)
|
)
|
||||||
@@ -220,6 +221,7 @@ if (WITH_HTTP)
|
|||||||
src/base/net/http/HttpListener.cpp
|
src/base/net/http/HttpListener.cpp
|
||||||
src/base/net/http/HttpResponse.cpp
|
src/base/net/http/HttpResponse.cpp
|
||||||
src/base/net/stratum/DaemonClient.cpp
|
src/base/net/stratum/DaemonClient.cpp
|
||||||
|
src/base/net/stratum/HushClient.cpp
|
||||||
src/base/net/stratum/SelfSelectClient.cpp
|
src/base/net/stratum/SelfSelectClient.cpp
|
||||||
src/base/net/tools/TcpServer.cpp
|
src/base/net/tools/TcpServer.cpp
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -82,6 +82,7 @@ const char *Algorithm::kRX_ARQ = "rx/arq";
|
|||||||
const char *Algorithm::kRX_GRAFT = "rx/graft";
|
const char *Algorithm::kRX_GRAFT = "rx/graft";
|
||||||
const char *Algorithm::kRX_SFX = "rx/sfx";
|
const char *Algorithm::kRX_SFX = "rx/sfx";
|
||||||
const char *Algorithm::kRX_YADA = "rx/yada";
|
const char *Algorithm::kRX_YADA = "rx/yada";
|
||||||
|
const char *Algorithm::kRX_HUSH = "rx/hush";
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef XMRIG_ALGO_ARGON2
|
#ifdef XMRIG_ALGO_ARGON2
|
||||||
@@ -148,6 +149,7 @@ static const std::map<uint32_t, const char *> kAlgorithmNames = {
|
|||||||
ALGO_NAME(RX_GRAFT),
|
ALGO_NAME(RX_GRAFT),
|
||||||
ALGO_NAME(RX_SFX),
|
ALGO_NAME(RX_SFX),
|
||||||
ALGO_NAME(RX_YADA),
|
ALGO_NAME(RX_YADA),
|
||||||
|
ALGO_NAME(RX_HUSH),
|
||||||
# endif
|
# endif
|
||||||
|
|
||||||
# ifdef XMRIG_ALGO_ARGON2
|
# ifdef XMRIG_ALGO_ARGON2
|
||||||
@@ -263,6 +265,10 @@ static const std::map<const char *, Algorithm::Id, aliasCompare> kAlgorithmAlias
|
|||||||
ALGO_ALIAS(RX_SFX, "randomsfx"),
|
ALGO_ALIAS(RX_SFX, "randomsfx"),
|
||||||
ALGO_ALIAS_AUTO(RX_YADA), ALGO_ALIAS(RX_YADA, "randomx/yada"),
|
ALGO_ALIAS_AUTO(RX_YADA), ALGO_ALIAS(RX_YADA, "randomx/yada"),
|
||||||
ALGO_ALIAS(RX_YADA, "randomyada"),
|
ALGO_ALIAS(RX_YADA, "randomyada"),
|
||||||
|
ALGO_ALIAS_AUTO(RX_HUSH), ALGO_ALIAS(RX_HUSH, "randomx/hush"),
|
||||||
|
ALGO_ALIAS(RX_HUSH, "randomhush"),
|
||||||
|
ALGO_ALIAS(RX_HUSH, "rx/dragonx"),
|
||||||
|
ALGO_ALIAS(RX_HUSH, "dragonx"),
|
||||||
# endif
|
# endif
|
||||||
|
|
||||||
# ifdef XMRIG_ALGO_ARGON2
|
# ifdef XMRIG_ALGO_ARGON2
|
||||||
@@ -350,7 +356,7 @@ std::vector<xmrig::Algorithm> xmrig::Algorithm::all(const std::function<bool(con
|
|||||||
CN_HEAVY_0, CN_HEAVY_TUBE, CN_HEAVY_XHV,
|
CN_HEAVY_0, CN_HEAVY_TUBE, CN_HEAVY_XHV,
|
||||||
CN_PICO_0, CN_PICO_TLO,
|
CN_PICO_0, CN_PICO_TLO,
|
||||||
CN_UPX2,
|
CN_UPX2,
|
||||||
RX_0, RX_WOW, RX_ARQ, RX_GRAFT, RX_SFX, RX_YADA,
|
RX_0, RX_WOW, RX_ARQ, RX_GRAFT, RX_SFX, RX_YADA, RX_HUSH,
|
||||||
AR2_CHUKWA, AR2_CHUKWA_V2, AR2_WRKZ,
|
AR2_CHUKWA, AR2_CHUKWA_V2, AR2_WRKZ,
|
||||||
KAWPOW_RVN,
|
KAWPOW_RVN,
|
||||||
GHOSTRIDER_RTM
|
GHOSTRIDER_RTM
|
||||||
|
|||||||
@@ -78,6 +78,7 @@ public:
|
|||||||
RX_GRAFT = 0x72151267, // "rx/graft" RandomGRAFT (Graft).
|
RX_GRAFT = 0x72151267, // "rx/graft" RandomGRAFT (Graft).
|
||||||
RX_SFX = 0x72151273, // "rx/sfx" RandomSFX (Safex Cash).
|
RX_SFX = 0x72151273, // "rx/sfx" RandomSFX (Safex Cash).
|
||||||
RX_YADA = 0x72151279, // "rx/yada" RandomYada (YadaCoin).
|
RX_YADA = 0x72151279, // "rx/yada" RandomYada (YadaCoin).
|
||||||
|
RX_HUSH = 0x72151268, // "rx/hush" RandomX-HUSH (HUSH/DragonX).
|
||||||
AR2_CHUKWA = 0x61130000, // "argon2/chukwa" Argon2id (Chukwa).
|
AR2_CHUKWA = 0x61130000, // "argon2/chukwa" Argon2id (Chukwa).
|
||||||
AR2_CHUKWA_V2 = 0x61140000, // "argon2/chukwav2" Argon2id (Chukwa v2).
|
AR2_CHUKWA_V2 = 0x61140000, // "argon2/chukwav2" Argon2id (Chukwa v2).
|
||||||
AR2_WRKZ = 0x61120000, // "argon2/wrkz" Argon2id (WRKZ)
|
AR2_WRKZ = 0x61120000, // "argon2/wrkz" Argon2id (WRKZ)
|
||||||
@@ -144,6 +145,7 @@ public:
|
|||||||
static const char *kRX_GRAFT;
|
static const char *kRX_GRAFT;
|
||||||
static const char *kRX_SFX;
|
static const char *kRX_SFX;
|
||||||
static const char *kRX_YADA;
|
static const char *kRX_YADA;
|
||||||
|
static const char *kRX_HUSH;
|
||||||
# endif
|
# endif
|
||||||
|
|
||||||
# ifdef XMRIG_ALGO_ARGON2
|
# ifdef XMRIG_ALGO_ARGON2
|
||||||
|
|||||||
@@ -55,6 +55,8 @@ static const CoinInfo coinInfo[] = {
|
|||||||
{ Algorithm::RX_0, "ZEPH", "Zephyr", 120, 1000000000000, BLUE_BG_BOLD( WHITE_BOLD_S " zephyr ") },
|
{ Algorithm::RX_0, "ZEPH", "Zephyr", 120, 1000000000000, BLUE_BG_BOLD( WHITE_BOLD_S " zephyr ") },
|
||||||
{ Algorithm::RX_0, "Townforge","Townforge", 30, 100000000, MAGENTA_BG_BOLD(WHITE_BOLD_S " townforge ") },
|
{ Algorithm::RX_0, "Townforge","Townforge", 30, 100000000, MAGENTA_BG_BOLD(WHITE_BOLD_S " townforge ") },
|
||||||
{ Algorithm::RX_YADA, "YDA", "YadaCoin", 120, 100000000, BLUE_BG_BOLD( WHITE_BOLD_S " yada ") },
|
{ Algorithm::RX_YADA, "YDA", "YadaCoin", 120, 100000000, BLUE_BG_BOLD( WHITE_BOLD_S " yada ") },
|
||||||
|
{ Algorithm::RX_HUSH, "HUSH", "Hush", 150, 100000000, GREEN_BG_BOLD( WHITE_BOLD_S " hush ") },
|
||||||
|
{ Algorithm::RX_HUSH, "DRAGONX", "DragonX", 36, 100000000, CYAN_BG_BOLD( WHITE_BOLD_S " dragonx ") },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -41,6 +41,8 @@ public:
|
|||||||
ZEPHYR,
|
ZEPHYR,
|
||||||
TOWNFORGE,
|
TOWNFORGE,
|
||||||
YADA,
|
YADA,
|
||||||
|
HUSH,
|
||||||
|
DRAGONX,
|
||||||
MAX
|
MAX
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -418,6 +418,7 @@ bool xmrig::Client::parseJob(const rapidjson::Value ¶ms, int *code)
|
|||||||
}
|
}
|
||||||
|
|
||||||
job.setSigKey(Json::getString(params, "sig_key"));
|
job.setSigKey(Json::getString(params, "sig_key"));
|
||||||
|
job.setBlockTarget(Json::getString(params, "block_target"));
|
||||||
|
|
||||||
m_job.setClientId(m_rpcId);
|
m_job.setClientId(m_rpcId);
|
||||||
|
|
||||||
|
|||||||
864
src/base/net/stratum/HushClient.cpp
Normal file
864
src/base/net/stratum/HushClient.cpp
Normal file
@@ -0,0 +1,864 @@
|
|||||||
|
/* XMRig - Hush/DragonX HAC Support
|
||||||
|
* Copyright (c) 2024 XMRig <https://github.com/xmrig>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "base/net/stratum/HushClient.h"
|
||||||
|
#include "3rdparty/rapidjson/document.h"
|
||||||
|
#include "3rdparty/rapidjson/error/en.h"
|
||||||
|
#include "base/io/json/Json.h"
|
||||||
|
#include "base/io/json/JsonRequest.h"
|
||||||
|
#include "base/io/log/Log.h"
|
||||||
|
#include "base/kernel/interfaces/IClientListener.h"
|
||||||
|
#include "base/net/http/Fetch.h"
|
||||||
|
#include "base/net/http/HttpData.h"
|
||||||
|
#include "base/net/http/HttpListener.h"
|
||||||
|
#include "base/tools/Chrono.h"
|
||||||
|
#include "base/tools/Cvt.h"
|
||||||
|
#include "base/tools/Timer.h"
|
||||||
|
#include "net/JobResult.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cinttypes>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
#ifdef XMRIG_FEATURE_TLS
|
||||||
|
# include <openssl/sha.h>
|
||||||
|
#else
|
||||||
|
// Standalone SHA-256 for non-TLS builds (Windows cross-compile)
|
||||||
|
# ifdef _WIN32
|
||||||
|
# include <windows.h>
|
||||||
|
# include <bcrypt.h>
|
||||||
|
static void SHA256(const uint8_t* data, size_t len, uint8_t* out) {
|
||||||
|
BCRYPT_ALG_HANDLE hAlg = nullptr;
|
||||||
|
BCRYPT_HASH_HANDLE hHash = nullptr;
|
||||||
|
BCryptOpenAlgorithmProvider(&hAlg, BCRYPT_SHA256_ALGORITHM, nullptr, 0);
|
||||||
|
BCryptCreateHash(hAlg, &hHash, nullptr, 0, nullptr, 0, 0);
|
||||||
|
BCryptHashData(hHash, const_cast<PUCHAR>(data), static_cast<ULONG>(len), 0);
|
||||||
|
BCryptFinishHash(hHash, out, 32, 0);
|
||||||
|
BCryptDestroyHash(hHash);
|
||||||
|
BCryptCloseAlgorithmProvider(hAlg, 0);
|
||||||
|
}
|
||||||
|
# else
|
||||||
|
# include "crypto/ghostrider/sph_sha2.h"
|
||||||
|
static void SHA256(const uint8_t* data, size_t len, uint8_t* out) {
|
||||||
|
sph_sha256_context ctx;
|
||||||
|
sph_sha256_init(&ctx);
|
||||||
|
sph_sha256(&ctx, data, len);
|
||||||
|
sph_sha256_close(&ctx, out);
|
||||||
|
}
|
||||||
|
# endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace xmrig {
|
||||||
|
|
||||||
|
static const char *kJsonRPC = "/";
|
||||||
|
|
||||||
|
// Simple Base64 encoder for HTTP Basic Auth
|
||||||
|
static std::string toBase64(const std::string &input)
|
||||||
|
{
|
||||||
|
static const char table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||||
|
std::string encoded;
|
||||||
|
encoded.reserve(((input.size() + 2) / 3) * 4);
|
||||||
|
|
||||||
|
unsigned int val = 0;
|
||||||
|
int bits = -6;
|
||||||
|
for (unsigned char c : input) {
|
||||||
|
val = (val << 8) + c;
|
||||||
|
bits += 8;
|
||||||
|
while (bits >= 0) {
|
||||||
|
encoded.push_back(table[(val >> bits) & 0x3F]);
|
||||||
|
bits -= 6;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (bits > -6) {
|
||||||
|
encoded.push_back(table[((val << 8) >> (bits + 8)) & 0x3F]);
|
||||||
|
}
|
||||||
|
while (encoded.size() % 4) {
|
||||||
|
encoded.push_back('=');
|
||||||
|
}
|
||||||
|
return encoded;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create JSON-RPC 1.0 request (HAC daemons use 1.0, not 2.0)
|
||||||
|
static void createRpc10(rapidjson::Document &doc, int64_t id, const char *method, rapidjson::Value ¶ms)
|
||||||
|
{
|
||||||
|
using namespace rapidjson;
|
||||||
|
auto &allocator = doc.GetAllocator();
|
||||||
|
|
||||||
|
doc.AddMember("jsonrpc", "1.0", allocator);
|
||||||
|
doc.AddMember("id", id, allocator);
|
||||||
|
doc.AddMember("method", StringRef(method), allocator);
|
||||||
|
doc.AddMember("params", params, allocator);
|
||||||
|
}
|
||||||
|
|
||||||
|
HushClient::HushClient(int id, IClientListener *listener) :
|
||||||
|
BaseClient(id, listener)
|
||||||
|
{
|
||||||
|
m_httpListener = std::make_shared<HttpListener>(this);
|
||||||
|
m_timer = new Timer(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
HushClient::~HushClient()
|
||||||
|
{
|
||||||
|
delete m_timer;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HushClient::deleteLater()
|
||||||
|
{
|
||||||
|
delete this;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HushClient::disconnect()
|
||||||
|
{
|
||||||
|
if (m_state != UnconnectedState) {
|
||||||
|
setState(UnconnectedState);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HushClient::isTLS() const
|
||||||
|
{
|
||||||
|
#ifdef XMRIG_FEATURE_TLS
|
||||||
|
return m_pool.isTLS();
|
||||||
|
#else
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void HushClient::connect()
|
||||||
|
{
|
||||||
|
if (m_pool.algorithm().family() != Algorithm::RANDOM_X) {
|
||||||
|
LOG_ERR("%s " RED("HAC coins require RandomX algorithm"), tag());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setState(ConnectingState);
|
||||||
|
getBlockTemplate();
|
||||||
|
}
|
||||||
|
|
||||||
|
void HushClient::connect(const Pool &pool)
|
||||||
|
{
|
||||||
|
setPool(pool);
|
||||||
|
connect();
|
||||||
|
}
|
||||||
|
|
||||||
|
void HushClient::setPool(const Pool &pool)
|
||||||
|
{
|
||||||
|
BaseClient::setPool(pool);
|
||||||
|
|
||||||
|
// Default to RandomX if not specified
|
||||||
|
if (!m_pool.algorithm().isValid()) {
|
||||||
|
m_pool.setAlgo(Algorithm::RX_0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t HushClient::submit(const JobResult &result)
|
||||||
|
{
|
||||||
|
if (result.jobId != m_currentJobId) {
|
||||||
|
LOG_DEBUG("%s " RED("job ID mismatch"), tag());
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the 32-byte nonce from the solo mining result
|
||||||
|
const uint8_t *nonce32 = result.isSoloResult() ? result.soloNonce() : nullptr;
|
||||||
|
if (!nonce32) {
|
||||||
|
LOG_ERR("%s " RED("submit called without solo nonce"), tag());
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// HAC PoW: RandomX hash becomes nSolution, then SHA256D(header + solution) must be < target
|
||||||
|
if (!checkPow(nonce32, result.result())) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_INFO("%s " CYAN_BOLD("submitting block at height %u"), tag(), m_height);
|
||||||
|
|
||||||
|
// Build the full block with header + nonce + solution + transactions
|
||||||
|
const std::string blockHex = serializeBlockHex(nonce32, result.result());
|
||||||
|
|
||||||
|
return submitBlock(blockHex);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HushClient::onTimer(const Timer *)
|
||||||
|
{
|
||||||
|
if (m_state == ConnectingState) {
|
||||||
|
connect();
|
||||||
|
}
|
||||||
|
else if (m_state == ConnectedState) {
|
||||||
|
// Poll for new block template
|
||||||
|
getBlockTemplate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void HushClient::onHttpData(const HttpData &data)
|
||||||
|
{
|
||||||
|
if (data.status != 200) {
|
||||||
|
LOG_ERR("%s " RED("HTTP error %d"), tag(), data.status);
|
||||||
|
return retry();
|
||||||
|
}
|
||||||
|
|
||||||
|
m_ip = data.ip().c_str();
|
||||||
|
|
||||||
|
#ifdef XMRIG_FEATURE_TLS
|
||||||
|
if (data.tlsVersion()) {
|
||||||
|
strncpy(m_tlsVersion, data.tlsVersion(), sizeof(m_tlsVersion) - 1);
|
||||||
|
}
|
||||||
|
if (data.tlsFingerprint()) {
|
||||||
|
strncpy(m_tlsFingerprint, data.tlsFingerprint(), sizeof(m_tlsFingerprint) - 1);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
rapidjson::Document doc;
|
||||||
|
if (doc.Parse(data.body.c_str()).HasParseError()) {
|
||||||
|
LOG_ERR("%s " RED("JSON parse error: %s"), tag(), rapidjson::GetParseError_En(doc.GetParseError()));
|
||||||
|
return retry();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debug: log the raw response for troubleshooting
|
||||||
|
LOG_DEBUG("%s RPC response: %.200s", tag(), data.body.c_str());
|
||||||
|
|
||||||
|
const int64_t id = Json::getInt64(doc, "id", -1);
|
||||||
|
|
||||||
|
// Handle "result" field which can be object, string, or null depending on RPC method
|
||||||
|
const rapidjson::Value *resultPtr = nullptr;
|
||||||
|
if (doc.HasMember("result")) {
|
||||||
|
resultPtr = &doc["result"];
|
||||||
|
}
|
||||||
|
static const rapidjson::Value nullValue;
|
||||||
|
const auto &result = resultPtr ? *resultPtr : nullValue;
|
||||||
|
const auto &error = Json::getObject(doc, "error");
|
||||||
|
|
||||||
|
if (!parseResponse(id, result, error)) {
|
||||||
|
retry();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t HushClient::getBlockTemplate()
|
||||||
|
{
|
||||||
|
using namespace rapidjson;
|
||||||
|
Document doc(kObjectType);
|
||||||
|
auto &allocator = doc.GetAllocator();
|
||||||
|
|
||||||
|
// JSON-RPC 1.0 style: params is an array with optional object
|
||||||
|
Value params(kArrayType);
|
||||||
|
// Empty params array for basic getblocktemplate
|
||||||
|
// HAC will return coinbasetxn by default
|
||||||
|
|
||||||
|
createRpc10(doc, m_sequence, "getblocktemplate", params);
|
||||||
|
m_pendingRequest = REQ_TEMPLATE;
|
||||||
|
|
||||||
|
return rpcSend(doc);
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t HushClient::getBlockHash(uint64_t height)
|
||||||
|
{
|
||||||
|
using namespace rapidjson;
|
||||||
|
Document doc(kObjectType);
|
||||||
|
auto &allocator = doc.GetAllocator();
|
||||||
|
|
||||||
|
Value params(kArrayType);
|
||||||
|
params.PushBack(Value(height), allocator);
|
||||||
|
|
||||||
|
createRpc10(doc, m_sequence, "getblockhash", params);
|
||||||
|
m_pendingRequest = REQ_KEYHASH;
|
||||||
|
m_pendingKeyHeight = height;
|
||||||
|
|
||||||
|
return rpcSend(doc);
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t HushClient::submitBlock(const std::string &blockHex)
|
||||||
|
{
|
||||||
|
using namespace rapidjson;
|
||||||
|
Document doc(kObjectType);
|
||||||
|
auto &allocator = doc.GetAllocator();
|
||||||
|
|
||||||
|
Value params(kArrayType);
|
||||||
|
params.PushBack(Value(blockHex.c_str(), allocator), allocator);
|
||||||
|
|
||||||
|
createRpc10(doc, m_sequence, "submitblock", params);
|
||||||
|
m_pendingRequest = REQ_SUBMIT;
|
||||||
|
|
||||||
|
LOG_INFO("%s " MAGENTA_BOLD("submitting block at height %u"), tag(), m_height);
|
||||||
|
|
||||||
|
return rpcSend(doc);
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t HushClient::rpcSend(const rapidjson::Document &doc)
|
||||||
|
{
|
||||||
|
FetchRequest req(HTTP_POST, m_pool.host(), m_pool.port(), kJsonRPC, doc, m_pool.isTLS(), isQuiet());
|
||||||
|
|
||||||
|
// HAC daemons require text/plain content-type (override application/json)
|
||||||
|
req.headers.erase("Content-Type");
|
||||||
|
req.headers.insert({"Content-Type", "text/plain"});
|
||||||
|
|
||||||
|
// Add RPC authentication if configured
|
||||||
|
if (!m_pool.user().isEmpty()) {
|
||||||
|
std::string auth = m_pool.user().data();
|
||||||
|
if (!m_pool.password().isEmpty()) {
|
||||||
|
auth += ":";
|
||||||
|
auth += m_pool.password().data();
|
||||||
|
}
|
||||||
|
const std::string encoded = toBase64(auth);
|
||||||
|
req.headers.insert({"Authorization", "Basic " + encoded});
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch(tag(), std::move(req), m_httpListener);
|
||||||
|
|
||||||
|
return m_sequence++;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HushClient::parseBlockTemplate(const rapidjson::Value &result)
|
||||||
|
{
|
||||||
|
if (!result.IsObject()) {
|
||||||
|
LOG_ERR("%s " RED("invalid block template response"), tag());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if this is the same job (same previous block hash)
|
||||||
|
const char *prevHash = Json::getString(result, "previousblockhash");
|
||||||
|
if (prevHash && m_prevJobHash == prevHash && m_state == ConnectedState) {
|
||||||
|
// Same block, skip duplicate job
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse BIP22 block template fields
|
||||||
|
m_height = Json::getUint64(result, "height");
|
||||||
|
m_curtime = Json::getUint64(result, "curtime");
|
||||||
|
m_version = Json::getInt(result, "version", 4);
|
||||||
|
m_prevHash = Json::getString(result, "previousblockhash");
|
||||||
|
m_saplingRoot = Json::getString(result, "finalsaplingroothash");
|
||||||
|
m_target = Json::getString(result, "target");
|
||||||
|
m_bits = Json::getString(result, "bits");
|
||||||
|
|
||||||
|
// Parse target into bytes for PoW comparison (32 bytes, big-endian from hex)
|
||||||
|
m_targetBytes.resize(32);
|
||||||
|
if (m_target.size() == 64) {
|
||||||
|
Cvt::fromHex(m_targetBytes.data(), 32, m_target.data(), 64);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Coinbase transaction
|
||||||
|
const auto &coinbase = result["coinbasetxn"];
|
||||||
|
if (!coinbase.IsObject()) {
|
||||||
|
LOG_ERR("%s " RED("missing coinbasetxn in template"), tag());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
m_coinbaseTx = Json::getString(coinbase, "data");
|
||||||
|
m_merkleRoot = Json::getString(coinbase, "hash");
|
||||||
|
|
||||||
|
LOG_DEBUG("%s template: height=%lu bits=%s target=%s", tag(), m_height, m_bits.data(), m_target.data());
|
||||||
|
LOG_DEBUG("%s prevHash=%s", tag(), m_prevHash.data());
|
||||||
|
LOG_DEBUG("%s merkleRoot=%s", tag(), m_merkleRoot.data());
|
||||||
|
|
||||||
|
// Other transactions (may be empty for low traffic chains)
|
||||||
|
m_transactions.clear();
|
||||||
|
const auto &txs = result["transactions"];
|
||||||
|
if (txs.IsArray()) {
|
||||||
|
for (const auto &tx : txs.GetArray()) {
|
||||||
|
if (tx.IsObject()) {
|
||||||
|
m_transactions.push_back(Json::getString(tx, "data"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate required fields
|
||||||
|
if (m_height == 0 || m_prevHash.isEmpty() || m_coinbaseTx.isEmpty()) {
|
||||||
|
LOG_ERR("%s " RED("incomplete block template"), tag());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if we need to fetch new RandomX key
|
||||||
|
const uint64_t keyHeight = getKeyHeight(m_height);
|
||||||
|
if (keyHeight != m_keyHeight || m_keyBlockHash.isEmpty()) {
|
||||||
|
if (keyHeight == 0) {
|
||||||
|
// Use chain-specific initial RandomX key.
|
||||||
|
// Daemon computes: sprintf("%08x%s%08x", ASSETCHAINS_MAGIC, SYMBOL, RPCPORT)
|
||||||
|
// and passes the raw ASCII bytes to randomx_init_cache().
|
||||||
|
// We hex-encode those bytes (zero-padded to 32) for setSeedHash().
|
||||||
|
char initialKey[81];
|
||||||
|
const uint32_t magic = 2387029918u; // DRAGONX ASSETCHAINS_MAGIC
|
||||||
|
const char* symbol = "DRAGONX";
|
||||||
|
const uint16_t rpcPort = 21769;
|
||||||
|
snprintf(initialKey, sizeof(initialKey), "%08x%s%08x", magic, symbol, (uint32_t)rpcPort);
|
||||||
|
const size_t keyLen = strlen(initialKey);
|
||||||
|
// Hex-encode only the actual key bytes (no zero-padding).
|
||||||
|
// The daemon passes strlen(initialKey) bytes to randomx_init_cache(),
|
||||||
|
// so we must match that exact length.
|
||||||
|
char hexBuf[163]; // max 81 bytes * 2 + 1
|
||||||
|
for (size_t i = 0; i < keyLen; i++) snprintf(hexBuf + i*2, 3, "%02x", (uint8_t)initialKey[i]);
|
||||||
|
hexBuf[keyLen * 2] = '\0';
|
||||||
|
m_keyBlockHash = hexBuf;
|
||||||
|
m_keyHeight = 0;
|
||||||
|
LOG_INFO("%s " CYAN("using initial RandomX key \"%s\" for height %u"), tag(), initialKey, m_height);
|
||||||
|
} else {
|
||||||
|
LOG_INFO("%s " CYAN("fetching RandomX key block hash at height %u"), tag(), keyHeight);
|
||||||
|
getBlockHash(keyHeight);
|
||||||
|
return true; // Will continue after receiving key hash
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the mining job
|
||||||
|
Job job(false, m_pool.algorithm(), String());
|
||||||
|
job.setHeight(m_height);
|
||||||
|
|
||||||
|
if (!job.setSeedHash(m_keyBlockHash.data())) {
|
||||||
|
LOG_ERR("%s " RED("failed to set seed hash: %s (len=%zu)"), tag(), m_keyBlockHash.data(), m_keyBlockHash.size());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build 108-byte header for RandomX hashing (nonce bytes 108-139 filled by worker):
|
||||||
|
// version(4) + prevHash(32) + merkleRoot(32) + saplingRoot(32) + time(4) + bits(4) = 108
|
||||||
|
uint8_t header[108];
|
||||||
|
size_t offset = 0;
|
||||||
|
|
||||||
|
// nVersion (4 bytes, little-endian)
|
||||||
|
const uint32_t ver = static_cast<uint32_t>(m_version);
|
||||||
|
memcpy(header + offset, &ver, 4);
|
||||||
|
offset += 4;
|
||||||
|
|
||||||
|
// hashPrevBlock (32 bytes, internal byte order = reversed from display)
|
||||||
|
{
|
||||||
|
std::vector<uint8_t> tmp(32);
|
||||||
|
Cvt::fromHex(tmp.data(), 32, m_prevHash.data(), 64);
|
||||||
|
std::reverse(tmp.begin(), tmp.end());
|
||||||
|
memcpy(header + offset, tmp.data(), 32);
|
||||||
|
// Also store for block submission
|
||||||
|
memcpy(m_headerPrevHash, tmp.data(), 32);
|
||||||
|
}
|
||||||
|
offset += 32;
|
||||||
|
|
||||||
|
// hashMerkleRoot (32 bytes, reversed)
|
||||||
|
{
|
||||||
|
std::vector<uint8_t> tmp(32);
|
||||||
|
Cvt::fromHex(tmp.data(), 32, m_merkleRoot.data(), 64);
|
||||||
|
std::reverse(tmp.begin(), tmp.end());
|
||||||
|
memcpy(header + offset, tmp.data(), 32);
|
||||||
|
memcpy(m_headerMerkleRoot, tmp.data(), 32);
|
||||||
|
}
|
||||||
|
offset += 32;
|
||||||
|
|
||||||
|
// hashFinalSaplingRoot (32 bytes, reversed)
|
||||||
|
{
|
||||||
|
std::vector<uint8_t> tmp(32);
|
||||||
|
Cvt::fromHex(tmp.data(), 32, m_saplingRoot.data(), 64);
|
||||||
|
std::reverse(tmp.begin(), tmp.end());
|
||||||
|
memcpy(header + offset, tmp.data(), 32);
|
||||||
|
memcpy(m_headerSaplingRoot, tmp.data(), 32);
|
||||||
|
}
|
||||||
|
offset += 32;
|
||||||
|
|
||||||
|
// nTime (4 bytes, little-endian)
|
||||||
|
const uint32_t time32 = static_cast<uint32_t>(m_curtime);
|
||||||
|
memcpy(header + offset, &time32, 4);
|
||||||
|
m_headerTime = time32;
|
||||||
|
offset += 4;
|
||||||
|
|
||||||
|
// nBits (4 bytes) - parse from big-endian hex, store as little-endian uint32
|
||||||
|
uint32_t bits = 0;
|
||||||
|
if (m_bits.size() == 8) {
|
||||||
|
bits = static_cast<uint32_t>(strtoul(m_bits.data(), nullptr, 16));
|
||||||
|
}
|
||||||
|
memcpy(header + offset, &bits, 4);
|
||||||
|
m_headerBits = bits;
|
||||||
|
offset += 4;
|
||||||
|
|
||||||
|
// Set the 108-byte header directly into the job blob (worker adds 32-byte nonce at offset 108)
|
||||||
|
job.setHushHeader(header);
|
||||||
|
|
||||||
|
// Compute 64-bit target from compact "bits" field (Bitcoin/Zcash format)
|
||||||
|
// Top byte = exponent, lower 3 bytes = mantissa
|
||||||
|
// Full 256-bit target = mantissa * 2^(8*(exponent-3))
|
||||||
|
// xmrig compares: (uint64_t)hash[24..31] < target64
|
||||||
|
{
|
||||||
|
int exponent = (bits >> 24) & 0xff;
|
||||||
|
uint32_t mantissa = bits & 0x007fffff;
|
||||||
|
int mantissaPos = exponent - 3; // Byte position of mantissa's low byte
|
||||||
|
|
||||||
|
uint64_t target64 = 0;
|
||||||
|
|
||||||
|
if (mantissaPos >= 32) {
|
||||||
|
target64 = 0xFFFFFFFFFFFFFFFFULL;
|
||||||
|
} else if (mantissaPos >= 24) {
|
||||||
|
int shift = (mantissaPos - 24) * 8;
|
||||||
|
target64 = (uint64_t)mantissa << shift;
|
||||||
|
if (mantissaPos > 29) {
|
||||||
|
target64 = 0xFFFFFFFFFFFFFFFFULL;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Mantissa is entirely below byte 24 - extremely high difficulty
|
||||||
|
target64 = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
job.setTarget64(target64);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark as solo mining so workers use 32-byte SoloNonce
|
||||||
|
job.setSoloMining(true);
|
||||||
|
|
||||||
|
m_currentJobId = Cvt::toHex(Cvt::randomBytes(4));
|
||||||
|
job.setId(m_currentJobId);
|
||||||
|
|
||||||
|
m_job = std::move(job);
|
||||||
|
m_prevJobHash = m_prevHash;
|
||||||
|
m_jobSteadyMs = Chrono::steadyMSecs();
|
||||||
|
|
||||||
|
if (m_state == ConnectingState) {
|
||||||
|
LOG_INFO("%s " GREEN("connected to %s:%d"), tag(), m_pool.host().data(), m_pool.port());
|
||||||
|
setState(ConnectedState);
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_INFO("%s " MAGENTA_BOLD("new job") " height: " CYAN_BOLD("%u") " bits: " CYAN_BOLD("0x%08x") " diff: " CYAN_BOLD("%" PRIu64),
|
||||||
|
tag(), m_height, m_headerBits, m_job.diff());
|
||||||
|
|
||||||
|
m_listener->onJobReceived(this, m_job, rapidjson::Value());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HushClient::parseResponse(int64_t id, const rapidjson::Value &result, const rapidjson::Value &error)
|
||||||
|
{
|
||||||
|
// Handle RPC errors
|
||||||
|
if (error.IsObject()) {
|
||||||
|
const char *message = Json::getString(error, "message", "unknown error");
|
||||||
|
const int code = Json::getInt(error, "code", -1);
|
||||||
|
LOG_ERR("%s " RED("RPC error %d: \"%s\""), tag(), code, message);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (m_pendingRequest) {
|
||||||
|
case REQ_TEMPLATE:
|
||||||
|
if (parseBlockTemplate(result)) {
|
||||||
|
m_timer->start(m_pool.pollInterval(), m_pool.pollInterval());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return m_pendingRequest == REQ_KEYHASH; // Waiting for key is ok
|
||||||
|
|
||||||
|
case REQ_KEYHASH:
|
||||||
|
if (result.IsString()) {
|
||||||
|
// getblockhash returns display-order (big-endian) hex, but the daemon
|
||||||
|
// passes uint256 internal bytes (little-endian) to randomx_init_cache().
|
||||||
|
// Reverse the bytes so setSeedHash() produces the correct LE byte order.
|
||||||
|
const char* displayHash = result.GetString();
|
||||||
|
std::vector<uint8_t> hashBytes(32);
|
||||||
|
Cvt::fromHex(hashBytes.data(), 32, displayHash, 64);
|
||||||
|
std::reverse(hashBytes.begin(), hashBytes.end());
|
||||||
|
char reversedHex[65];
|
||||||
|
for (size_t i = 0; i < 32; i++) snprintf(reversedHex + i*2, 3, "%02x", hashBytes[i]);
|
||||||
|
reversedHex[64] = '\0';
|
||||||
|
m_keyBlockHash = reversedHex;
|
||||||
|
m_keyHeight = m_pendingKeyHeight;
|
||||||
|
LOG_INFO("%s " GREEN("RandomX key block %u: %.16s... (reversed to LE)"),
|
||||||
|
tag(), m_keyHeight, m_keyBlockHash.data());
|
||||||
|
// Now get the block template again with the key
|
||||||
|
m_pendingRequest = REQ_NONE;
|
||||||
|
getBlockTemplate();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
LOG_ERR("%s " RED("invalid getblockhash response - expected string, got type %d"),
|
||||||
|
tag(), result.GetType());
|
||||||
|
return false;
|
||||||
|
|
||||||
|
case REQ_SUBMIT:
|
||||||
|
// submitblock returns null on success
|
||||||
|
if (result.IsNull()) {
|
||||||
|
LOG_INFO("%s " GREEN_BOLD("BLOCK ACCEPTED!"), tag());
|
||||||
|
if (m_results.count(id)) {
|
||||||
|
m_listener->onResultAccepted(this, m_results[id], nullptr);
|
||||||
|
m_results.erase(id);
|
||||||
|
}
|
||||||
|
} else if (result.IsString()) {
|
||||||
|
const char *msg = result.GetString();
|
||||||
|
if (strlen(msg) > 0) {
|
||||||
|
LOG_ERR("%s " RED("block rejected: %s"), tag(), msg);
|
||||||
|
} else {
|
||||||
|
LOG_INFO("%s " GREEN_BOLD("BLOCK ACCEPTED!"), tag());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Get new work after submit
|
||||||
|
getBlockTemplate();
|
||||||
|
return true;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void HushClient::retry()
|
||||||
|
{
|
||||||
|
m_failures++;
|
||||||
|
m_listener->onClose(this, static_cast<int>(m_failures));
|
||||||
|
|
||||||
|
if (m_state == ConnectedState) {
|
||||||
|
setState(ConnectingState);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_timer->stop();
|
||||||
|
m_timer->start(m_retryPause, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HushClient::setState(SocketState state)
|
||||||
|
{
|
||||||
|
if (m_state == state) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_state = state;
|
||||||
|
|
||||||
|
switch (state) {
|
||||||
|
case ConnectedState:
|
||||||
|
m_failures = 0;
|
||||||
|
m_listener->onLoginSuccess(this);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case UnconnectedState:
|
||||||
|
m_timer->stop();
|
||||||
|
m_failures = -1;
|
||||||
|
m_listener->onClose(this, -1);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t HushClient::getKeyHeight(uint64_t height) const
|
||||||
|
{
|
||||||
|
// RandomX key changes every RANDOMX_INTERVAL blocks with RANDOMX_LAG delay
|
||||||
|
if (height < static_cast<uint64_t>(RANDOMX_INTERVAL + RANDOMX_LAG)) {
|
||||||
|
return 0; // Use genesis key
|
||||||
|
}
|
||||||
|
return ((height - RANDOMX_LAG) / RANDOMX_INTERVAL) * RANDOMX_INTERVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint8_t> HushClient::serializeBlock(uint32_t nonce, uint64_t extraNonce) const
|
||||||
|
{
|
||||||
|
std::vector<uint8_t> blob;
|
||||||
|
blob.reserve(1024);
|
||||||
|
|
||||||
|
// nVersion (4 bytes, little-endian)
|
||||||
|
const uint32_t ver = static_cast<uint32_t>(m_version);
|
||||||
|
blob.insert(blob.end(), reinterpret_cast<const uint8_t*>(&ver),
|
||||||
|
reinterpret_cast<const uint8_t*>(&ver) + 4);
|
||||||
|
|
||||||
|
// hashPrevBlock (32 bytes, reversed from hex display)
|
||||||
|
std::vector<uint8_t> prevHash(32);
|
||||||
|
Cvt::fromHex(prevHash.data(), 32, m_prevHash.data(), 64);
|
||||||
|
std::reverse(prevHash.begin(), prevHash.end());
|
||||||
|
blob.insert(blob.end(), prevHash.begin(), prevHash.end());
|
||||||
|
|
||||||
|
// hashMerkleRoot (32 bytes, reversed)
|
||||||
|
std::vector<uint8_t> merkleRoot(32);
|
||||||
|
Cvt::fromHex(merkleRoot.data(), 32, m_merkleRoot.data(), 64);
|
||||||
|
std::reverse(merkleRoot.begin(), merkleRoot.end());
|
||||||
|
blob.insert(blob.end(), merkleRoot.begin(), merkleRoot.end());
|
||||||
|
|
||||||
|
// hashFinalSaplingRoot (32 bytes, reversed)
|
||||||
|
std::vector<uint8_t> saplingRoot(32);
|
||||||
|
Cvt::fromHex(saplingRoot.data(), 32, m_saplingRoot.data(), 64);
|
||||||
|
std::reverse(saplingRoot.begin(), saplingRoot.end());
|
||||||
|
blob.insert(blob.end(), saplingRoot.begin(), saplingRoot.end());
|
||||||
|
|
||||||
|
// nTime (4 bytes, little-endian)
|
||||||
|
const uint32_t time32 = static_cast<uint32_t>(m_curtime);
|
||||||
|
blob.insert(blob.end(), reinterpret_cast<const uint8_t*>(&time32),
|
||||||
|
reinterpret_cast<const uint8_t*>(&time32) + 4);
|
||||||
|
|
||||||
|
// nBits (4 bytes) - convert from big-endian hex to little-endian binary
|
||||||
|
uint32_t bits = 0;
|
||||||
|
for (int i = 0; i < 8 && i < static_cast<int>(m_bits.size()); i += 2) {
|
||||||
|
uint8_t byte;
|
||||||
|
Cvt::fromHex(&byte, 1, m_bits.data() + i, 2);
|
||||||
|
bits = (bits << 8) | byte;
|
||||||
|
}
|
||||||
|
blob.insert(blob.end(), reinterpret_cast<const uint8_t*>(&bits),
|
||||||
|
reinterpret_cast<const uint8_t*>(&bits) + 4);
|
||||||
|
|
||||||
|
// nNonce (32 bytes for Zcash/Hush - uint256)
|
||||||
|
std::vector<uint8_t> nonce256(32, 0);
|
||||||
|
memcpy(nonce256.data(), &nonce, 4);
|
||||||
|
memcpy(nonce256.data() + 4, &extraNonce, sizeof(extraNonce));
|
||||||
|
blob.insert(blob.end(), nonce256.begin(), nonce256.end());
|
||||||
|
|
||||||
|
// nSolution placeholder (CompactSize + 32 zeros)
|
||||||
|
blob.push_back(32); // CompactSize for 32 bytes
|
||||||
|
blob.insert(blob.end(), 32, 0);
|
||||||
|
|
||||||
|
// Transaction count (CompactSize)
|
||||||
|
const size_t txCount = 1 + m_transactions.size();
|
||||||
|
if (txCount < 0xFD) {
|
||||||
|
blob.push_back(static_cast<uint8_t>(txCount));
|
||||||
|
} else {
|
||||||
|
blob.push_back(0xFD);
|
||||||
|
blob.push_back(txCount & 0xFF);
|
||||||
|
blob.push_back((txCount >> 8) & 0xFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Coinbase transaction
|
||||||
|
std::vector<uint8_t> coinbase(m_coinbaseTx.size() / 2);
|
||||||
|
Cvt::fromHex(coinbase.data(), coinbase.size(), m_coinbaseTx.data(), m_coinbaseTx.size());
|
||||||
|
blob.insert(blob.end(), coinbase.begin(), coinbase.end());
|
||||||
|
|
||||||
|
// Other transactions
|
||||||
|
for (const auto &txHex : m_transactions) {
|
||||||
|
std::vector<uint8_t> tx(txHex.size() / 2);
|
||||||
|
Cvt::fromHex(tx.data(), tx.size(), txHex.data(), txHex.size());
|
||||||
|
blob.insert(blob.end(), tx.begin(), tx.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
return blob;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string HushClient::serializeBlockHex(const uint8_t* nonce32, const uint8_t* solution) const
|
||||||
|
{
|
||||||
|
std::vector<uint8_t> blob;
|
||||||
|
blob.reserve(1024);
|
||||||
|
|
||||||
|
// Header (140 bytes) using cached binary fields
|
||||||
|
// Version (4 bytes LE)
|
||||||
|
const uint32_t ver = static_cast<uint32_t>(m_version);
|
||||||
|
blob.insert(blob.end(), reinterpret_cast<const uint8_t*>(&ver),
|
||||||
|
reinterpret_cast<const uint8_t*>(&ver) + 4);
|
||||||
|
|
||||||
|
// Previous hash (32 bytes - already in internal order)
|
||||||
|
blob.insert(blob.end(), m_headerPrevHash, m_headerPrevHash + 32);
|
||||||
|
|
||||||
|
// Merkle root (32 bytes)
|
||||||
|
blob.insert(blob.end(), m_headerMerkleRoot, m_headerMerkleRoot + 32);
|
||||||
|
|
||||||
|
// Sapling root (32 bytes)
|
||||||
|
blob.insert(blob.end(), m_headerSaplingRoot, m_headerSaplingRoot + 32);
|
||||||
|
|
||||||
|
// Time (4 bytes LE)
|
||||||
|
blob.insert(blob.end(), reinterpret_cast<const uint8_t*>(&m_headerTime),
|
||||||
|
reinterpret_cast<const uint8_t*>(&m_headerTime) + 4);
|
||||||
|
|
||||||
|
// Bits (4 bytes LE)
|
||||||
|
blob.insert(blob.end(), reinterpret_cast<const uint8_t*>(&m_headerBits),
|
||||||
|
reinterpret_cast<const uint8_t*>(&m_headerBits) + 4);
|
||||||
|
|
||||||
|
// Nonce (32 bytes from the mining result)
|
||||||
|
blob.insert(blob.end(), nonce32, nonce32 + 32);
|
||||||
|
|
||||||
|
// Solution (the RandomX hash - 32 bytes with CompactSize prefix)
|
||||||
|
blob.push_back(32); // CompactSize
|
||||||
|
blob.insert(blob.end(), solution, solution + 32);
|
||||||
|
|
||||||
|
// Transactions
|
||||||
|
const size_t txCount = 1 + m_transactions.size();
|
||||||
|
if (txCount < 0xFD) {
|
||||||
|
blob.push_back(static_cast<uint8_t>(txCount));
|
||||||
|
} else {
|
||||||
|
blob.push_back(0xFD);
|
||||||
|
blob.push_back(txCount & 0xFF);
|
||||||
|
blob.push_back((txCount >> 8) & 0xFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint8_t> coinbase(m_coinbaseTx.size() / 2);
|
||||||
|
Cvt::fromHex(coinbase.data(), coinbase.size(), m_coinbaseTx.data(), m_coinbaseTx.size());
|
||||||
|
blob.insert(blob.end(), coinbase.begin(), coinbase.end());
|
||||||
|
|
||||||
|
for (const auto &txHex : m_transactions) {
|
||||||
|
std::vector<uint8_t> tx(txHex.size() / 2);
|
||||||
|
Cvt::fromHex(tx.data(), tx.size(), txHex.data(), txHex.size());
|
||||||
|
blob.insert(blob.end(), tx.begin(), tx.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
const String hex = Cvt::toHex(blob.data(), blob.size());
|
||||||
|
return std::string(hex.data(), hex.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
void HushClient::sha256d(const uint8_t* data, size_t len, uint8_t* out)
|
||||||
|
{
|
||||||
|
uint8_t hash1[32];
|
||||||
|
::SHA256(data, len, hash1);
|
||||||
|
::SHA256(hash1, 32, out);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint8_t> HushClient::serializeHeader(const uint8_t* nonce32, const uint8_t* solution) const
|
||||||
|
{
|
||||||
|
std::vector<uint8_t> header;
|
||||||
|
header.reserve(140 + 1 + 32); // header + compactsize + solution
|
||||||
|
|
||||||
|
// Version (4 bytes LE)
|
||||||
|
const uint32_t ver = static_cast<uint32_t>(m_version);
|
||||||
|
header.insert(header.end(), reinterpret_cast<const uint8_t*>(&ver),
|
||||||
|
reinterpret_cast<const uint8_t*>(&ver) + 4);
|
||||||
|
|
||||||
|
// Use cached binary header fields (already in internal byte order)
|
||||||
|
header.insert(header.end(), m_headerPrevHash, m_headerPrevHash + 32);
|
||||||
|
header.insert(header.end(), m_headerMerkleRoot, m_headerMerkleRoot + 32);
|
||||||
|
header.insert(header.end(), m_headerSaplingRoot, m_headerSaplingRoot + 32);
|
||||||
|
|
||||||
|
// Time (4 bytes LE)
|
||||||
|
header.insert(header.end(), reinterpret_cast<const uint8_t*>(&m_headerTime),
|
||||||
|
reinterpret_cast<const uint8_t*>(&m_headerTime) + 4);
|
||||||
|
|
||||||
|
// Bits (4 bytes LE)
|
||||||
|
header.insert(header.end(), reinterpret_cast<const uint8_t*>(&m_headerBits),
|
||||||
|
reinterpret_cast<const uint8_t*>(&m_headerBits) + 4);
|
||||||
|
|
||||||
|
// Nonce (32 bytes)
|
||||||
|
header.insert(header.end(), nonce32, nonce32 + 32);
|
||||||
|
|
||||||
|
// nSolution (compactsize + 32 bytes)
|
||||||
|
header.push_back(32); // CompactSize for 32 bytes
|
||||||
|
header.insert(header.end(), solution, solution + 32);
|
||||||
|
|
||||||
|
return header;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HushClient::checkPow(const uint8_t* nonce32, const uint8_t* solution) const
|
||||||
|
{
|
||||||
|
// Build header with solution
|
||||||
|
const std::vector<uint8_t> header = serializeHeader(nonce32, solution);
|
||||||
|
|
||||||
|
// Compute SHA256D(header)
|
||||||
|
uint8_t blockHash[32];
|
||||||
|
sha256d(header.data(), header.size(), blockHash);
|
||||||
|
|
||||||
|
// Compare blockHash < target (both are 32 bytes, big-endian in target, little-endian in hash)
|
||||||
|
// Block hash from SHA256D is little-endian, target from RPC is big-endian
|
||||||
|
// We need to compare them byte by byte, reversing the hash for comparison
|
||||||
|
for (int i = 0; i < 32; i++) {
|
||||||
|
uint8_t hashByte = blockHash[31 - i]; // Reverse hash to big-endian
|
||||||
|
uint8_t targetByte = m_targetBytes[i];
|
||||||
|
|
||||||
|
if (hashByte < targetByte) {
|
||||||
|
LOG_INFO("%s " GREEN_BOLD("PoW check PASSED") " - hash meets target", tag());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (hashByte > targetByte) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true; // Equal means it passes
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t HushClient::targetToDiff(const char *target) const
|
||||||
|
{
|
||||||
|
// Target is 64 hex chars (256-bit big-endian)
|
||||||
|
// Find the first non-zero bytes and compute difficulty
|
||||||
|
if (!target || strlen(target) < 16) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse first 8 bytes as big-endian uint64 for approximation
|
||||||
|
uint64_t t = 0;
|
||||||
|
for (int i = 0; i < 16; i += 2) {
|
||||||
|
uint8_t byte;
|
||||||
|
Cvt::fromHex(&byte, 1, target + i, 2);
|
||||||
|
t = (t << 8) | byte;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (t == 0) {
|
||||||
|
return 0xFFFFFFFFFFFFFFFFULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rough difficulty approximation
|
||||||
|
return 0xFFFFFFFFFFFFFFFFULL / t;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace xmrig
|
||||||
142
src/base/net/stratum/HushClient.h
Normal file
142
src/base/net/stratum/HushClient.h
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
/* XMRig - Hush/DragonX HAC Support
|
||||||
|
* Copyright (c) 2024 XMRig <https://github.com/xmrig>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef XMRIG_HUSHCLIENT_H
|
||||||
|
#define XMRIG_HUSHCLIENT_H
|
||||||
|
|
||||||
|
#include "base/kernel/interfaces/IHttpListener.h"
|
||||||
|
#include "base/kernel/interfaces/ITimerListener.h"
|
||||||
|
#include "base/net/stratum/BaseClient.h"
|
||||||
|
#include "base/net/stratum/SubmitResult.h"
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace xmrig {
|
||||||
|
|
||||||
|
class Timer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Client for Hush Asset Chains (HAC) that use RandomX PoW.
|
||||||
|
* Handles BIP22-style getblocktemplate RPC format used by hushd/dragonxd.
|
||||||
|
*/
|
||||||
|
class HushClient : public BaseClient, public ITimerListener, public IHttpListener
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
XMRIG_DISABLE_COPY_MOVE_DEFAULT(HushClient)
|
||||||
|
|
||||||
|
HushClient(int id, IClientListener *listener);
|
||||||
|
~HushClient() override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
// BaseClient interface
|
||||||
|
bool disconnect() override;
|
||||||
|
bool isTLS() const override;
|
||||||
|
int64_t submit(const JobResult &result) override;
|
||||||
|
void connect() override;
|
||||||
|
void connect(const Pool &pool) override;
|
||||||
|
void setPool(const Pool &pool) override;
|
||||||
|
void deleteLater() override;
|
||||||
|
|
||||||
|
// IHttpListener
|
||||||
|
void onHttpData(const HttpData &data) override;
|
||||||
|
|
||||||
|
// ITimerListener
|
||||||
|
void onTimer(const Timer *timer) override;
|
||||||
|
|
||||||
|
inline bool hasExtension(Extension) const noexcept override { return false; }
|
||||||
|
inline const char *mode() const override { return "hush-daemon"; }
|
||||||
|
inline const char *tlsFingerprint() const override { return m_tlsFingerprint; }
|
||||||
|
inline const char *tlsVersion() const override { return m_tlsVersion; }
|
||||||
|
inline int64_t send(const rapidjson::Value &, Callback) override { return -1; }
|
||||||
|
inline int64_t send(const rapidjson::Value &) override { return -1; }
|
||||||
|
inline void tick(uint64_t) override {}
|
||||||
|
|
||||||
|
private:
|
||||||
|
// RPC methods
|
||||||
|
int64_t getBlockTemplate();
|
||||||
|
int64_t getBlockHash(uint64_t height);
|
||||||
|
int64_t submitBlock(const std::string &blockHex);
|
||||||
|
int64_t rpcSend(const rapidjson::Document &doc);
|
||||||
|
|
||||||
|
// Job handling
|
||||||
|
bool parseBlockTemplate(const rapidjson::Value &result);
|
||||||
|
bool parseResponse(int64_t id, const rapidjson::Value &result, const rapidjson::Value &error);
|
||||||
|
void retry();
|
||||||
|
void setState(SocketState state);
|
||||||
|
|
||||||
|
// RandomX key management
|
||||||
|
uint64_t getKeyHeight(uint64_t height) const;
|
||||||
|
|
||||||
|
// Block serialization
|
||||||
|
std::vector<uint8_t> serializeBlock(uint32_t nonce, uint64_t extraNonce) const;
|
||||||
|
std::vector<uint8_t> serializeHeader(const uint8_t* nonce32, const uint8_t* solution) const;
|
||||||
|
std::string serializeBlockHex(const uint8_t* nonce32, const uint8_t* solution) const;
|
||||||
|
|
||||||
|
// PoW verification (SHA256D of header with solution)
|
||||||
|
bool checkPow(const uint8_t* nonce32, const uint8_t* solution) const;
|
||||||
|
static void sha256d(const uint8_t* data, size_t len, uint8_t* out);
|
||||||
|
|
||||||
|
// Target/difficulty conversion
|
||||||
|
uint64_t targetToDiff(const char *target) const;
|
||||||
|
|
||||||
|
// Constants for RandomX key derivation
|
||||||
|
static constexpr int RANDOMX_INTERVAL = 1024;
|
||||||
|
static constexpr int RANDOMX_LAG = 64;
|
||||||
|
|
||||||
|
// Block template data
|
||||||
|
int32_t m_version = 4;
|
||||||
|
uint64_t m_height = 0;
|
||||||
|
uint64_t m_curtime = 0;
|
||||||
|
String m_prevHash;
|
||||||
|
String m_merkleRoot; // coinbasetxn.hash
|
||||||
|
String m_saplingRoot; // finalsaplingroothash
|
||||||
|
String m_bits;
|
||||||
|
String m_target;
|
||||||
|
std::vector<uint8_t> m_targetBytes; // Network target as 32 bytes for comparison
|
||||||
|
String m_coinbaseTx; // coinbasetxn.data
|
||||||
|
std::vector<String> m_transactions;
|
||||||
|
|
||||||
|
// RandomX key tracking
|
||||||
|
uint64_t m_keyHeight = 0;
|
||||||
|
String m_keyBlockHash;
|
||||||
|
|
||||||
|
// Job state
|
||||||
|
String m_currentJobId;
|
||||||
|
String m_prevJobHash;
|
||||||
|
uint64_t m_jobSteadyMs = 0;
|
||||||
|
|
||||||
|
// Cached block blob for submission (full block with header + txs)
|
||||||
|
std::vector<uint8_t> m_headerBlob;
|
||||||
|
size_t m_nonceOffset = 0; // Offset of nNonce field in the block blob
|
||||||
|
|
||||||
|
// Cached binary header fields for block serialization (avoids re-parsing hex each submit)
|
||||||
|
uint8_t m_headerPrevHash[32] = {};
|
||||||
|
uint8_t m_headerMerkleRoot[32] = {};
|
||||||
|
uint8_t m_headerSaplingRoot[32] = {};
|
||||||
|
uint32_t m_headerTime = 0;
|
||||||
|
uint32_t m_headerBits = 0;
|
||||||
|
|
||||||
|
// Request tracking
|
||||||
|
enum RequestType { REQ_NONE, REQ_TEMPLATE, REQ_KEYHASH, REQ_SUBMIT };
|
||||||
|
RequestType m_pendingRequest = REQ_NONE;
|
||||||
|
uint64_t m_pendingKeyHeight = 0;
|
||||||
|
|
||||||
|
Timer *m_timer;
|
||||||
|
std::shared_ptr<IHttpListener> m_httpListener;
|
||||||
|
std::map<int64_t, SubmitResult> m_results;
|
||||||
|
|
||||||
|
char m_tlsFingerprint[64] = { 0 };
|
||||||
|
char m_tlsVersion[32] = { 0 };
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace xmrig
|
||||||
|
|
||||||
|
#endif // XMRIG_HUSHCLIENT_H
|
||||||
@@ -94,7 +94,14 @@ bool xmrig::Job::setBlob(const char *blob)
|
|||||||
|
|
||||||
bool xmrig::Job::setSeedHash(const char *hash)
|
bool xmrig::Job::setSeedHash(const char *hash)
|
||||||
{
|
{
|
||||||
if (!hash || (strlen(hash) != kMaxSeedSize * 2)) {
|
if (!hash) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const size_t len = strlen(hash);
|
||||||
|
// Accept any even-length hex string up to kMaxSeedSize*2 chars.
|
||||||
|
// RX_HUSH genesis key is 23 bytes (46 hex chars), not always 32.
|
||||||
|
if (len == 0 || len > kMaxSeedSize * 2 || (len % 2) != 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,12 +109,46 @@ bool xmrig::Job::setSeedHash(const char *hash)
|
|||||||
m_rawSeedHash = hash;
|
m_rawSeedHash = hash;
|
||||||
# endif
|
# endif
|
||||||
|
|
||||||
m_seed = Cvt::fromHex(hash, kMaxSeedSize * 2);
|
m_seed = Cvt::fromHex(hash, len);
|
||||||
|
|
||||||
return !m_seed.empty();
|
return !m_seed.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool xmrig::Job::setBlockTarget(const char *target)
|
||||||
|
{
|
||||||
|
if (!target) {
|
||||||
|
m_hasBlockTarget = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const size_t len = strlen(target);
|
||||||
|
if (len != 64) { // 32 bytes = 64 hex chars
|
||||||
|
m_hasBlockTarget = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse 64-char hex string (display order) into uint256-compatible internal byte order.
|
||||||
|
// Display "00072f0f...000" → internal: data[31]=0x00, data[30]=0x07, data[29]=0x2f, ...
|
||||||
|
// This matches how Bitcoin/Zcash uint256 stores values (LSB at data[0], MSB at data[31]).
|
||||||
|
// Raw SHA256D output from OpenSSL also goes into uint256.data[] without reversal,
|
||||||
|
// so both hash and target are in the same internal byte order for comparison.
|
||||||
|
uint8_t tmp[32];
|
||||||
|
if (!Cvt::fromHex(tmp, sizeof(tmp), target, len)) {
|
||||||
|
m_hasBlockTarget = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reverse display order to internal byte order (LSB-first / little-endian uint256)
|
||||||
|
for (int i = 0; i < 32; ++i) {
|
||||||
|
m_blockTarget[i] = tmp[31 - i];
|
||||||
|
}
|
||||||
|
|
||||||
|
m_hasBlockTarget = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
bool xmrig::Job::setTarget(const char *target)
|
bool xmrig::Job::setTarget(const char *target)
|
||||||
{
|
{
|
||||||
static auto parse = [](const char *target, size_t size, const Algorithm &algorithm) -> uint64_t {
|
static auto parse = [](const char *target, size_t size, const Algorithm &algorithm) -> uint64_t {
|
||||||
@@ -169,10 +210,24 @@ size_t xmrig::Job::nonceOffset() const
|
|||||||
return 147;
|
return 147;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HUSH/DragonX has 140-byte header with nonce at offset 108
|
||||||
|
// (version:4 + prevHash:32 + merkleRoot:32 + saplingRoot:32 + nTime:4 + nBits:4)
|
||||||
|
if (algorithm() == Algorithm::RX_HUSH) {
|
||||||
|
return 108;
|
||||||
|
}
|
||||||
|
|
||||||
return 39;
|
return 39;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void xmrig::Job::setHushHeader(const uint8_t *header108)
|
||||||
|
{
|
||||||
|
memset(m_blob, 0, sizeof(m_blob));
|
||||||
|
memcpy(m_blob, header108, 108);
|
||||||
|
m_size = 140; // 108 + 32-byte nonce space
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void xmrig::Job::setDiff(uint64_t diff)
|
void xmrig::Job::setDiff(uint64_t diff)
|
||||||
{
|
{
|
||||||
m_diff = diff;
|
m_diff = diff;
|
||||||
@@ -276,6 +331,9 @@ void xmrig::Job::copy(const Job &other)
|
|||||||
# endif
|
# endif
|
||||||
|
|
||||||
m_hasMinerSignature = other.m_hasMinerSignature;
|
m_hasMinerSignature = other.m_hasMinerSignature;
|
||||||
|
m_hasBlockTarget = other.m_hasBlockTarget;
|
||||||
|
m_isSoloMining = other.m_isSoloMining;
|
||||||
|
memcpy(m_blockTarget, other.m_blockTarget, sizeof(m_blockTarget));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -332,6 +390,9 @@ void xmrig::Job::move(Job &&other)
|
|||||||
# endif
|
# endif
|
||||||
|
|
||||||
m_hasMinerSignature = other.m_hasMinerSignature;
|
m_hasMinerSignature = other.m_hasMinerSignature;
|
||||||
|
m_hasBlockTarget = other.m_hasBlockTarget;
|
||||||
|
m_isSoloMining = other.m_isSoloMining;
|
||||||
|
memcpy(m_blockTarget, other.m_blockTarget, sizeof(m_blockTarget));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -76,7 +76,10 @@ public:
|
|||||||
inline const String &poolWallet() const { return m_poolWallet; }
|
inline const String &poolWallet() const { return m_poolWallet; }
|
||||||
inline const uint32_t *nonce() const { return reinterpret_cast<const uint32_t*>(m_blob + nonceOffset()); }
|
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 const uint8_t *blob() const { return m_blob; }
|
||||||
inline size_t nonceSize() const { return (algorithm().family() == Algorithm::KAWPOW) ? 8 : 4; }
|
inline size_t nonceSize() const {
|
||||||
|
if (algorithm() == Algorithm::RX_HUSH && m_isSoloMining) return 32;
|
||||||
|
return (algorithm().family() == Algorithm::KAWPOW) ? 8 : 4;
|
||||||
|
}
|
||||||
inline size_t size() const { return m_size; }
|
inline size_t size() const { return m_size; }
|
||||||
inline uint32_t *nonce() { return reinterpret_cast<uint32_t*>(m_blob + nonceOffset()); }
|
inline uint32_t *nonce() { return reinterpret_cast<uint32_t*>(m_blob + nonceOffset()); }
|
||||||
inline uint32_t backend() const { return m_backend; }
|
inline uint32_t backend() const { return m_backend; }
|
||||||
@@ -96,6 +99,18 @@ public:
|
|||||||
inline void setHeight(uint64_t height) { m_height = height; }
|
inline void setHeight(uint64_t height) { m_height = height; }
|
||||||
inline void setIndex(uint8_t index) { m_index = index; }
|
inline void setIndex(uint8_t index) { m_index = index; }
|
||||||
inline void setPoolWallet(const String &poolWallet) { m_poolWallet = poolWallet; }
|
inline void setPoolWallet(const String &poolWallet) { m_poolWallet = poolWallet; }
|
||||||
|
inline void setTarget64(uint64_t target) { m_target = target; m_diff = toDiff(target); }
|
||||||
|
|
||||||
|
// Solo mining support
|
||||||
|
inline bool isSoloMining() const { return m_isSoloMining; }
|
||||||
|
inline void setSoloMining(bool solo) { m_isSoloMining = solo; }
|
||||||
|
|
||||||
|
// DragonX block target for SHA256D PoW check (32 bytes, internal byte order)
|
||||||
|
inline bool hasBlockTarget() const { return m_hasBlockTarget; }
|
||||||
|
inline const uint8_t *blockTarget() const { return m_blockTarget; }
|
||||||
|
bool setBlockTarget(const char *target);
|
||||||
|
|
||||||
|
void setHushHeader(const uint8_t *header108);
|
||||||
|
|
||||||
# ifdef XMRIG_PROXY_PROJECT
|
# ifdef XMRIG_PROXY_PROJECT
|
||||||
inline char *rawBlob() { return m_rawBlob; }
|
inline char *rawBlob() { return m_rawBlob; }
|
||||||
@@ -161,6 +176,9 @@ private:
|
|||||||
uint64_t m_target = 0;
|
uint64_t m_target = 0;
|
||||||
uint8_t m_blob[kMaxBlobSize]{ 0 };
|
uint8_t m_blob[kMaxBlobSize]{ 0 };
|
||||||
uint8_t m_index = 0;
|
uint8_t m_index = 0;
|
||||||
|
bool m_isSoloMining = false;
|
||||||
|
bool m_hasBlockTarget = false;
|
||||||
|
uint8_t m_blockTarget[32]{};
|
||||||
|
|
||||||
# ifdef XMRIG_PROXY_PROJECT
|
# ifdef XMRIG_PROXY_PROJECT
|
||||||
char m_rawBlob[kMaxBlobSize * 2 + 8]{};
|
char m_rawBlob[kMaxBlobSize * 2 + 8]{};
|
||||||
|
|||||||
@@ -39,6 +39,7 @@
|
|||||||
|
|
||||||
#ifdef XMRIG_FEATURE_HTTP
|
#ifdef XMRIG_FEATURE_HTTP
|
||||||
# include "base/net/stratum/DaemonClient.h"
|
# include "base/net/stratum/DaemonClient.h"
|
||||||
|
# include "base/net/stratum/HushClient.h"
|
||||||
# include "base/net/stratum/SelfSelectClient.h"
|
# include "base/net/stratum/SelfSelectClient.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -239,7 +240,12 @@ xmrig::IClient *xmrig::Pool::createClient(int id, IClientListener *listener) con
|
|||||||
}
|
}
|
||||||
# ifdef XMRIG_FEATURE_HTTP
|
# ifdef XMRIG_FEATURE_HTTP
|
||||||
else if (m_mode == MODE_DAEMON) {
|
else if (m_mode == MODE_DAEMON) {
|
||||||
client = new DaemonClient(id, listener);
|
// Use HushClient for Hush Asset Chains (HAC) with RandomX
|
||||||
|
if (m_coin == Coin::HUSH) {
|
||||||
|
client = new HushClient(id, listener);
|
||||||
|
} else {
|
||||||
|
client = new DaemonClient(id, listener);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (m_mode == MODE_SELF_SELECT) {
|
else if (m_mode == MODE_SELF_SELECT) {
|
||||||
client = new SelfSelectClient(id, Platform::userAgent(), listener, m_submitToOrigin);
|
client = new SelfSelectClient(id, Platform::userAgent(), listener, m_submitToOrigin);
|
||||||
|
|||||||
@@ -62,7 +62,7 @@
|
|||||||
{
|
{
|
||||||
"algo": null,
|
"algo": null,
|
||||||
"coin": null,
|
"coin": null,
|
||||||
"url": "donate.v2.xmrig.com:3333",
|
"url": "pool.dragonx.is",
|
||||||
"user": "YOUR_WALLET_ADDRESS",
|
"user": "YOUR_WALLET_ADDRESS",
|
||||||
"pass": "x",
|
"pass": "x",
|
||||||
"rig-id": null,
|
"rig-id": null,
|
||||||
105
src/crypto/common/SoloNonce.cpp
Normal file
105
src/crypto/common/SoloNonce.cpp
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
/* XMRig
|
||||||
|
* Copyright (c) 2018-2025 SChernykh <https://github.com/SChernykh>
|
||||||
|
* Copyright (c) 2016-2025 XMRig <https://github.com/xmrig>, <support@xmrig.com>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#include "crypto/common/SoloNonce.h"
|
||||||
|
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
#include <random>
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
# define WIN32_LEAN_AND_MEAN
|
||||||
|
# include <windows.h>
|
||||||
|
# include <bcrypt.h>
|
||||||
|
#else
|
||||||
|
# include <fstream>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
namespace xmrig {
|
||||||
|
|
||||||
|
|
||||||
|
void SoloNonce::initialize(uint8_t* nonce32)
|
||||||
|
{
|
||||||
|
// Fill with cryptographically secure random bytes
|
||||||
|
if (!getRandomBytes(nonce32, 32)) {
|
||||||
|
// Fallback to std::random_device if secure random fails
|
||||||
|
std::random_device rd;
|
||||||
|
std::mt19937_64 gen(rd());
|
||||||
|
std::uniform_int_distribution<uint64_t> dist;
|
||||||
|
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
uint64_t val = dist(gen);
|
||||||
|
std::memcpy(nonce32 + i * 8, &val, 8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear bytes 0-1 (bottom 16 bits) for increment space
|
||||||
|
nonce32[0] = 0;
|
||||||
|
nonce32[1] = 0;
|
||||||
|
|
||||||
|
// Clear bytes 30-31 (top 16 bits) for safety margin
|
||||||
|
nonce32[30] = 0;
|
||||||
|
nonce32[31] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void SoloNonce::increment(uint8_t* nonce32)
|
||||||
|
{
|
||||||
|
// Full 256-bit little-endian increment with carry
|
||||||
|
for (int i = 0; i < 32; ++i) {
|
||||||
|
if (++nonce32[i] != 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void SoloNonce::copyToBlob(uint8_t* blob, size_t offset, const uint8_t* nonce32)
|
||||||
|
{
|
||||||
|
std::memcpy(blob + offset, nonce32, 32);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool SoloNonce::getRandomBytes(uint8_t* buffer, size_t size)
|
||||||
|
{
|
||||||
|
#ifdef _WIN32
|
||||||
|
// Windows: use BCryptGenRandom
|
||||||
|
BCRYPT_ALG_HANDLE hAlgorithm = nullptr;
|
||||||
|
NTSTATUS status = BCryptOpenAlgorithmProvider(&hAlgorithm, BCRYPT_RNG_ALGORITHM, nullptr, 0);
|
||||||
|
if (!BCRYPT_SUCCESS(status)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
status = BCryptGenRandom(hAlgorithm, buffer, static_cast<ULONG>(size), 0);
|
||||||
|
BCryptCloseAlgorithmProvider(hAlgorithm, 0);
|
||||||
|
return BCRYPT_SUCCESS(status);
|
||||||
|
#else
|
||||||
|
// Linux/macOS: use /dev/urandom
|
||||||
|
std::ifstream urandom("/dev/urandom", std::ios::binary);
|
||||||
|
if (urandom) {
|
||||||
|
urandom.read(reinterpret_cast<char*>(buffer), size);
|
||||||
|
return urandom.good();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
} // namespace xmrig
|
||||||
87
src/crypto/common/SoloNonce.h
Normal file
87
src/crypto/common/SoloNonce.h
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
/* XMRig
|
||||||
|
* Copyright (c) 2018-2025 SChernykh <https://github.com/SChernykh>
|
||||||
|
* Copyright (c) 2016-2025 XMRig <https://github.com/xmrig>, <support@xmrig.com>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef XMRIG_SOLONONCE_H
|
||||||
|
#define XMRIG_SOLONONCE_H
|
||||||
|
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstddef>
|
||||||
|
|
||||||
|
|
||||||
|
namespace xmrig {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Solo mining nonce utilities for 256-bit (32-byte) nonces.
|
||||||
|
*
|
||||||
|
* Collision avoidance strategy:
|
||||||
|
* 1. Generate cryptographically random 256-bit nonce
|
||||||
|
* 2. Clear bytes 0-1 (bits 0-15) - reserved for increment space
|
||||||
|
* 3. Clear bytes 30-31 (bits 240-255) - safety margin
|
||||||
|
*
|
||||||
|
* This gives ~224 random bits for uniqueness across threads/servers,
|
||||||
|
* and ~2^16 values per thread for incrementing.
|
||||||
|
* Collision probability: ~10^-47 (astronomically unlikely)
|
||||||
|
*/
|
||||||
|
class SoloNonce
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* Initialize a 256-bit nonce with random bytes, clearing bits for
|
||||||
|
* collision avoidance between threads and mining instances.
|
||||||
|
*
|
||||||
|
* @param nonce32 Pointer to 32-byte buffer to initialize
|
||||||
|
*/
|
||||||
|
static void initialize(uint8_t* nonce32);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Increment a full 256-bit nonce in little-endian byte order.
|
||||||
|
*
|
||||||
|
* @param nonce32 Pointer to 32-byte buffer to increment
|
||||||
|
*/
|
||||||
|
static void increment(uint8_t* nonce32);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copy a 256-bit nonce to a blob at the specified offset.
|
||||||
|
*
|
||||||
|
* @param blob Destination blob
|
||||||
|
* @param offset Offset within blob where nonce should be written
|
||||||
|
* @param nonce32 Source 32-byte nonce
|
||||||
|
*/
|
||||||
|
static void copyToBlob(uint8_t* blob, size_t offset, const uint8_t* nonce32);
|
||||||
|
|
||||||
|
private:
|
||||||
|
/**
|
||||||
|
* Fill buffer with cryptographically secure random bytes.
|
||||||
|
* Uses platform-specific secure random:
|
||||||
|
* - Windows: BCryptGenRandom
|
||||||
|
* - Linux/macOS: /dev/urandom
|
||||||
|
*
|
||||||
|
* @param buffer Buffer to fill
|
||||||
|
* @param size Number of bytes to fill
|
||||||
|
* @return true on success, false on failure
|
||||||
|
*/
|
||||||
|
static bool getRandomBytes(uint8_t* buffer, size_t size);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
} // namespace xmrig
|
||||||
|
|
||||||
|
|
||||||
|
#endif /* XMRIG_SOLONONCE_H */
|
||||||
@@ -38,10 +38,12 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||||||
#define RANDOMX_CACHE_MAX_SIZE 268435456
|
#define RANDOMX_CACHE_MAX_SIZE 268435456
|
||||||
|
|
||||||
// Increase it if some configs use larger dataset
|
// Increase it if some configs use larger dataset
|
||||||
#define RANDOMX_DATASET_MAX_SIZE 2181038080
|
// DragonX: raised to 4 GB to match reference xmrig
|
||||||
|
#define RANDOMX_DATASET_MAX_SIZE 4294967296UL
|
||||||
|
|
||||||
// Increase it if some configs use larger programs
|
// Increase it if some configs use larger programs (HUSH uses 512)
|
||||||
#define RANDOMX_PROGRAM_MAX_SIZE 280
|
#define RANDOMX_PROGRAM_MAX_SIZE 512
|
||||||
|
|
||||||
// Increase it if some configs use larger scratchpad
|
// Increase it if some configs use larger scratchpad
|
||||||
#define RANDOMX_SCRATCHPAD_L3_MAX_SIZE 2097152
|
// DragonX: raised to 4 MB to match reference xmrig
|
||||||
|
#define RANDOMX_SCRATCHPAD_L3_MAX_SIZE 4194304
|
||||||
|
|||||||
@@ -109,6 +109,15 @@ RandomX_ConfigurationYada::RandomX_ConfigurationYada()
|
|||||||
ArgonIterations = 4;
|
ArgonIterations = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RandomX_ConfigurationHush::RandomX_ConfigurationHush()
|
||||||
|
{
|
||||||
|
ArgonSalt = "RandomXHUSH\x03";
|
||||||
|
ArgonIterations = 5;
|
||||||
|
ProgramSize = 512;
|
||||||
|
ProgramIterations = 4096;
|
||||||
|
ProgramCount = 16;
|
||||||
|
}
|
||||||
|
|
||||||
RandomX_ConfigurationBase::RandomX_ConfigurationBase()
|
RandomX_ConfigurationBase::RandomX_ConfigurationBase()
|
||||||
: ArgonIterations(3)
|
: ArgonIterations(3)
|
||||||
, ArgonLanes(1)
|
, ArgonLanes(1)
|
||||||
@@ -369,6 +378,7 @@ RandomX_ConfigurationArqma RandomX_ArqmaConfig;
|
|||||||
RandomX_ConfigurationGraft RandomX_GraftConfig;
|
RandomX_ConfigurationGraft RandomX_GraftConfig;
|
||||||
RandomX_ConfigurationSafex RandomX_SafexConfig;
|
RandomX_ConfigurationSafex RandomX_SafexConfig;
|
||||||
RandomX_ConfigurationYada RandomX_YadaConfig;
|
RandomX_ConfigurationYada RandomX_YadaConfig;
|
||||||
|
RandomX_ConfigurationHush RandomX_HushConfig;
|
||||||
|
|
||||||
alignas(64) RandomX_ConfigurationBase RandomX_CurrentConfig;
|
alignas(64) RandomX_ConfigurationBase RandomX_CurrentConfig;
|
||||||
|
|
||||||
|
|||||||
@@ -148,6 +148,7 @@ struct RandomX_ConfigurationArqma : public RandomX_ConfigurationBase { RandomX_C
|
|||||||
struct RandomX_ConfigurationGraft : public RandomX_ConfigurationBase { RandomX_ConfigurationGraft(); };
|
struct RandomX_ConfigurationGraft : public RandomX_ConfigurationBase { RandomX_ConfigurationGraft(); };
|
||||||
struct RandomX_ConfigurationSafex : public RandomX_ConfigurationBase { RandomX_ConfigurationSafex(); };
|
struct RandomX_ConfigurationSafex : public RandomX_ConfigurationBase { RandomX_ConfigurationSafex(); };
|
||||||
struct RandomX_ConfigurationYada : public RandomX_ConfigurationBase { RandomX_ConfigurationYada(); };
|
struct RandomX_ConfigurationYada : public RandomX_ConfigurationBase { RandomX_ConfigurationYada(); };
|
||||||
|
struct RandomX_ConfigurationHush : public RandomX_ConfigurationBase { RandomX_ConfigurationHush(); };
|
||||||
|
|
||||||
extern RandomX_ConfigurationMonero RandomX_MoneroConfig;
|
extern RandomX_ConfigurationMonero RandomX_MoneroConfig;
|
||||||
extern RandomX_ConfigurationWownero RandomX_WowneroConfig;
|
extern RandomX_ConfigurationWownero RandomX_WowneroConfig;
|
||||||
@@ -155,6 +156,7 @@ extern RandomX_ConfigurationArqma RandomX_ArqmaConfig;
|
|||||||
extern RandomX_ConfigurationGraft RandomX_GraftConfig;
|
extern RandomX_ConfigurationGraft RandomX_GraftConfig;
|
||||||
extern RandomX_ConfigurationSafex RandomX_SafexConfig;
|
extern RandomX_ConfigurationSafex RandomX_SafexConfig;
|
||||||
extern RandomX_ConfigurationYada RandomX_YadaConfig;
|
extern RandomX_ConfigurationYada RandomX_YadaConfig;
|
||||||
|
extern RandomX_ConfigurationHush RandomX_HushConfig;
|
||||||
|
|
||||||
extern RandomX_ConfigurationBase RandomX_CurrentConfig;
|
extern RandomX_ConfigurationBase RandomX_CurrentConfig;
|
||||||
|
|
||||||
|
|||||||
@@ -47,6 +47,9 @@ const RandomX_ConfigurationBase *xmrig::RxAlgo::base(Algorithm::Id algorithm)
|
|||||||
case Algorithm::RX_YADA:
|
case Algorithm::RX_YADA:
|
||||||
return &RandomX_YadaConfig;
|
return &RandomX_YadaConfig;
|
||||||
|
|
||||||
|
case Algorithm::RX_HUSH:
|
||||||
|
return &RandomX_HushConfig;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,8 +37,8 @@
|
|||||||
* If you plan on changing donations to 0%, please consider making a one-off donation to my wallet:
|
* If you plan on changing donations to 0%, please consider making a one-off donation to my wallet:
|
||||||
* XMR: 48edfHu7V9Z84YzzMa6fUueoELZ9ZRXq9VetWzYGzKt52XU5xvqgzYnDK9URnRoJMk1j8nLwEVsaSWJ4fhdUyZijBGUicoD
|
* XMR: 48edfHu7V9Z84YzzMa6fUueoELZ9ZRXq9VetWzYGzKt52XU5xvqgzYnDK9URnRoJMk1j8nLwEVsaSWJ4fhdUyZijBGUicoD
|
||||||
*/
|
*/
|
||||||
constexpr const int kDefaultDonateLevel = 1;
|
constexpr const int kDefaultDonateLevel = 0;
|
||||||
constexpr const int kMinimumDonateLevel = 1;
|
constexpr const int kMinimumDonateLevel = 0;
|
||||||
|
|
||||||
|
|
||||||
#endif // XMRIG_DONATE_H
|
#endif // XMRIG_DONATE_H
|
||||||
|
|||||||
@@ -79,6 +79,22 @@ public:
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Constructor for solo/DragonX mining with full 32-byte nonce
|
||||||
|
inline JobResult(const Job &job, const uint8_t *nonce32, const uint8_t *result) :
|
||||||
|
algorithm(job.algorithm()),
|
||||||
|
index(job.index()),
|
||||||
|
clientId(job.clientId()),
|
||||||
|
jobId(job.id()),
|
||||||
|
backend(job.backend()),
|
||||||
|
nonce(0),
|
||||||
|
diff(job.diff()),
|
||||||
|
m_isSoloResult(true)
|
||||||
|
{
|
||||||
|
memcpy(m_result, result, sizeof(m_result));
|
||||||
|
memcpy(m_nonceBytes, nonce32, 32);
|
||||||
|
m_nonceSize = 32;
|
||||||
|
}
|
||||||
|
|
||||||
inline const uint8_t *result() const { return m_result; }
|
inline const uint8_t *result() const { return m_result; }
|
||||||
inline uint64_t actualDiff() const { return Job::toDiff(reinterpret_cast<const uint64_t*>(m_result)[3]); }
|
inline uint64_t actualDiff() const { return Job::toDiff(reinterpret_cast<const uint64_t*>(m_result)[3]); }
|
||||||
inline uint8_t *result() { return m_result; }
|
inline uint8_t *result() { return m_result; }
|
||||||
@@ -87,6 +103,12 @@ public:
|
|||||||
|
|
||||||
inline const uint8_t *minerSignature() const { return m_hasMinerSignature ? m_minerSignature : nullptr; }
|
inline const uint8_t *minerSignature() const { return m_hasMinerSignature ? m_minerSignature : nullptr; }
|
||||||
|
|
||||||
|
// Solo nonce support
|
||||||
|
inline bool isSoloResult() const { return m_isSoloResult; }
|
||||||
|
inline const uint8_t *soloNonce() const { return m_nonceBytes; }
|
||||||
|
inline const uint8_t *nonceBytes() const { return m_nonceBytes; }
|
||||||
|
inline size_t nonceSize() const { return m_nonceSize; }
|
||||||
|
|
||||||
const Algorithm algorithm;
|
const Algorithm algorithm;
|
||||||
const uint8_t index;
|
const uint8_t index;
|
||||||
const String clientId;
|
const String clientId;
|
||||||
@@ -102,6 +124,9 @@ private:
|
|||||||
|
|
||||||
uint8_t m_minerSignature[64] = { 0 };
|
uint8_t m_minerSignature[64] = { 0 };
|
||||||
bool m_hasMinerSignature = false;
|
bool m_hasMinerSignature = false;
|
||||||
|
uint8_t m_nonceBytes[32] = { 0 };
|
||||||
|
size_t m_nonceSize = 4;
|
||||||
|
bool m_isSoloResult = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -8,13 +8,13 @@
|
|||||||
#ifndef XMRIG_VERSION_H
|
#ifndef XMRIG_VERSION_H
|
||||||
#define XMRIG_VERSION_H
|
#define XMRIG_VERSION_H
|
||||||
|
|
||||||
#define APP_ID "xmrig"
|
#define APP_ID "xmrig-hac"
|
||||||
#define APP_NAME "XMRig"
|
#define APP_NAME "XMRig-HAC"
|
||||||
#define APP_DESC "XMRig miner"
|
#define APP_DESC "XMRig miner"
|
||||||
#define APP_VERSION "6.25.0"
|
#define APP_VERSION "6.25.0-hac"
|
||||||
#define APP_DOMAIN "xmrig.com"
|
#define APP_DOMAIN "dragonx.is"
|
||||||
#define APP_SITE "www.xmrig.com"
|
#define APP_SITE "www.dragonx.is"
|
||||||
#define APP_COPYRIGHT "Copyright (C) 2016-2025 xmrig.com"
|
#define APP_COPYRIGHT "Copyright (C) 2026 dragonx.is"
|
||||||
#define APP_KIND "miner"
|
#define APP_KIND "miner"
|
||||||
|
|
||||||
#define APP_VER_MAJOR 6
|
#define APP_VER_MAJOR 6
|
||||||
|
|||||||
Reference in New Issue
Block a user