From bbd2c704f9d6637532d9a267a86c66afa59e54d6 Mon Sep 17 00:00:00 2001 From: Tanguy Pruvot Date: Thu, 21 May 2015 07:43:53 +0200 Subject: [PATCH] multipool: Squashed commit (v2) commit a9d3c1ffdb71d2a4985749acba3d424161154ab4 Author: Tanguy Pruvot Date: Thu May 21 05:39:24 2015 +0200 multipool: last changes before squashed merge and fix net diff on wallets.. was longpoll specific commit a63f0024f3f1fb52d2c4369518bf87c33a9e16ae Author: Tanguy Pruvot Date: Thu May 21 05:02:27 2015 +0200 update api sample for the protocol 1.5 commit adda14b22edde6485932be56550166478f6f00dd Author: Tanguy Pruvot Date: Thu May 21 04:43:25 2015 +0200 stats: store pool number in scanlog commit e1a0274b01c29409ce16f9096b9985a35cf78ba7 Author: Tanguy Pruvot Date: Thu May 21 03:36:15 2015 +0200 api: switchpool and new pool stats variables (API v1.5) add accepted/rej by pool, wait time on conditional, net diff and rate also add scantime per pool config option and do some pool cleanup.. commit 1a30450ad2a5e068983531b9d2a96629b970c1e8 Author: Tanguy Pruvot Date: Wed May 20 06:39:09 2015 +0200 prevent concurrent pool switching and limit condtionnal wait messages to the first thread/device commit e3922c7a331a3ad2730bc83082fcd6b2547542f5 Author: Tanguy Pruvot Date: Wed May 20 05:39:45 2015 +0200 add some pool rotate options, like pool time-limit update sample pools.conf for time rotation commit 312bd905412d49fd5a9f9e7ff2bc72b23edf38ed Author: Tanguy Pruvot Date: Wed May 20 04:31:19 2015 +0200 do not try to restart threads from threads Start inconditionally the stratum and longpoll threads, these threads are just waiting a tq_push() if unused... so add some checks to know if vars are set for the right pool commit d4a9428fefdd9e9d70c3c8231f10961e7cd41760 Author: Tanguy Pruvot Date: Wed May 20 01:06:31 2015 +0200 pools: add name and removed attributes also increase max defined pools to 8 to be tested on windows.. commit d840d683ecb2cc4767f0a0612b8359c52d4bad29 Author: Tanguy Pruvot Date: Tue May 19 22:33:11 2015 +0200 parse json config file pools array commit d6c29b1f7f6b786c56e1f0cb8a90305f06cc7aec Author: Tanguy Pruvot Date: Tue May 19 03:29:30 2015 +0200 multi-pools: prepare storage/switch of credentials for the moment: - allow the storage of multiple -o params (and user/pass) - allow a failover pool on connection failed - switch to the next pool with the "seturl" api remote command - longpoll to stratum switch (reverse to check...) todo: mix stratum/getwork, new api commands, json config... commit 2d6b3fddf6631d7df1ac6ca74eee91c33a3c09ee Author: Tanguy Pruvot Date: Fri May 22 08:26:40 2015 +0200 multipool: increase stability, but not 100% perfect several problems fixed: - submit to the pool set in work (source pool) - longpoll curl timeout could be too high and could lock the switch - mutexes cant be copied on windows (stratum global var to fully remove later) I linked the -T timeout option to curl and tried to remove all fixed timeout values commit 6fd935c369cf33949dab98c8b09b2ca8cab3e7ea Author: Tanguy Pruvot Date: Fri May 22 11:23:07 2015 +0200 stratum: remove last rpc_ vars in stratum thread commit ee9c821525be303282e5dab512ffd2ae81ad524f Author: Tanguy Pruvot Date: Sat May 23 03:53:50 2015 +0200 stratum: do not alloc empty merkle tree commit 69852a2874bd18c4ed1daa9180a10d12976424dc Author: Tanguy Pruvot Date: Sat May 23 04:25:12 2015 +0200 stratum: properly free jobs on disconnect Signed-off-by: Tanguy Pruvot --- api.cpp | 103 +++++-- api/index.php | 12 +- ccminer.cpp | 824 ++++++++++++++++++++++++++++++++++++++++---------- hashlog.cpp | 8 +- miner.h | 76 ++++- pools.conf | 27 ++ stats.cpp | 2 + util.cpp | 124 ++++++-- 8 files changed, 948 insertions(+), 228 deletions(-) create mode 100644 pools.conf diff --git a/api.cpp b/api.cpp index 9020066..1246975 100644 --- a/api.cpp +++ b/api.cpp @@ -8,7 +8,7 @@ * Software Foundation; either version 2 of the License, or (at your option) * any later version. See COPYING for more details. */ -#define APIVERSION "1.4" +#define APIVERSION "1.5" #ifdef WIN32 # define _WINSOCK_DEPRECATED_NO_WARNINGS @@ -92,13 +92,12 @@ static int bye = 0; extern char *opt_api_allow; extern int opt_api_listen; /* port */ extern int opt_api_remote; -extern uint32_t accepted_count; -extern uint32_t rejected_count; -extern int num_cpus; + +// current stratum... extern struct stratum_ctx stratum; -extern char* rpc_user; // sysinfos.cpp +extern int num_cpus; extern float cpu_temp(int); extern uint32_t cpu_clock(int); // cuda.cpp @@ -111,6 +110,8 @@ char driver_version[32] = { 0 }; static void gpustatus(int thr_id) { + struct pool_infos *p = &pools[cur_pooln]; + if (thr_id >= 0 && thr_id < opt_n_threads) { struct cgpu_info *cgpu = &thr_info[thr_id].gpu; int gpuid = cgpu->gpu_id; @@ -127,8 +128,8 @@ static void gpustatus(int thr_id) cuda_gpu_clocks(cgpu); // todo: per gpu - cgpu->accepted = accepted_count; - cgpu->rejected = rejected_count; + cgpu->accepted = p->accepted_count; + cgpu->rejected = p->rejected_count; cgpu->khashes = stats_get_speed(thr_id, 0.0) / 1000.0; @@ -165,19 +166,27 @@ static char *getsummary(char *params) { char algo[64]; *algo = '\0'; time_t ts = time(NULL); - double uptime = difftime(ts, startup); - double accps = (60.0 * accepted_count) / (uptime ? uptime : 1.0); + double accps, uptime = difftime(ts, startup); + uint32_t wait_time = 0, accepted_count = 0, rejected_count = 0; + for (int p = 0; p < num_pools; p++) { + wait_time += pools[cur_pooln].wait_time; + accepted_count += pools[cur_pooln].accepted_count; + rejected_count += pools[cur_pooln].rejected_count; + } + accps = (60.0 * accepted_count) / (uptime ? uptime : 1.0); get_currentalgo(algo, sizeof(algo)); *buffer = '\0'; sprintf(buffer, "NAME=%s;VER=%s;API=%s;" "ALGO=%s;GPUS=%d;KHS=%.2f;ACC=%d;REJ=%d;" - "ACCMN=%.3f;DIFF=%.6f;UPTIME=%.0f;TS=%u|", + "ACCMN=%.3f;DIFF=%.6f;NETKHS=%.2f;" + "POOLS=%u;WAIT=%u;UPTIME=%.0f;TS=%u|", PACKAGE_NAME, PACKAGE_VERSION, APIVERSION, - algo, active_gpus, (double)global_hashrate / 1000.0, + algo, active_gpus, (double)global_hashrate / 1000., accepted_count, rejected_count, - accps, net_diff > 0. ? net_diff : stratum_diff, uptime, (uint32_t) ts); + accps, net_diff > 0. ? net_diff : stratum_diff, (double)net_hashrate / 1000., + num_pools, wait_time, uptime, (uint32_t) ts); return buffer; } @@ -186,31 +195,30 @@ static char *getsummary(char *params) */ static char *getpoolnfo(char *params) { - char *p = buffer; + char *s = buffer; char jobid[128] = { 0 }; char nonce[128] = { 0 }; - *p = '\0'; + struct pool_infos *p = &pools[cur_pooln]; - if (!stratum.url) { - sprintf(p, "|"); - return p; - } + *s = '\0'; if (stratum.job.job_id) strncpy(jobid, stratum.job.job_id, sizeof(stratum.job.job_id)); - if (stratum.job.xnonce2) { /* used temporary to be sure all is ok */ - cbin2hex(nonce, (const char*) stratum.job.xnonce2, stratum.xnonce2_size); + sprintf(nonce, "0x"); + cbin2hex(&nonce[2], (const char*) stratum.job.xnonce2, stratum.xnonce2_size); } - snprintf(p, MYBUFSIZ, "URL=%s;USER=%s;H=%u;JOB=%s;DIFF=%.6f;N2SZ=%d;N2=0x%s;PING=%u;DISCO=%u;UPTIME=%u|", - stratum.url, rpc_user ? rpc_user : "", - stratum.job.height, jobid, stratum.job.diff, + snprintf(s, MYBUFSIZ, "URL=%s;USER=%s;ACC=%d;REJ=%d;H=%u;JOB=%s;DIFF=%.6f;" + "N2SZ=%d;N2=%s;PING=%u;DISCO=%u;WAIT=%u;UPTIME=%u|", + p->url, p->type & POOL_STRATUM ? p->user : "", + p->accepted_count, p->rejected_count, + stratum.job.height, jobid, stratum_diff, (int) stratum.xnonce2_size, nonce, stratum.answer_msec, - stratum.disconnects, (uint32_t) (time(NULL) - stratum.tm_connected)); + p->disconnects, p->wait_time, p->work_time); - return p; + return s; } /*****************************************************************************/ @@ -342,9 +350,9 @@ static char *getscanlog(char *params) *buffer = '\0'; for (int i = 0; i < records; i++) { time_t ts = data[i].tm_upd; - p += sprintf(p, "H=%u;JOB=%u;N=%u;FROM=0x%x;SCANTO=0x%x;" + p += sprintf(p, "H=%u;P=%u;JOB=%u;N=%u;FROM=0x%x;SCANTO=0x%x;" "COUNT=0x%x;FOUND=%u;TS=%u|", - data[i].height, data[i].njobid, data[i].nonce, data[i].scanned_from, data[i].scanned_to, + data[i].height, data[i].npool, data[i].njobid, data[i].nonce, data[i].scanned_from, data[i].scanned_to, (data[i].scanned_to - data[i].scanned_from), data[i].tm_sent ? 1 : 0, (uint32_t)ts); } return buffer; @@ -380,19 +388,47 @@ static bool check_remote_access(void) return (opt_api_remote > 0); } +/** + * Set pool by index (pools array in json config) + * switchpool|1| + */ +static char *remote_switchpool(char *params) +{ + bool ret = false; + *buffer = '\0'; + if (!check_remote_access()) + return buffer; + if (!params || strlen(params) == 0) { + // rotate pool test + ret = pool_switch_next(); + } else { + int n = atoi(params); + if (n == cur_pooln) + ret = true; + else if (n < num_pools) + ret = pool_switch(n); + } + sprintf(buffer, "%s|", ret ? "ok" : "fail"); + return buffer; +} + /** * Change pool url (see --url parameter) - * seturl|stratum+tcp://Danila.1:X@pool.ipominer.com:3335| + * seturl|stratum+tcp://:@mine.xpool.ca:1131| */ -extern bool stratum_need_reset; static char *remote_seturl(char *params) { + bool ret; *buffer = '\0'; if (!check_remote_access()) return buffer; - parse_arg('o', params); - stratum_need_reset = true; - sprintf(buffer, "%s", "ok|"); + if (!params || strlen(params) == 0) { + // rotate pool test + ret = pool_switch_next(); + } else { + ret = pool_switch_url(params); + } + sprintf(buffer, "%s|", ret ? "ok" : "fail"); return buffer; } @@ -425,7 +461,8 @@ struct CMDS { { "scanlog", getscanlog }, /* remote functions */ - { "seturl", remote_seturl }, + { "seturl", remote_seturl }, /* prefer switchpool, deprecated */ + { "switchpool", remote_switchpool }, { "quit", remote_quit }, /* keep it the last */ diff --git a/api/index.php b/api/index.php index 9fd08c1..1d79a49 100644 --- a/api/index.php +++ b/api/index.php @@ -1,5 +1,5 @@ '.$val.' kH/s'; break; case 'NAME': @@ -246,7 +252,7 @@ span.elipsis { display: inline-block; max-width: 130px; overflow: hidden; } diff --git a/ccminer.cpp b/ccminer.cpp index dff1832..609c990 100644 --- a/ccminer.cpp +++ b/ccminer.cpp @@ -79,6 +79,7 @@ struct workio_cmd { union { struct work *work; } u; + int pooln; }; enum sha_algos { @@ -154,8 +155,12 @@ static const char *algo_names[] = { }; bool opt_debug = false; +bool opt_debug_threads = false; bool opt_protocol = false; bool opt_benchmark = false; + +// todo: limit use of these flags, +// prefer the pools[] attributes bool want_longpoll = true; bool have_longpoll = false; bool want_stratum = true; @@ -163,6 +168,7 @@ bool have_stratum = false; bool allow_gbt = true; bool allow_mininginfo = true; bool check_dups = false; + static bool submit_old = false; bool use_syslog = false; bool use_colors = true; @@ -171,7 +177,8 @@ bool opt_quiet = false; static int opt_retries = -1; static int opt_fail_pause = 30; static int opt_time_limit = 0; -int opt_timeout = 270; +static time_t firstwork_time = 0; +int opt_timeout = 60; // curl static int opt_scantime = 10; static json_t *opt_config; static const bool opt_time = true; @@ -205,11 +212,25 @@ bool opt_autotune = true; bool abort_flag = false; char *jane_params = NULL; +// pools (failover/getwork infos) +struct pool_infos pools[MAX_POOLS] = { 0 }; +int num_pools = 1; +volatile int cur_pooln = 0; +bool opt_pool_failover = true; +volatile bool pool_is_switching = false; +volatile int pool_switch_count = 0; +bool conditional_pool_rotate = false; + +// current connection char *rpc_user = NULL; static char *rpc_pass; -static char *rpc_userpass = NULL; char *rpc_url; static char *short_url = NULL; + +struct stratum_ctx stratum = { 0 }; +pthread_mutex_t stratum_sock_lock; +pthread_mutex_t stratum_work_lock; + char *opt_cert; char *opt_proxy; long opt_proxy_type; @@ -222,13 +243,10 @@ int api_thr_id = -1; bool stratum_need_reset = false; struct work_restart *work_restart = NULL; static int app_exit_code = EXIT_CODE_OK; -struct stratum_ctx stratum = { 0 }; uint32_t zr5_pok = 0; pthread_mutex_t applog_lock; static pthread_mutex_t stats_lock; -uint32_t accepted_count = 0L; -uint32_t rejected_count = 0L; static double thr_hashrates[MAX_GPUS] = { 0 }; uint64_t global_hashrate = 0; double stratum_diff = 0.0; @@ -236,7 +254,7 @@ double net_diff = 0; uint64_t net_hashrate = 0; uint64_t net_blocks = 0; // conditional mining -bool conditional_state = true; +uint8_t conditional_state[MAX_GPUS] = { 0 }; double opt_max_temp = 0.0; double opt_max_diff = 0.0; double opt_max_rate = 0.0; @@ -264,7 +282,7 @@ Usage: " PROGRAM_NAME " [OPTIONS]\n\ Options:\n\ -a, --algo=ALGO specify the hash algorithm to use\n\ anime Animecoin\n\ - blake Blake 256 (SFR/NEOS)\n\ + blake Blake 256 (SFR)\n\ blakecoin Fast Blake 256 (8 rounds)\n\ deep Deepcoin\n\ dmd-gr Diamond-Groestl\n\ @@ -293,8 +311,7 @@ Options:\n\ x13 X13 (MaruCoin)\n\ x14 X14\n\ x15 X15\n\ - x17 X17 (peoplecurrency)\n\ - whirlpoolx Vanilla coin\n\ + x17 X17\n\ zr5 ZR5 (ZiftrCoin)\n\ -d, --devices Comma separated list of CUDA devices to use.\n\ Device IDs start counting from 0! Alternatively takes\n\ @@ -316,11 +333,11 @@ Options:\n\ (default: retry indefinitely)\n\ -R, --retry-pause=N time to pause between retries, in seconds (default: 30)\n\ --time-limit maximum time [s] to mine before exiting the program.\n\ - -T, --timeout=N network timeout, in seconds (default: 270)\n\ + -T, --timeout=N network timeout, in seconds (default: 60)\n\ -s, --scantime=N upper bound on time spent scanning current work when\n\ long polling is unavailable, in seconds (default: 10)\n\ -n, --ndevs list cuda devices\n\ - -N, --statsavg number of samples used to display hashrate (default: 30)\n\ + -N, --statsavg number of samples used to compute hashrate (default: 30)\n\ --no-gbt disable getblocktemplate support (height check in solo)\n\ --no-longpoll disable X-Long-Polling support\n\ --no-stratum disable X-Stratum support\n\ @@ -330,8 +347,8 @@ Options:\n\ -P, --protocol-dump verbose dump of protocol-level activities\n\ --cpu-affinity set process affinity to cpu core(s), mask 0x3 for cores 0 and 1\n\ --cpu-priority set process priority (default: 3) 0 idle, 2 normal to 5 highest\n\ - -b, --api-bind IP/Port for the miner API (default: 127.0.0.1:4068)\n\ - --api-remote Allow remote control\n\ + -b, --api-bind=port IP:port for the miner API (default: 127.0.0.1:4068), 0 disabled\n\ + --api-remote Allow remote control, like pool switching\n\ --max-temp=N Only mine if gpu temp is less than specified value\n\ --max-rate=N[KMG] Only mine if net hashrate is less than specified value\n\ --max-diff=N Only mine if net difficulty is less than specified value\n" @@ -382,6 +399,12 @@ static struct option const options[] = { { "max-diff", 1, NULL, 1061 }, { "max-rate", 1, NULL, 1062 }, { "pass", 1, NULL, 'p' }, + { "pool-name", 1, NULL, 1100 }, // pool + { "pool-removed", 1, NULL, 1101 }, // pool + { "pool-scantime", 1, NULL, 1102 }, // pool + { "pool-time-limit", 1, NULL, 1108 }, + { "pool-max-diff", 1, NULL, 1161 }, // pool + { "pool-max-rate", 1, NULL, 1162 }, // pool { "protocol-dump", 0, NULL, 'P' }, { "proxy", 1, NULL, 'x' }, { "quiet", 0, NULL, 'q' }, @@ -420,6 +443,27 @@ Scrypt specific options:\n\ --no-autotune disable auto-tuning of kernel launch parameters\n\ "; +#define CFG_NULL 0 +#define CFG_POOL 1 +struct opt_config_array { + int cat; + const char *name; // json key + const char *longname; // global opt name if different +} cfg_array_keys[] = { + { CFG_POOL, "url", NULL }, /* let this key first, increment pools */ + { CFG_POOL, "user", NULL }, + { CFG_POOL, "pass", NULL }, + { CFG_POOL, "userpass", NULL }, + { CFG_POOL, "name", "pool-name" }, + { CFG_POOL, "scantime", "pool-scantime" }, + { CFG_POOL, "max-diff", "pool-max-diff" }, + { CFG_POOL, "max-rate", "pool-max-rate" }, + { CFG_POOL, "removed", "pool-removed" }, + { CFG_POOL, "disabled", "pool-removed" }, // sample alias + { CFG_POOL, "time-limit", "pool-time-limit" }, + { CFG_NULL, NULL, NULL } +}; + struct work _ALIGN(64) g_work; time_t g_work_time; pthread_mutex_t g_work_lock; @@ -541,7 +585,8 @@ static void calc_network_diff(struct work *work) uchar rtarget[48] = { 0 }; uint64_t diffone = 0xFFFF000000000000ull; //swab64(0xFFFFull); uint64_t *data64, d64; - uint32_t nbits = have_stratum ? swab32(work->data[18]) : work->data[18]; + // todo: endian reversed on longpoll could be zr5 specific... + uint32_t nbits = have_longpoll ? work->data[18] : swab32(work->data[18]); uint32_t shift = (swab32(nbits) & 0xff); // 0x1c = 28 uint32_t bits = (nbits & 0xffffff); int shfb = 8 * (26 - (shift - 3)); @@ -588,7 +633,8 @@ static void calc_network_diff(struct work *work) d64 = 1; net_diff = (double)diffone / d64; // 43.281 if (opt_debug) - applog(LOG_DEBUG, "diff: %08x -> shift %u, bits %08x, shfb %d -> %.5f", nbits, shift, bits, shfb, net_diff); + applog(LOG_DEBUG, "diff: %08x -> shift %u, bits %08x, shfb %d -> %.5f (pool %u)", + nbits, shift, bits, shfb, net_diff, work->pooln); } static bool work_decode(const json_t *val, struct work *work) @@ -676,10 +722,11 @@ static void calc_target_diff(struct work *work) } } -static int share_result(int result, const char *reason) +static int share_result(int result, int pooln, const char *reason) { char s[32] = { 0 }; double hashrate = 0.; + struct pool_infos *p = &pools[pooln]; pthread_mutex_lock(&stats_lock); @@ -687,16 +734,16 @@ static int share_result(int result, const char *reason) hashrate += stats_get_speed(i, thr_hashrates[i]); } - result ? accepted_count++ : rejected_count++; + result ? p->accepted_count++ : p->rejected_count++; pthread_mutex_unlock(&stats_lock); global_hashrate = llround(hashrate); format_hashrate(hashrate, s); applog(LOG_NOTICE, "accepted: %lu/%lu (%.2f%%), %s %s", - accepted_count, - accepted_count + rejected_count, - 100. * accepted_count / (accepted_count + rejected_count), + p->accepted_count, + p->accepted_count + p->rejected_count, + 100. * p->accepted_count / (p->accepted_count + p->rejected_count), s, use_colors ? (result ? CL_GRN "yay!!!" : CL_RED "booooo") @@ -704,11 +751,11 @@ static int share_result(int result, const char *reason) if (reason) { applog(LOG_WARNING, "reject reason: %s", reason); - if (strncasecmp(reason, "low difficulty", 14) == 0) { + /* if (strncasecmp(reason, "low difficulty", 14) == 0) { opt_difficulty = (opt_difficulty * 2.0) / 3.0; applog(LOG_WARNING, "factor reduced to : %0.2f", opt_difficulty); return 0; - } + } */ if (!check_dups && strncasecmp(reason, "duplicate", 9) == 0) { applog(LOG_WARNING, "enabling duplicates check feature"); check_dups = true; @@ -719,6 +766,7 @@ static int share_result(int result, const char *reason) static bool submit_upstream_work(CURL *curl, struct work *work) { + struct pool_infos *pool = &pools[work->pooln]; json_t *val, *res, *reason; bool stale_work = false; char s[384]; @@ -754,7 +802,7 @@ static bool submit_upstream_work(CURL *curl, struct work *work) } calc_target_diff(work); - if (have_stratum) { + if (pool->type & POOL_STRATUM) { uint32_t sent = 0; uint32_t ntime, nonce; uint16_t nvote; @@ -795,12 +843,12 @@ static bool submit_upstream_work(CURL *curl, struct work *work) nvotestr = bin2hex((const uchar*)(&nvote), 2); sprintf(s, "{\"method\": \"mining.submit\", \"params\": [\"%s\", \"%s\", \"%s\", \"%s\", \"%s\", \"%s\"], \"id\":4}", - rpc_user, work->job_id + 8, xnonce2str, ntimestr, noncestr, nvotestr); + pool->user, work->job_id + 8, xnonce2str, ntimestr, noncestr, nvotestr); free(nvotestr); } else { sprintf(s, "{\"method\": \"mining.submit\", \"params\": [\"%s\", \"%s\", \"%s\", \"%s\", \"%s\"], \"id\":4}", - rpc_user, work->job_id + 8, xnonce2str, ntimestr, noncestr); + pool->user, work->job_id + 8, xnonce2str, ntimestr, noncestr); } free(xnonce2str); free(ntimestr); @@ -843,7 +891,7 @@ static bool submit_upstream_work(CURL *curl, struct work *work) str); /* issue JSON-RPC request */ - val = json_rpc_call(curl, rpc_url, rpc_userpass, s, false, false, NULL); + val = json_rpc_call_pool(curl, pool, s, false, false, NULL); if (unlikely(!val)) { applog(LOG_ERR, "submit_upstream_work json_rpc_call failed"); return false; @@ -851,7 +899,10 @@ static bool submit_upstream_work(CURL *curl, struct work *work) res = json_object_get(val, "result"); reason = json_object_get(val, "reject-reason"); - if (!share_result(json_is_true(res), reason ? json_string_value(reason) : NULL)) { + if (!share_result(json_is_true(res), + work->pooln, + reason ? json_string_value(reason) : NULL)) + { if (check_dups) hashlog_purge_job(work->job_id); } @@ -911,12 +962,12 @@ static const char *gbt_req = static bool get_blocktemplate(CURL *curl, struct work *work) { + struct pool_infos *pool = &pools[work->pooln]; if (!allow_gbt) return false; int curl_err = 0; - json_t *val = json_rpc_call(curl, rpc_url, rpc_userpass, gbt_req, - want_longpoll, have_longpoll, &curl_err); + json_t *val = json_rpc_call_pool(curl, pool, gbt_req, false, false, &curl_err); if (!val && curl_err == -1) { // when getblocktemplate is not supported, disable it @@ -940,12 +991,13 @@ static const char *info_req = static bool get_mininginfo(CURL *curl, struct work *work) { + struct pool_infos *pool = &pools[work->pooln]; + int curl_err = 0; + if (have_stratum || !allow_mininginfo) return false; - int curl_err = 0; - json_t *val = json_rpc_call(curl, rpc_url, rpc_userpass, info_req, - want_longpoll, have_longpoll, &curl_err); + json_t *val = json_rpc_call_pool(curl, pool, info_req, false, false, &curl_err); if (!val && curl_err == -1) { allow_mininginfo = false; @@ -982,19 +1034,24 @@ static const char *rpc_req = static bool get_upstream_work(CURL *curl, struct work *work) { - json_t *val; bool rc; struct timeval tv_start, tv_end, diff; + struct pool_infos *pool = &pools[work->pooln]; + json_t *val; + + if (opt_debug_threads) + applog(LOG_DEBUG, "%s: want_longpoll=%d have_longpoll=%d", + __func__, want_longpoll, have_longpoll); gettimeofday(&tv_start, NULL); - val = json_rpc_call(curl, rpc_url, rpc_userpass, rpc_req, - want_longpoll, false, NULL); + /* want_longpoll/have_longpoll required here to init/unlock the lp thread */ + val = json_rpc_call_pool(curl, pool, rpc_req, want_longpoll, have_longpoll, NULL); gettimeofday(&tv_end, NULL); - if (have_stratum) { + if (have_stratum || unlikely(work->pooln != cur_pooln)) { if (val) json_decref(val); - return true; + return false; } if (!val) @@ -1056,20 +1113,32 @@ static bool workio_get_work(struct workio_cmd *wc, CURL *curl) struct work *ret_work; int failures = 0; - ret_work = (struct work*)aligned_calloc(sizeof(*ret_work)); + ret_work = (struct work*)aligned_calloc(sizeof(struct work)); if (!ret_work) return false; + /* assign pool number before rpc calls */ + ret_work->pooln = wc->pooln; + // applog(LOG_DEBUG, "%s: pool %d", __func__, wc->pooln); + /* obtain new work from bitcoin via JSON-RPC */ while (!get_upstream_work(curl, ret_work)) { + + if (unlikely(ret_work->pooln != cur_pooln)) { + applog(LOG_ERR, "get_work json_rpc_call failed"); + aligned_free(ret_work); + tq_push(wc->thr->q, NULL); + return true; + } + if (unlikely((opt_retries >= 0) && (++failures > opt_retries))) { - applog(LOG_ERR, "json_rpc_call failed, terminating workio thread"); + applog(LOG_ERR, "get_work json_rpc_call failed"); aligned_free(ret_work); return false; } /* pause, then restart work-request loop */ - applog(LOG_ERR, "json_rpc_call failed, retry after %d seconds", + applog(LOG_ERR, "get_work failed, retry after %d seconds", opt_fail_pause); sleep(opt_fail_pause); } @@ -1084,14 +1153,19 @@ static bool workio_get_work(struct workio_cmd *wc, CURL *curl) static bool workio_submit_work(struct workio_cmd *wc, CURL *curl) { int failures = 0; + uint32_t pooln = wc->pooln; + // applog(LOG_DEBUG, "%s: pool %d", __func__, wc->pooln); /* submit solution to bitcoin via JSON-RPC */ while (!submit_upstream_work(curl, wc->u.work)) { + if (pooln != cur_pooln) { + applog(LOG_DEBUG, "work from pool %u discarded", pooln); + return true; + } if (unlikely((opt_retries >= 0) && (++failures > opt_retries))) { applog(LOG_ERR, "...terminating workio thread"); return false; } - /* pause, then restart work-request loop */ if (!opt_benchmark) applog(LOG_ERR, "...retry after %d seconds", opt_fail_pause); @@ -1138,12 +1212,24 @@ static void *workio_thread(void *userdata) break; } + if (!ok && pool_is_switching) { + if (opt_debug_threads) + applog(LOG_DEBUG, "%s died during switch, recovering", __func__); + ok = true; + } + if (!ok && num_pools > 1) { + if (opt_debug_threads) + applog(LOG_DEBUG, "%s died, failover", __func__); + ok = true; + } + workio_cmd_free(wc); } - tq_freeze(mythr->q); + if (opt_debug_threads) + applog(LOG_DEBUG, "%s() died", __func__); curl_easy_cleanup(curl); - + tq_freeze(mythr->q); return NULL; } @@ -1169,6 +1255,7 @@ static bool get_work(struct thr_info *thr, struct work *work) wc->cmd = WC_GET_WORK; wc->thr = thr; + wc->pooln = cur_pooln; /* send work request to workio thread */ if (!tq_push(thr_info[work_thr_id].q, wc)) { @@ -1202,7 +1289,8 @@ static bool submit_work(struct thr_info *thr, const struct work *work_in) wc->cmd = WC_SUBMIT_WORK; wc->thr = thr; - memcpy(wc->u.work, work_in, sizeof(*work_in)); + memcpy(wc->u.work, work_in, sizeof(struct work)); + wc->pooln = work_in->pooln; /* send solution to workio thread */ if (!tq_push(thr_info[work_thr_id].q, wc)) @@ -1225,7 +1313,7 @@ static void stratum_gen_work(struct stratum_ctx *sctx, struct work *work) return; } - pthread_mutex_lock(&sctx->work_lock); + pthread_mutex_lock(&stratum_work_lock); // store the job ntime as high part of jobid snprintf(work->job_id, sizeof(work->job_id), "%07x %s", @@ -1235,6 +1323,8 @@ static void stratum_gen_work(struct stratum_ctx *sctx, struct work *work) // also store the bloc number work->height = sctx->job.height; + // and the pool of the current stratum + work->pooln = sctx->pooln; /* Generate merkle root */ switch (opt_algo) { @@ -1301,7 +1391,7 @@ static void stratum_gen_work(struct stratum_ctx *sctx, struct work *work) // applog(LOG_DEBUG, "DEBUG: vote=%hx reward=%hx", ext[0], ext[1]); } - pthread_mutex_unlock(&sctx->work_lock); + pthread_mutex_unlock(&stratum_work_lock); if (opt_debug) { uint32_t utm = work->data[17]; @@ -1349,13 +1439,14 @@ void restart_threads(void) static bool wanna_mine(int thr_id) { bool state = true; + bool allow_pool_rotate = (thr_id == 0 && num_pools > 1 && !pool_is_switching); if (opt_max_temp > 0.0) { #ifdef USE_WRAPNVML struct cgpu_info * cgpu = &thr_info[thr_id].gpu; float temp = gpu_temp(cgpu); if (temp > opt_max_temp) { - if (conditional_state && !opt_quiet) + if (!conditional_state[thr_id] && !opt_quiet) applog(LOG_INFO, "GPU #%d: temperature too high (%.0f°c), waiting...", device_map[thr_id], temp); state = false; @@ -1363,19 +1454,25 @@ static bool wanna_mine(int thr_id) #endif } if (opt_max_diff > 0.0 && net_diff > opt_max_diff) { - if (conditional_state && !opt_quiet) + int next = pool_get_first_valid(cur_pooln+1); + if (num_pools > 1 && pools[next].max_diff != pools[cur_pooln].max_diff) + conditional_pool_rotate = allow_pool_rotate; + if (!conditional_state[thr_id] && !opt_quiet && !thr_id) applog(LOG_INFO, "network diff too high, waiting..."); state = false; } if (opt_max_rate > 0.0 && net_hashrate > opt_max_rate) { - if (conditional_state && !opt_quiet) { + int next = pool_get_first_valid(cur_pooln+1); + if (pools[next].max_rate != pools[cur_pooln].max_rate) + conditional_pool_rotate = allow_pool_rotate; + if (!conditional_state[thr_id] && !opt_quiet && !thr_id) { char rate[32]; format_hashrate(opt_max_rate, rate); applog(LOG_INFO, "network hashrate too high, waiting %s...", rate); } state = false; } - conditional_state = state; + conditional_state[thr_id] = (uint8_t) !state; return state; } @@ -1386,7 +1483,6 @@ static void *miner_thread(void *userdata) struct work work; uint32_t max_nonce; uint32_t end_nonce = UINT32_MAX / opt_n_threads * (thr_id + 1) - (thr_id + 1); - time_t firstwork_time = 0; bool work_done = false; bool extrajob = false; char s[16]; @@ -1477,7 +1573,7 @@ static void *miner_thread(void *userdata) pthread_mutex_lock(&g_work_lock); if ((time(NULL) - g_work_time) >= scan_time || nonceptr[0] >= (end_nonce - 0x100)) { if (opt_debug && g_work_time && !opt_quiet) - applog(LOG_DEBUG, "work time %u/%us nonce %x/%x", time(NULL) - g_work_time, + applog(LOG_DEBUG, "work time %u/%us nonce %x/%x", (time(NULL) - g_work_time), scan_time, nonceptr[0], end_nonce); /* obtain new work from internal workio thread */ if (unlikely(!get_work(mythr, &g_work))) { @@ -1535,13 +1631,28 @@ static void *miner_thread(void *userdata) /* conditional mining */ if (!wanna_mine(thr_id)) { - sleep(5); + + // conditional pool switch + if (num_pools > 1 && conditional_pool_rotate) { + if (!pool_is_switching) + pool_switch_next(); + else if (time(NULL) - firstwork_time > 35) { + if (!opt_quiet) + applog(LOG_WARNING, "Pool switching timed out (1)..."); + pools[cur_pooln].wait_time += 1; + pool_is_switching = false; + } + sleep(1); + continue; + } + + sleep(5); pools[cur_pooln].wait_time += 5; continue; } /* prevent gpu scans before a job is received */ - if (have_stratum && !firstwork_time && work.data[0] == 0) { - sleep(1); + if (have_stratum && work.data[0] == 0) { + sleep(1); pools[cur_pooln].wait_time += 1; continue; } @@ -1556,16 +1667,31 @@ static void *miner_thread(void *userdata) int passed = (int)(time(NULL) - firstwork_time); int remain = (int)(opt_time_limit - passed); if (remain < 0) { + if (num_pools > 1 && pools[cur_pooln].time_limit) { + if (!pool_is_switching) { + if (!opt_quiet) + applog(LOG_INFO, "Pool mining timeout of %ds reached, rotate...", opt_time_limit); + pool_switch_next(); + } else if (passed > 35) { + // ensure we dont stay locked if pool_is_switching is not reset... + applog(LOG_WARNING, "Pool switch to %d timed out...", cur_pooln); + pools[cur_pooln].wait_time += 1; + pool_is_switching = false; + } + sleep(1); + continue; + } app_exit_code = EXIT_CODE_TIME_LIMIT; abort_flag = true; if (opt_benchmark) { char rate[32]; - format_hashrate(global_hashrate, rate); + format_hashrate((double)global_hashrate, rate); applog(LOG_NOTICE, "Benchmark: %s", rate); usleep(200*1000); fprintf(stderr, "%llu\n", (long long unsigned int) global_hashrate); } else { - applog(LOG_NOTICE, "Mining timeout of %ds reached, exiting...", opt_time_limit); + applog(LOG_NOTICE, + "Mining timeout of %ds reached, exiting...", opt_time_limit); } workio_abort(); break; @@ -1873,6 +1999,9 @@ static void *miner_thread(void *userdata) applog(LOG_NOTICE, "Total: %s", s); } + // since pool start + pools[cur_pooln].work_time = (uint32_t) (time(NULL) - firstwork_time); + // X-Mining-Hashrate global_hashrate = llround(hashrate); } @@ -1911,38 +2040,51 @@ static void *miner_thread(void *userdata) } out: + if (opt_debug_threads) + applog(LOG_DEBUG, "%s() died", __func__); tq_freeze(mythr->q); - return NULL; } static void *longpoll_thread(void *userdata) { struct thr_info *mythr = (struct thr_info *)userdata; + struct pool_infos *pool; CURL *curl = NULL; - char *copy_start, *hdr_path = NULL, *lp_url = NULL; + char *hdr_path = NULL, *lp_url = NULL; bool need_slash = false; + int pooln, switchn; curl = curl_easy_init(); if (unlikely(!curl)) { - applog(LOG_ERR, "CURL initialization failed"); + applog(LOG_ERR, "%s() CURL init failed", __func__); goto out; } -start: - hdr_path = (char*)tq_pop(mythr->q, NULL); +wait_lp_url: + hdr_path = (char*)tq_pop(mythr->q, NULL); // wait /LP url if (!hdr_path) goto out; + if (!(pools[cur_pooln].type & POOL_STRATUM)) { + pooln = cur_pooln; + pool = &pools[pooln]; + } else { + // hack... + have_stratum = true; + } + + // to detect pool switch during loop + switchn = pool_switch_count; + /* full URL */ if (strstr(hdr_path, "://")) { lp_url = hdr_path; hdr_path = NULL; } - /* absolute path, on current server */ else { - copy_start = (*hdr_path == '/') ? (hdr_path + 1) : hdr_path; + char *copy_start = (*hdr_path == '/') ? (hdr_path + 1) : hdr_path; if (rpc_url[strlen(rpc_url) - 1] != '/') need_slash = true; @@ -1953,18 +2095,32 @@ start: sprintf(lp_url, "%s%s%s", rpc_url, need_slash ? "/" : "", copy_start); } - applog(LOG_INFO, "Long-polling enabled on %s", lp_url); + if (!pool_is_switching) + applog(LOG_BLUE, "Long-polling on %s", lp_url); + + pool_is_switching = false; + + pool->type |= POOL_LONGPOLL; + +longpoll_retry: while (1) { - json_t *val, *soval; - int err; + json_t *val = NULL, *soval; + int err = 0; - val = json_rpc_call(curl, lp_url, rpc_userpass, rpc_req, - false, true, &err); - if (have_stratum) { + if (opt_debug_threads) + applog(LOG_DEBUG, "longpoll %d: %d count %d %d, switching=%d, have_stratum=%d", + pooln, cur_pooln, switchn, pool_switch_count, pool_is_switching, have_stratum); + + // exit on pool switch + if (switchn != pool_switch_count) + goto need_reinit; + + val = json_rpc_longpoll(curl, lp_url, pool, rpc_req, &err); + if (have_stratum || switchn != pool_switch_count) { if (val) json_decref(val); - goto out; + goto need_reinit; } if (likely(val)) { soval = json_object_get(json_object_get(val, "result"), "submitold"); @@ -1984,22 +2140,22 @@ start: pthread_mutex_unlock(&g_work_lock); json_decref(val); } else { - pthread_mutex_lock(&g_work_lock); - g_work_time -= LP_SCANTIME; - pthread_mutex_unlock(&g_work_lock); - restart_threads(); + // to check... + g_work_time = 0; if (err != CURLE_OPERATION_TIMEDOUT) { - have_longpoll = false; - free(hdr_path); - free(lp_url); - lp_url = NULL; + if (opt_debug_threads) applog(LOG_DEBUG, "%s() err %d, retry in %s seconds", + __func__, err, opt_fail_pause); sleep(opt_fail_pause); - goto start; + goto longpoll_retry; } } } out: + have_longpoll = false; + if (opt_debug_threads) + applog(LOG_DEBUG, "%s() died", __func__); + free(hdr_path); free(lp_url); tq_freeze(mythr->q); @@ -2007,6 +2163,15 @@ out: curl_easy_cleanup(curl); return NULL; + +need_reinit: + /* this thread should not die to allow pool switch */ + have_longpoll = false; + if (opt_debug_threads) + applog(LOG_DEBUG, "%s() reinit...", __func__); + if (hdr_path) free(hdr_path); hdr_path = NULL; + if (lp_url) free(lp_url); lp_url = NULL; + goto wait_lp_url; } static bool stratum_handle_response(char *buf) @@ -2038,7 +2203,7 @@ static bool stratum_handle_response(char *buf) // store time required to the pool to answer to a submit stratum.answer_msec = (1000 * diff.tv_sec) + (uint32_t) (0.001 * diff.tv_usec); - share_result(json_is_true(res_val), + share_result(json_is_true(res_val), stratum.pooln, err_val ? json_string_value(json_array_get(err_val, 1)) : NULL); ret = true; @@ -2052,49 +2217,70 @@ out: static void *stratum_thread(void *userdata) { struct thr_info *mythr = (struct thr_info *)userdata; + struct pool_infos *pool; + stratum_ctx *ctx = &stratum; + int pooln, switchn; char *s; +wait_stratum_url: stratum.url = (char*)tq_pop(mythr->q, NULL); if (!stratum.url) goto out; - applog(LOG_BLUE, "Starting on %s", stratum.url); + + if (!pool_is_switching) + applog(LOG_BLUE, "Starting on %s", stratum.url); + + ctx->pooln = pooln = cur_pooln; + switchn = pool_switch_count; + pool = &pools[pooln]; + + pool_is_switching = false; + stratum_need_reset = false; while (1) { int failures = 0; if (stratum_need_reset) { stratum_need_reset = false; - stratum_disconnect(&stratum); - if (strcmp(stratum.url, rpc_url)) { - free(stratum.url); - stratum.url = strdup(rpc_url); - applog(LOG_BLUE, "Connection changed to %s", short_url); - } else if (!opt_quiet) { - applog(LOG_DEBUG, "Stratum connection reset"); - } + if (stratum.url) + stratum_disconnect(&stratum); + else + stratum.url = strdup(pool->url); // may be useless } while (!stratum.curl) { pthread_mutex_lock(&g_work_lock); g_work_time = 0; + g_work.data[0] = 0; pthread_mutex_unlock(&g_work_lock); restart_threads(); - if (!stratum_connect(&stratum, stratum.url) || + if (!stratum_connect(&stratum, pool->url) || !stratum_subscribe(&stratum) || - !stratum_authorize(&stratum, rpc_user, rpc_pass)) { + !stratum_authorize(&stratum, pool->user, pool->pass)) + { stratum_disconnect(&stratum); if (opt_retries >= 0 && ++failures > opt_retries) { - applog(LOG_ERR, "...terminating workio thread"); - tq_push(thr_info[work_thr_id].q, NULL); - goto out; + if (opt_pool_failover) { + applog(LOG_WARNING, "Stratum connect timeout, failover..."); + pool_switch_next(); + } else { + applog(LOG_ERR, "...terminating workio thread"); + //tq_push(thr_info[work_thr_id].q, NULL); + workio_abort(); + goto out; + } } + if (switchn != pool_switch_count) + goto pool_switched; if (!opt_benchmark) applog(LOG_ERR, "...retry after %d seconds", opt_fail_pause); sleep(opt_fail_pause); } } + if (switchn != pool_switch_count) goto pool_switched; + if (stratum.job.job_id && (!g_work_time || strncmp(stratum.job.job_id, g_work.job_id + 8, 120))) { pthread_mutex_lock(&g_work_lock); @@ -2106,7 +2292,7 @@ static void *stratum_thread(void *userdata) applog(LOG_BLUE, "%s block %d, diff %.2f", algo_names[opt_algo], stratum.job.height, net_diff); else - applog(LOG_BLUE, "%s %s block %d", short_url, algo_names[opt_algo], + applog(LOG_BLUE, "%s %s block %d", pool->short_url, algo_names[opt_algo], stratum.job.height); } restart_threads(); @@ -2114,20 +2300,28 @@ static void *stratum_thread(void *userdata) hashlog_purge_old(); stats_purge_old(); } else if (opt_debug && !opt_quiet) { - applog(LOG_BLUE, "%s asks job %d for block %d", short_url, + applog(LOG_BLUE, "%s asks job %d for block %d", pool->short_url, strtoul(stratum.job.job_id, NULL, 16), stratum.job.height); } pthread_mutex_unlock(&g_work_lock); } - if (!stratum_socket_full(&stratum, 120)) { - applog(LOG_ERR, "Stratum connection timed out"); + // check we are on the right pool + if (switchn != pool_switch_count) goto pool_switched; + + if (!stratum_socket_full(&stratum, opt_timeout)) { + if (opt_debug) + applog(LOG_WARNING, "Stratum connection timed out"); s = NULL; } else s = stratum_recv_line(&stratum); + + // double check we are on the right pool + if (switchn != pool_switch_count) goto pool_switched; + if (!s) { stratum_disconnect(&stratum); - applog(LOG_ERR, "Stratum connection interrupted"); + applog(LOG_WARNING, "Stratum connection interrupted"); continue; } if (!stratum_handle_method(&stratum, s)) @@ -2136,7 +2330,231 @@ static void *stratum_thread(void *userdata) } out: + if (opt_debug_threads) + applog(LOG_DEBUG, "%s() died", __func__); + return NULL; + +pool_switched: + /* this thread should not die on pool switch */ + stratum_disconnect(&(pools[pooln].stratum)); + if (stratum.url) free(stratum.url); stratum.url = NULL; + if (opt_debug_threads) + applog(LOG_DEBUG, "%s() reinit...", __func__); + goto wait_stratum_url; +} + +// store current credentials in pools container +void pool_set_creds(int pooln) +{ + struct pool_infos *p = &pools[pooln]; + + p->id = pooln; + // default flags not 0 + p->allow_mininginfo = allow_mininginfo; + p->allow_gbt = allow_gbt; + p->check_dups = check_dups; + + snprintf(p->url, sizeof(p->url), "%s", rpc_url); + snprintf(p->short_url, sizeof(p->short_url), "%s", short_url); + snprintf(p->user, sizeof(p->user), "%s", rpc_user); + snprintf(p->pass, sizeof(p->pass), "%s", rpc_pass); + + // init pools options with cmdline ones (if set before -c) + p->max_diff = opt_max_diff; + p->max_rate = opt_max_rate; + p->scantime = opt_scantime; + + if (strlen(rpc_url)) { + if (!strncasecmp(rpc_url, "stratum", 7)) + p->type = POOL_STRATUM; + else /* if (!strncasecmp(rpc_url, "http", 4)) */ + p->type = POOL_GETWORK; // todo: or longpoll + p->status |= POOL_ST_VALID; + } + p->status |= POOL_ST_DEFINED; +} + +// attributes only set by a json pools config +void pool_set_attr(int pooln, const char* key, char* arg) +{ + struct pool_infos *p = &pools[pooln]; + + if (!strcasecmp(key, "name")) { + snprintf(p->name, sizeof(p->name), "%s", arg); + return; + } + if (!strcasecmp(key, "scantime")) { + p->scantime = atoi(arg); + return; + } + if (!strcasecmp(key, "max-diff")) { + p->max_diff = atof(arg); + return; + } + if (!strcasecmp(key, "max-rate")) { + p->max_rate = atof(arg); + return; + } + if (!strcasecmp(key, "time-limit")) { + p->time_limit = atoi(arg); + return; + } + if (!strcasecmp(key, "removed")) { + int removed = atoi(arg); + if (removed) { + p->status |= POOL_ST_REMOVED; + } + return; + } +} + +// pool switching code +bool pool_switch(int pooln) +{ + int prevn = cur_pooln; + struct pool_infos *prev = &pools[cur_pooln]; + struct pool_infos* p = NULL; + + // save prev stratum connection infos (struct) + if (prev->type & POOL_STRATUM) { + // may not be the right moment to free, + // to check if required on submit... + stratum_free_job(&stratum); + prev->stratum = stratum; + } + + if (pooln < num_pools) { + cur_pooln = pooln; + p = &pools[cur_pooln]; + } else { + applog(LOG_ERR, "Switch to inexistant pool %d!", pooln); + return false; + } + + // save global attributes + prev->allow_mininginfo = allow_mininginfo; + prev->allow_gbt = allow_gbt; + prev->check_dups = check_dups; + + pthread_mutex_lock(&g_work_lock); + + free(rpc_user); rpc_user = strdup(p->user); + free(rpc_pass); rpc_pass = strdup(p->pass); + free(rpc_url); rpc_url = strdup(p->url); + + short_url = p->short_url; // just a pointer, no alloc + + opt_scantime = p->scantime; + + opt_max_diff = p->max_diff; + opt_max_rate = p->max_rate; + opt_time_limit = p->time_limit; + + want_stratum = have_stratum = (p->type & POOL_STRATUM) != 0; + + pthread_mutex_unlock(&g_work_lock); + + if (prevn != cur_pooln) { + + pool_switch_count++; + g_work_time = 0; + g_work.data[0] = 0; + pool_is_switching = true; + stratum_need_reset = true; + // used to get the pool uptime + firstwork_time = time(NULL); + restart_threads(); + + // restore flags + allow_gbt = p->allow_gbt; + allow_mininginfo = p->allow_mininginfo; + check_dups = p->check_dups; + + if (want_stratum) { + + // temporary... until stratum code cleanup + stratum = p->stratum; + stratum.pooln = cur_pooln; + + // unlock the stratum thread + tq_push(thr_info[stratum_thr_id].q, strdup(rpc_url)); + applog(LOG_BLUE, "Switch to stratum pool %d: %s", cur_pooln, + strlen(p->name) ? p->name : p->short_url); + } else { + applog(LOG_BLUE, "Switch to pool %d: %s", cur_pooln, + strlen(p->name) ? p->name : p->short_url); + } + + // will unlock the longpoll thread on /LP url receive + want_longpoll = (p->type & POOL_LONGPOLL) || !(p->type & POOL_STRATUM); + if (want_longpoll) { + pthread_mutex_lock(&g_work_lock); + // will issue a lp_url request to unlock the longpoll thread + have_longpoll = false; + get_work(&thr_info[0], &g_work); + pthread_mutex_unlock(&g_work_lock); + } + + } + return true; +} + +// search available pool +int pool_get_first_valid(int startfrom) +{ + int next = 0; + struct pool_infos *p; + for (int i=0; istatus & POOL_ST_VALID)) + continue; + if (p->status & (POOL_ST_DISABLED | POOL_ST_REMOVED)) + continue; + next = pooln; + break; + } + return next; +} + +// switch to next available pool +bool pool_switch_next() +{ + if (num_pools > 1) { + int pooln = pool_get_first_valid(cur_pooln+1); + return pool_switch(pooln); + } else { + // no switch possible + if (!opt_quiet) + applog(LOG_DEBUG, "No other pools to try..."); + return false; + } +} + +// seturl from api remote +bool pool_switch_url(char *params) +{ + int prevn = cur_pooln, nextn; + parse_arg('o', params); + // cur_pooln modified by parse_arg('o'), get new pool num + nextn = cur_pooln; + // and to handle the "hot swap" from current one... + cur_pooln = prevn; + if (nextn == prevn) + return false; + return pool_switch(nextn); +} + +// debug stuff +void pool_dump_infos() +{ + struct pool_infos *p; + for (int i=0; ishort_url, p->user, p->scantime); + } } static void show_version_and_exit(void) @@ -2293,6 +2711,7 @@ void parse_arg(int key, char *arg) case 'p': free(rpc_pass); rpc_pass = strdup(arg); + pool_set_creds(cur_pooln); break; case 'P': opt_protocol = true; @@ -2339,8 +2758,17 @@ void parse_arg(int key, char *arg) case 'u': free(rpc_user); rpc_user = strdup(arg); + pool_set_creds(cur_pooln); break; case 'o': /* --url */ + if (pools[cur_pooln].type != POOL_UNUSED) { + // rotate pool pointer + cur_pooln = (cur_pooln + 1) % MAX_POOLS; + num_pools = max(cur_pooln+1, num_pools); + // change some defaults if multi pools + if (opt_retries == -1) opt_retries = 1; + if (opt_fail_pause == 30) opt_fail_pause = 5; + } p = strstr(arg, "://"); if (p) { if (strncasecmp(arg, "http://", 7) && strncasecmp(arg, "https://", 8) && @@ -2364,8 +2792,6 @@ void parse_arg(int key, char *arg) ap = strstr(rpc_url, "://") + 3; sp = strchr(ap, ':'); if (sp && sp < p) { - free(rpc_userpass); - rpc_userpass = strdup(ap); free(rpc_user); rpc_user = (char*)calloc(sp - ap + 1, 1); strncpy(rpc_user, ap, sp - ap); @@ -2381,18 +2807,18 @@ void parse_arg(int key, char *arg) short_url = ap; } have_stratum = !opt_benchmark && !strncasecmp(rpc_url, "stratum", 7); + pool_set_creds(cur_pooln); break; case 'O': /* --userpass */ p = strchr(arg, ':'); if (!p) show_usage_and_exit(1); - free(rpc_userpass); - rpc_userpass = strdup(arg); free(rpc_user); rpc_user = (char*)calloc(p - arg + 1, 1); strncpy(rpc_user, arg, p - arg); free(rpc_pass); rpc_pass = strdup(p + 1); + pool_set_creds(cur_pooln); break; case 'x': /* --proxy */ if (!strncasecmp(arg, "socks4://", 9)) @@ -2409,6 +2835,7 @@ void parse_arg(int key, char *arg) opt_proxy_type = CURLPROXY_HTTP; free(opt_proxy); opt_proxy = strdup(arg); + pool_set_creds(cur_pooln); break; case 1001: free(opt_cert); @@ -2554,6 +2981,28 @@ void parse_arg(int key, char *arg) show_usage_and_exit(1); opt_difficulty = d; break; + + /* PER POOL CONFIG OPTIONS */ + + case 1100: /* pool name */ + pool_set_attr(cur_pooln, "name", arg); + break; + case 1101: /* pool removed */ + pool_set_attr(cur_pooln, "removed", arg); + break; + case 1102: /* pool scantime */ + pool_set_attr(cur_pooln, "scantime", arg); + break; + case 1108: /* pool time-limit */ + pool_set_attr(cur_pooln, "time-limit", arg); + break; + case 1161: /* pool max-diff */ + pool_set_attr(cur_pooln, "max-diff", arg); + break; + case 1162: /* pool max-rate */ + pool_set_attr(cur_pooln, "max-rate", arg); + break; + case 'V': show_version_and_exit(); case 'h': @@ -2567,24 +3016,91 @@ void parse_arg(int key, char *arg) } /** - * Parse json config file + * Parse json config */ -static void parse_config(void) +static bool parse_pool_array(json_t *obj) +{ + size_t idx; + json_t *p, *val; + + if (!json_is_array(obj)) + return false; + + // array of objects [ {}, {} ] + json_array_foreach(obj, idx, p) + { + if (!json_is_object(p)) + continue; + + for (int i = 0; i < ARRAY_SIZE(cfg_array_keys); i++) + { + int opt = -1; + char *s = NULL; + if (cfg_array_keys[i].cat != CFG_POOL) + continue; + + val = json_object_get(p, cfg_array_keys[i].name); + if (!val) + continue; + + for (int k = 0; k < ARRAY_SIZE(options); k++) + { + const char *alias = cfg_array_keys[i].longname; + if (alias && !strcasecmp(options[k].name, alias)) { + opt = k; + break; + } + if (!alias && !strcasecmp(options[k].name, cfg_array_keys[i].name)) { + opt = k; + break; + } + } + if (opt == -1) + continue; + + if (json_is_string(val)) { + s = strdup(json_string_value(val)); + if (!s) + continue; + + // applog(LOG_DEBUG, "pool key %s '%s'", options[opt].name, s); + parse_arg(options[opt].val, s); + free(s); + } else { + // numeric or bool + char buf[32] = { 0 }; + double d = 0.; + if (json_is_true(val)) d = 1.; + else if (json_is_integer(val)) + d = 1.0 * json_integer_value(val); + else if (json_is_real(val)) + d = json_real_value(val); + snprintf(buf, sizeof(buf)-1, "%f", d); + // applog(LOG_DEBUG, "pool key %s '%f'", options[opt].name, d); + parse_arg(options[opt].val, buf); + } + } + } + return true; +} + +void parse_config(json_t* json_obj) { int i; json_t *val; - if (!json_is_object(opt_config)) + if (!json_is_object(json_obj)) return; for (i = 0; i < ARRAY_SIZE(options); i++) { if (!options[i].name) break; - if (!strcmp(options[i].name, "config")) + + if (!strcasecmp(options[i].name, "config")) continue; - val = json_object_get(opt_config, options[i].name); + val = json_object_get(json_obj, options[i].name); if (!val) continue; @@ -2613,6 +3129,11 @@ static void parse_config(void) applog(LOG_ERR, "JSON option %s invalid", options[i].name); } + + val = json_object_get(json_obj, "pools"); + if (val && json_typeof(val) == JSON_ARRAY) { + parse_pool_array(val); + } } static void parse_cmdline(int argc, char *argv[]) @@ -2636,7 +3157,7 @@ static void parse_cmdline(int argc, char *argv[]) show_usage_and_exit(1); } - parse_config(); + parse_config(opt_config); if (opt_algo == ALGO_HEAVY && opt_vote == 9999) { fprintf(stderr, "%s: Heavycoin hash requires block reward vote parameter (see --vote)\n", @@ -2767,20 +3288,18 @@ int main(int argc, char *argv[]) show_usage_and_exit(1); } - if (!rpc_userpass) { - rpc_userpass = (char*)malloc(strlen(rpc_user) + strlen(rpc_pass) + 2); - if (!rpc_userpass) - return 1; - sprintf(rpc_userpass, "%s:%s", rpc_user, rpc_pass); - } - /* init stratum data.. */ memset(&stratum.url, 0, sizeof(stratum)); + pthread_mutex_init(&stratum_sock_lock, NULL); + pthread_mutex_init(&stratum_work_lock, NULL); pthread_mutex_init(&stats_lock, NULL); pthread_mutex_init(&g_work_lock, NULL); - pthread_mutex_init(&stratum.sock_lock, NULL); - pthread_mutex_init(&stratum.work_lock, NULL); + + if (opt_debug) + pool_dump_infos(); + cur_pooln = pool_get_first_valid(0); + pool_switch(cur_pooln); flags = !opt_benchmark && strncmp(rpc_url, "https:", 6) ? (CURL_GLOBAL_ALL & ~CURL_GLOBAL_SSL) @@ -2867,53 +3386,50 @@ int main(int argc, char *argv[]) if (!thr_info) return EXIT_CODE_SW_INIT_ERROR; - /* init workio thread info */ - work_thr_id = opt_n_threads; - thr = &thr_info[work_thr_id]; - thr->id = work_thr_id; + /* longpoll thread */ + longpoll_thr_id = opt_n_threads + 1; + thr = &thr_info[longpoll_thr_id]; + thr->id = longpoll_thr_id; thr->q = tq_new(); if (!thr->q) return EXIT_CODE_SW_INIT_ERROR; - /* start work I/O thread */ - if (pthread_create(&thr->pth, NULL, workio_thread, thr)) { - applog(LOG_ERR, "workio thread create failed"); + /* always start the longpoll thread (will wait a tq_push on workio /LP) */ + if (unlikely(pthread_create(&thr->pth, NULL, longpoll_thread, thr))) { + applog(LOG_ERR, "longpoll thread create failed"); return EXIT_CODE_SW_INIT_ERROR; } - if (want_longpoll && !have_stratum) { - /* init longpoll thread info */ - longpoll_thr_id = opt_n_threads + 1; - thr = &thr_info[longpoll_thr_id]; - thr->id = longpoll_thr_id; - thr->q = tq_new(); - if (!thr->q) - return EXIT_CODE_SW_INIT_ERROR; + /* stratum thread */ + stratum_thr_id = opt_n_threads + 2; + thr = &thr_info[stratum_thr_id]; + thr->id = stratum_thr_id; + thr->q = tq_new(); + if (!thr->q) + return EXIT_CODE_SW_INIT_ERROR; - /* start longpoll thread */ - if (unlikely(pthread_create(&thr->pth, NULL, longpoll_thread, thr))) { - applog(LOG_ERR, "longpoll thread create failed"); - return EXIT_CODE_SW_INIT_ERROR; - } + /* always start the stratum thread (will wait a tq_push) */ + if (unlikely(pthread_create(&thr->pth, NULL, stratum_thread, thr))) { + applog(LOG_ERR, "stratum thread create failed"); + return EXIT_CODE_SW_INIT_ERROR; } - if (want_stratum) { - /* init stratum thread info */ - stratum_thr_id = opt_n_threads + 2; - thr = &thr_info[stratum_thr_id]; - thr->id = stratum_thr_id; - thr->q = tq_new(); - if (!thr->q) - return EXIT_CODE_SW_INIT_ERROR; + /* init workio thread */ + work_thr_id = opt_n_threads; + thr = &thr_info[work_thr_id]; + thr->id = work_thr_id; + thr->q = tq_new(); + if (!thr->q) + return EXIT_CODE_SW_INIT_ERROR; - /* start stratum thread */ - if (unlikely(pthread_create(&thr->pth, NULL, stratum_thread, thr))) { - applog(LOG_ERR, "stratum thread create failed"); - return EXIT_CODE_SW_INIT_ERROR; - } + if (pthread_create(&thr->pth, NULL, workio_thread, thr)) { + applog(LOG_ERR, "workio thread create failed"); + return EXIT_CODE_SW_INIT_ERROR; + } - if (have_stratum) - tq_push(thr_info[stratum_thr_id].q, strdup(rpc_url)); + /* real start of the stratum work */ + if (want_stratum && have_stratum) { + tq_push(thr_info[stratum_thr_id].q, strdup(rpc_url)); } #ifdef USE_WRAPNVML diff --git a/hashlog.cpp b/hashlog.cpp index c30eb28..876ce3a 100644 --- a/hashlog.cpp +++ b/hashlog.cpp @@ -18,13 +18,17 @@ /* from miner.h struct hashlog_data { - uint32_t tm_sent; + uint8_t npool; + uint8_t pool_type; uint32_t height; + uint32_t njobid; + uint32_t nonce; uint32_t scanned_from; uint32_t scanned_to; uint32_t last_from; uint32_t tm_add; uint32_t tm_upd; + uint32_t tm_sent; }; */ @@ -75,6 +79,8 @@ void hashlog_remember_submit(struct work* work, uint32_t nonce) data.height = work->height; data.njobid = (uint32_t) njobid; data.tm_add = data.tm_upd = data.tm_sent = (uint32_t) time(NULL); + data.npool = (uint8_t) cur_pooln; + data.pool_type = pools[cur_pooln].type; tlastshares[key] = data; } diff --git a/miner.h b/miner.h index eb8c734..8cb5db4 100644 --- a/miner.h +++ b/miner.h @@ -423,16 +423,25 @@ struct stats_data { uint32_t tm_stat; uint32_t hashcount; uint32_t height; + double difficulty; double hashrate; + uint8_t thr_id; uint8_t gpu_id; uint8_t hashfound; uint8_t ignored; + + uint8_t npool; + uint8_t pool_type; + uint16_t align; }; struct hashlog_data { - uint32_t tm_sent; + uint8_t npool; + uint8_t pool_type; + uint16_t align; + uint32_t height; uint32_t njobid; uint32_t nonce; @@ -441,6 +450,7 @@ struct hashlog_data { uint32_t last_from; uint32_t tm_add; uint32_t tm_upd; + uint32_t tm_sent; }; /* end of api */ @@ -529,8 +539,6 @@ extern uint32_t gpus_intensity[MAX_GPUS]; extern void format_hashrate(double hashrate, char *output); extern void applog(int prio, const char *fmt, ...); void get_defconfig_path(char *out, size_t bufsize, char *argv0); -extern json_t *json_rpc_call(CURL *curl, const char *url, const char *userpass, - const char *rpc_req, bool, bool, int *); extern void cbin2hex(char *out, const char *in, size_t len); extern char *bin2hex(const unsigned char *in, size_t len); extern bool hex2bin(unsigned char *p, const char *hexstr, size_t len); @@ -567,7 +575,6 @@ struct stratum_ctx { curl_socket_t sock; size_t sockbuf_size; char *sockbuf; - pthread_mutex_t sock_lock; double next_diff; @@ -576,11 +583,10 @@ struct stratum_ctx { unsigned char *xnonce1; size_t xnonce2_size; struct stratum_job job; - pthread_mutex_t work_lock; struct timeval tv_submit; uint32_t answer_msec; - uint32_t disconnects; + int pooln; time_t tm_connected; int srvtime_diff; @@ -602,11 +608,68 @@ struct work { double difficulty; uint32_t height; + uint8_t pooln; uint32_t scanned_from; uint32_t scanned_to; }; +#define MAX_POOLS 8 +struct pool_infos { + uint8_t id; +#define POOL_UNUSED 0 +#define POOL_GETWORK 1 +#define POOL_STRATUM 2 +#define POOL_LONGPOLL 4 + uint8_t type; +#define POOL_ST_DEFINED 1 +#define POOL_ST_VALID 2 +#define POOL_ST_DISABLED 4 +#define POOL_ST_REMOVED 8 + uint16_t status; + char name[64]; + // credentials + char url[256]; + char short_url[64]; + char user[64]; + char pass[64]; + // config options + double max_diff; + double max_rate; + int time_limit; + int scantime; + // connection + struct stratum_ctx stratum; + uint8_t allow_gbt; + uint8_t allow_mininginfo; + uint16_t check_dups; // 16_t for align + int retries; + int fail_pause; + int timeout; + // stats + uint32_t work_time; + uint32_t wait_time; + uint32_t accepted_count; + uint32_t rejected_count; + uint32_t disconnects; +}; + +extern struct pool_infos pools[MAX_POOLS]; +extern int num_pools; +extern volatile int cur_pooln; + +int pool_get_first_valid(int startfrom); +void pool_set_creds(int pooln); +void pool_set_attr(int pooln, const char* key, char* arg); +bool pool_switch_url(char *params); +bool pool_switch(int pooln); +bool pool_switch_next(void); + +json_t * json_rpc_call_pool(CURL *curl, struct pool_infos*, + const char *req, bool lp_scan, bool lp, int *err); +json_t * json_rpc_longpoll(CURL *curl, char *lp_url, struct pool_infos*, + const char *req, int *err); + bool stratum_socket_full(struct stratum_ctx *sctx, int timeout); bool stratum_send_line(struct stratum_ctx *sctx, char *s); char *stratum_recv_line(struct stratum_ctx *sctx); @@ -615,6 +678,7 @@ void stratum_disconnect(struct stratum_ctx *sctx); bool stratum_subscribe(struct stratum_ctx *sctx); bool stratum_authorize(struct stratum_ctx *sctx, const char *user, const char *pass); bool stratum_handle_method(struct stratum_ctx *sctx, const char *s); +void stratum_free_job(struct stratum_ctx *sctx); void hashlog_remember_submit(struct work* work, uint32_t nonce); void hashlog_remember_scan_range(struct work* work); diff --git a/pools.conf b/pools.conf new file mode 100644 index 0000000..892051c --- /dev/null +++ b/pools.conf @@ -0,0 +1,27 @@ +{ + "_note": "Sample rotation of 2 pools", + + "pools":[{ + "name": "Longpoll sample", + "url": "http://ziftr.suprnova.cc:9991", + "user": "tpruvot.win", + "pass": "x", + "max-diff": 0.0, + "time-limit": 180, + "disabled": false + }, + { + "name": "Stratum sample", + "url": "stratum+tcp://zrc-stratum.suprnova.cc:2257", + "user": "tpruvot.elite", + "pass": "x", + "time-limit": 300 + }], + + "algo" : "zr5", + + "api-bind" : "0.0.0.0", + "api-remote" : true, + + "no-gbt": true +} diff --git a/stats.cpp b/stats.cpp index ab2d314..4dd84a2 100644 --- a/stats.cpp +++ b/stats.cpp @@ -41,6 +41,8 @@ void stats_remember_speed(int thr_id, uint32_t hashcount, double hashrate, uint8 data.thr_id = (uint8_t) thr_id; data.tm_stat = (uint32_t) time(NULL); data.height = height; + data.npool = (uint8_t) cur_pooln; + data.pool_type = pools[cur_pooln].type; data.hashcount = hashcount; data.hashfound = found; data.hashrate = hashrate; diff --git a/util.cpp b/util.cpp index 195ad1c..deae91b 100644 --- a/util.cpp +++ b/util.cpp @@ -36,6 +36,9 @@ #include "miner.h" #include "elist.h" +extern pthread_mutex_t stratum_sock_lock; +extern pthread_mutex_t stratum_work_lock; + bool opt_tracegpu = false; struct data_buffer { @@ -385,9 +388,12 @@ static int sockopt_keepalive_cb(void *userdata, curl_socket_t fd, } #endif -json_t *json_rpc_call(CURL *curl, const char *url, +/* For getwork (longpoll or wallet) - not stratum pools! + * DO NOT USE DIRECTLY + */ +static json_t *json_rpc_call(CURL *curl, const char *url, const char *userpass, const char *rpc_req, - bool longpoll_scan, bool longpoll, int *curl_err) + bool longpoll_scan, bool longpoll, bool keepalive, int *curl_err) { json_t *val, *err_val, *res_val; int rc; @@ -395,10 +401,10 @@ json_t *json_rpc_call(CURL *curl, const char *url, struct upload_buffer upload_data; json_error_t err; struct curl_slist *headers = NULL; - char* httpdata; + char *httpdata; char len_hdr[64], hashrate_hdr[64]; char curl_err_str[CURL_ERROR_SIZE] = { 0 }; - long timeout = longpoll ? opt_timeout : 30; + long timeout = longpoll ? opt_timeout*2 : opt_timeout; struct header_info hi = { 0 }; bool lp_scanning = longpoll_scan && !have_longpoll; @@ -435,7 +441,7 @@ json_t *json_rpc_call(CURL *curl, const char *url, curl_easy_setopt(curl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC); } #if LIBCURL_VERSION_NUM >= 0x070f06 - if (longpoll) + if (keepalive) curl_easy_setopt(curl, CURLOPT_SOCKOPTFUNCTION, sockopt_keepalive_cb); #endif curl_easy_setopt(curl, CURLOPT_POST, 1); @@ -486,7 +492,8 @@ json_t *json_rpc_call(CURL *curl, const char *url, } if (!all_data.buf || !all_data.len) { - applog(LOG_ERR, "Empty data received in json_rpc_call."); + if (!have_longpoll) // seems normal on longpoll timeout + applog(LOG_ERR, "Empty data received in json_rpc_call."); goto err_out; } @@ -571,6 +578,31 @@ err_out: return NULL; } +/* getwork calls with pool pointer (wallet/longpoll pools) */ +json_t *json_rpc_call_pool(CURL *curl, struct pool_infos *pool, const char *req, + bool longpoll_scan, bool longpoll, int *curl_err) +{ + char userpass[256]; + // todo, malloc and store that in pool array + snprintf(userpass, sizeof(userpass), "%s%c%s", pool->user, + strlen(pool->pass)?':':'\0', pool->pass); + + return json_rpc_call(curl, pool->url, userpass, req, longpoll_scan, false, false, curl_err); +} + +/* called only from longpoll thread */ +json_t *json_rpc_longpoll(CURL *curl, char *lp_url, struct pool_infos *pool, const char *req, int *curl_err) +{ + char userpass[256]; + snprintf(userpass, sizeof(userpass), "%s%c%s", pool->user, + strlen(pool->pass)?':':'\0', pool->pass); + + // on pool rotate by time-limit, this keepalive can be a problem + bool keepalive = pool->time_limit == 0 || pool->time_limit > opt_timeout*4; + + return json_rpc_call(curl, lp_url, userpass, req, want_longpoll, have_longpoll, keepalive, curl_err); +} + /** * Unlike malloc, calloc set the memory to zero */ @@ -774,9 +806,9 @@ bool stratum_send_line(struct stratum_ctx *sctx, char *s) if (opt_protocol) applog(LOG_DEBUG, "> %s", s); - pthread_mutex_lock(&sctx->sock_lock); + pthread_mutex_lock(&stratum_sock_lock); ret = send_line(sctx->sock, s); - pthread_mutex_unlock(&sctx->sock_lock); + pthread_mutex_unlock(&stratum_sock_lock); return ret; } @@ -797,6 +829,7 @@ static bool socket_full(curl_socket_t sock, int timeout) bool stratum_socket_full(struct stratum_ctx *sctx, int timeout) { + if (!sctx->sockbuf) return false; return strlen(sctx->sockbuf) || socket_full(sctx->sock, timeout); } @@ -821,10 +854,13 @@ char *stratum_recv_line(struct stratum_ctx *sctx) ssize_t len, buflen; char *tok, *sret = NULL; + if (!sctx->sockbuf) + return NULL; + if (!strstr(sctx->sockbuf, "\n")) { bool ret = true; time_t rstart = time(NULL); - if (!socket_full(sctx->sock, 60)) { + if (!socket_full(sctx->sock, opt_timeout)) { applog(LOG_ERR, "stratum_recv_line timed out"); goto out; } @@ -845,7 +881,7 @@ char *stratum_recv_line(struct stratum_ctx *sctx) } } else stratum_buffer_append(sctx, s); - } while (time(NULL) - rstart < 60 && !strstr(sctx->sockbuf, "\n")); + } while (time(NULL) - rstart < opt_timeout && !strstr(sctx->sockbuf, "\n")); if (!ret) { applog(LOG_ERR, "stratum_recv_line failed"); @@ -888,13 +924,13 @@ bool stratum_connect(struct stratum_ctx *sctx, const char *url) CURL *curl; int rc; - pthread_mutex_lock(&sctx->sock_lock); + pthread_mutex_lock(&stratum_sock_lock); if (sctx->curl) curl_easy_cleanup(sctx->curl); sctx->curl = curl_easy_init(); if (!sctx->curl) { applog(LOG_ERR, "CURL initialization failed"); - pthread_mutex_unlock(&sctx->sock_lock); + pthread_mutex_unlock(&stratum_sock_lock); return false; } curl = sctx->curl; @@ -903,21 +939,21 @@ bool stratum_connect(struct stratum_ctx *sctx, const char *url) sctx->sockbuf_size = RBUFSIZE; } sctx->sockbuf[0] = '\0'; - pthread_mutex_unlock(&sctx->sock_lock); + pthread_mutex_unlock(&stratum_sock_lock); if (url != sctx->url) { free(sctx->url); sctx->url = strdup(url); } free(sctx->curl_url); - sctx->curl_url = (char*)malloc(strlen(url)); + sctx->curl_url = (char*)malloc(strlen(url)+1); sprintf(sctx->curl_url, "http%s", strstr(url, "://")); if (opt_protocol) curl_easy_setopt(curl, CURLOPT_VERBOSE, 1); curl_easy_setopt(curl, CURLOPT_URL, sctx->curl_url); curl_easy_setopt(curl, CURLOPT_FRESH_CONNECT, 1); - curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 30); + curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, opt_timeout); curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, sctx->curl_err_str); curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); curl_easy_setopt(curl, CURLOPT_TCP_NODELAY, 1); @@ -957,16 +993,41 @@ bool stratum_connect(struct stratum_ctx *sctx, const char *url) return true; } +void stratum_free_job(struct stratum_ctx *sctx) +{ + pthread_mutex_lock(&stratum_work_lock); + if (sctx->job.job_id) { + free(sctx->job.job_id); + } + if (sctx->job.merkle_count) { + for (int i = 0; i < sctx->job.merkle_count; i++) { + free(sctx->job.merkle[i]); + sctx->job.merkle[i] = NULL; + } + free(sctx->job.merkle); + } + free(sctx->job.coinbase); + // note: xnonce2 is not allocated + memset(&(sctx->job.job_id), 0, sizeof(struct stratum_job)); + pthread_mutex_unlock(&stratum_work_lock); +} + void stratum_disconnect(struct stratum_ctx *sctx) { - pthread_mutex_lock(&sctx->sock_lock); + pthread_mutex_lock(&stratum_sock_lock); if (sctx->curl) { - sctx->disconnects++; + pools[sctx->pooln].disconnects++; curl_easy_cleanup(sctx->curl); sctx->curl = NULL; - sctx->sockbuf[0] = '\0'; + if (sctx->sockbuf) + sctx->sockbuf[0] = '\0'; + // free(sctx->sockbuf); + // sctx->sockbuf = NULL; + } + if (sctx->job.job_id) { + stratum_free_job(sctx); } - pthread_mutex_unlock(&sctx->sock_lock); + pthread_mutex_unlock(&stratum_sock_lock); } static const char *get_stratum_session_id(json_t *val) @@ -1012,19 +1073,19 @@ static bool stratum_parse_extranonce(struct stratum_ctx *sctx, json_t *params, i goto out; } - pthread_mutex_lock(&sctx->work_lock); + pthread_mutex_lock(&stratum_work_lock); if (sctx->xnonce1) free(sctx->xnonce1); sctx->xnonce1_size = strlen(xnonce1) / 2; sctx->xnonce1 = (uchar*) calloc(1, sctx->xnonce1_size); if (unlikely(!sctx->xnonce1)) { applog(LOG_ERR, "Failed to alloc xnonce1"); - pthread_mutex_unlock(&sctx->work_lock); + pthread_mutex_unlock(&stratum_work_lock); goto out; } hex2bin(sctx->xnonce1, xnonce1, sctx->xnonce1_size); sctx->xnonce2_size = xn2_size; - pthread_mutex_unlock(&sctx->work_lock); + pthread_mutex_unlock(&stratum_work_lock); if (pndx == 0 && opt_debug) /* pool dynamic change */ applog(LOG_DEBUG, "Stratum set nonce %s with extranonce2 size=%d", @@ -1103,12 +1164,12 @@ start: if (opt_debug && sid) applog(LOG_DEBUG, "Stratum session id: %s", sid); - pthread_mutex_lock(&sctx->work_lock); + pthread_mutex_lock(&stratum_work_lock); if (sctx->session_id) free(sctx->session_id); sctx->session_id = sid ? strdup(sid) : NULL; sctx->next_diff = 1.0; - pthread_mutex_unlock(&sctx->work_lock); + pthread_mutex_unlock(&stratum_work_lock); out: free(s); @@ -1253,8 +1314,8 @@ static bool stratum_notify(struct stratum_ctx *sctx, json_t *params) bool clean, ret = false; int merkle_count, i; json_t *merkle_arr; - uchar **merkle; - uchar(*merkle_tree)[32] = NULL; + uchar **merkle = NULL; + // uchar(*merkle_tree)[32] = { 0 }; int ntime; job_id = json_string_value(json_array_get(params, 0)); @@ -1287,7 +1348,8 @@ static bool stratum_notify(struct stratum_ctx *sctx, json_t *params) applog(LOG_DEBUG, "stratum time is at least %ds in the future", ntime); } - merkle = (uchar**) malloc(merkle_count * sizeof(char *)); + if (merkle_count) + merkle = (uchar**) malloc(merkle_count * sizeof(char *)); for (i = 0; i < merkle_count; i++) { const char *s = json_string_value(json_array_get(merkle_arr, i)); if (!s || strlen(s) != 64) { @@ -1301,7 +1363,7 @@ static bool stratum_notify(struct stratum_ctx *sctx, json_t *params) hex2bin(merkle[i], s, 32); } - pthread_mutex_lock(&sctx->work_lock); + pthread_mutex_lock(&stratum_work_lock); coinb1_size = strlen(coinb1) / 2; coinb2_size = strlen(coinb2) / 2; @@ -1341,7 +1403,7 @@ static bool stratum_notify(struct stratum_ctx *sctx, json_t *params) sctx->job.diff = sctx->next_diff; - pthread_mutex_unlock(&sctx->work_lock); + pthread_mutex_unlock(&stratum_work_lock); ret = true; @@ -1358,9 +1420,9 @@ static bool stratum_set_difficulty(struct stratum_ctx *sctx, json_t *params) if (diff <= 0.0) return false; - pthread_mutex_lock(&sctx->work_lock); + pthread_mutex_lock(&stratum_work_lock); sctx->next_diff = diff; - pthread_mutex_unlock(&sctx->work_lock); + pthread_mutex_unlock(&stratum_work_lock); /* store for api stats */ if (diff != stratum_diff) {