Refactored rx-slow-hash.c

- Straight-forward call interface: `void rx_slow_hash(const char *seedhash, const void *data, size_t length, char *result_hash)`
- Consensus chain seed hash is now updated by calling `rx_set_main_seedhash` whenever a block is added/removed or a reorg happens
- `rx_slow_hash` will compute correct hash no matter if `rx_set_main_seedhash` was called or not (the only difference is performance)
- New environment variable `MONERO_RANDOMX_FULL_MEM` to force use the full dataset for PoW verification (faster block verification)
- When dataset is used for PoW verification, dataset updates don't stall other threads (verification is done in light mode then)
- When mining is running, PoW checks now also use dataset for faster verification
This commit is contained in:
SChernykh 2022-12-10 18:30:59 +01:00
parent 9367b432f6
commit dab7d01dc0
10 changed files with 397 additions and 252 deletions

View File

@ -30,29 +30,39 @@
#pragma once #pragma once
#ifdef _WIN32 #ifdef _WIN32
#include <windows.h> #include <windows.h>
#define CTHR_MUTEX_TYPE HANDLE
#define CTHR_MUTEX_INIT NULL #define CTHR_RWLOCK_TYPE SRWLOCK
#define CTHR_MUTEX_LOCK(x) do { if (x == NULL) { \ #define CTHR_RWLOCK_INIT SRWLOCK_INIT
HANDLE p = CreateMutex(NULL, FALSE, NULL); \ #define CTHR_RWLOCK_LOCK_WRITE(x) AcquireSRWLockExclusive(&x)
if (InterlockedCompareExchangePointer((PVOID*)&x, (PVOID)p, NULL) != NULL) \ #define CTHR_RWLOCK_UNLOCK_WRITE(x) ReleaseSRWLockExclusive(&x)
CloseHandle(p); \ #define CTHR_RWLOCK_LOCK_READ(x) AcquireSRWLockShared(&x)
} WaitForSingleObject(x, INFINITE); } while(0) #define CTHR_RWLOCK_UNLOCK_READ(x) ReleaseSRWLockShared(&x)
#define CTHR_MUTEX_UNLOCK(x) ReleaseMutex(x) #define CTHR_RWLOCK_TRYLOCK_READ(x) TryAcquireSRWLockShared(&x)
#define CTHR_THREAD_TYPE HANDLE #define CTHR_THREAD_TYPE HANDLE
#define CTHR_THREAD_RTYPE void #define CTHR_THREAD_RTYPE void
#define CTHR_THREAD_RETURN return #define CTHR_THREAD_RETURN return
#define CTHR_THREAD_CREATE(thr, func, arg) thr = (HANDLE)_beginthread(func, 0, arg) #define CTHR_THREAD_CREATE(thr, func, arg) ((thr = (HANDLE)_beginthread(func, 0, arg)) != -1L)
#define CTHR_THREAD_JOIN(thr) WaitForSingleObject(thr, INFINITE) #define CTHR_THREAD_JOIN(thr) WaitForSingleObject((HANDLE)thr, INFINITE)
#else #else
#include <pthread.h> #include <pthread.h>
#define CTHR_MUTEX_TYPE pthread_mutex_t
#define CTHR_MUTEX_INIT PTHREAD_MUTEX_INITIALIZER #define CTHR_RWLOCK_TYPE pthread_rwlock_t
#define CTHR_MUTEX_LOCK(x) pthread_mutex_lock(&x) #define CTHR_RWLOCK_INIT PTHREAD_RWLOCK_INITIALIZER
#define CTHR_MUTEX_UNLOCK(x) pthread_mutex_unlock(&x) #define CTHR_RWLOCK_LOCK_WRITE(x) pthread_rwlock_wrlock(&x)
#define CTHR_RWLOCK_UNLOCK_WRITE(x) pthread_rwlock_unlock(&x)
#define CTHR_RWLOCK_LOCK_READ(x) pthread_rwlock_rdlock(&x)
#define CTHR_RWLOCK_UNLOCK_READ(x) pthread_rwlock_unlock(&x)
#define CTHR_RWLOCK_TRYLOCK_READ(x) (pthread_rwlock_tryrdlock(&x) == 0)
#define CTHR_THREAD_TYPE pthread_t #define CTHR_THREAD_TYPE pthread_t
#define CTHR_THREAD_RTYPE void * #define CTHR_THREAD_RTYPE void *
#define CTHR_THREAD_RETURN return NULL #define CTHR_THREAD_RETURN return NULL
#define CTHR_THREAD_CREATE(thr, func, arg) pthread_create(&thr, NULL, func, arg) #define CTHR_THREAD_CREATE(thr, func, arg) (pthread_create(&thr, NULL, func, arg) == 0)
#define CTHR_THREAD_JOIN(thr) pthread_join(thr, NULL) #define CTHR_THREAD_JOIN(thr) pthread_join(thr, NULL)
#endif #endif

View File

@ -97,5 +97,9 @@ void rx_slow_hash_allocate_state(void);
void rx_slow_hash_free_state(void); void rx_slow_hash_free_state(void);
uint64_t rx_seedheight(const uint64_t height); uint64_t rx_seedheight(const uint64_t height);
void rx_seedheights(const uint64_t height, uint64_t *seed_height, uint64_t *next_height); void rx_seedheights(const uint64_t height, uint64_t *seed_height, uint64_t *next_height);
void rx_slow_hash(const uint64_t mainheight, const uint64_t seedheight, const char *seedhash, const void *data, size_t length, char *hash, int miners, int is_alt);
void rx_reorg(const uint64_t split_height); void rx_set_main_seedhash(const char *seedhash, size_t max_dataset_init_threads);
void rx_slow_hash(const char *seedhash, const void *data, size_t length, char *result_hash);
void rx_set_miner_thread(uint32_t value, size_t max_dataset_init_threads);
uint32_t rx_get_miner_thread(void);

