upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/src/randomx_pow.cpp
diff options
context:
space:
mode:
authorbitflash-maintainer <noreply@bitflash.invalid>2026-09-05 22:46:52 +0000
committerbitflash-maintainer <noreply@bitflash.invalid>2026-09-05 22:46:52 +0000
commitceae3d076b5ad11ee84729ae43f14e9312896646 (patch)
tree7e9132aa871fdc01cb95268d2f5ea3bb11853ddf /src/randomx_pow.cpp
parentbe63e83d79e7e00e6b8ca889a70e6a19886aa1d8 (diff)
parent66e0b9158d050c46b1ff2509faeee3f4573cac1c (diff)
Bring main onto the line this project actually ships
main had drifted to a history with no ancestor in common with the tree we build and deploy. It sat at be63e83 (2026-08-09) with 28 commits; the shipped line, deploy/soak-2026-08-21, had 383. The two shared nothing -- git merge-base returns no result at all -- so anyone cloning this repository landed on a default branch that was neither current nor related to the running node. The 28 commits on main were audited before this merge: every one is already present in the tree by content, including the two that are hardest to see because they were rewritten afterwards -- the payout reorg-safety work (PayoutBlockStillOnChain, since evolved to re-check on every retry rather than once at maturity) and the nostr relay certificate verification (SSL_set1_host, now with a Windows root-store path). Nothing on main is lost by this merge. Recorded as a merge rather than a force-push: main only ever moves forward, its old history stays reachable as the first parent, and this is revertible. The tree is taken wholesale from the merged branch, so main's content is now exactly what was built and tested. What this brings onto main: - compactblock: BIP152-style compact block relay ported from upstream 1.2.23, primitives and unit test only, with the RAND_bytes failure path closed and the reconstruction allocation bounded. - upstream/main 17741d9: isolated testnet parameters and genesis, optional 2 MB large pages for RandomX. Mainnet genesis verified byte-identical through upstream's BuildGenesisBlock() refactor. - deploy: every image built on bookworm so binaries run on Debian 12, and the container build now honours the RandomX and libsecp256k1 pins it had been silently ignoring since before 1.2.15. Gates at this tree: 15/15 selftests, compactblock's 17 checks, consensus constants unchanged against e4f898c apart from the pre-existing MAX_BLOCK_SIZE_GEN, mainnet genesis intact, money-log check ok.
Diffstat (limited to 'src/randomx_pow.cpp')
-rw-r--r--src/randomx_pow.cpp150
1 files changed, 133 insertions, 17 deletions
diff --git a/src/randomx_pow.cpp b/src/randomx_pow.cpp
index d617ba2..a509543 100644
--- a/src/randomx_pow.cpp
+++ b/src/randomx_pow.cpp
@@ -8,6 +8,7 @@
8#include <thread> 8#include <thread>
9#include <vector> 9#include <vector>
10#include <mutex> 10#include <mutex>
11#include <system_error>
11 12
12// Fixed key (seed) that determines the RandomX cache/dataset. Keeping it 13// Fixed key (seed) that determines the RandomX cache/dataset. Keeping it
13// constant avoids per-epoch "reseed" logic; for the CPU+RAM fairness goal 14// constant avoids per-epoch "reseed" logic; for the CPU+RAM fairness goal
@@ -19,40 +20,117 @@ static randomx_cache* g_cache = NULL;
19static randomx_dataset* g_dataset = NULL; 20static randomx_dataset* g_dataset = NULL;
20static randomx_vm* g_vmVerify = NULL; 21static randomx_vm* g_vmVerify = NULL;
21static CCriticalSection g_csVerify; 22static CCriticalSection g_csVerify;
23static std::mutex g_csInit;
22static bool g_fInit = false; 24static bool g_fInit = false;
23static bool g_fFast = false; 25static bool g_fFast = false;
24 26
27// RandomX reads a 256 MB cache, a 2 GB dataset and a 2 MB scratchpad per VM
28// at random, so most reads miss the TLB on 4 KB pages. 2 MB pages recover
29// roughly a tenth of the hash rate.
30//
31// The pool must be reserved first (vm.nr_hugepages), and RandomX returns NULL
32// instead of falling back, so each allocation below retries without the flag.
33bool fRandomXLargePages = true; // cleared by -nolargepages
34static bool g_fLargeCache = false;
35static bool g_fLargeDataset = false;
36static bool g_fLargeAny = false;
37
38// How to reserve the large-page pool, for the hint printed when a large-page
39// allocation falls back to normal pages. The mechanism is OS-specific.
40static const char* LargePagesHint()
41{
42#ifdef _WIN32
43 return "enable the \"Lock pages in memory\" privilege and run elevated";
44#else
45 return "reserve them with: sysctl vm.nr_hugepages=1280";
46#endif
47}
48
49static randomx_flags WithLargePages(randomx_flags f)
50{
51 return (randomx_flags)(f | RANDOMX_FLAG_LARGE_PAGES);
52}
53
54// Ask for a cache in large pages, then plain pages. Sets fLargeOut to say
55// which one answered.
56static randomx_cache* AllocCache(randomx_flags flags, bool& fLargeOut)
57{
58 fLargeOut = false;
59 if (fRandomXLargePages)
60 {
61 randomx_cache* c = randomx_alloc_cache(WithLargePages(flags));
62 if (c)
63 {
64 fLargeOut = true;
65 return c;
66 }
67 }
68 return randomx_alloc_cache(flags);
69}
70
25 71
26bool RandomXInit() 72bool RandomXInit()
27{ 73{
28 if (g_fInit) 74 if (g_fInit)
29 return true; 75 return true;
30 76
77 std::lock_guard<std::mutex> lock(g_csInit);
78 if (g_fInit)
79 return true;
80
31 // Detect the best flags for this CPU (JIT, hardware AES, Argon2). 81 // Detect the best flags for this CPU (JIT, hardware AES, Argon2).
32 g_flags = randomx_get_flags(); 82 g_flags = randomx_get_flags();
33 83
34 g_cache = randomx_alloc_cache(g_flags); 84 g_cache = AllocCache(g_flags, g_fLargeCache);
35 if (!g_cache) 85 if (!g_cache)
36 { 86 {
37 // Try without JIT as a fallback 87 // Try without JIT as a fallback
38 g_flags = RANDOMX_FLAG_DEFAULT; 88 g_flags = RANDOMX_FLAG_DEFAULT;
39 g_cache = randomx_alloc_cache(g_flags); 89 g_cache = AllocCache(g_flags, g_fLargeCache);
40 if (!g_cache) 90 if (!g_cache)
41 return error("RandomX: failed to allocate cache (256 MB)"); 91 return error("RandomX: failed to allocate cache (256 MB)");
42 } 92 }
43 randomx_init_cache(g_cache, RANDOMX_KEY, strlen(RANDOMX_KEY)); 93 randomx_init_cache(g_cache, RANDOMX_KEY, strlen(RANDOMX_KEY));
44 94
45 // Verification VM in light mode (cache only) 95 // Verification VM in light mode (cache only). The scratchpad is only 2 MB
46 g_vmVerify = randomx_create_vm(g_flags, g_cache, NULL); 96 // and this VM is created once, but it hashes every block the node ever
97 // validates, so it gets the same treatment.
98 bool fLargeVerify = false;
99 if (fRandomXLargePages)
100 {
101 g_vmVerify = randomx_create_vm(WithLargePages(g_flags), g_cache, NULL);
102 fLargeVerify = (g_vmVerify != NULL);
103 }
104 if (!g_vmVerify)
105 g_vmVerify = randomx_create_vm(g_flags, g_cache, NULL);
47 if (!g_vmVerify) 106 if (!g_vmVerify)
48 return error("RandomX: failed to create verification VM"); 107 return error("RandomX: failed to create verification VM");
49 108
109 g_fLargeAny = g_fLargeCache || fLargeVerify;
50 g_fInit = true; 110 g_fInit = true;
51 printf("RandomX: initialized (cache 256 MB, flags=%d)\n", (int)g_flags); 111 printf("RandomX: initialized (cache 256 MB, flags=%d, large pages: %s)\n",
112 (int)g_flags, RandomXLargePagesStatus());
113 if (fRandomXLargePages && !g_fLargeCache)
114 printf("RandomX: large pages unavailable for the cache; %s "
115 "for about 10%% more hash rate\n", LargePagesHint());
52 return true; 116 return true;
53} 117}
54 118
55 119
120const char* RandomXLargePagesStatus()
121{
122 if (!fRandomXLargePages)
123 return "off (-nolargepages)";
124 if (g_fLargeCache && g_fLargeDataset)
125 return "cache + dataset";
126 if (g_fLargeCache)
127 return "cache only";
128 if (g_fLargeDataset)
129 return "dataset only";
130 return g_fLargeAny ? "scratchpad only" : "unavailable";
131}
132
133
56// One dataset, once, no matter how many miners ask for it. 134// One dataset, once, no matter how many miners ask for it.
57// 135//
58// Every miner thread calls this at startup. The g_fFast guard below is only 136// Every miner thread calls this at startup. The g_fFast guard below is only
@@ -82,8 +160,18 @@ bool RandomXInitDataset(int nThreads)
82 if (g_fFast) 160 if (g_fFast)
83 return true; 161 return true;
84 162
85 randomx_flags fastFlags = (randomx_flags)(g_flags | RANDOMX_FLAG_FULL_MEM); 163 // Only RANDOMX_FLAG_LARGE_PAGES means anything to randomx_alloc_dataset;
86 g_dataset = randomx_alloc_dataset(fastFlags); 164 // the rest of g_flags belongs to the VMs that read it.
165 if (fRandomXLargePages)
166 {
167 g_dataset = randomx_alloc_dataset(WithLargePages(RANDOMX_FLAG_DEFAULT));
168 g_fLargeDataset = (g_dataset != NULL);
169 if (!g_dataset)
170 printf("RandomX: large pages unavailable for the 2 GB dataset; %s "
171 "for about 10%% more hash rate\n", LargePagesHint());
172 }
173 if (!g_dataset)
174 g_dataset = randomx_alloc_dataset(RANDOMX_FLAG_DEFAULT);
87 if (!g_dataset) 175 if (!g_dataset)
88 { 176 {
89 printf("RandomX: not enough memory for the 2 GB dataset, staying in light mode\n"); 177 printf("RandomX: not enough memory for the 2 GB dataset, staying in light mode\n");
@@ -96,13 +184,28 @@ bool RandomXInitDataset(int nThreads)
96 184
97 std::vector<std::thread> workers; 185 std::vector<std::thread> workers;
98 unsigned long per = total / nThreads; 186 unsigned long per = total / nThreads;
99 for (int i = 0; i < nThreads; i++) 187 try
188 {
189 for (int i = 0; i < nThreads; i++)
190 {
191 unsigned long start = i * per;
192 unsigned long count = (i == nThreads - 1) ? (total - start) : per;
193 workers.emplace_back([start, count]() {
194 randomx_init_dataset(g_dataset, g_cache, start, count);
195 });
196 }
197 }
198 catch (const std::system_error& e)
100 { 199 {
101 unsigned long start = i * per; 200 // Same refusal as the allocation failure above: light mode is slow
102 unsigned long count = (i == nThreads - 1) ? (total - start) : per; 201 // but alive, std::terminate is neither. Join the workers that did
103 workers.emplace_back([start, count]() { 202 // start before releasing the dataset out from under them.
104 randomx_init_dataset(g_dataset, g_cache, start, count); 203 for (auto& w : workers) w.join();
105 }); 204 randomx_release_dataset(g_dataset);
205 g_dataset = NULL;
206 printf("RandomX: cannot spawn dataset init threads (%s), staying in light mode\n",
207 e.what());
208 return false;
106 } 209 }
107 for (auto& w : workers) w.join(); 210 for (auto& w : workers) w.join();
108 211
@@ -137,13 +240,26 @@ void* RandomXCreateMinerVM()
137{ 240{
138 if (!g_fInit && !RandomXInit()) 241 if (!g_fInit && !RandomXInit())
139 return NULL; 242 return NULL;
243
244 randomx_flags flags = g_flags;
245 randomx_cache* cache = g_cache;
246 randomx_dataset* dataset = NULL;
140 if (g_fFast && g_dataset) 247 if (g_fFast && g_dataset)
141 { 248 {
142 randomx_flags fastFlags = (randomx_flags)(g_flags | RANDOMX_FLAG_FULL_MEM); 249 flags = (randomx_flags)(g_flags | RANDOMX_FLAG_FULL_MEM);
143 return randomx_create_vm(fastFlags, NULL, g_dataset); 250 cache = NULL;
251 dataset = g_dataset;
252 }
253
254 // Each thread gets its own 2 MB scratchpad, so the large-page pool can run
255 // dry partway through a fleet of miners. Threads that miss out still run.
256 if (fRandomXLargePages)
257 {
258 randomx_vm* vm = randomx_create_vm(WithLargePages(flags), cache, dataset);
259 if (vm)
260 return vm;
144 } 261 }
145 // Light mode: own VM sharing the cache 262 return randomx_create_vm(flags, cache, dataset);
146 return randomx_create_vm(g_flags, g_cache, NULL);
147} 263}
148 264
149 265