// Copyright (c) 2019-2020, The Monero Project // // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, are // permitted provided that the following conditions are met: // // 1. Redistributions of source code must retain the above copyright notice, this list of // conditions and the following disclaimer. // // 2. Redistributions in binary form must reproduce the above copyright notice, this list // of conditions and the following disclaimer in the documentation and/or other // materials provided with the distribution. // // 3. Neither the name of the copyright holder nor the names of its contributors may be // used to endorse or promote products derived from this software without specific // prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY // EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL // THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include #include #include #include #include #include #include #include #include "randomx.h" #include "c_threads.h" #include "hash-ops.h" #include "misc_log_ex.h" #define RX_LOGCAT "randomx" #if defined(_MSC_VER) #define THREADV __declspec(thread) #else #define THREADV __thread #endif typedef struct rx_state { CTHR_MUTEX_TYPE rs_mutex; char rs_hash[HASH_SIZE]; uint64_t rs_height; randomx_cache *rs_cache; } rx_state; static CTHR_MUTEX_TYPE rx_mutex = CTHR_MUTEX_INIT; 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 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) { fprintf(stderr, "%s\n", msg); #ifdef NDEBUG _exit(1); #else abort(); #endif } static inline int disabled_flags(void) { static int flags = -1; if (flags != -1) { return flags; } const char *env = getenv("MONERO_RANDOMX_UMASK"); if (!env) { flags = 0; } else { char* endptr; long int value = strtol(env, &endptr, 0); if (endptr != env && value >= 0 && value < INT_MAX) { flags = value; } else { flags = 0; } } return flags; } static inline int enabled_flags(void) { static int flags = -1; if (flags != -1) { return flags; } flags = randomx_get_flags(); return flags; } #define SEEDHASH_EPOCH_BLOCKS 2048 /* Must be same as BLOCKS_SYNCHRONIZING_MAX_COUNT in cryptonote_config.h */ #define SEEDHASH_EPOCH_LAG 64 static inline int is_power_of_2(uint64_t n) { return n && (n & (n-1)) == 0; } static int get_seedhash_epoch_lag(void) { static unsigned int lag = (unsigned int)-1; if (lag != (unsigned int)-1) return lag; const char *e = getenv("SEEDHASH_EPOCH_LAG"); if (e) { lag = atoi(e); if (lag > SEEDHASH_EPOCH_LAG || !is_power_of_2(lag)) lag = SEEDHASH_EPOCH_LAG; } else { lag = SEEDHASH_EPOCH_LAG; } return lag; } static unsigned int get_seedhash_epoch_blocks(void) { static unsigned int blocks = (unsigned int)-1; if (blocks != (unsigned int)-1) return blocks; const char *e = getenv("SEEDHASH_EPOCH_BLOCKS"); if (e) { blocks = atoi(e); if (blocks < 2 || blocks > SEEDHASH_EPOCH_BLOCKS || !is_power_of_2(blocks)) blocks = SEEDHASH_EPOCH_BLOCKS; } else { blocks = SEEDHASH_EPOCH_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) { const uint64_t seedhash_epoch_lag = get_seedhash_epoch_lag(); const uint64_t seedhash_epoch_blocks = get_seedhash_epoch_blocks(); uint64_t s_height = (height <= seedhash_epoch_blocks+seedhash_epoch_lag) ? 0 : (height - seedhash_epoch_lag - 1) & ~(seedhash_epoch_blocks-1); return s_height; } void rx_seedheights(const uint64_t height, uint64_t *seedheight, uint64_t *nextheight) { *seedheight = rx_seedheight(height); *nextheight = rx_seedheight(height + get_seedhash_epoch_lag()); } typedef struct seedinfo { randomx_cache *si_cache; unsigned long si_start; unsigned long si_count; } seedinfo; static CTHR_THREAD_RTYPE rx_seedthread(void *arg) { seedinfo *si = arg; randomx_init_dataset(rx_dataset, si->si_cache, si->si_start, si->si_count); CTHR_THREAD_RETURN; } static void rx_initdata(randomx_cache *rs_cache, const int miners, const uint64_t seedheight) { if (miners > 1) { unsigned long delta = randomx_dataset_item_count() / miners; 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 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) { CTHR_MUTEX_LOCK(rx_dataset_mutex); if (rx_dataset != NULL) { randomx_dataset *rd = rx_dataset; rx_dataset = NULL; randomx_release_dataset(rd); } rx_dataset_nomem = 0; rx_dataset_nolp = 0; CTHR_MUTEX_UNLOCK(rx_dataset_mutex); }