View File

@ -43,32 +43,38 @@
#define RX_LOGCAT "randomx" #define RX_LOGCAT "randomx"
static CTHR_RWLOCK_TYPE main_dataset_lock = CTHR_RWLOCK_INIT;
static CTHR_RWLOCK_TYPE main_cache_lock = CTHR_RWLOCK_INIT;
static randomx_dataset *main_dataset = NULL;
static randomx_cache *main_cache = NULL;
static char main_seedhash[HASH_SIZE];
static int main_seedhash_set = 0;
static CTHR_RWLOCK_TYPE secondary_cache_lock = CTHR_RWLOCK_INIT;
static randomx_cache *secondary_cache = NULL;
static char secondary_seedhash[HASH_SIZE];
static int secondary_seedhash_set = 0;
#if defined(_MSC_VER) #if defined(_MSC_VER)
#define THREADV __declspec(thread) #define THREADV __declspec(thread)
#else #else
#define THREADV __thread #define THREADV __thread
#endif #endif
typedef struct rx_state { static THREADV randomx_vm *main_vm_full = NULL;
CTHR_MUTEX_TYPE rs_mutex; static THREADV randomx_vm *main_vm_light = NULL;
char rs_hash[HASH_SIZE]; static THREADV randomx_vm *secondary_vm_light = NULL;
uint64_t rs_height;
randomx_cache *rs_cache;
} rx_state;
static CTHR_MUTEX_TYPE rx_mutex = CTHR_MUTEX_INIT; static THREADV uint32_t miner_thread = 0;
static CTHR_MUTEX_TYPE rx_dataset_mutex = CTHR_MUTEX_INIT;
static rx_state rx_s[2] = {{CTHR_MUTEX_INIT,{0},0,0},{CTHR_MUTEX_INIT,{0},0,0}}; static bool is_main(const char* seedhash) { return main_seedhash_set && (memcmp(seedhash, main_seedhash, HASH_SIZE) == 0); }
static bool is_secondary(const char* seedhash) { return secondary_seedhash_set && (memcmp(seedhash, secondary_seedhash, HASH_SIZE) == 0); }
static randomx_dataset *rx_dataset;
static int rx_dataset_nomem;
static int rx_dataset_nolp;
static uint64_t rx_dataset_height;
static THREADV randomx_vm *rx_vm = NULL;
static void local_abort(const char *msg) static void local_abort(const char *msg)
{ {
merror(RX_LOGCAT, "%s", msg);
fprintf(stderr, "%s\n", msg); fprintf(stderr, "%s\n", msg);
#ifdef NDEBUG #ifdef NDEBUG
_exit(1); _exit(1);
@ -77,6 +83,16 @@ static void local_abort(const char *msg)
#endif #endif
} }
static void hash2hex(const char* hash, char* hex) {
const char* d = "0123456789abcdef";
for (int i = 0; i < HASH_SIZE; ++i) {
const uint8_t b = hash[i];
hex[i * 2 + 0] = d[b >> 4];
hex[i * 2 + 1] = d[b & 15];
}
hex[HASH_SIZE * 2] = '\0';
}
static inline int disabled_flags(void) { static inline int disabled_flags(void) {
static int flags = -1; static int flags = -1;
@ -157,19 +173,6 @@ static unsigned int get_seedhash_epoch_blocks(void)
return blocks; return blocks;
} }
void rx_reorg(const uint64_t split_height) {
int i;
CTHR_MUTEX_LOCK(rx_mutex);
for (i=0; i<2; i++) {
if (split_height <= rx_s[i].rs_height) {
if (rx_s[i].rs_height == rx_dataset_height)
rx_dataset_height = 1;
rx_s[i].rs_height = 1; /* set to an invalid seed height */
}
}
CTHR_MUTEX_UNLOCK(rx_mutex);
}
uint64_t rx_seedheight(const uint64_t height) { uint64_t rx_seedheight(const uint64_t height) {
const uint64_t seedhash_epoch_lag = get_seedhash_epoch_lag(); const uint64_t seedhash_epoch_lag = get_seedhash_epoch_lag();
const uint64_t seedhash_epoch_blocks = get_seedhash_epoch_blocks(); const uint64_t seedhash_epoch_blocks = get_seedhash_epoch_blocks();
@ -183,6 +186,95 @@ void rx_seedheights(const uint64_t height, uint64_t *seedheight, uint64_t *nexth
*nextheight = rx_seedheight(height + get_seedhash_epoch_lag()); *nextheight = rx_seedheight(height + get_seedhash_epoch_lag());
} }
static void rx_alloc_dataset(randomx_flags flags, randomx_dataset** dataset, int ignore_env)
{
if (*dataset) {
return;
}
if (disabled_flags() & RANDOMX_FLAG_FULL_MEM) {
static int shown = 0;
if (!shown) {
shown = 1;
minfo(RX_LOGCAT, "RandomX dataset is disabled by MONERO_RANDOMX_UMASK environment variable.");
}
return;
}
if (!ignore_env && !getenv("MONERO_RANDOMX_FULL_MEM")) {
static int shown = 0;
if (!shown) {
shown = 1;
minfo(RX_LOGCAT, "RandomX dataset is not enabled by default. Use MONERO_RANDOMX_FULL_MEM environment variable to enable it.");
}
return;
}
*dataset = randomx_alloc_dataset((flags | RANDOMX_FLAG_LARGE_PAGES) & ~disabled_flags());
if (!*dataset) {
mwarning(RX_LOGCAT, "Couldn't allocate RandomX dataset using large pages");
*dataset = randomx_alloc_dataset(flags & ~disabled_flags());
if (!*dataset) {
merror(RX_LOGCAT, "Couldn't allocate RandomX dataset");
}
}
}
static void rx_alloc_cache(randomx_flags flags, randomx_cache** cache)
{
if (*cache) {
return;
}
*cache = randomx_alloc_cache((flags | RANDOMX_FLAG_LARGE_PAGES) & ~disabled_flags());
if (!*cache) {
mwarning(RX_LOGCAT, "Couldn't allocate RandomX cache using large pages");
*cache = randomx_alloc_cache(flags & ~disabled_flags());
if (!*cache) local_abort("Couldn't allocate RandomX cache");
}
}
static void rx_init_full_vm(randomx_flags flags, randomx_vm** vm)
{
if (*vm || !main_dataset || (disabled_flags() & RANDOMX_FLAG_FULL_MEM)) {
return;
}
if ((flags & RANDOMX_FLAG_JIT) && !miner_thread) {
flags |= RANDOMX_FLAG_SECURE;
}
*vm = randomx_create_vm((flags | RANDOMX_FLAG_LARGE_PAGES | RANDOMX_FLAG_FULL_MEM) & ~disabled_flags(), NULL, main_dataset);
if (!*vm) {
mwarning(RX_LOGCAT, "Couldn't allocate RandomX full VM using large pages");
*vm = randomx_create_vm((flags | RANDOMX_FLAG_FULL_MEM) & ~disabled_flags(), NULL, main_dataset);
if (!*vm) {
merror(RX_LOGCAT, "Couldn't allocate RandomX full VM");
}
}
}
static void rx_init_light_vm(randomx_flags flags, randomx_vm** vm, randomx_cache* cache)
{
if (*vm) {
randomx_vm_set_cache(*vm, cache);
return;
}
if ((flags & RANDOMX_FLAG_JIT) && !miner_thread) {
flags |= RANDOMX_FLAG_SECURE;
}
flags &= ~RANDOMX_FLAG_FULL_MEM;
*vm = randomx_create_vm((flags | RANDOMX_FLAG_LARGE_PAGES) & ~disabled_flags(), cache, NULL);
if (!*vm) {
mwarning(RX_LOGCAT, "Couldn't allocate RandomX light VM using large pages");
*vm = randomx_create_vm(flags & ~disabled_flags(), cache, NULL);
if (!*vm) local_abort("Couldn't allocate RandomX light VM");
}
}
typedef struct seedinfo { typedef struct seedinfo {
randomx_cache *si_cache; randomx_cache *si_cache;
unsigned long si_start; unsigned long si_start;
@ -191,187 +283,230 @@ typedef struct seedinfo {
static CTHR_THREAD_RTYPE rx_seedthread(void *arg) { static CTHR_THREAD_RTYPE rx_seedthread(void *arg) {
seedinfo *si = arg; seedinfo *si = arg;
randomx_init_dataset(rx_dataset, si->si_cache, si->si_start, si->si_count); randomx_init_dataset(main_dataset, si->si_cache, si->si_start, si->si_count);
CTHR_THREAD_RETURN; CTHR_THREAD_RETURN;
} }
static void rx_initdata(randomx_cache *rs_cache, const int miners, const uint64_t seedheight) { static void rx_init_dataset(size_t max_threads) {
if (miners > 1) { if (!main_dataset) {
unsigned long delta = randomx_dataset_item_count() / miners; return;
unsigned long start = 0;
int i;
seedinfo *si;
CTHR_THREAD_TYPE *st;
si = malloc(miners * sizeof(seedinfo));
if (si == NULL)
local_abort("Couldn't allocate RandomX mining threadinfo");
st = malloc(miners * sizeof(CTHR_THREAD_TYPE));
if (st == NULL) {
free(si);
local_abort("Couldn't allocate RandomX mining threadlist");
} }
for (i=0; i<miners-1; i++) {
si[i].si_cache = rs_cache; // leave 2 CPU cores for other tasks
const size_t num_threads = (max_threads < 4) ? 1 : (max_threads - 2);
seedinfo* si = malloc(num_threads * sizeof(seedinfo));
if (!si) local_abort("Couldn't allocate RandomX mining threadinfo");
const uint32_t delta = randomx_dataset_item_count() / num_threads;
uint32_t start = 0;
const size_t n1 = num_threads - 1;
for (size_t i = 0; i < n1; ++i) {
si[i].si_cache = main_cache;
si[i].si_start = start; si[i].si_start = start;
si[i].si_count = delta; si[i].si_count = delta;
start += delta; start += delta;
} }
si[i].si_cache = rs_cache;
si[i].si_start = start; si[n1].si_cache = main_cache;
si[i].si_count = randomx_dataset_item_count() - start; si[n1].si_start = start;
for (i=1; i<miners; i++) { si[n1].si_count = randomx_dataset_item_count() - start;
CTHR_THREAD_CREATE(st[i], rx_seedthread, &si[i]);
CTHR_THREAD_TYPE *st = malloc(num_threads * sizeof(CTHR_THREAD_TYPE));
if (!st) local_abort("Couldn't allocate RandomX mining threadlist");
CTHR_RWLOCK_LOCK_READ(main_cache_lock);
for (size_t i = 0; i < n1; ++i) {
if (!CTHR_THREAD_CREATE(st[i], rx_seedthread, &si[i])) {
local_abort("Couldn't start RandomX seed thread");
} }
randomx_init_dataset(rx_dataset, rs_cache, 0, si[0].si_count);
for (i=1; i<miners; i++) {
CTHR_THREAD_JOIN(st[i]);
} }
rx_seedthread(&si[n1]);
for (size_t i = 0; i < n1; ++i) CTHR_THREAD_JOIN(st[i]);
CTHR_RWLOCK_UNLOCK_READ(main_cache_lock);
free(st); free(st);
free(si); free(si);
minfo(RX_LOGCAT, "RandomX dataset initialized");
}
typedef struct thread_info {
char seedhash[HASH_SIZE];
size_t max_threads;
} thread_info;
static CTHR_THREAD_RTYPE rx_set_main_seedhash_thread(void *arg) {
thread_info* info = arg;
CTHR_RWLOCK_LOCK_WRITE(main_dataset_lock);
CTHR_RWLOCK_LOCK_WRITE(main_cache_lock);
// Double check that seedhash wasn't already updated
if (is_main(info->seedhash)) {
CTHR_RWLOCK_UNLOCK_WRITE(main_cache_lock);
CTHR_RWLOCK_UNLOCK_WRITE(main_dataset_lock);
free(info);
CTHR_THREAD_RETURN;
}
memcpy(main_seedhash, info->seedhash, HASH_SIZE);
main_seedhash_set = 1;
char buf[HASH_SIZE * 2 + 1];
hash2hex(main_seedhash, buf);
minfo(RX_LOGCAT, "RandomX new main seed hash is %s", buf);
const randomx_flags flags = enabled_flags() & ~disabled_flags();
rx_alloc_dataset(flags, &main_dataset, 0);
rx_alloc_cache(flags, &main_cache);
randomx_init_cache(main_cache, info->seedhash, HASH_SIZE);
minfo(RX_LOGCAT, "RandomX main cache initialized");
CTHR_RWLOCK_UNLOCK_WRITE(main_cache_lock);
// From this point, rx_slow_hash can calculate hashes in light mode, but dataset is not initialized yet
rx_init_dataset(info->max_threads);
CTHR_RWLOCK_UNLOCK_WRITE(main_dataset_lock);
free(info);
CTHR_THREAD_RETURN;
}
void rx_set_main_seedhash(const char *seedhash, size_t max_dataset_init_threads) {
// Early out if seedhash didn't change
if (is_main(seedhash)) {
return;
}
// Update main cache and dataset in the background
thread_info* info = malloc(sizeof(thread_info));
if (!info) local_abort("Couldn't allocate RandomX mining threadinfo");
memcpy(info->seedhash, seedhash, HASH_SIZE);
info->max_threads = max_dataset_init_threads;
CTHR_THREAD_TYPE t;
if (!CTHR_THREAD_CREATE(t, rx_set_main_seedhash_thread, info)) {
local_abort("Couldn't start RandomX seed thread");
}
}
void rx_slow_hash(const char *seedhash, const void *data, size_t length, char *result_hash) {
const randomx_flags flags = enabled_flags() & ~disabled_flags();
int success = 0;
// Fast path (seedhash == main_seedhash)
// Multiple threads can run in parallel in fast or light mode, 1-2 ms or 10-15 ms per hash per thread
if (is_main(seedhash)) {
// If CTHR_RWLOCK_TRYLOCK_READ fails it means dataset is being initialized now, so use the light mode
if (main_dataset && CTHR_RWLOCK_TRYLOCK_READ(main_dataset_lock)) {
// Double check that main_seedhash didn't change
if (is_main(seedhash)) {
rx_init_full_vm(flags, &main_vm_full);
if (main_vm_full) {
randomx_calculate_hash(main_vm_full, data, length, result_hash);
success = 1;
}
}
CTHR_RWLOCK_UNLOCK_READ(main_dataset_lock);
} else { } else {
randomx_init_dataset(rx_dataset, rs_cache, 0, randomx_dataset_item_count()); CTHR_RWLOCK_LOCK_READ(main_cache_lock);
// Double check that main_seedhash didn't change
if (is_main(seedhash)) {
rx_init_light_vm(flags, &main_vm_light, main_cache);
randomx_calculate_hash(main_vm_light, data, length, result_hash);
success = 1;
} }
rx_dataset_height = seedheight; CTHR_RWLOCK_UNLOCK_READ(main_cache_lock);
}
void rx_slow_hash(const uint64_t mainheight, const uint64_t seedheight, const char *seedhash, const void *data, size_t length,
char *hash, int miners, int is_alt) {
uint64_t s_height = rx_seedheight(mainheight);
int toggle = (s_height & get_seedhash_epoch_blocks()) != 0;
randomx_flags flags = enabled_flags() & ~disabled_flags();
rx_state *rx_sp;
randomx_cache *cache;
CTHR_MUTEX_LOCK(rx_mutex);
/* if alt block but with same seed as mainchain, no need for alt cache */
if (is_alt) {
if (s_height == seedheight && !memcmp(rx_s[toggle].rs_hash, seedhash, HASH_SIZE))
is_alt = 0;
} else {
/* RPC could request an earlier block on mainchain */
if (s_height > seedheight)
is_alt = 1;
/* miner can be ahead of mainchain */
else if (s_height < seedheight)
toggle ^= 1;
}
toggle ^= (is_alt != 0);
rx_sp = &rx_s[toggle];
CTHR_MUTEX_LOCK(rx_sp->rs_mutex);
CTHR_MUTEX_UNLOCK(rx_mutex);
cache = rx_sp->rs_cache;
if (cache == NULL) {
if (!(disabled_flags() & RANDOMX_FLAG_LARGE_PAGES)) {
cache = randomx_alloc_cache(flags | RANDOMX_FLAG_LARGE_PAGES);
if (cache == NULL) {
mdebug(RX_LOGCAT, "Couldn't use largePages for RandomX cache");
}
}
if (cache == NULL) {
cache = randomx_alloc_cache(flags);
if (cache == NULL)
local_abort("Couldn't allocate RandomX cache");
}
}
if (rx_sp->rs_height != seedheight || rx_sp->rs_cache == NULL || memcmp(seedhash, rx_sp->rs_hash, HASH_SIZE)) {
randomx_init_cache(cache, seedhash, HASH_SIZE);
rx_sp->rs_cache = cache;
rx_sp->rs_height = seedheight;
memcpy(rx_sp->rs_hash, seedhash, HASH_SIZE);
}
if (rx_vm == NULL) {
if ((flags & RANDOMX_FLAG_JIT) && !miners) {
flags |= RANDOMX_FLAG_SECURE & ~disabled_flags();
}
if (miners && (disabled_flags() & RANDOMX_FLAG_FULL_MEM)) {
miners = 0;
}
if (miners) {
CTHR_MUTEX_LOCK(rx_dataset_mutex);
if (!rx_dataset_nomem) {
if (rx_dataset == NULL) {
if (!(disabled_flags() & RANDOMX_FLAG_LARGE_PAGES)) {
rx_dataset = randomx_alloc_dataset(RANDOMX_FLAG_LARGE_PAGES);
if (rx_dataset == NULL) {
mdebug(RX_LOGCAT, "Couldn't use largePages for RandomX dataset");
}
}
if (rx_dataset == NULL)
rx_dataset = randomx_alloc_dataset(RANDOMX_FLAG_DEFAULT);
if (rx_dataset != NULL)
rx_initdata(rx_sp->rs_cache, miners, seedheight);
}
}
if (rx_dataset != NULL)
flags |= RANDOMX_FLAG_FULL_MEM;
else {
miners = 0;
if (!rx_dataset_nomem) {
rx_dataset_nomem = 1;
mwarning(RX_LOGCAT, "Couldn't allocate RandomX dataset for miner");
}
}
CTHR_MUTEX_UNLOCK(rx_dataset_mutex);
}
if (!(disabled_flags() & RANDOMX_FLAG_LARGE_PAGES) && !rx_dataset_nolp) {
rx_vm = randomx_create_vm(flags | RANDOMX_FLAG_LARGE_PAGES, rx_sp->rs_cache, rx_dataset);
if(rx_vm == NULL) { //large pages failed
mdebug(RX_LOGCAT, "Couldn't use largePages for RandomX VM");
rx_dataset_nolp = 1;
}
}
if (rx_vm == NULL)
rx_vm = randomx_create_vm(flags, rx_sp->rs_cache, rx_dataset);
if(rx_vm == NULL) {//fallback if everything fails
flags = RANDOMX_FLAG_DEFAULT | (miners ? RANDOMX_FLAG_FULL_MEM : 0);
rx_vm = randomx_create_vm(flags, rx_sp->rs_cache, rx_dataset);
}
if (rx_vm == NULL)
local_abort("Couldn't allocate RandomX VM");
} else if (miners) {
CTHR_MUTEX_LOCK(rx_dataset_mutex);
if (rx_dataset != NULL && rx_dataset_height != seedheight)
rx_initdata(cache, miners, seedheight);
else if (rx_dataset == NULL) {
/* this is a no-op if the cache hasn't changed */
randomx_vm_set_cache(rx_vm, rx_sp->rs_cache);
}
CTHR_MUTEX_UNLOCK(rx_dataset_mutex);
} else {
/* this is a no-op if the cache hasn't changed */
randomx_vm_set_cache(rx_vm, rx_sp->rs_cache);
}
/* mainchain users can run in parallel */
if (!is_alt)
CTHR_MUTEX_UNLOCK(rx_sp->rs_mutex);
randomx_calculate_hash(rx_vm, data, length, hash);
/* altchain slot users always get fully serialized */
if (is_alt)
CTHR_MUTEX_UNLOCK(rx_sp->rs_mutex);
}
void rx_slow_hash_allocate_state(void) {
}
void rx_slow_hash_free_state(void) {
if (rx_vm != NULL) {
randomx_destroy_vm(rx_vm);
rx_vm = NULL;
} }
} }
void rx_stop_mining(void) { if (success) {
CTHR_MUTEX_LOCK(rx_dataset_mutex); return;
if (rx_dataset != NULL) {
randomx_dataset *rd = rx_dataset;
rx_dataset = NULL;
randomx_release_dataset(rd);
} }
rx_dataset_nomem = 0;
rx_dataset_nolp = 0; char buf[HASH_SIZE * 2 + 1];
CTHR_MUTEX_UNLOCK(rx_dataset_mutex);
// Slow path (seedhash != main_seedhash, but seedhash == secondary_seedhash)
// Multiple threads can run in parallel in light mode, 10-15 ms per hash per thread
if (!secondary_cache) {
CTHR_RWLOCK_LOCK_WRITE(secondary_cache_lock);
if (!secondary_cache) {
hash2hex(seedhash, buf);
minfo(RX_LOGCAT, "RandomX new secondary seed hash is %s", buf);
rx_alloc_cache(flags, &secondary_cache);
randomx_init_cache(secondary_cache, seedhash, HASH_SIZE);
minfo(RX_LOGCAT, "RandomX secondary cache updated");
memcpy(secondary_seedhash, seedhash, HASH_SIZE);
secondary_seedhash_set = 1;
}
CTHR_RWLOCK_UNLOCK_WRITE(secondary_cache_lock);
}
CTHR_RWLOCK_LOCK_READ(secondary_cache_lock);
if (is_secondary(seedhash)) {
rx_init_light_vm(flags, &secondary_vm_light, secondary_cache);
randomx_calculate_hash(secondary_vm_light, data, length, result_hash);
success = 1;
}
CTHR_RWLOCK_UNLOCK_READ(secondary_cache_lock);
if (success) {
return;
}
// Slowest path (seedhash != main_seedhash, seedhash != secondary_seedhash)
// Only one thread runs at a time and updates secondary_seedhash if needed, up to 200-500 ms per hash
CTHR_RWLOCK_LOCK_WRITE(secondary_cache_lock);
if (!is_secondary(seedhash)) {
hash2hex(seedhash, buf);
minfo(RX_LOGCAT, "RandomX new secondary seed hash is %s", buf);
randomx_init_cache(secondary_cache, seedhash, HASH_SIZE);
minfo(RX_LOGCAT, "RandomX secondary cache updated");
memcpy(secondary_seedhash, seedhash, HASH_SIZE);
secondary_seedhash_set = 1;
}
rx_init_light_vm(flags, &secondary_vm_light, secondary_cache);
randomx_calculate_hash(secondary_vm_light, data, length, result_hash);
CTHR_RWLOCK_UNLOCK_WRITE(secondary_cache_lock);
}
void rx_set_miner_thread(uint32_t value, size_t max_dataset_init_threads) {
miner_thread = value;
// If dataset is not allocated yet, try to allocate and initialize it
CTHR_RWLOCK_LOCK_WRITE(main_dataset_lock);
if (main_dataset) {
CTHR_RWLOCK_UNLOCK_WRITE(main_dataset_lock);
return;
}
const randomx_flags flags = enabled_flags() & ~disabled_flags();
rx_alloc_dataset(flags, &main_dataset, 1);
rx_init_dataset(max_dataset_init_threads);
CTHR_RWLOCK_UNLOCK_WRITE(main_dataset_lock);
}
uint32_t rx_get_miner_thread() {
return miner_thread;
}
void rx_slow_hash_allocate_state() {}
static void rx_destroy_vm(randomx_vm** vm) {
if (*vm) {
randomx_destroy_vm(*vm);
*vm = NULL;
}
}
void rx_slow_hash_free_state() {
rx_destroy_vm(&main_vm_full);
rx_destroy_vm(&main_vm_light);
rx_destroy_vm(&secondary_vm_light);
} }

View File

@ -82,6 +82,7 @@
using namespace epee; using namespace epee;
#include "miner.h" #include "miner.h"
#include "crypto/hash.h"
extern "C" void slow_hash_allocate_state(); extern "C" void slow_hash_allocate_state();
@ -436,7 +437,6 @@ namespace cryptonote
{ {
m_stop = true; m_stop = true;
} }
extern "C" void rx_stop_mining(void);
//----------------------------------------------------------------------------------------------------- //-----------------------------------------------------------------------------------------------------
bool miner::stop() bool miner::stop()
{ {
@ -469,7 +469,6 @@ namespace cryptonote
MINFO("Mining has been stopped, " << m_threads.size() << " finished" ); MINFO("Mining has been stopped, " << m_threads.size() << " finished" );
m_threads.clear(); m_threads.clear();
m_threads_autodetect.clear(); m_threads_autodetect.clear();
rx_stop_mining();
return true; return true;
} }
//----------------------------------------------------------------------------------------------------- //-----------------------------------------------------------------------------------------------------
@ -524,6 +523,8 @@ namespace cryptonote
bool miner::worker_thread() bool miner::worker_thread()
{ {
const uint32_t th_local_index = m_thread_index++; // atomically increment, getting value before increment const uint32_t th_local_index = m_thread_index++; // atomically increment, getting value before increment
crypto::rx_set_miner_thread(th_local_index, tools::get_max_concurrency());
MLOG_SET_THREAD_NAME(std::string("[miner ") + std::to_string(th_local_index) + "]"); MLOG_SET_THREAD_NAME(std::string("[miner ") + std::to_string(th_local_index) + "]");
MGINFO("Miner thread was started ["<< th_local_index << "]"); MGINFO("Miner thread was started ["<< th_local_index << "]");
uint32_t nonce = m_starter_nonce + th_local_index; uint32_t nonce = m_starter_nonce + th_local_index;

View File

@ -456,6 +456,14 @@ bool Blockchain::init(BlockchainDB* db, const network_type nettype, bool offline
if (!update_next_cumulative_weight_limit()) if (!update_next_cumulative_weight_limit())
return false; return false;
} }
if (m_hardfork->get_current_version() >= RX_BLOCK_VERSION)
{
const crypto::hash seedhash = get_block_id_by_height(crypto::rx_seedheight(m_db->height()));
if (seedhash != crypto::null_hash)
rx_set_main_seedhash(seedhash.data, tools::get_max_concurrency());
}
return true; return true;
} }
//------------------------------------------------------------------ //------------------------------------------------------------------
@ -570,6 +578,12 @@ void Blockchain::pop_blocks(uint64_t nblocks)
if (stop_batch) if (stop_batch)
m_db->batch_stop(); m_db->batch_stop();
if (m_hardfork->get_current_version() >= RX_BLOCK_VERSION)
{
const crypto::hash seedhash = get_block_id_by_height(crypto::rx_seedheight(m_db->height()));
rx_set_main_seedhash(seedhash.data, tools::get_max_concurrency());
}
} }
//------------------------------------------------------------------ //------------------------------------------------------------------
// This function tells BlockchainDB to remove the top block from the // This function tells BlockchainDB to remove the top block from the
@ -1239,18 +1253,20 @@ bool Blockchain::switch_to_alternative_blockchain(std::list<block_extended_info>
} }
m_hardfork->reorganize_from_chain_height(split_height); m_hardfork->reorganize_from_chain_height(split_height);
get_block_longhash_reorg(split_height);
std::shared_ptr<tools::Notify> reorg_notify = m_reorg_notify; std::shared_ptr<tools::Notify> reorg_notify = m_reorg_notify;
if (reorg_notify) if (reorg_notify)
reorg_notify->notify("%s", std::to_string(split_height).c_str(), "%h", std::to_string(m_db->height()).c_str(), reorg_notify->notify("%s", std::to_string(split_height).c_str(), "%h", std::to_string(m_db->height()).c_str(),
"%n", std::to_string(m_db->height() - split_height).c_str(), "%d", std::to_string(discarded_blocks).c_str(), NULL); "%n", std::to_string(m_db->height() - split_height).c_str(), "%d", std::to_string(discarded_blocks).c_str(), NULL);
const uint64_t new_height = m_db->height();
const crypto::hash seedhash = get_block_id_by_height(crypto::rx_seedheight(new_height));
crypto::hash prev_id; crypto::hash prev_id;
if (!get_block_hash(alt_chain.back().bl, prev_id)) if (!get_block_hash(alt_chain.back().bl, prev_id))
MERROR("Failed to get block hash of an alternative chain's tip"); MERROR("Failed to get block hash of an alternative chain's tip");
else else
send_miner_notifications(prev_id, alt_chain.back().already_generated_coins); send_miner_notifications(new_height, seedhash, prev_id, alt_chain.back().already_generated_coins);
for (const auto& notifier : m_block_notifiers) for (const auto& notifier : m_block_notifiers)
{ {
@ -1262,6 +1278,9 @@ bool Blockchain::switch_to_alternative_blockchain(std::list<block_extended_info>
} }
} }
if (m_hardfork->get_current_version() >= RX_BLOCK_VERSION)
rx_set_main_seedhash(seedhash.data, tools::get_max_concurrency());
MGINFO_GREEN("REORGANIZE SUCCESS! on height: " << split_height << ", new blockchain size: " << m_db->height()); MGINFO_GREEN("REORGANIZE SUCCESS! on height: " << split_height << ", new blockchain size: " << m_db->height());
return true; return true;
} }
@ -2001,7 +2020,7 @@ bool Blockchain::handle_alternative_block(const block& b, const crypto::hash& id
{ {
seedhash = get_block_id_by_height(seedheight); seedhash = get_block_id_by_height(seedheight);
} }
get_altblock_longhash(bei.bl, proof_of_work, get_current_blockchain_height(), bei.height, seedheight, seedhash); get_altblock_longhash(bei.bl, proof_of_work, seedhash);
} else } else
{ {
get_block_longhash(this, bei.bl, proof_of_work, bei.height, 0); get_block_longhash(this, bei.bl, proof_of_work, bei.height, 0);
@ -4552,11 +4571,15 @@ leave:
} }
} }
send_miner_notifications(id, already_generated_coins); const crypto::hash seedhash = get_block_id_by_height(crypto::rx_seedheight(new_height));
send_miner_notifications(new_height, seedhash, id, already_generated_coins);
for (const auto& notifier: m_block_notifiers) for (const auto& notifier: m_block_notifiers)
notifier(new_height - 1, {std::addressof(bl), 1}); notifier(new_height - 1, {std::addressof(bl), 1});
if (m_hardfork->get_current_version() >= RX_BLOCK_VERSION)
rx_set_main_seedhash(seedhash.data, tools::get_max_concurrency());
return true; return true;
} }
//------------------------------------------------------------------ //------------------------------------------------------------------
@ -5761,24 +5784,15 @@ void Blockchain::cache_block_template(const block &b, const cryptonote::account_
m_btc_valid = true; m_btc_valid = true;
} }
void Blockchain::send_miner_notifications(const crypto::hash &prev_id, uint64_t already_generated_coins) void Blockchain::send_miner_notifications(uint64_t height, const crypto::hash &seed_hash, const crypto::hash &prev_id, uint64_t already_generated_coins)
{ {
if (m_miner_notifiers.empty()) if (m_miner_notifiers.empty())
return; return;
const uint64_t height = m_db->height();
const uint8_t major_version = m_hardfork->get_ideal_version(height); const uint8_t major_version = m_hardfork->get_ideal_version(height);
const difficulty_type diff = get_difficulty_for_next_block(); const difficulty_type diff = get_difficulty_for_next_block();
const uint64_t median_weight = m_current_block_cumul_weight_median; const uint64_t median_weight = m_current_block_cumul_weight_median;
crypto::hash seed_hash = crypto::null_hash;
if (m_hardfork->get_current_version() >= RX_BLOCK_VERSION)
{
uint64_t seed_height, next_height;
crypto::rx_seedheights(height, &seed_height, &next_height);
seed_hash = get_block_id_by_height(seed_height);
}
std::vector<tx_block_template_backlog_entry> tx_backlog; std::vector<tx_block_template_backlog_entry> tx_backlog;
m_tx_pool.get_block_template_backlog(tx_backlog); m_tx_pool.get_block_template_backlog(tx_backlog);

View File

@ -1598,9 +1598,11 @@ namespace cryptonote
/** /**
* @brief sends new block notifications to ZMQ `miner_data` subscribers * @brief sends new block notifications to ZMQ `miner_data` subscribers
* *
* @param height current blockchain height
* @param seed_hash seed hash to use for mining
* @param prev_id hash of new blockchain tip * @param prev_id hash of new blockchain tip
* @param already_generated_coins total coins mined by the network so far * @param already_generated_coins total coins mined by the network so far
*/ */
void send_miner_notifications(const crypto::hash &prev_id, uint64_t already_generated_coins); void send_miner_notifications(uint64_t height, const crypto::hash &seed_hash, const crypto::hash &prev_id, uint64_t already_generated_coins);
}; };
} // namespace cryptonote } // namespace cryptonote

