GOSTCoin CUDA miner project, compatible with most nvidia cards, containing only gostd algo
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1315 lines
33 KiB

/**
* XMR RPC 2.0 Stratum and BBR Scratchpad
* tpruvot@github - October 2016 - Under GPLv3 Licence
*/
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h> // mkdir
#include <miner.h>
#ifdef _MSC_VER
#include "mman.h" // mmap
#include <direct.h> // _mkdir
#define chdir(x) _chdir(x)
#define mkdir(x) _mkdir(x)
#define getcwd(d,sz) _getcwd(d,sz)
#define unlink(x) _unlink(x)
#define PATH_MAX MAX_PATH
#else
#include <sys/mman.h> // mmap
#endif
#ifndef PRIu64
#define PRIu64 "I64u"
#endif
#include <algos.h>
#include "xmr-rpc.h"
#include "wildkeccak.h"
double target_to_diff_rpc2(uint32_t* target)
{
// unlike other algos, xmr diff is very low
if (opt_algo == ALGO_CRYPTONIGHT && target[7]) {
// simplified to get 1.0 for 1000
return (double) (UINT32_MAX / target[7]) / 1000;
}
else if (opt_algo == ALGO_CRYPTOLIGHT && target[7]) {
return (double) (UINT32_MAX / target[7]) / 1000;
}
else if (opt_algo == ALGO_WILDKECCAK) {
return target_to_diff(target) * 1000;
}
return target_to_diff(target); // util.cpp
}
extern struct stratum_ctx stratum;
bool jobj_binary(const json_t *obj, const char *key, void *buf, size_t buflen);
pthread_mutex_t rpc2_job_lock;
pthread_mutex_t rpc2_work_lock;
pthread_mutex_t rpc2_login_lock;
//pthread_mutex_t rpc2_getscratchpad_lock;
char* opt_scratchpad_url = NULL;
uint64_t* pscratchpad_buff = NULL;
// hide addendums flood on start
static bool opt_quiet_start = true;
static const char * pscratchpad_local_cache = NULL;
static const char cachedir_suffix[] = "boolberry"; /* scratchpad cache saved as ~/.cache/boolberry/scratchpad.bin */
static char scratchpad_file[PATH_MAX];
static time_t prev_save = 0;
static struct scratchpad_hi current_scratchpad_hi;
static struct addendums_array_entry add_arr[WILD_KECCAK_ADDENDUMS_ARRAY_SIZE];
static char *rpc2_job_id = NULL;
static char *rpc2_blob = NULL;
static uint32_t rpc2_target = 0;
static size_t rpc2_bloblen = 0;
static struct work rpc2_work;
static char rpc2_id[64] = { 0 };
static uint64_t last_found_nonce = 0;
static const char* get_json_string_param(const json_t *val, const char* param_name)
{
json_t *tmp;
tmp = json_object_get(val, param_name);
if(!tmp) {
return NULL;
}
return json_string_value(tmp);
}
static size_t hex2bin_len(unsigned char *p, const char *hexstr, size_t len)
{
char hex_byte[3];
char *ep;
size_t count = 0;
hex_byte[2] = '\0';
while (*hexstr && len) {
if (!hexstr[1]) {
applog(LOG_ERR, "hex2bin str truncated");
return 0;
}
hex_byte[0] = hexstr[0];
hex_byte[1] = hexstr[1];
*p = (unsigned char) strtol(hex_byte, &ep, 16);
if (*ep) {
applog(LOG_ERR, "hex2bin failed on '%s'", hex_byte);
return 0;
}
count++;
p++;
hexstr += 2;
len--;
}
return (/*len == 0 &&*/ *hexstr == 0) ? count : 0;
}
static bool parse_height_info(const json_t *hi_section, struct scratchpad_hi* phi)
{
unsigned char prevhash[32] = { 0 };
const char* block_id;
uint64_t hi_h;
size_t len;
if(!phi || !hi_section) {
applog(LOG_ERR, "parse_height_info: wrong params");
return false;
}
json_t *height = json_object_get(hi_section, "height");
if(!height) {
applog(LOG_ERR, "JSON inval hi, no height param");
goto err_out;
}
if(!json_is_integer(height)) {
applog(LOG_ERR, "JSON inval hi: height is not integer ");
goto err_out;
}
hi_h = (uint64_t)json_integer_value(height);
if(!hi_h) {
applog(LOG_ERR, "JSON inval hi: height is 0");
goto err_out;
}
block_id = get_json_string_param(hi_section, "block_id");
if(!block_id) {
applog(LOG_ERR, "JSON inval hi: block_id not found ");
goto err_out;
}
len = hex2bin_len(prevhash, block_id, 32);
if(len != 32) {
applog(LOG_ERR, "JSON inval hi: block_id wrong len %d", len);
goto err_out;
}
phi->height = hi_h;
memcpy(phi->prevhash, prevhash, 32);
return true;
err_out:
return false;
}
static void reset_scratchpad(void)
{
current_scratchpad_hi.height = 0;
scratchpad_size = 0;
//unlink(scratchpad_file);
}
static bool patch_scratchpad_with_addendum(uint64_t global_add_startpoint, uint64_t* padd_buff, size_t count/*uint64 units*/)
{
for(size_t i = 0; i < count; i += 4) {
uint64_t global_offset = (padd_buff[i]%(global_add_startpoint/4))*4;
for(size_t j = 0; j != 4; j++)
pscratchpad_buff[global_offset + j] ^= padd_buff[i + j];
}
return true;
}
static bool apply_addendum(uint64_t* padd_buff, size_t count/*uint64 units*/)
{
if(WILD_KECCAK_SCRATCHPAD_BUFFSIZE <= (scratchpad_size + count)*8 ) {
applog(LOG_ERR, "!!!!!!! WILD_KECCAK_SCRATCHPAD_BUFFSIZE overflowed !!!!!!!! please increase this constant! ");
return false;
}
if(!patch_scratchpad_with_addendum(scratchpad_size, padd_buff, count)) {
applog(LOG_ERR, "patch_scratchpad_with_addendum is broken, resetting scratchpad");
reset_scratchpad();
return false;
}
for(int k = 0; k != count; k++)
pscratchpad_buff[scratchpad_size+k] = padd_buff[k];
scratchpad_size += count;
return true;
}
static bool pop_addendum(struct addendums_array_entry* entry)
{
if(!entry)
return false;
if(!entry->add_size || !entry->prev_hi.height) {
applog(LOG_ERR, "wrong parameters");
return false;
}
patch_scratchpad_with_addendum(scratchpad_size - entry->add_size, &pscratchpad_buff[scratchpad_size - entry->add_size], (size_t) entry->add_size);
scratchpad_size = scratchpad_size - entry->add_size;
memcpy(&current_scratchpad_hi, &entry->prev_hi, sizeof(entry->prev_hi));
memset(entry, 0, sizeof(struct addendums_array_entry));
return true;
}
// playback scratchpad addendums for whole add_arr
static bool revert_scratchpad()
{
size_t p = 0;
size_t i = 0;
size_t arr_size = ARRAY_SIZE(add_arr);
for(p=0; p != arr_size; p++) {
i = arr_size-(p+1);
if(!add_arr[i].prev_hi.height)
continue;
pop_addendum(&add_arr[i]);
}
return true;
}
static bool push_addendum_info(struct scratchpad_hi* pprev_hi, uint64_t size /* uint64 units count*/)
{
size_t i = 0;
size_t arr_size = ARRAY_SIZE(add_arr);
// Find last free entry
for(i=0; i != arr_size; i++) {
if(!add_arr[i].prev_hi.height)
break;
}
if(i >= arr_size) {
// Shift array
memmove(&add_arr[0], &add_arr[1], (arr_size-1)*sizeof(add_arr[0]));
i = arr_size - 1;
}
add_arr[i].prev_hi = *pprev_hi;
add_arr[i].add_size = size;
return true;
}
static bool addendum_decode(const json_t *addm)
{
struct scratchpad_hi hi;
unsigned char prevhash[32];
uint64_t* padd_buff;
uint64_t old_height;
json_t* hi_section = json_object_get(addm, "hi");
if (!hi_section) {
//applog(LOG_ERR, "JSON addms field not found");
//return false;
return true;
}
if(!parse_height_info(hi_section, &hi)) {
return false;
}
const char* prev_id_str = get_json_string_param(addm, "prev_id");
if(!prev_id_str) {
applog(LOG_ERR, "JSON prev_id is not a string");
return false;
}
if(!hex2bin(prevhash, prev_id_str, 32)) {
applog(LOG_ERR, "JSON prev_id is not valid hex string");
return false;
}
if(current_scratchpad_hi.height != hi.height -1)
{
if(current_scratchpad_hi.height > hi.height -1) {
//skip low scratchpad
applog(LOG_ERR, "addendum with hi.height=%lld skiped since current_scratchpad_hi.height=%lld", hi.height, current_scratchpad_hi.height);
return true;
}
//TODO: ADD SPLIT HANDLING HERE
applog(LOG_ERR, "JSON height in addendum-1 (%lld-1) missmatched with current_scratchpad_hi.height(%lld), reverting scratchpad and re-login",
hi.height, current_scratchpad_hi.height);
revert_scratchpad();
//init re-login
strcpy(rpc2_id, "");
return false;
}
if(memcmp(prevhash, current_scratchpad_hi.prevhash, 32)) {
//TODO: ADD SPLIT HANDLING HERE
applog(LOG_ERR, "JSON prev_id in addendum missmatched with current_scratchpad_hi.prevhash");
return false;
}
const char* addm_hexstr = get_json_string_param(addm, "addm");
if(!addm_hexstr) {
applog(LOG_ERR, "JSON prev_id in addendum missmatched with current_scratchpad_hi.prevhash");
return false;
}
size_t add_len = strlen(addm_hexstr);
if(add_len%64) {
applog(LOG_ERR, "JSON wrong addm hex str len");
return false;
}
padd_buff = (uint64_t*) calloc(1, add_len/2);
if (!padd_buff) {
applog(LOG_ERR, "out of memory, wanted %zu", add_len/2);
return false;
}
if(!hex2bin((unsigned char*)padd_buff, addm_hexstr, add_len/2)) {
applog(LOG_ERR, "JSON wrong addm hex str len");
goto err_out;
}
if(!apply_addendum(padd_buff, add_len/16)) {
applog(LOG_ERR, "JSON Failed to apply_addendum!");
goto err_out;
}
free(padd_buff);
push_addendum_info(&current_scratchpad_hi, add_len/16);
old_height = current_scratchpad_hi.height;
current_scratchpad_hi = hi;
if (!opt_quiet && !opt_quiet_start)
applog(LOG_BLUE, "ADDENDUM APPLIED: Block %lld", (long long) current_scratchpad_hi.height);
return true;
err_out:
free(padd_buff);
return false;
}
static bool addendums_decode(const json_t *job)
{
json_t* paddms = json_object_get(job, "addms");
if (!paddms) {
//applog(LOG_ERR, "JSON addms field not found");
//return false;
return true;
}
if(!json_is_array(paddms)) {
applog(LOG_ERR, "JSON addms field is not array");
return false;
}
size_t add_sz = json_array_size(paddms);
for (size_t i = 0; i < add_sz; i++)
{
json_t *addm = json_array_get(paddms, i);
if (!addm) {
applog(LOG_ERR, "Internal error: failed to get addm");
return false;
}
if(!addendum_decode(addm))
return false;
}
return true;
}
bool rpc2_job_decode(const json_t *job, struct work *work)
{
json_t *tmp;
size_t blobLen;
const char *job_id;
const char *hexblob;
tmp = json_object_get(job, "job_id");
if (!tmp) {
applog(LOG_ERR, "JSON inval job id");
goto err_out;
}
if(opt_algo == ALGO_WILDKECCAK && !addendums_decode(job)) {
applog(LOG_ERR, "JSON failed to process addendums");
goto err_out;
}
// now allow ADDENDUM notices (after the init)
opt_quiet_start = false;
job_id = json_string_value(tmp);
tmp = json_object_get(job, "blob");
if (!tmp) {
applog(LOG_ERR, "JSON inval blob");
goto err_out;
}
hexblob = json_string_value(tmp);
blobLen = strlen(hexblob);
if (blobLen % 2 != 0 || ((blobLen / 2) < 40 && blobLen != 0) || (blobLen / 2) > 128)
{
applog(LOG_ERR, "JSON invalid blob length");
goto err_out;
}
if (blobLen != 0)
{
pthread_mutex_lock(&rpc2_job_lock);
char *blob = (char*) calloc(1, blobLen / 2);
if (!hex2bin(blob, hexblob, blobLen / 2))
{
applog(LOG_ERR, "JSON inval blob");
pthread_mutex_unlock(&rpc2_job_lock);
goto err_out;
}
if (rpc2_blob) {
free(rpc2_blob);
}
rpc2_bloblen = blobLen / 2;
rpc2_blob = (char*) malloc(rpc2_bloblen);
memcpy(rpc2_blob, blob, blobLen / 2);
free(blob);
uint32_t target;
jobj_binary(job, "target", &target, 4);
if(rpc2_target != target) {
double difficulty = (((double) UINT32_MAX) / target);
stratum.job.diff = difficulty;
rpc2_target = target;
}
if (rpc2_job_id) {
// reset job share counter
if (strcmp(rpc2_job_id, job_id)) stratum.job.shares_count = 0;
free(rpc2_job_id);
}
rpc2_job_id = strdup(job_id);
pthread_mutex_unlock(&rpc2_job_lock);
}
if(work)
{
if (!rpc2_blob) {
applog(LOG_ERR, "Requested work before work was received");
goto err_out;
}
memcpy(work->data, rpc2_blob, rpc2_bloblen);
memset(work->target, 0xff, sizeof(work->target));
work->target[7] = rpc2_target;
work->targetdiff = target_to_diff_rpc2(work->target);
snprintf(work->job_id, sizeof(work->job_id), "%s", rpc2_job_id);
}
if (opt_algo == ALGO_WILDKECCAK)
wildkeccak_scratchpad_need_update(pscratchpad_buff);
return true;
err_out:
return false;
}
extern struct work _ALIGN(64) g_work;
extern volatile time_t g_work_time;
extern bool submit_old;
bool rpc2_stratum_job(struct stratum_ctx *sctx, json_t *id, json_t *params)
{
bool ret = false;
pthread_mutex_lock(&rpc2_work_lock);
ret = rpc2_job_decode(params, &rpc2_work);
// update miner threads work
ret = ret && rpc2_stratum_gen_work(sctx, &g_work);
restart_threads();
pthread_mutex_unlock(&rpc2_work_lock);
return ret;
}
bool rpc2_stratum_gen_work(struct stratum_ctx *sctx, struct work *work)
{
// pthread_mutex_lock(&rpc2_work_lock);
memcpy(work, &rpc2_work, sizeof(struct work));
if (stratum_diff != sctx->job.diff) {
char sdiff[32] = { 0 };
stratum_diff = sctx->job.diff;
if (opt_showdiff && work->targetdiff != stratum_diff)
snprintf(sdiff, 32, " (%g)", work->targetdiff);
if (stratum_diff >= 1e6)
applog(LOG_WARNING, "Stratum difficulty set to %.1f M%s", stratum_diff/1e6, sdiff);
else
applog(LOG_WARNING, "Stratum difficulty set to %.0f%s", stratum_diff, sdiff);
}
if (work->target[7] != rpc2_target) {
work->target[7] = rpc2_target;
work->targetdiff = target_to_diff_rpc2(work->target);
g_work_time = 0;
restart_threads();
}
// pthread_mutex_unlock(&rpc2_work_lock);
return (work->data[0] != 0);
}
#define JSON_SUBMIT_BUF_LEN 512
// called by submit_upstream_work()
bool rpc2_stratum_submit(struct pool_infos *pool, struct work *work)
{
char _ALIGN(64) s[JSON_SUBMIT_BUF_LEN];
uint8_t _ALIGN(64) hash[32];
uint8_t _ALIGN(64) data[88];
char *noncestr, *hashhex;
int idnonce = work->submit_nonce_id;
memcpy(&data[0], work->data, 88);
if (opt_algo == ALGO_WILDKECCAK) {
// 64 bits nonce
memcpy(&data[1], work->nonces, 8);
// pass if the previous hash is not the current previous hash
if(!submit_old && memcmp(&work->data[3], &g_work.data[3], 28)) {
if (opt_debug) applog(LOG_DEBUG, "stale work detected");
pool->stales_count++;
return false;
}
noncestr = bin2hex((unsigned char*) &data[1], 8);
// "nonce":"5794ec8000000000" => 0x0000000080ec9457
memcpy(&last_found_nonce, work->nonces, 8);
wildkeccak_hash(hash, data, NULL, 0);
work_set_target_ratio(work, (uint32_t*) hash);
}
else if (opt_algo == ALGO_CRYPTOLIGHT) {
uint32_t nonce = work->nonces[idnonce];
noncestr = bin2hex((unsigned char*) &nonce, 4);
last_found_nonce = nonce;
cryptolight_hash(hash, data, 76);
work_set_target_ratio(work, (uint32_t*) hash);
}
else if (opt_algo == ALGO_CRYPTONIGHT) {
uint32_t nonce = work->nonces[idnonce];
noncestr = bin2hex((unsigned char*) &nonce, 4);
last_found_nonce = nonce;
cryptonight_hash(hash, data, 76);
work_set_target_ratio(work, (uint32_t*) hash);
}
if (hash[31] != 0)
return false; // prevent bad hashes
hashhex = bin2hex((unsigned char*)hash, 32);
snprintf(s, sizeof(s), "{\"method\":\"submit\",\"params\":"
"{\"id\":\"%s\",\"job_id\":\"%s\",\"nonce\":\"%s\",\"result\":\"%s\"}, \"id\":%u}",
rpc2_id, work->job_id, noncestr, hashhex, stratum.job.shares_count + 10);
free(hashhex);
free(noncestr);
gettimeofday(&stratum.tv_submit, NULL);
if(!stratum_send_line(&stratum, s)) {
applog(LOG_ERR, "%s stratum_send_line failed", __func__);
return false;
}
//stratum.sharediff = target_to_diff_rpc2((uint32_t*)hash);
stratum.sharediff = work->sharediff[idnonce];
return true;
}
bool rpc2_login_decode(const json_t *val)
{
const char *id;
const char *s;
json_t *res = json_object_get(val, "result");
if(!res) {
applog(LOG_ERR, "JSON invalid result");
goto err_out;
}
json_t *tmp;
tmp = json_object_get(res, "id");
if(!tmp) {
applog(LOG_ERR, "JSON inval id");
goto err_out;
}
id = json_string_value(tmp);
if(!id) {
applog(LOG_ERR, "JSON id is not a string");
goto err_out;
}
strncpy(rpc2_id, id, sizeof(rpc2_id)-1);
if(opt_debug)
applog(LOG_DEBUG, "Auth id: %s", id);
tmp = json_object_get(res, "status");
if(!tmp) {
applog(LOG_ERR, "JSON inval status");
goto err_out;
}
s = json_string_value(tmp);
if(!s) {
applog(LOG_ERR, "JSON status is not a string");
goto err_out;
}
if(strcmp(s, "OK")) {
applog(LOG_ERR, "JSON returned status \"%s\"", s);
goto err_out;
}
return true;
err_out:
return false;
}
bool store_scratchpad_to_file(bool do_fsync)
{
char file_name_buff[PATH_MAX] = { 0 };
FILE *fp;
int ret;
if(opt_algo != ALGO_WILDKECCAK) return true;
if(!scratchpad_size || !pscratchpad_buff) return true;
snprintf(file_name_buff, sizeof(file_name_buff), "%s.tmp", pscratchpad_local_cache);
unlink(file_name_buff);
fp = fopen(file_name_buff, "wbx");
if (!fp) {
applog(LOG_ERR, "failed to create file %s: %s", file_name_buff, strerror(errno));
return false;
}
struct scratchpad_file_header sf = { 0 };
memcpy(sf.add_arr, add_arr, sizeof(sf.add_arr));
sf.current_hi = current_scratchpad_hi;
sf.scratchpad_size = scratchpad_size;
if ((fwrite(&sf, sizeof(sf), 1, fp) != 1) ||
(fwrite(pscratchpad_buff, 8, (size_t) scratchpad_size, fp) != scratchpad_size)) {
applog(LOG_ERR, "failed to write file %s: %s", file_name_buff, strerror(errno));
fclose(fp);
unlink(file_name_buff);
return false;
}
fflush(fp);
/*if (do_fsync) {
if (fsync(fileno(fp)) == -1) {
applog(LOG_ERR, "failed to fsync file %s: %s", file_name_buff, strerror(errno));
fclose(fp);
unlink(file_name_buff);
return false;
}
}*/
if (fclose(fp) == EOF) {
applog(LOG_ERR, "failed to write file %s: %s", file_name_buff, strerror(errno));
unlink(file_name_buff);
return false;
}
ret = rename(file_name_buff, pscratchpad_local_cache);
if (ret == -1) {
applog(LOG_ERR, "failed to rename %s to %s: %s",
file_name_buff, pscratchpad_local_cache, strerror(errno));
unlink(file_name_buff);
return false;
}
applog(LOG_DEBUG, "saved scratchpad to %s (%zu+%zu bytes)", pscratchpad_local_cache,
sizeof(struct scratchpad_file_header), (size_t)scratchpad_size * 8);
return true;
}
/* TODO: repetitive error+log spam handling */
bool load_scratchpad_from_file(const char *fname)
{
FILE *fp;
long flen;
if(opt_algo != ALGO_WILDKECCAK) return true;
fp = fopen(fname, "rb");
if (!fp) {
if (errno != ENOENT) {
applog(LOG_ERR, "failed to load %s: %s", fname, strerror(errno));
}
return false;
}
struct scratchpad_file_header fh = { 0 };
if ((fread(&fh, sizeof(fh), 1, fp) != 1)) {
applog(LOG_ERR, "read error from %s: %s", fname, strerror(errno));
fclose(fp);
return false;
}
if ((fh.scratchpad_size*8 > (WILD_KECCAK_SCRATCHPAD_BUFFSIZE)) ||(fh.scratchpad_size%4)) {
applog(LOG_ERR, "file %s size invalid (%" PRIu64 "), max=%zu",
fname, fh.scratchpad_size*8, WILD_KECCAK_SCRATCHPAD_BUFFSIZE);
fclose(fp);
return false;
}
if (fread(pscratchpad_buff, 8, (size_t) fh.scratchpad_size, fp) != fh.scratchpad_size) {
applog(LOG_ERR, "read error from %s: %s", fname, strerror(errno));
fclose(fp);
return false;
}
scratchpad_size = fh.scratchpad_size;
current_scratchpad_hi = fh.current_hi;
memcpy(&add_arr[0], &fh.add_arr[0], sizeof(fh.add_arr));
flen = (long)scratchpad_size*8;
if (!opt_quiet) {
applog(LOG_INFO, "Scratchpad size %ld kB at block %" PRIu64, flen/1024, current_scratchpad_hi.height);
}
fclose(fp);
prev_save = time(NULL);
return true;
}
bool dump_scratchpad_to_file_debug()
{
char file_name_buff[1024] = { 0 };
if(opt_algo != ALGO_WILDKECCAK) return true;
snprintf(file_name_buff, sizeof(file_name_buff), "scratchpad_%" PRIu64 "_%llx.scr",
current_scratchpad_hi.height, (long long) last_found_nonce);
/* do not bother rewriting if it exists already */
FILE *fp = fopen(file_name_buff, "w");
if(!fp) {
applog(LOG_WARNING, "failed to open file %s: %s", file_name_buff, strerror(errno));
return false;
}
if (fwrite(pscratchpad_buff, 8, (size_t) scratchpad_size, fp) != scratchpad_size) {
applog(LOG_ERR, "failed to write file %s: %s", file_name_buff, strerror(errno));
fclose(fp);
return false;
}
if (fclose(fp) == EOF) {
applog(LOG_ERR, "failed to write file %s: %s", file_name_buff, strerror(errno));
return false;
}
fclose(fp);
return true;
}
static bool try_mkdir_chdir(const char *dirn)
{
if (chdir(dirn) == -1) {
if (errno == ENOENT) {
#ifdef WIN32
if (mkdir(dirn) == -1) {
#else
if (mkdir(dirn, 0700) == -1) {
#endif
applog(LOG_ERR, "mkdir failed: %s", strerror(errno));
return false;
}
if (chdir(dirn) == -1) {
applog(LOG_ERR, "chdir failed: %s", strerror(errno));
return false;
}
} else {
applog(LOG_ERR, "chdir failed: %s", strerror(errno));
return false;
}
}
return true;
}
static size_t curl_write_data(void *ptr, size_t size, size_t nmemb, FILE *stream)
{
size_t written = fwrite(ptr, size, nmemb, stream);
return written;
}
static bool download_inital_scratchpad(const char* path_to, const char* url)
{
CURL *curl;
CURLcode res;
char curl_error_buff[CURL_ERROR_SIZE] = { 0 };
FILE *fp = fopen(path_to,"wb");
if (!fp) {
applog(LOG_ERR, "Failed to create file %s error %d", path_to, errno);
return false;
}
applog(LOG_INFO, "Downloading scratchpad....");
curl_global_cleanup();
res = curl_global_init(CURL_GLOBAL_ALL);
if (res != CURLE_OK) {
applog(LOG_WARNING, "curl curl_global_init error: %d", (int) res);
}
curl = curl_easy_init();
if (!curl) {
applog(LOG_INFO, "Failed to curl_easy_init.");
fclose(fp);
unlink(path_to);
return false;
}
if (opt_protocol && opt_debug) {
curl_easy_setopt(curl, CURLOPT_VERBOSE, 1);
}
curl_easy_setopt(curl, CURLOPT_URL, url);
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 30);
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 300);
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, curl_error_buff);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_write_data);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp);
//curl_easy_setopt(curl, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0);
if (opt_cert) {
curl_easy_setopt(curl, CURLOPT_CAINFO, opt_cert);
} else {
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
}
res = curl_easy_perform(curl);
if (res != CURLE_OK) {
if (res == CURLE_OUT_OF_MEMORY) {
applog(LOG_ERR, "Failed to download file, not enough memory!");
applog(LOG_ERR, "curl error: %s", curl_error_buff);
} else {
applog(LOG_ERR, "Failed to download file, error: %s", curl_error_buff);
}
} else {
applog(LOG_INFO, "Scratchpad downloaded.");
}
/* always cleanup */
curl_easy_cleanup(curl);
fflush(fp);
fclose(fp);
if (res != CURLE_OK) {
unlink(path_to);
return false;
}
return true;
}
#ifndef WIN32
void GetScratchpad()
{
const char *phome_var_name = "HOME";
size_t sz = WILD_KECCAK_SCRATCHPAD_BUFFSIZE;
char cachedir[PATH_MAX];
if(!getenv(phome_var_name)) {
applog(LOG_ERR, "$%s not set", phome_var_name);
exit(1);
}
else if(!try_mkdir_chdir(getenv(phome_var_name))) {
exit(1);
}
if(!try_mkdir_chdir(".cache")) exit(1);
if(!try_mkdir_chdir(cachedir_suffix)) exit(1);
if(getcwd(cachedir, sizeof(cachedir) - 22) == NULL) {
applog(LOG_ERR, "getcwd failed: %s", strerror(errno));
exit(1);
}
snprintf(scratchpad_file, sizeof(scratchpad_file), "%s/scratchpad.bin", cachedir);
pscratchpad_local_cache = scratchpad_file;
if (!opt_quiet)
applog(LOG_INFO, "Scratchpad file %s", pscratchpad_local_cache);
pscratchpad_buff = (uint64_t*) mmap(0, sz, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB | MAP_POPULATE, 0, 0);
if(pscratchpad_buff == MAP_FAILED)
{
if(opt_debug) applog(LOG_DEBUG, "hugetlb not available");
pscratchpad_buff = (uint64_t*) malloc(sz);
if(!pscratchpad_buff) {
applog(LOG_ERR, "Scratchpad allocation failed");
exit(1);
}
} else {
if(opt_debug) applog(LOG_DEBUG, "using hugetlb");
}
madvise(pscratchpad_buff, sz, MADV_RANDOM | MADV_WILLNEED | MADV_HUGEPAGE);
mlock(pscratchpad_buff, sz);
if(!load_scratchpad_from_file(pscratchpad_local_cache))
{
if(!opt_scratchpad_url) {
applog(LOG_ERR, "Scratchpad URL not set. Please specify correct scratchpad url by -k or --scratchpad option");
exit(1);
}
if(!download_inital_scratchpad(pscratchpad_local_cache, opt_scratchpad_url)) {
applog(LOG_ERR, "Scratchpad not found and not downloaded. Please specify correct scratchpad url by -k or --scratchpad option");
exit(1);
}
if(!load_scratchpad_from_file(pscratchpad_local_cache)) {
applog(LOG_ERR, "Failed to load scratchpad data after downloading, probably broken scratchpad link, please restart miner with correct inital scratcpad link(-k or --scratchpad )");
unlink(pscratchpad_local_cache);
exit(1);
}
}
}
#else /* Windows */
void GetScratchpad()
{
bool scratchpad_need_update = false;
size_t sz = WILD_KECCAK_SCRATCHPAD_BUFFSIZE;
const char* phome_var_name = "LOCALAPPDATA";
char cachedir[PATH_MAX];
if(!getenv(phome_var_name)) {
applog(LOG_ERR, "%s env var is not set", phome_var_name);
exit(1);
}
else if(!try_mkdir_chdir(getenv(phome_var_name))) {
exit(1);
}
if(!try_mkdir_chdir(".cache"))
exit(1);
if(!try_mkdir_chdir(cachedir_suffix))
exit(1);
if(getcwd(cachedir, sizeof(cachedir) - 22) == NULL) {
applog(LOG_ERR, "getcwd failed: %s", strerror(errno));
exit(1);
}
snprintf(scratchpad_file, sizeof(scratchpad_file), "%s\\scratchpad.bin", cachedir);
pscratchpad_local_cache = scratchpad_file;
if (!opt_quiet)
applog(LOG_INFO, "Scratchpad file %s", pscratchpad_local_cache);
if (pscratchpad_buff) {
reset_scratchpad();
wildkeccak_scratchpad_need_update(NULL);
scratchpad_need_update = true;
free(pscratchpad_buff);
pscratchpad_buff = NULL;
}
pscratchpad_buff = (uint64_t*) malloc(sz);
if(!pscratchpad_buff) {
applog(LOG_ERR, "Scratchpad allocation failed");
exit(1);
}
if(!load_scratchpad_from_file(pscratchpad_local_cache))
{
if(!opt_scratchpad_url) {
applog(LOG_ERR, "Scratchpad URL not set. Please specify correct scratchpad url by -k or --scratchpad option");
exit(1);
}
free(pscratchpad_buff);
pscratchpad_buff = NULL;
if(!download_inital_scratchpad(pscratchpad_local_cache, opt_scratchpad_url)) {
applog(LOG_ERR, "Scratchpad not found and not downloaded. Please specify correct scratchpad url by -k or --scratchpad option");
exit(1);
}
pscratchpad_buff = (uint64_t*) malloc(sz);
if(!pscratchpad_buff) {
applog(LOG_ERR, "Scratchpad allocation failed");
exit(1);
}
if(!load_scratchpad_from_file(pscratchpad_local_cache)) {
applog(LOG_ERR, "Failed to load scratchpad data after downloading, probably broken scratchpad link, please restart miner with correct inital scratcpad link(-k or --scratchpad )");
unlink(pscratchpad_local_cache);
exit(1);
}
}
if (scratchpad_need_update)
wildkeccak_scratchpad_need_update(pscratchpad_buff);
}
#endif /* GetScratchpad() linux */
static bool rpc2_getfullscratchpad_decode(const json_t *val)
{
const char* status;
const char* scratch_hex;
size_t len;
json_t *hi;
json_t *res = json_object_get(val, "result");
if(!res) {
applog(LOG_ERR, "JSON invalid result in rpc2_getfullscratchpad_decode");
goto err_out;
}
//check status
status = get_json_string_param(res, "status");
if (!status ) {
applog(LOG_ERR, "JSON status is not a string");
goto err_out;
}
if(strcmp(status, "OK")) {
applog(LOG_ERR, "JSON returned status \"%s\"", status);
goto err_out;
}
//parse scratchpad
scratch_hex = get_json_string_param(res, "scratchpad_hex");
if (!scratch_hex) {
applog(LOG_ERR, "JSON scratch_hex is not a string");
goto err_out;
}
len = hex2bin_len((unsigned char*)pscratchpad_buff, scratch_hex, WILD_KECCAK_SCRATCHPAD_BUFFSIZE);
if (!len) {
applog(LOG_ERR, "JSON scratch_hex is not valid hex");
goto err_out;
}
if (len%8 || len%32) {
applog(LOG_ERR, "JSON scratch_hex is not valid size=%d bytes", len);
goto err_out;
}
//parse hi
hi = json_object_get(res, "hi");
if(!hi) {
applog(LOG_ERR, "JSON inval hi");
goto err_out;
}
if(!parse_height_info(hi, &current_scratchpad_hi))
{
applog(LOG_ERR, "JSON inval hi, failed to parse");
goto err_out;
}
applog(LOG_INFO, "Fetched scratchpad size %d bytes", len);
scratchpad_size = len/8;
return true;
err_out: return false;
}
static bool rpc2_stratum_getscratchpad(struct stratum_ctx *sctx)
{
bool ret = false;
json_t *val = NULL;
json_error_t err;
char *s, *sret;
if(opt_algo != ALGO_WILDKECCAK) return true;
s = (char*) calloc(1, 1024);
if (!s)
goto out;
sprintf(s, "{\"method\": \"getfullscratchpad\", \"params\": {\"id\": \"%s\", \"agent\": \"" USER_AGENT "\"}, \"id\": 1}", rpc2_id);
applog(LOG_INFO, "Getting full scratchpad....");
if (!stratum_send_line(sctx, s))
goto out;
//sret = stratum_recv_line_timeout(sctx, 920);
sret = stratum_recv_line(sctx);
if (!sret)
goto out;
applog(LOG_DEBUG, "Getting full scratchpad received line");
val = JSON_LOADS(sret, &err);
free(sret);
if (!val) {
applog(LOG_ERR, "JSON decode rpc2_getscratchpad response failed(%d): %s", err.line, err.text);
goto out;
}
applog(LOG_DEBUG, "Getting full scratchpad parsed line");
ret = rpc2_getfullscratchpad_decode(val);
out:
free(s);
if (val)
json_decref(val);
return ret;
}
bool rpc2_stratum_authorize(struct stratum_ctx *sctx, const char *user, const char *pass)
{
bool ret = false;
json_t *val = NULL, *res_val, *err_val, *job_val = NULL;
json_error_t err;
char *sret;
char *s = (char*) calloc(1, 320 + strlen(user) + strlen(pass));
if (opt_algo == ALGO_WILDKECCAK) {
char *prevhash = bin2hex((const unsigned char*)current_scratchpad_hi.prevhash, 32);
sprintf(s, "{\"method\":\"login\",\"params\":{\"login\":\"%s\",\"pass\":\"%s\","
"\"hi\":{\"height\":%" PRIu64 ",\"block_id\":\"%s\"},"
"\"agent\":\"" USER_AGENT "\"},\"id\":2}",
user, pass, current_scratchpad_hi.height, prevhash);
free(prevhash);
} else {
sprintf(s, "{\"method\":\"login\",\"params\":{\"login\":\"%s\",\"pass\":\"%s\","
"\"agent\":\"" USER_AGENT "\"},\"id\":2}",
user, pass);
}
if (!stratum_send_line(sctx, s))
goto out;
while (1) {
sret = stratum_recv_line(sctx);
if (!sret)
goto out;
if (!stratum_handle_method(sctx, sret))
break;
free(sret);
}
val = JSON_LOADS(sret, &err);
free(sret);
if (!val) {
applog(LOG_ERR, "JSON decode failed(%d): %s", err.line, err.text);
goto out;
}
res_val = json_object_get(val, "result");
err_val = json_object_get(val, "error");
if (!res_val || json_is_false(res_val) ||
(err_val && !json_is_null(err_val))) {
applog(LOG_ERR, "Stratum authentication failed");
if (err_val) {
const char *msg = json_string_value(json_object_get(err_val,"message"));
if (msg && strlen(msg)) {
if (strstr(msg, "scratchpad too old") && pscratchpad_local_cache) {
if (unlink(pscratchpad_local_cache) == 0) {
applog(LOG_INFO, "Outdated scratchpad, deleted...", pscratchpad_local_cache);
GetScratchpad();
goto out;
}
}
applog(LOG_NOTICE, "%s", msg);
}
}
goto out;
}
rpc2_login_decode(val);
job_val = json_object_get(res_val, "job");
pthread_mutex_lock(&rpc2_work_lock);
if(job_val) rpc2_job_decode(job_val, &rpc2_work);
pthread_mutex_unlock(&rpc2_work_lock);
ret = true;
out:
free(s);
if (val)
json_decref(val);
return ret;
}
bool rpc2_stratum_request_job(struct stratum_ctx *sctx)
{
json_t *val = NULL, *res_val, *err_val;
json_error_t err;
bool ret = false;
char *sret;
char *s = (char*) calloc(1, 10*2048);
if (!s) {
applog(LOG_ERR, "Stratum job OOM!");
return ret;
}
if (opt_algo == ALGO_WILDKECCAK) {
char* prevhash = bin2hex((const unsigned char*)current_scratchpad_hi.prevhash, 32);
sprintf(s, "{\"method\":\"getjob\",\"params\": {"
"\"id\":\"%s\", \"hi\": {\"height\": %" PRIu64 ",\"block_id\":\"%s\" }, \"agent\": \"" USER_AGENT "\"},"
"\"id\":1}",
rpc2_id, current_scratchpad_hi.height, prevhash);
free(prevhash);
} else {
sprintf(s, "{\"method\":\"getjob\",\"params\":{\"id\":\"%s\"},\"id\":1}", rpc2_id);
}
if(!stratum_send_line(sctx, s)) {
applog(LOG_ERR, "Stratum failed to send getjob line");
goto out;
}
sret = stratum_recv_line(sctx);
if (!sret) {
applog(LOG_ERR, "Stratum failed to recv getjob line");
goto out;
}
val = JSON_LOADS(sret, &err);
free(sret);
if (!val) {
applog(LOG_ERR, "JSON getwork decode failed(%d): %s", err.line, err.text);
goto out;
}
res_val = json_object_get(val, "result");
err_val = json_object_get(val, "error");
if (!res_val || json_is_false(res_val) ||
(err_val && !json_is_null(err_val))) {
applog(LOG_ERR, "Stratum getjob failed");
goto out;
}
pthread_mutex_lock(&rpc2_work_lock);
rpc2_job_decode(res_val, &rpc2_work);
pthread_mutex_unlock(&rpc2_work_lock);
ret = true;
out:
if (val)
json_decref(val);
return ret;
}
int rpc2_stratum_thread_stuff(struct pool_infos* pool)
{
int opt_fail_pause = 10;
if(!strcmp(rpc2_id, "")) {
if (!opt_quiet)
applog(LOG_DEBUG, "disconnecting...");
stratum_disconnect(&stratum);
//not logged in, try to relogin
if (!opt_quiet)
applog(LOG_DEBUG, "Re-connect and relogin...");
if(!stratum_connect(&stratum, stratum.url) || !stratum_authorize(&stratum, pool->user, pool->pass)) {
stratum_disconnect(&stratum);
applog(LOG_ERR, "Failed...retry after %d seconds", opt_fail_pause);
sleep(opt_fail_pause);
}
}
if(!scratchpad_size && opt_algo == ALGO_WILDKECCAK) {
if(!rpc2_stratum_getscratchpad(&stratum)) {
stratum_disconnect(&stratum);
applog(LOG_ERR, "...retry after %d seconds", opt_fail_pause);
sleep(opt_fail_pause);
}
store_scratchpad_to_file(false);
prev_save = time(NULL);
if(!rpc2_stratum_request_job(&stratum)) {
stratum_disconnect(&stratum);
applog(LOG_ERR, "...retry after %d seconds", opt_fail_pause);
sleep(opt_fail_pause);
}
}
/* save every 12 hours */
if ((time(NULL) - prev_save) > 12*3600) {
store_scratchpad_to_file(false);
prev_save = time(NULL);
}
if (rpc2_work.job_id && (!g_work_time || strcmp(rpc2_work.job_id, g_work.job_id))) {
pthread_mutex_lock(&rpc2_work_lock);
rpc2_stratum_gen_work(&stratum, &g_work);
g_work_time = time(NULL);
pthread_mutex_unlock(&rpc2_work_lock);
if (opt_debug) applog(LOG_DEBUG, "Stratum detected new block");
restart_threads();
}
return 0;
}
void rpc2_init()
{
memset(&current_scratchpad_hi, 0, sizeof(struct scratchpad_hi));
memset(&rpc2_work, 0, sizeof(struct work));
pthread_mutex_init(&rpc2_job_lock, NULL);
pthread_mutex_init(&rpc2_work_lock, NULL);
pthread_mutex_init(&rpc2_login_lock, NULL);
//pthread_mutex_init(&rpc2_getscratchpad_lock, NULL);
}