Skip to content

Commit 51df282

Browse files
committed
cryptonote_core: support RandomX V2 and commitments in v17
See docs/BLOCK_HASHING.md This commit does required consensus changes only.
1 parent 6f284e8 commit 51df282

17 files changed

+132
-40
lines changed

docs/BLOCK_HASHING.md

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# Block hashing
2+
3+
## Background
4+
5+
In Bitcoin, block IDs and Proof-of-Work (PoW) hashes are calculated the same way, which is why on-chain block IDs always start
6+
with many 0s. However, in Monero, block IDs and PoW hashes are calcuated using different hash functions and slightly different
7+
inputs. This is because the hash functions used for PoW (CryptoNight and RandomX) are much slower to run than other
8+
cryptographic hash functions, specifically Keccak256, which Monero uses for block IDs. By contrast, Bitcoin uses SHA-256 for
9+
both block IDs and PoW. The reason that CryptoNight and RandomX were chosen for PoW in Monero is to protect against
10+
application-specific integrated circuits (ASICs) from dominanting the network hashrate like in Bitcoin. In theory, this
11+
would restore the principle of "1 CPU, 1 vote" outlined in the Satoshi whitepaper, and bring greater decentralization to the
12+
Monero exosystem. This document aims to describe exactly how block IDs and PoW hashes are calculated in the Monero reference
13+
codebase.
14+
15+
## CryptoNight epoch
16+
17+
To be reductive, the underlying design of Cryptonight is to 1) fill a 2MB buffer of memory using a memory-hard function, 2)
18+
perform over a millon random reads on that buffer to permutate some state, and then 3) do a final hash on the state using
19+
a random choice between four independent hash fuctions. There are 4 variants of CryptoNight: v0, v1, v2, and v4. This is the
20+
data flow for how block hashing in the CryptoNight epoch (Monero fork versions v1-v11) is performed:
21+
22+
![CryptoNight data flow](resources/cryptonight-data-flow.png)
23+
24+
## RandomX epoch
25+
26+
Like, CryptoNight, RandomX also uses a memory hard function to fill a large space of memory called a "cache". However, this
27+
space is 256MB, not 2MB. It can be further expanded to 2GB to trade memory usage for compute speed. Unlike CryptoNight's cache,
28+
the RandomX cache is computed only once every 2048 blocks, not once per block. Monero uses a "seed" block ID of a previous block
29+
in the chain to fill the memory space. And to make ASICs even harder, RandomX uses random instruction execution as well. This
30+
is the data flow for how block hashing in the RandomX epoch (Monero fork versions v12-present) is performed:
31+
32+
![RandomX data flow](resources/randomx-data-flow.png)
33+
34+
## RandomX, with commitments, epoch
35+
36+
Because RandomX can be slow to run, especially while building the cache (nearly 1/2 a second on my machine), it inadvertantly
37+
can introduce a Denial-of-Service (DoS) vector. For this reason, RandomX hash commitments can be added, so the PoW can be
38+
partially verified using only Blake2B, a much lighter hash function. For simplicity's sake, we can also remove the number of
39+
transactions in the block from the block hashing blob. This is the data flow for how block hashing works with these changes:
40+
41+
![RandomX commitment data flow](resources/randomx-commitment-data-flow.png)
42+
43+
The way that the hashes are calculated allows us to do partial PoW verification like so:
44+
45+
![Partial RandomX verification data flow](resources/partial-pow-verify-data-flow.png)
46+
47+
A node can be given simply the block header, block content hash, and intermediate PoW hash, and verify that some PoW was done
48+
using only `randomx_calculate_commitment()` (Blake2B undernath). Passing this partial verification requires PoW to done using
49+
Blake2B up to the relevant difficulty. While doing so is much easier than doing the full RandomX PoW, it still requires
50+
significant work. It is very important that we can calculate the block ID from the same information (or a subset thereof)
51+
that we calculate PoW from, since each block header contains the previous block ID. Binding the block headers as such makes
52+
faking intermediate PoW on chains of blocks exponentially harder the longer the chain is.
87.2 KB
Loading
56.8 KB
Loading
108 KB
Loading
102 KB
Loading