View File

@ -669,10 +669,10 @@ namespace cryptonote
return true; return true;
} }
//--------------------------------------------------------------- //---------------------------------------------------------------
void get_altblock_longhash(const block& b, crypto::hash& res, const uint64_t main_height, const uint64_t height, const uint64_t seed_height, const crypto::hash& seed_hash) void get_altblock_longhash(const block& b, crypto::hash& res, const crypto::hash& seed_hash)
{ {
blobdata bd = get_block_hashing_blob(b); blobdata bd = get_block_hashing_blob(b);
rx_slow_hash(main_height, seed_height, seed_hash.data, bd.data(), bd.size(), res.data, 0, 1); rx_slow_hash(seed_hash.data, bd.data(), bd.size(), res.data);
} }
bool get_block_longhash(const Blockchain *pbc, const blobdata& bd, crypto::hash& res, const uint64_t height, const int major_version, const crypto::hash *seed_hash, const int miners) bool get_block_longhash(const Blockchain *pbc, const blobdata& bd, crypto::hash& res, const uint64_t height, const int major_version, const crypto::hash *seed_hash, const int miners)
@ -686,20 +686,16 @@ namespace cryptonote
} }
if (major_version >= RX_BLOCK_VERSION) if (major_version >= RX_BLOCK_VERSION)
{ {
uint64_t seed_height, main_height;
crypto::hash hash; crypto::hash hash;
if (pbc != NULL) if (pbc != NULL)
{ {
seed_height = rx_seedheight(height); const uint64_t seed_height = rx_seedheight(height);
hash = seed_hash ? *seed_hash : pbc->get_pending_block_id_by_height(seed_height); hash = seed_hash ? *seed_hash : pbc->get_pending_block_id_by_height(seed_height);
main_height = pbc->get_current_blockchain_height();
} else } else
{ {
memset(&hash, 0, sizeof(hash)); // only happens when generating genesis block memset(&hash, 0, sizeof(hash)); // only happens when generating genesis block
seed_height = 0;
main_height = 0;
} }
rx_slow_hash(main_height, seed_height, hash.data, bd.data(), bd.size(), res.data, seed_hash ? 0 : miners, !!seed_hash); rx_slow_hash(hash.data, bd.data(), bd.size(), res.data);
} else { } else {
const int pow_variant = major_version >= 7 ? major_version - 6 : 0; const int pow_variant = major_version >= 7 ? major_version - 6 : 0;
crypto::cn_slow_hash(bd.data(), bd.size(), res, pow_variant, height); crypto::cn_slow_hash(bd.data(), bd.size(), res, pow_variant, height);
@ -713,20 +709,10 @@ namespace cryptonote
return get_block_longhash(pbc, bd, res, height, b.major_version, seed_hash, miners); return get_block_longhash(pbc, bd, res, height, b.major_version, seed_hash, miners);
} }
bool get_block_longhash(const Blockchain *pbc, const block& b, crypto::hash& res, const uint64_t height, const int miners) crypto::hash get_block_longhash(const Blockchain *pbc, const block& b, const uint64_t height, const crypto::hash *seed_hash, const int miners)
{
return get_block_longhash(pbc, b, res, height, NULL, miners);
}
crypto::hash get_block_longhash(const Blockchain *pbc, const block& b, const uint64_t height, const int miners)
{ {
crypto::hash p = crypto::null_hash; crypto::hash p = crypto::null_hash;
get_block_longhash(pbc, b, p, height, miners); get_block_longhash(pbc, b, p, height, seed_hash, miners);
return p; return p;
} }
void get_block_longhash_reorg(const uint64_t split_height)
{
rx_reorg(split_height);
}
} }

