1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
|
// Copyright (c) 2026 Bitflash developers
// Distributed under the MIT/X11 software license.
//
// RandomX PoW implementation. See randomx_pow.h.
#include "headers.h"
#include <randomx.h>
#include <thread>
#include <vector>
#include <mutex>
// Fixed key (seed) that determines the RandomX cache/dataset. Keeping it
// constant avoids per-epoch "reseed" logic; for the CPU+RAM fairness goal
// this is sufficient (RandomX already neutralizes GPU/ASIC by design).
static const char* RANDOMX_KEY = "Bitflash/RandomX/v1/one-cpu-one-vote";
static randomx_flags g_flags = RANDOMX_FLAG_DEFAULT;
static randomx_cache* g_cache = NULL;
static randomx_dataset* g_dataset = NULL;
static randomx_vm* g_vmVerify = NULL;
static CCriticalSection g_csVerify;
static std::mutex g_csInit;
static bool g_fInit = false;
static bool g_fFast = false;
// RandomX reads a 256 MB cache, a 2 GB dataset and a 2 MB scratchpad per VM
// at random, so most reads miss the TLB on 4 KB pages. 2 MB pages recover
// roughly a tenth of the hash rate.
//
// The pool must be reserved first (vm.nr_hugepages), and RandomX returns NULL
// instead of falling back, so each allocation below retries without the flag.
bool fRandomXLargePages = true; // cleared by -nolargepages
static bool g_fLargeCache = false;
static bool g_fLargeDataset = false;
static bool g_fLargeAny = false;
// How to reserve the large-page pool, for the hint printed when a large-page
// allocation falls back to normal pages. The mechanism is OS-specific.
static const char* LargePagesHint()
{
#ifdef _WIN32
return "enable the \"Lock pages in memory\" privilege and run elevated";
#else
return "reserve them with: sysctl vm.nr_hugepages=1280";
#endif
}
static randomx_flags WithLargePages(randomx_flags f)
{
return (randomx_flags)(f | RANDOMX_FLAG_LARGE_PAGES);
}
// Ask for a cache in large pages, then plain pages. Sets fLargeOut to say
// which one answered.
static randomx_cache* AllocCache(randomx_flags flags, bool& fLargeOut)
{
fLargeOut = false;
if (fRandomXLargePages)
{
randomx_cache* c = randomx_alloc_cache(WithLargePages(flags));
if (c)
{
fLargeOut = true;
return c;
}
}
return randomx_alloc_cache(flags);
}
bool RandomXInit()
{
if (g_fInit)
return true;
std::lock_guard<std::mutex> lock(g_csInit);
if (g_fInit)
return true;
// Detect the best flags for this CPU (JIT, hardware AES, Argon2).
g_flags = randomx_get_flags();
g_cache = AllocCache(g_flags, g_fLargeCache);
if (!g_cache)
{
// Try without JIT as a fallback
g_flags = RANDOMX_FLAG_DEFAULT;
g_cache = AllocCache(g_flags, g_fLargeCache);
if (!g_cache)
return error("RandomX: failed to allocate cache (256 MB)");
}
randomx_init_cache(g_cache, RANDOMX_KEY, strlen(RANDOMX_KEY));
// Verification VM in light mode (cache only). The scratchpad is only 2 MB
// and this VM is created once, but it hashes every block the node ever
// validates, so it gets the same treatment.
bool fLargeVerify = false;
if (fRandomXLargePages)
{
g_vmVerify = randomx_create_vm(WithLargePages(g_flags), g_cache, NULL);
fLargeVerify = (g_vmVerify != NULL);
}
if (!g_vmVerify)
g_vmVerify = randomx_create_vm(g_flags, g_cache, NULL);
if (!g_vmVerify)
return error("RandomX: failed to create verification VM");
g_fLargeAny = g_fLargeCache || fLargeVerify;
g_fInit = true;
printf("RandomX: initialized (cache 256 MB, flags=%d, large pages: %s)\n",
(int)g_flags, RandomXLargePagesStatus());
if (fRandomXLargePages && !g_fLargeCache)
printf("RandomX: large pages unavailable for the cache; %s "
"for about 10%% more hash rate\n", LargePagesHint());
return true;
}
const char* RandomXLargePagesStatus()
{
if (!fRandomXLargePages)
return "off (-nolargepages)";
if (g_fLargeCache && g_fLargeDataset)
return "cache + dataset";
if (g_fLargeCache)
return "cache only";
if (g_fLargeDataset)
return "dataset only";
return g_fLargeAny ? "scratchpad only" : "unavailable";
}
// One dataset, once, no matter how many miners ask for it.
//
// Every miner thread calls this at startup. The g_fFast guard below is only
// set at the very end, after the ~2 GB dataset has been filled, which takes
// tens of seconds -- so without this lock all of them sail past the guard
// together and each allocates its own dataset. Measured in production: a
// 32-core machine logged "initializing ~2 GB dataset" 34 times and committed
// 65 GB of private pages against a startup message promising 2142 MB.
//
// The waste was the mild half. g_dataset is assigned the moment the memory is
// allocated, before it holds anything, so the last thread to allocate would
// swing the pointer out from under the threads still filling their own. A
// miner could then hash against a dataset another thread had not finished
// writing, producing proof of work that no other node can reproduce.
//
// Holding the lock across the whole initialisation is deliberate: a thread
// that arrives mid-init has nothing useful to do until the dataset exists,
// and blocking is how it waits for exactly that.
static std::mutex g_csDataset;
bool RandomXInitDataset(int nThreads)
{
if (!g_fInit && !RandomXInit())
return false;
std::lock_guard<std::mutex> lock(g_csDataset);
if (g_fFast)
return true;
// Only RANDOMX_FLAG_LARGE_PAGES means anything to randomx_alloc_dataset;
// the rest of g_flags belongs to the VMs that read it.
if (fRandomXLargePages)
{
g_dataset = randomx_alloc_dataset(WithLargePages(RANDOMX_FLAG_DEFAULT));
g_fLargeDataset = (g_dataset != NULL);
if (!g_dataset)
printf("RandomX: large pages unavailable for the 2 GB dataset; %s "
"for about 10%% more hash rate\n", LargePagesHint());
}
if (!g_dataset)
g_dataset = randomx_alloc_dataset(RANDOMX_FLAG_DEFAULT);
if (!g_dataset)
{
printf("RandomX: not enough memory for the 2 GB dataset, staying in light mode\n");
return false;
}
unsigned long total = randomx_dataset_item_count();
if (nThreads < 1) nThreads = 1;
printf("RandomX: initializing ~2 GB dataset with %d threads...\n", nThreads);
std::vector<std::thread> workers;
unsigned long per = total / nThreads;
for (int i = 0; i < nThreads; i++)
{
unsigned long start = i * per;
unsigned long count = (i == nThreads - 1) ? (total - start) : per;
workers.emplace_back([start, count]() {
randomx_init_dataset(g_dataset, g_cache, start, count);
});
}
for (auto& w : workers) w.join();
g_fFast = true;
printf("RandomX: dataset ready (fast mode active)\n");
return true;
}
bool RandomXFastReady()
{
return g_fFast;
}
uint256 RandomXPoWHash(const void* pHeader, size_t nSize)
{
uint256 result = 0;
if (!g_fInit && !RandomXInit())
return result;
CRITICAL_BLOCK(g_csVerify)
{
unsigned char hash[RANDOMX_HASH_SIZE];
randomx_calculate_hash(g_vmVerify, pHeader, nSize, hash);
memcpy(&result, hash, RANDOMX_HASH_SIZE);
}
return result;
}
void* RandomXCreateMinerVM()
{
if (!g_fInit && !RandomXInit())
return NULL;
randomx_flags flags = g_flags;
randomx_cache* cache = g_cache;
randomx_dataset* dataset = NULL;
if (g_fFast && g_dataset)
{
flags = (randomx_flags)(g_flags | RANDOMX_FLAG_FULL_MEM);
cache = NULL;
dataset = g_dataset;
}
// Each thread gets its own 2 MB scratchpad, so the large-page pool can run
// dry partway through a fleet of miners. Threads that miss out still run.
if (fRandomXLargePages)
{
randomx_vm* vm = randomx_create_vm(WithLargePages(flags), cache, dataset);
if (vm)
return vm;
}
return randomx_create_vm(flags, cache, dataset);
}
void RandomXDestroyMinerVM(void* vm)
{
if (vm)
randomx_destroy_vm((randomx_vm*)vm);
}
uint256 RandomXHashWithVM(void* vm, const void* pHeader, size_t nSize)
{
uint256 result = 0;
if (!vm)
return result;
unsigned char hash[RANDOMX_HASH_SIZE];
randomx_calculate_hash((randomx_vm*)vm, pHeader, nSize, hash);
memcpy(&result, hash, RANDOMX_HASH_SIZE);
return result;
}
|