text: /**
   * 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
  
  #if defined(__APPLE__) && !defined(MAP_HUGETLB)
  #define MAP_ANONYMOUS MAP_ANON
  #define MAP_HUGETLB 0
  #define MAP_POPULATE 0
  #define MADV_HUGEPAGE 0
  #endif
  
  #ifndef MADV_HUGEPAGE
  #define MADV_HUGEPAGE 0
  #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);
  	}
  	if (opt_proxy) {
  		curl_easy_setopt(curl, CURLOPT_PROXY, opt_proxy);
  		curl_easy_setopt(curl, CURLOPT_PROXYTYPE, opt_proxy_type);
  	}
  	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);
  }
  