/** * XMR RPC 2.0 Specific Stuff * tpruvot@github - October 2016 */ #include #include #include #include #include // mkdir #include #ifdef _MSC_VER #include "mman.h" // mmap #include // _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 // mmap #endif #ifndef PRIu64 #define PRIu64 "I64u" #endif #include #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(¤t_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(¤t_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, ¤t_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(¤t_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); }