View File

@ -144,14 +144,10 @@ namespace cryptonote
); );
class Blockchain; class Blockchain;
bool get_block_longhash(const Blockchain *pb, const blobdata& bd, crypto::hash& res, const uint64_t height, bool get_block_longhash(const Blockchain *pb, const blobdata& bd, crypto::hash& res, const uint64_t height, const int major_version, const crypto::hash *seed_hash, const int miners = 0);
const int major_version, const crypto::hash *seed_hash, const int miners); bool get_block_longhash(const Blockchain *pb, const block& b, crypto::hash& res, const uint64_t height, const crypto::hash *seed_hash = nullptr, const int miners = 0);
bool get_block_longhash(const Blockchain *pb, const block& b, crypto::hash& res, const uint64_t height, const int miners); crypto::hash get_block_longhash(const Blockchain *pb, const block& b, const uint64_t height, const crypto::hash *seed_hash = nullptr, const int miners = 0);
bool get_block_longhash(const Blockchain *pb, const block& b, crypto::hash& res, const uint64_t height, const crypto::hash *seed_hash, const int miners); void get_altblock_longhash(const block& b, crypto::hash& res, const crypto::hash& seed_hash);
void get_altblock_longhash(const block& b, crypto::hash& res, const uint64_t main_height, const uint64_t height,
const uint64_t seed_height, const crypto::hash& seed_hash);
crypto::hash get_block_longhash(const Blockchain *pb, const block& b, const uint64_t height, const int miners);
void get_block_longhash_reorg(const uint64_t split_height);
} }

