diff options
| author | bitflash-maintainer <noreply@bitflash.invalid> | 2026-09-05 22:46:52 +0000 |
|---|---|---|
| committer | bitflash-maintainer <noreply@bitflash.invalid> | 2026-09-05 22:46:52 +0000 |
| commit | ceae3d076b5ad11ee84729ae43f14e9312896646 (patch) | |
| tree | 7e9132aa871fdc01cb95268d2f5ea3bb11853ddf /src/randomx_pow.cpp | |
| parent | be63e83d79e7e00e6b8ca889a70e6a19886aa1d8 (diff) | |
| parent | 66e0b9158d050c46b1ff2509faeee3f4573cac1c (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.cpp | 150 |
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; | |||
| 19 | static randomx_dataset* g_dataset = NULL; | 20 | static randomx_dataset* g_dataset = NULL; |
| 20 | static randomx_vm* g_vmVerify = NULL; | 21 | static randomx_vm* g_vmVerify = NULL; |
| 21 | static CCriticalSection g_csVerify; | 22 | static CCriticalSection g_csVerify; |
| 23 | static std::mutex g_csInit; | ||
| 22 | static bool g_fInit = false; | 24 | static bool g_fInit = false; |
| 23 | static bool g_fFast = false; | 25 | static 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. | ||
| 33 | bool fRandomXLargePages = true; // cleared by -nolargepages | ||
| 34 | static bool g_fLargeCache = false; | ||
| 35 | static bool g_fLargeDataset = false; | ||
| 36 | static 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. | ||
| 40 | static 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 | |||
| 49 | static 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. | ||
| 56 | static 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 | ||
| 26 | bool RandomXInit() | 72 | bool 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 | ||
| 120 | const 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 | ||