Fix RandomX validation exploit with difficulty reset, improve build system
- One-time difficulty reset to minimum for 17 blocks at activation height - Separate RandomX build dirs for linux/win64 cross-compilation - Add build target tracking to auto-clean on target switch - Automated win64 release packaging to release-win64/ - Add build artifacts to .gitignore - Miner memory diagnostics and large-page dataset fallback
This commit is contained in:
63
doc/CHANGELOG-randomx-optimization.md
Normal file
63
doc/CHANGELOG-randomx-optimization.md
Normal file
@@ -0,0 +1,63 @@
|
||||
# Changelog: RandomX Mining Optimization
|
||||
|
||||
## Summary
|
||||
|
||||
Optimizes RandomX miner memory usage by replacing per-thread dataset/cache allocation with a shared `RandomXDatasetManager`. Reduces memory footprint from ~2.3GB × N threads to ~2.3GB total.
|
||||
|
||||
## Problem
|
||||
|
||||
The original RandomX miner implementation allocated a full RandomX dataset (~2GB) and cache (~256MB) for each mining thread. With multiple threads, this quickly exhausted available memory:
|
||||
|
||||
- 4 threads: ~9.2GB
|
||||
- 8 threads: ~18.4GB
|
||||
- 16 threads: ~36.8GB
|
||||
|
||||
## Solution
|
||||
|
||||
Implemented a shared `RandomXDatasetManager` that allocates a single dataset and cache, shared by all miner threads via read-only access (explicitly supported by RandomX design).
|
||||
|
||||
## Changes
|
||||
|
||||
### `src/miner.cpp`
|
||||
|
||||
- **Added `RandomXDatasetManager` class:**
|
||||
- Manages a single shared RandomX cache and dataset.
|
||||
- Thread-safe key updates using `boost::shared_mutex` (readers-writer lock).
|
||||
- `Initialize(key)`: Allocates cache and dataset, initializes with key, builds dataset using all CPU cores.
|
||||
- `UpdateKey(newKey)`: Acquires exclusive lock, reinitializes cache with new key, rebuilds dataset.
|
||||
- `GetDataset()`: Returns pointer to shared dataset for VM creation.
|
||||
- `AcquireSharedLock()` / `ReleaseSharedLock()`: Miner threads hold shared lock while hashing to prevent key updates mid-hash.
|
||||
- `Shutdown()`: Releases all RandomX resources.
|
||||
|
||||
- **Modified `GenerateBitcoins()`:**
|
||||
- Initializes `RandomXDatasetManager` with initial key before spawning miner threads.
|
||||
- Calls `Shutdown()` when mining stops.
|
||||
|
||||
- **Modified `RandomXMiner()`:**
|
||||
- Creates lightweight per-thread VM using shared dataset (`randomx_create_vm(..., manager.GetDataset())`).
|
||||
- Acquires shared lock before hashing, releases after.
|
||||
- On key rotation: releases shared lock, calls `manager.UpdateKey()`, reacquires shared lock, recreates VM.
|
||||
- Cleanup only destroys thread's VM — dataset/cache owned by manager.
|
||||
|
||||
## Memory Usage
|
||||
|
||||
| Threads | Before | After |
|
||||
|---------|--------|-------|
|
||||
| 1 | ~2.3GB | ~2.3GB |
|
||||
| 4 | ~9.2GB | ~2.3GB |
|
||||
| 8 | ~18.4GB | ~2.3GB |
|
||||
| 16 | ~36.8GB | ~2.3GB |
|
||||
|
||||
## Thread Safety
|
||||
|
||||
- RandomX dataset is read-only after initialization — safe to share across threads.
|
||||
- `boost::shared_mutex` ensures:
|
||||
- Multiple threads can hash concurrently (shared lock).
|
||||
- Key updates are exclusive (exclusive lock blocks until all shared locks release).
|
||||
- No thread hashes with stale dataset during key change.
|
||||
|
||||
## Files Modified
|
||||
|
||||
| File | Lines Added | Lines Removed |
|
||||
|------|-------------|---------------|
|
||||
| `src/miner.cpp` | ~80 | ~20 |
|
||||
95
doc/CHANGELOG-randomx-validation.md
Normal file
95
doc/CHANGELOG-randomx-validation.md
Normal file
@@ -0,0 +1,95 @@
|
||||
# Changelog: RandomX Validation Bug Fix
|
||||
|
||||
## Summary
|
||||
|
||||
Fixes a critical exploit allowing miners on RandomX-based Hush Arrakis Chains (HACs) to bypass RandomX proof-of-work validation entirely. Adds proper RandomX solution verification to the block validation pipeline, fixes the miner's RandomX input construction, and introduces per-chain activation heights.
|
||||
|
||||
## Bug Description
|
||||
|
||||
Three issues combined to allow the bypass:
|
||||
|
||||
1. `CheckEquihashSolution()` returned `true` immediately for non-Equihash chains, never verifying the RandomX solution stored in `nSolution`.
|
||||
2. `CheckProofOfWork()` checked the standard SHA-256d `GetHash()` against the difficulty target instead of a RandomX hash, allowing miners to brute-force the cheap hash.
|
||||
3. The miner passed `&randomxInput, sizeof randomxInput` (the address and size of a `CDataStream` struct) to `randomx_calculate_hash()` instead of the actual serialized data, producing non-deterministic hashes.
|
||||
|
||||
A malicious miner could put arbitrary bytes in `nSolution`, iterate nonces until the standard header hash met the target, and the block would pass all validation — completely bypassing RandomX CPU-hardness.
|
||||
|
||||
## Changes
|
||||
|
||||
### `src/primitives/block.h`
|
||||
|
||||
- Added `CRandomXInput` class (modeled after `CEquihashInput`) that serializes the block header **including `nNonce`** but **excluding `nSolution`**, providing deterministic and reproducible input for RandomX hashing.
|
||||
|
||||
### `src/hush_globals.h`
|
||||
|
||||
- Added `int32_t ASSETCHAINS_RANDOMX_VALIDATION = -1` global variable to hold the per-chain activation height (`-1` = disabled).
|
||||
|
||||
### `src/hush_defs.h`
|
||||
|
||||
- Added `extern int32_t ASSETCHAINS_RANDOMX_VALIDATION` declaration.
|
||||
|
||||
### `src/pow.h`
|
||||
|
||||
- Added declaration for `bool CheckRandomXSolution(const CBlockHeader *pblock, int32_t height)`.
|
||||
- Added declarations for `int GetRandomXInterval()` and `int GetRandomXBlockLag()` (moved from `miner.cpp`).
|
||||
|
||||
### `src/pow.cpp`
|
||||
|
||||
- Added `#include "RandomX/src/randomx.h"` and `#include <mutex>`.
|
||||
- Moved `GetRandomXInterval()` and `GetRandomXBlockLag()` definitions here from `miner.cpp` so they are accessible from both the miner and the validator.
|
||||
- Implemented `CheckRandomXSolution()`:
|
||||
- Returns `true` immediately for non-RandomX chains, pre-activation heights, and when `HUSH_LOADINGBLOCKS != 0`.
|
||||
- Validates `nSolution` is exactly `RANDOMX_HASH_SIZE` (32) bytes.
|
||||
- Derives the correct RandomX key for the given height (initial key from chain params, or block-hash key after rotation interval).
|
||||
- Serializes the block header via `CRandomXInput` into a `CDataStream`.
|
||||
- Calls `randomx_calculate_hash()` with the correct data pointer (`&ss[0]`) and size (`ss.size()`).
|
||||
- Compares the computed hash against `pblock->nSolution`.
|
||||
- Caches the RandomX VM and cache between calls (protected by mutex), re-initializing only when the key changes.
|
||||
|
||||
### `src/main.cpp`
|
||||
|
||||
- In `CheckBlockHeader()`: added `CheckRandomXSolution()` call inside the `fCheckPOW` block, after the existing `CheckEquihashSolution()` call. Invalid RandomX solutions are rejected with `DoS(100)`.
|
||||
|
||||
### `src/hush_bitcoind.h`
|
||||
|
||||
- Added `#include "pow.h"`.
|
||||
- In `hush_checkPOW()`: added `CheckRandomXSolution()` call after the existing `CheckEquihashSolution()` call.
|
||||
|
||||
### `src/miner.cpp`
|
||||
|
||||
- Changed `GetRandomXInterval()` and `GetRandomXBlockLag()` from inline definitions to forward declarations (definitions moved to `pow.cpp`).
|
||||
- Fixed RandomX input construction in `RandomXMiner()`:
|
||||
- Uses `CRandomXInput` to serialize the block header without `nSolution` (instead of serializing the entire block).
|
||||
- Passes `&randomxInput[0], randomxInput.size()` to `randomx_calculate_hash()` (instead of `&randomxInput, sizeof randomxInput`).
|
||||
- Added `SetSkipRandomXValidation(true/false)` around `TestBlockValidity()` to avoid double-validation OOM.
|
||||
- Added null-checks before `randomx_destroy_vm()` to prevent double-free crashes.
|
||||
|
||||
### `src/hush_utils.h`
|
||||
|
||||
- After `ASSETCHAINS_ALGO` and `SMART_CHAIN_SYMBOL` are set during chain initialization, sets `ASSETCHAINS_RANDOMX_VALIDATION` per chain:
|
||||
- `DRAGONX`: `1` (TBD: set to coordinated upgrade height)
|
||||
- `TUMIN`: `1` (TBD: set to coordinated upgrade height)
|
||||
- All other RandomX HACs: `1` (enforced from height 1)
|
||||
- Non-RandomX chains: remains `-1` (disabled)
|
||||
|
||||
## Activation Heights
|
||||
|
||||
| Chain | Height | Notes |
|
||||
|-------|--------|-------|
|
||||
| DRAGONX | TBD | Existing chain, needs coordinated upgrade |
|
||||
| TUMIN | TBD | Existing chain, needs coordinated upgrade |
|
||||
| All other RandomX HACs | 1 | Enforced from genesis+1 |
|
||||
|
||||
## Files Modified
|
||||
|
||||
| File | Lines Added | Lines Removed |
|
||||
|------|-------------|---------------|
|
||||
| `src/primitives/block.h` | ~20 | 0 |
|
||||
| `src/hush_globals.h` | 1 | 0 |
|
||||
| `src/hush_defs.h` | 1 | 0 |
|
||||
| `src/pow.h` | 9 | 0 |
|
||||
| `src/pow.cpp` | ~105 | 0 |
|
||||
| `src/main.cpp` | 2 | 0 |
|
||||
| `src/hush_bitcoind.h` | 6 | 0 |
|
||||
| `src/miner.cpp` | 5 | 5 |
|
||||
| `src/hush_utils.h` | 10 | 0 |
|
||||
Reference in New Issue
Block a user