View File

@ -236,10 +236,8 @@ namespace cryptonote
*(uint32_t*)(hashing_blob.data() + 39) = SWAP32LE(nonce); *(uint32_t*)(hashing_blob.data() + 39) = SWAP32LE(nonce);
if (block.major_version >= RX_BLOCK_VERSION) if (block.major_version >= RX_BLOCK_VERSION)
{ {
const uint64_t seed_height = is_current ? info.seed_height : info.previous_seed_height;
const crypto::hash &seed_hash = is_current ? info.seed_hash : info.previous_seed_hash; const crypto::hash &seed_hash = is_current ? info.seed_hash : info.previous_seed_hash;
const uint64_t height = cryptonote::get_block_height(block); crypto::rx_slow_hash(seed_hash.data, hashing_blob.data(), hashing_blob.size(), hash.data);
crypto::rx_slow_hash(height, seed_height, seed_hash.data, hashing_blob.data(), hashing_blob.size(), hash.data, 0, 0);
} }
else else
{ {

View File

@ -155,8 +155,7 @@ bool wallet2::search_for_rpc_payment(uint64_t credits_target, uint32_t n_threads
const uint8_t major_version = hashing_blob[0]; const uint8_t major_version = hashing_blob[0];
if (major_version >= RX_BLOCK_VERSION) if (major_version >= RX_BLOCK_VERSION)
{ {
const int miners = 1; crypto::rx_slow_hash(seed_hash.data, hashing_blob.data(), hashing_blob.size(), hash[i].data);
crypto::rx_slow_hash(height, seed_height, seed_hash.data, hashing_blob.data(), hashing_blob.size(), hash[i].data, miners, 0);
} }
else else
{ {