external/randomx

Submodule randomx updated 79 files

src/crypto/hash-ops.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,13 +98,16 @@ bool tree_branch_hash(const char hash[HASH_SIZE], const char (*branch)[HASH_SIZE
9898
bool is_branch_in_tree(const char hash[HASH_SIZE], const char root[HASH_SIZE], const char (*branch)[HASH_SIZE], size_t depth, uint32_t path);
9999

100100
#define RX_BLOCK_VERSION 12
101+
#define RX_VARIANT_1 1
102+
#define RX_VARIANT_2 2
101103
void rx_slow_hash_allocate_state(void);
102104
void rx_slow_hash_free_state(void);
103105
uint64_t rx_seedheight(const uint64_t height);
104106
void rx_seedheights(const uint64_t height, uint64_t *seed_height, uint64_t *next_height);
105107

106108
void rx_set_main_seedhash(const char *seedhash, size_t max_dataset_init_threads);
107-
void rx_slow_hash(const char *seedhash, const void *data, size_t length, char *result_hash);
109+
void rx_slow_hash(const char *seedhash, const int variant, const void *data, size_t length, char *result_hash);
110+
void rx_commitment(const void *data, size_t length, const void *rx_hash, char *result_commitment);
108111

109112
void rx_set_miner_thread(uint32_t value, size_t max_dataset_init_threads);
110113
uint32_t rx_get_miner_thread(void);

src/crypto/rx-slow-hash.c

Lines changed: 34 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,11 @@ static int secondary_seedhash_set = 0;
6666
#define THREADV __thread
6767
#endif
6868

69+
static THREADV int main_vm_full_variant = 0;
6970
static THREADV randomx_vm *main_vm_full = NULL;
71+
static THREADV int main_vm_light_variant = 0;
7072
static THREADV randomx_vm *main_vm_light = NULL;
73+
static THREADV int secondary_vm_light_variant = 0;
7174
static THREADV randomx_vm *secondary_vm_light = NULL;
7275

7376
static THREADV uint32_t miner_thread = 0;
@@ -86,7 +89,7 @@ static void local_abort(const char *msg)
8689
#endif
8790
}
8891

89-
static void hash2hex(const char* hash, char* hex) {
92+
static void hash2hex(const char hash[HASH_SIZE], char hex[HASH_SIZE * 2 + 1]) {
9093
const char* d = "0123456789abcdef";
9194
for (int i = 0; i < HASH_SIZE; ++i) {
9295
const uint8_t b = hash[i];
@@ -189,7 +192,7 @@ void rx_seedheights(const uint64_t height, uint64_t *seedheight, uint64_t *nexth
189192
*nextheight = rx_seedheight(height + get_seedhash_epoch_lag());
190193
}
191194

192-
static void rx_alloc_dataset(randomx_flags flags, randomx_dataset** dataset, int ignore_env)
195+
static void rx_alloc_dataset(randomx_dataset** dataset, int ignore_env)
193196
{
194197
if (*dataset) {
195198
return;
@@ -213,6 +216,8 @@ static void rx_alloc_dataset(randomx_flags flags, randomx_dataset** dataset, int
213216
return;
214217
}
215218

219+
const randomx_flags flags = enabled_flags();
220+
216221
*dataset = randomx_alloc_dataset((flags | RANDOMX_FLAG_LARGE_PAGES) & ~disabled_flags());
217222
if (!*dataset) {
218223
alloc_err_msg("Couldn't allocate RandomX dataset using large pages");
@@ -223,12 +228,14 @@ static void rx_alloc_dataset(randomx_flags flags, randomx_dataset** dataset, int
223228
}
224229
}
225230

226-
static void rx_alloc_cache(randomx_flags flags, randomx_cache** cache)
231+
static void rx_alloc_cache(randomx_cache** cache)
227232
{
228233
if (*cache) {
229234
return;
230235
}
231236

237+
const randomx_flags flags = enabled_flags();
238+
232239
*cache = randomx_alloc_cache((flags | RANDOMX_FLAG_LARGE_PAGES) & ~disabled_flags());
233240
if (!*cache) {
234241
alloc_err_msg("Couldn't allocate RandomX cache using large pages");
@@ -237,17 +244,22 @@ static void rx_alloc_cache(randomx_flags flags, randomx_cache** cache)
237244
}
238245
}
239246

240-
static void rx_init_full_vm(randomx_flags flags, randomx_vm** vm)
247+
static void rx_init_full_vm(const int variant, randomx_vm** vm, int* vm_variant)
241248
{
242-
if (*vm || !main_dataset || (disabled_flags() & RANDOMX_FLAG_FULL_MEM)) {
249+
if (!main_dataset || (disabled_flags() & RANDOMX_FLAG_FULL_MEM)) {
250+
return;
251+
}
252+
else if (*vm && variant == *vm_variant) {
243253
return;
244254
}
245255

256+
randomx_flags flags = enabled_flags();
246257
if ((flags & RANDOMX_FLAG_JIT) && !miner_thread) {
247258
flags |= RANDOMX_FLAG_SECURE;
248259
}
249260

250261
*vm = randomx_create_vm((flags | RANDOMX_FLAG_LARGE_PAGES | RANDOMX_FLAG_FULL_MEM) & ~disabled_flags(), NULL, main_dataset);
262+
*vm_variant = variant;
251263
if (!*vm) {
252264
static int shown = 0;
253265
if (!shown) {
@@ -261,20 +273,22 @@ static void rx_init_full_vm(randomx_flags flags, randomx_vm** vm)
261273
}
262274
}
263275

264-
static void rx_init_light_vm(randomx_flags flags, randomx_vm** vm, randomx_cache* cache)
276+
static void rx_init_light_vm(const int variant, randomx_vm** vm, int* vm_variant, randomx_cache* cache)
265277
{
266-
if (*vm) {
278+
if (*vm && variant == *vm_variant) {
267279
randomx_vm_set_cache(*vm, cache);
268280
return;
269281
}
270282

283+
randomx_flags flags = enabled_flags();
271284
if ((flags & RANDOMX_FLAG_JIT) && !miner_thread) {
272285
flags |= RANDOMX_FLAG_SECURE;
273286
}
274287

275288
flags &= ~RANDOMX_FLAG_FULL_MEM;
276289

277290
*vm = randomx_create_vm((flags | RANDOMX_FLAG_LARGE_PAGES) & ~disabled_flags(), cache, NULL);
291+
*vm_variant = variant;
278292
if (!*vm) {
279293
static int shown = 0;
280294
if (!shown) {
@@ -367,9 +381,8 @@ static CTHR_THREAD_RTYPE rx_set_main_seedhash_thread(void *arg) {
367381
hash2hex(main_seedhash, buf);
368382
minfo(RX_LOGCAT, "RandomX new main seed hash is %s", buf);
369383

370-
const randomx_flags flags = enabled_flags() & ~disabled_flags();
371-
rx_alloc_dataset(flags, &main_dataset, 0);
372-
rx_alloc_cache(flags, &main_cache);
384+
rx_alloc_dataset(&main_dataset, 0);
385+
rx_alloc_cache(&main_cache);
373386

374387
randomx_init_cache(main_cache, info->seedhash, HASH_SIZE);
375388
minfo(RX_LOGCAT, "RandomX main cache initialized");
@@ -405,8 +418,7 @@ void rx_set_main_seedhash(const char *seedhash, size_t max_dataset_init_threads)
405418
CTHR_THREAD_CLOSE(t);
406419
}
407420

408-
void rx_slow_hash(const char *seedhash, const void *data, size_t length, char *result_hash) {
409-
const randomx_flags flags = enabled_flags() & ~disabled_flags();
421+
void rx_slow_hash(const char *seedhash, const int variant, const void *data, size_t length, char *result_hash) {
410422
int success = 0;
411423

412424
// Fast path (seedhash == main_seedhash)
@@ -416,7 +428,7 @@ void rx_slow_hash(const char *seedhash, const void *data, size_t length, char *r
416428
if (main_dataset && CTHR_RWLOCK_TRYLOCK_READ(main_dataset_lock)) {
417429
// Double check that main_seedhash didn't change
418430
if (is_main(seedhash)) {
419-
rx_init_full_vm(flags, &main_vm_full);
431+
rx_init_full_vm(variant, &main_vm_full, &main_vm_full_variant);
420432
if (main_vm_full) {
421433
randomx_calculate_hash(main_vm_full, data, length, result_hash);
422434
success = 1;
@@ -427,7 +439,7 @@ void rx_slow_hash(const char *seedhash, const void *data, size_t length, char *r
427439
CTHR_RWLOCK_LOCK_READ(main_cache_lock);
428440
// Double check that main_seedhash didn't change
429441
if (is_main(seedhash)) {
430-
rx_init_light_vm(flags, &main_vm_light, main_cache);
442+
rx_init_light_vm(variant, &main_vm_light, &main_vm_light_variant, main_cache);
431443
randomx_calculate_hash(main_vm_light, data, length, result_hash);
432444
success = 1;
433445
}
@@ -449,7 +461,7 @@ void rx_slow_hash(const char *seedhash, const void *data, size_t length, char *r
449461
hash2hex(seedhash, buf);
450462
minfo(RX_LOGCAT, "RandomX new secondary seed hash is %s", buf);
451463

452-
rx_alloc_cache(flags, &secondary_cache);
464+
rx_alloc_cache(&secondary_cache);
453465
randomx_init_cache(secondary_cache, seedhash, HASH_SIZE);
454466
minfo(RX_LOGCAT, "RandomX secondary cache updated");
455467
memcpy(secondary_seedhash, seedhash, HASH_SIZE);
@@ -460,7 +472,7 @@ void rx_slow_hash(const char *seedhash, const void *data, size_t length, char *r
460472

461473
CTHR_RWLOCK_LOCK_READ(secondary_cache_lock);
462474
if (is_secondary(seedhash)) {
463-
rx_init_light_vm(flags, &secondary_vm_light, secondary_cache);
475+
rx_init_light_vm(variant, &secondary_vm_light, &secondary_vm_light_variant, secondary_cache);
464476
randomx_calculate_hash(secondary_vm_light, data, length, result_hash);
465477
success = 1;
466478
}
@@ -482,11 +494,15 @@ void rx_slow_hash(const char *seedhash, const void *data, size_t length, char *r
482494
memcpy(secondary_seedhash, seedhash, HASH_SIZE);
483495
secondary_seedhash_set = 1;
484496
}
485-
rx_init_light_vm(flags, &secondary_vm_light, secondary_cache);
497+
rx_init_light_vm(variant, &secondary_vm_light, &secondary_vm_light_variant, secondary_cache);
486498
randomx_calculate_hash(secondary_vm_light, data, length, result_hash);
487499
CTHR_RWLOCK_UNLOCK_WRITE(secondary_cache_lock);
488500
}
489501

502+
void rx_commitment(const void *data, size_t length, const void *rx_hash, char *result_commitment) {
503+
randomx_calculate_commitment(data, length, rx_hash, result_commitment);
504+
}
505+
490506
void rx_set_miner_thread(uint32_t value, size_t max_dataset_init_threads) {
491507
miner_thread = value;
492508

@@ -497,8 +513,7 @@ void rx_set_miner_thread(uint32_t value, size_t max_dataset_init_threads) {
497513
return;
498514
}
499515

500-
const randomx_flags flags = enabled_flags() & ~disabled_flags();
501-
rx_alloc_dataset(flags, &main_dataset, 1);
516+
rx_alloc_dataset(&main_dataset, 1);
502517
rx_init_dataset(max_dataset_init_threads);
503518

504519
CTHR_RWLOCK_UNLOCK_WRITE(main_dataset_lock);

src/cryptonote_basic/cryptonote_format_utils.cpp

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1438,7 +1438,8 @@ namespace cryptonote
14381438
blobdata blob = t_serializable_object_to_blob(static_cast<block_header>(b));
14391439
crypto::hash tree_root_hash = get_tx_tree_hash(b);
14401440
blob.append(reinterpret_cast<const char*>(&tree_root_hash), sizeof(tree_root_hash));
1441-
blob.append(tools::get_varint_data(b.tx_hashes.size()+1));
1441+
if (b.major_version < HF_VERSION_POW_COMMITMENT)
1442+
blob.append(tools::get_varint_data(b.tx_hashes.size()+1));
14421443
return blob;
14431444
}
14441445
//---------------------------------------------------------------
@@ -1604,10 +1605,16 @@ namespace cryptonote
16041605
return get_tx_tree_hash(txs_ids);
16051606
}
16061607
//---------------------------------------------------------------
1608+
int get_randomx_variant_for_hf_version(const std::uint8_t hf_version)
1609+
{
1610+
return (hf_version >= HF_VERSION_RANDOMX_V2) ? RX_VARIANT_2 : RX_VARIANT_1;
1611+
}
1612+
//---------------------------------------------------------------
16071613
crypto::hash get_block_longhash(const blobdata_ref block_hashing_blob,
16081614
const uint64_t height,
16091615
const uint8_t major_version,
1610-
const crypto::hash &seed_hash)
1616+
const crypto::hash &seed_hash,
1617+
crypto::hash &intermediate_hash_out)
16111618
{
16121619
crypto::hash res;
16131620

@@ -1618,7 +1625,12 @@ namespace cryptonote
16181625
}
16191626
else if (major_version >= RX_BLOCK_VERSION) // RandomX
16201627
{
1621-
crypto::rx_slow_hash(seed_hash.data, block_hashing_blob.data(), block_hashing_blob.size(), res.data);
1628+
const int rx_variant = get_randomx_variant_for_hf_version(major_version);
1629+
crypto::rx_slow_hash(seed_hash.data, rx_variant,
1630+
block_hashing_blob.data(), block_hashing_blob.size(), res.data);
1631+
intermediate_hash_out = res;
1632+
if (major_version >= HF_VERSION_POW_COMMITMENT)
1633+
crypto::rx_commitment(block_hashing_blob.data(), block_hashing_blob.size(), res.data, res.data);
16221634
}
16231635
else // CryptoNight
16241636
{

src/cryptonote_basic/cryptonote_format_utils.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -261,10 +261,12 @@ namespace cryptonote
261261
void get_tx_tree_hash(const std::vector<crypto::hash>& tx_hashes, crypto::hash& h);
262262
crypto::hash get_tx_tree_hash(const std::vector<crypto::hash>& tx_hashes);
263263
crypto::hash get_tx_tree_hash(const block& b);
264+
int get_randomx_variant_for_hf_version(const std::uint8_t hf_version);
264265
crypto::hash get_block_longhash(const blobdata_ref block_hashing_blob,
265266
const uint64_t height,
266267
const uint8_t major_version,
267-
const crypto::hash &seed_hash);
268+
const crypto::hash &seed_hash,
269+
crypto::hash &intermediate_hash_out);
268270
bool is_valid_decomposed_amount(uint64_t amount);
269271
void get_hash_stats(uint64_t &tx_hashes_calculated, uint64_t &tx_hashes_cached, uint64_t &block_hashes_calculated, uint64_t & block_hashes_cached);
270272

0 commit comments

Comments
 (0)