diff --git a/README b/README index a8ad8e70..126cb2f6 100644 --- a/README +++ b/README @@ -286,6 +286,10 @@ from 2 to 8. Default 8 miners and is set to 30 seconds by default to help perpetuate transactions for the health of the bitcoin network. +"version_mask" : This is a mask of which bits in the version number it is valid +for a client to alter and is expressed as an hex string. Eg "00fff000" +Default is "1fffe000". + "serverurl" : This is the IP(s) to try to bind ckpool uniquely to, otherwise it will attempt to bind to all interfaces in port 3333 by default in pool mode and 3334 in proxy mode. Multiple entries can be specified as an array by diff --git a/ckpool.conf b/ckpool.conf index 465c5627..d4935da8 100644 --- a/ckpool.conf +++ b/ckpool.conf @@ -20,6 +20,7 @@ "nonce1length" : 4, "nonce2length" : 8, "update_interval" : 30, +"version_mask" : "1fffe000", "serverurl" : [ "ckpool.org:3333", "node.ckpool.org:3333", diff --git a/src/ckpool.c b/src/ckpool.c index 784a6a1f..2cb0565e 100644 --- a/src/ckpool.c +++ b/src/ckpool.c @@ -1,5 +1,5 @@ /* - * Copyright 2014-2017 Con Kolivas + * Copyright 2014-2018 Con Kolivas * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free @@ -980,7 +980,7 @@ bool send_json_msg(connsock_t *cs, const json_t *json_msg) /* Decode a string that should have a json message and return just the contents * of the result key or NULL. */ -static json_t *json_result(json_t *val) +json_t *json_result(json_t *val) { json_t *res_val = NULL, *err_val; @@ -1005,7 +1005,7 @@ static json_t *json_result(json_t *val) } /* Return the error value if one exists */ -static json_t *json_errval(json_t *val) +json_t *json_errval(json_t *val) { json_t *err_val = json_object_get(val, "error"); @@ -1444,8 +1444,8 @@ static void parse_config(ckpool_t *ckp) { json_t *json_conf, *arr_val; json_error_t err_val; + char *url, *vmask; int arr_size; - char *url; json_conf = json_load_file(ckp->config, JSON_DISABLE_EOF_CHECK, &err_val); if (!json_conf) { @@ -1469,6 +1469,11 @@ static void parse_config(ckpool_t *ckp) json_get_int(&ckp->nonce1length, json_conf, "nonce1length"); json_get_int(&ckp->nonce2length, json_conf, "nonce2length"); json_get_int(&ckp->update_interval, json_conf, "update_interval"); + json_get_string(&vmask, json_conf, "version_mask"); + if (vmask && strlen(vmask) && validhex(vmask)) + sscanf(vmask, "%x", &ckp->version_mask); + else + ckp->version_mask = 0x1fffe000; /* Look for an array first and then a single entry */ arr_val = json_object_get(json_conf, "serverurl"); if (!parse_serverurls(ckp, arr_val)) { diff --git a/src/ckpool.h b/src/ckpool.h index bc1cd564..1594f6cc 100644 --- a/src/ckpool.h +++ b/src/ckpool.h @@ -259,6 +259,8 @@ struct ckpool_instance { int update_interval; // Seconds between stratum updates + uint32_t version_mask; // Bits which set to true means allow miner to modify those bits + /* Proxy options */ int proxies; char **proxyurl; @@ -299,6 +301,8 @@ enum stratum_msgtype { SM_SHAREERR, SM_WORKERSTATS, SM_REQTXNS, + SM_CONFIGURE, + SM_VERSIONMASK, SM_NONE }; @@ -325,6 +329,8 @@ static const char __maybe_unused *stratum_msgs[] = { "shareerr", "workerstats", "reqtxns", + "mining.configure", + "vmask", "" }; @@ -367,6 +373,8 @@ json_t *json_rpc_call(connsock_t *cs, const char *rpc_req); json_t *json_rpc_response(connsock_t *cs, const char *rpc_req); void json_rpc_msg(connsock_t *cs, const char *rpc_req); bool send_json_msg(connsock_t *cs, const json_t *json_msg); +json_t *json_result(json_t *val); +json_t *json_errval(json_t *val); json_t *json_msg_result(const char *msg, json_t **res_val, json_t **err_val); bool json_get_string(char **store, const json_t *val, const char *res); diff --git a/src/generator.c b/src/generator.c index a2fa7f47..33dd9fa0 100644 --- a/src/generator.c +++ b/src/generator.c @@ -115,6 +115,8 @@ struct proxy_instance { int nonce1len; int nonce2len; + uint32_t version_mask; + tv_t last_message; double diff; @@ -1317,6 +1319,35 @@ static void send_notify(ckpool_t *ckp, proxy_instance_t *proxi, notify_instance_ send_diff(ckp, proxi); } +static void parse_configure(ckpool_t *ckp, proxy_instance_t *proxy, json_t *val) +{ + bool vroll = false; + json_t *res_val; + const char *buf; + + res_val = json_result(val); + if (!res_val) { + LOGDEBUG("Failed to find result response to mining.configure from proxy %d:%s", + proxy->id, proxy->url); + return; + } + vroll = json_is_true(json_object_get(res_val, "version-rolling")); + if (!vroll) { + LOGINFO("No version rolling from compatible proxy %d:%s", proxy->id, + proxy->url); + return; + } + buf = json_string_value(json_object_get(res_val, "version-rolling.mask")); + if (!buf || !strlen(buf)) { + LOGNOTICE("Invalid version-rolling.mask from proxy %d:%s", proxy->id, + proxy->url); + return; + } + sscanf(buf, "%x", &proxy->version_mask); + LOGINFO("Got vmask %s from proxy %d:%d %s", buf, proxy->id, proxy->subid, proxy->url); + stratum_set_proxy_vmask(ckp, proxy->id, proxy->subid, proxy->version_mask); +} + static bool parse_method(ckpool_t *ckp, proxy_instance_t *proxi, const char *msg) { json_t *val = NULL, *method, *err_val, *params; @@ -1346,6 +1377,9 @@ static bool parse_method(ckpool_t *ckp, proxy_instance_t *proxi, const char *msg if (strstr(msg, "mining.suggest")) { LOGINFO("Unhandled suggest_diff from proxy %d:%s", proxi->id, proxi->url); ret = true; + } else if (strstr(msg, "version-rolling")) { + parse_configure(ckp, proxi, val); + ret = true; } else LOGDEBUG("Failed to find method in json for parse_method"); goto out; @@ -1858,7 +1892,7 @@ static void *proxy_send(void *arg) int64_t client_id = 0, id; notify_instance_t *ni; json_t *jobid = NULL; - json_t *val; + json_t *val, *vmask; if (unlikely(msg)) { json_decref(msg->json_msg); @@ -1927,12 +1961,23 @@ static void *proxy_send(void *arg) continue; } - JSON_CPACK(val, "{s[soooo]soss}", "params", subproxy->auth, jobid, + vmask = json_object_get(msg->json_msg, "vmask"); + if (vmask) { + JSON_CPACK(val, "{s[sooooo]soss}", "params", subproxy->auth, jobid, + json_object_dup(msg->json_msg, "nonce2"), + json_object_dup(msg->json_msg, "ntime"), + json_object_dup(msg->json_msg, "nonce"), + json_copy(vmask), + "id", json_object_dup(msg->json_msg, "id"), + "method", "mining.submit"); + } else { + JSON_CPACK(val, "{s[soooo]soss}", "params", subproxy->auth, jobid, json_object_dup(msg->json_msg, "nonce2"), json_object_dup(msg->json_msg, "ntime"), json_object_dup(msg->json_msg, "nonce"), "id", json_object_dup(msg->json_msg, "id"), "method", "mining.submit"); + } add_json_msgq(&csmsgq, subproxy, &val); send_json_msgq(gdata, &csmsgq); } @@ -2020,6 +2065,30 @@ static void suggest_diff(ckpool_t *ckp, connsock_t *cs, proxy_instance_t *proxy) * if it fails upstream. */ } +static void request_configure(connsock_t *cs, proxy_instance_t *proxy) +{ + json_t *req; + bool ret; + + JSON_CPACK(req, "{s:i,s:s, s:[]}", + "id", 40, + "method", "mining.configure", + "params"); + ret = send_json_msg(cs, req); + json_decref(req); + if (!ret) { + LOGNOTICE("Proxy %d:%d %s failed to send message in request_configure", + proxy->id, proxy->subid, proxy->url); + if (cs->fd > 0) { + epoll_ctl(proxy->epfd, EPOLL_CTL_DEL, cs->fd, NULL); + Close(cs->fd); + } + } + /* Response will be parsed by receiver since response can be wildly + * variable. */ +} + + /* Upon failing connnect, subscribe, or auth, back off on the next attempt. * This function should be called on the parent proxy */ static void proxy_backoff(proxy_instance_t *proxy) @@ -2104,6 +2173,9 @@ static bool proxy_alive(ckpool_t *ckp, proxy_instance_t *proxi, connsock_t *cs, proxy_backoff(parent); goto out; } + /* Put a request for mining configure to see if the upstream pool + * supports version_mask */ + request_configure(cs, proxi); parent->auth_status = STATUS_SUCCESS; proxi->authorised = ret = true; parent->backoff = 0; diff --git a/src/libckpool.h b/src/libckpool.h index 3d29995c..6479bf99 100644 --- a/src/libckpool.h +++ b/src/libckpool.h @@ -267,7 +267,8 @@ enum share_err { SE_STALE, SE_NTIME_INVALID, SE_DUPE, - SE_HIGH_DIFF + SE_HIGH_DIFF, + SE_INVALID_VERSION_MASK }; static const char __maybe_unused *share_errs[] = { @@ -285,7 +286,8 @@ static const char __maybe_unused *share_errs[] = { "Stale", "Ntime out of range", "Duplicate", - "Above target" + "Above target", + "Invalid version mask" }; #define SHARE_ERR(x) share_errs[((x) + 9)] diff --git a/src/stratifier.c b/src/stratifier.c index ef377b6a..5c79739e 100644 --- a/src/stratifier.c +++ b/src/stratifier.c @@ -236,6 +236,9 @@ struct stratum_instance { * or other problem and should be dropped lazily if * this is set to 2 */ + bool vmask; /* Requested vmask */ + uint32_t version_mask; /* Mask to use for this client */ + int latency; /* Latency when on a mining node */ bool reconnect; /* This client really needs to reconnect */ @@ -309,6 +312,8 @@ struct proxy_base { bool subscribed; bool notified; + uint32_t version_mask; + int64_t clients; /* Incrementing client count */ int64_t max_clients; /* Maximum number of clients per subproxy */ int64_t bound_clients; /* Currently actively bound clients */ @@ -1864,7 +1869,8 @@ static void add_node_base(ckpool_t *ckp, json_t *val, bool trusted, int64_t clie /* Calculate share diff and fill in hash and swap. Need to hold workbase read count */ static double share_diff(char *coinbase, const uchar *enonce1bin, const workbase_t *wb, const char *nonce2, - const uint32_t ntime32, const char *nonce, uchar *hash, uchar *swap, int *cblen) + const uint32_t ntime32, uint32_t version_mask, const char *nonce, + uchar *hash, uchar *swap, int *cblen) { unsigned char merkle_root[32], merkle_sha[64]; uint32_t *data32, *swap32, benonce32; @@ -1896,6 +1902,13 @@ share_diff(char *coinbase, const uchar *enonce1bin, const workbase_t *wb, const memcpy(data, wb->headerbin, 80); memcpy(data + 36, merkle_root, 32); + /* Update nVersion when version_mask is in use */ + if (version_mask) { + version_mask = htobe32(version_mask); + data32 = (uint32_t *)data; + *data32 |= version_mask; + } + /* Insert the nonce value into the data */ hex2bin(&benonce32, nonce, 4); data32 = (uint32_t *)(data + 64 + 12); @@ -1974,8 +1987,8 @@ static void send_nodes_block(sdata_t *sdata, const json_t *block_val, const int6 /* Entered with workbase readcount. */ static void send_node_block(ckpool_t *ckp, sdata_t *sdata, const char *enonce1, const char *nonce, - const char *nonce2, const uint32_t ntime32, const int64_t jobid, - const double diff, const int64_t client_id, + const char *nonce2, const uint32_t ntime32, const uint32_t version_mask, + const int64_t jobid, const double diff, const int64_t client_id, const char *coinbase, const int cblen, const uchar *data) { if (sdata->node_instances) { @@ -1985,6 +1998,7 @@ static void send_node_block(ckpool_t *ckp, sdata_t *sdata, const char *enonce1, json_set_string(val, "nonce", nonce); json_set_string(val, "nonce2", nonce2); json_set_uint32(val, "ntime32", ntime32); + json_set_uint32(val, "version_mask", version_mask); json_set_int64(val, "jobid", jobid); json_set_double(val, "diff", diff); add_remote_blockdata(ckp, val, cblen, coinbase, data); @@ -2112,11 +2126,11 @@ static void submit_node_block(ckpool_t *ckp, sdata_t *sdata, json_t *val) char *coinbase = NULL, *enonce1 = NULL, *nonce = NULL, *nonce2 = NULL, *gbt_block, *coinbasehex, *swaphex; uchar *enonce1bin = NULL, hash[32], swap[80], flip32[32]; + uint32_t ntime32, version_mask = 0; char blockhash[68], cdfield[64]; json_t *bval, *bval_copy; int enonce1len, cblen; workbase_t *wb = NULL; - uint32_t ntime32; double diff; ts_t ts_now; int64_t id; @@ -2147,6 +2161,11 @@ static void submit_node_block(ckpool_t *ckp, sdata_t *sdata, json_t *val) goto out; } + if (!json_get_uint32(&version_mask, val, "version_mask")) { + /* No version mask is not fatal, assume it to be zero */ + LOGINFO("No version mask in node method block"); + } + LOGWARNING("Possible upstream block solve diff %lf !", diff); ts_realtime(&ts_now); @@ -2178,20 +2197,21 @@ static void submit_node_block(ckpool_t *ckp, sdata_t *sdata, json_t *val) hex2bin(enonce1bin, enonce1, enonce1len); coinbase = alloca(wb->coinb1len + wb->enonce1constlen + wb->enonce1varlen + wb->enonce2varlen + wb->coinb2len); /* Fill in the hashes */ - share_diff(coinbase, enonce1bin, wb, nonce2, ntime32, nonce, hash, swap, &cblen); + share_diff(coinbase, enonce1bin, wb, nonce2, ntime32, version_mask, nonce, hash, swap, &cblen); } /* Now we have enough to assemble a block */ gbt_block = process_block(wb, coinbase, cblen, swap, hash, flip32, blockhash); ret = local_block_submit(ckp, gbt_block, flip32, wb->height); - JSON_CPACK(bval, "{si,ss,ss,sI,ss,ss,ss,sI,sf,ss,ss,ss,ss}", + JSON_CPACK(bval, "{si,ss,ss,sI,ss,ss,si,ss,sI,sf,ss,ss,ss,ss}", "height", wb->height, "blockhash", blockhash, "confirmed", "n", "workinfoid", wb->id, "enonce1", enonce1, "nonce2", nonce2, + "version_mask", version_mask, "nonce", nonce, "reward", wb->coinbasevalue, "diff", diff, @@ -2626,6 +2646,11 @@ static void reconnect_global_clients(sdata_t *sdata) continue; if (!client->authorised) continue; + /* Does the client mandate a vmask but the best proxy not + * support it? Not ideal because there may be a better priority + * pool below the best priority one that does support it. */ + if (client->vmask && !proxy->version_mask) + continue; /* Is this client bound to a dead proxy? */ if (!client->reconnect) { /* This client is bound to a user proxy */ @@ -2873,31 +2898,6 @@ static void update_subscribe(ckpool_t *ckp, const char *cmd) check_proxy(sdata, proxy); } -/* Find the highest priority alive proxy belonging to userid and recruit extra - * subproxies. */ -static void recruit_best_userproxy(sdata_t *sdata, const int userid, const int recruits) -{ - proxy_t *proxy, *subproxy, *tmp, *subtmp; - int id = -1; - - mutex_lock(&sdata->proxy_lock); - HASH_ITER(hh, sdata->proxies, proxy, tmp) { - if (proxy->userid < userid) - continue; - if (proxy->userid > userid) - break; - HASH_ITER(sh, proxy->subproxies, subproxy, subtmp) { - if (subproxy->dead) - continue; - id = proxy->id; - } - } - mutex_unlock(&sdata->proxy_lock); - - if (id != -1) - generator_recruit(sdata->ckp, id, recruits); -} - /* Check how much headroom the userid proxies have and reconnect any clients * that are not bound to it that should be */ static void check_userproxies(sdata_t *sdata, proxy_t *proxy, const int userid) @@ -2919,6 +2919,10 @@ static void check_userproxies(sdata_t *sdata, proxy_t *proxy, const int userid) if (client->proxy->userid == userid && client->proxy->parent->priority <= proxy->parent->priority) continue; + /* Tested proxy doesn't have vmask support while client + * mandates it. */ + if (client->vmask && !proxy->version_mask) + continue; if (headroom-- < 1) continue; reconnects++; @@ -2927,11 +2931,11 @@ static void check_userproxies(sdata_t *sdata, proxy_t *proxy, const int userid) ck_runlock(&sdata->instance_lock); if (reconnects) { - LOGINFO("%d clients flagged for reconnect to user %d proxies", - reconnects, userid); + LOGINFO("%d clients flagged for reconnect to user %d proxy %d", + reconnects, userid, proxy->id); } if (headroom < 0) - recruit_best_userproxy(sdata, userid, -headroom); + generator_recruit(sdata->ckp, proxy->id, -headroom); } static void update_notify(ckpool_t *ckp, const char *cmd) @@ -3032,6 +3036,15 @@ out: json_decref(val); } +void stratum_set_proxy_vmask(ckpool_t *ckp, int id, int subid, uint32_t version_mask) +{ + proxy_t *proxy; + + proxy = existing_subproxy(ckp->sdata, id, subid); + proxy->version_mask = version_mask; + LOGINFO("Stratum Proxy %d:%d had version mask set to %08x", id, subid, version_mask); +} + static void stratum_send_diff(sdata_t *sdata, const stratum_instance_t *client); static void update_diff(ckpool_t *ckp, const char *cmd) @@ -4720,7 +4733,7 @@ static bool new_enonce1(ckpool_t *ckp, sdata_t *ckp_sdata, sdata_t *sdata, strat static void stratum_send_message(sdata_t *sdata, const stratum_instance_t *client, const char *msg); /* Need to hold sdata->proxy_lock */ -static proxy_t *__best_subproxy(proxy_t *proxy) +static proxy_t *__best_subproxy(proxy_t *proxy, const bool vmask) { proxy_t *subproxy, *best = NULL, *tmp; int64_t max_headroom; @@ -4733,6 +4746,8 @@ static proxy_t *__best_subproxy(proxy_t *proxy) continue; if (!subproxy->sdata->current_workbase) continue; + if (vmask && !subproxy->version_mask) + continue; /* This subproxy data is checked without holding the correct * instance_lock but an incorrect value here is harmless */ subproxy_headroom = subproxy->max_clients - subproxy->clients - subproxy->connecting; @@ -4751,8 +4766,9 @@ static proxy_t *__best_subproxy(proxy_t *proxy) /* Choose the stratifier data for a new client. Use the main ckp_sdata except * in proxy mode where we find a subproxy based on the current proxy with room * for more clients. Signal the generator to recruit more subproxies if we are - * running out of room. */ -static sdata_t *select_sdata(ckpool_t *ckp, sdata_t *ckp_sdata, const int userid) + * running out of room. Needs to be entered with client holding a ref count */ +static sdata_t *select_sdata(ckpool_t *ckp, sdata_t *ckp_sdata, const bool vmask, + const int userid) { proxy_t *global, *proxy, *tmp, *best = NULL; @@ -4762,14 +4778,18 @@ static sdata_t *select_sdata(ckpool_t *ckp, sdata_t *ckp_sdata, const int userid /* Proxies are ordered by priority so first available will be the best * priority */ mutex_lock(&ckp_sdata->proxy_lock); - best = global = ckp_sdata->proxy; + global = ckp_sdata->proxy; + /* If the client needs a version mask, only use sdata from pools with + * one set. */ + if (!vmask || ckp->version_mask) + best = global; HASH_ITER(hh, ckp_sdata->proxies, proxy, tmp) { if (proxy->userid < userid) continue; if (proxy->userid > userid) break; - best = __best_subproxy(proxy); + best = __best_subproxy(proxy, vmask); if (best) break; } @@ -4881,7 +4901,7 @@ static json_t *parse_subscribe(stratum_instance_t *client, const int64_t client_ return json_string("params not an array"); } - sdata = select_sdata(ckp, ckp_sdata, 0); + sdata = select_sdata(ckp, ckp_sdata, client->vmask, 0); if (unlikely(!ckp->node && (!sdata || !sdata->current_workbase))) { LOGWARNING("Failed to provide subscription due to no %s", sdata ? "current workbase" : "sdata"); stratum_send_message(ckp_sdata, client, "Pool Initialising"); @@ -4936,7 +4956,7 @@ static json_t *parse_subscribe(stratum_instance_t *client, const int64_t client_ if (userid == -1) userid = userid_from_sessionip(ckp_sdata, client->address); if (userid != -1) { - sdata_t *user_sdata = select_sdata(ckp, ckp_sdata, userid); + sdata_t *user_sdata = select_sdata(ckp, ckp_sdata, client->vmask, userid); if (user_sdata) sdata = user_sdata; @@ -5866,7 +5886,8 @@ downstream_block(ckpool_t *ckp, sdata_t *sdata, const json_t *val, const int cbl static void test_blocksolve(const stratum_instance_t *client, const workbase_t *wb, const uchar *data, const uchar *hash, const double diff, const char *coinbase, int cblen, - const char *nonce2, const char *nonce, const uint32_t ntime32, const bool stale) + const char *nonce2, const char *nonce, const uint32_t ntime32, const uint32_t version_mask, + const bool stale) { char blockhash[68], cdfield[64], *gbt_block; sdata_t *sdata = client->sdata; @@ -5889,8 +5910,8 @@ test_blocksolve(const stratum_instance_t *client, const workbase_t *wb, const uc sprintf(cdfield, "%lu,%lu", ts_now.tv_sec, ts_now.tv_nsec); gbt_block = process_block(wb, coinbase, cblen, data, hash, flip32, blockhash); - send_node_block(ckp, sdata, client->enonce1, nonce, nonce2, ntime32, wb->id, - diff, client->id, coinbase, cblen, data); + send_node_block(ckp, sdata, client->enonce1, nonce, nonce2, ntime32, version_mask, + wb->id, diff, client->id, coinbase, cblen, data); val = json_object(); json_set_int(val, "height", wb->height); @@ -5907,6 +5928,7 @@ test_blocksolve(const stratum_instance_t *client, const workbase_t *wb, const uc json_set_string(val, "nonce2", nonce2); json_set_string(val, "nonce", nonce); json_set_uint32(val, "ntime32", ntime32); + json_set_uint32(val, "version_mask", version_mask); json_set_int64(val, "reward", wb->coinbasevalue); json_set_double(val, "diff", diff); json_set_string(val, "createdate", cdfield); @@ -5936,7 +5958,8 @@ test_blocksolve(const stratum_instance_t *client, const workbase_t *wb, const uc /* Needs to be entered with workbase readcount and client holding a ref count. */ static double submission_diff(const stratum_instance_t *client, const workbase_t *wb, const char *nonce2, - const uint32_t ntime32, const char *nonce, uchar *hash, const bool stale) + const uint32_t ntime32, const uint32_t version_mask, + const char *nonce, uchar *hash, const bool stale) { char *coinbase; uchar swap[80]; @@ -5946,10 +5969,10 @@ static double submission_diff(const stratum_instance_t *client, const workbase_t coinbase = ckalloc(wb->coinb1len + wb->enonce1constlen + wb->enonce1varlen + wb->enonce2varlen + wb->coinb2len); /* Calculate the diff of the share here */ - ret = share_diff(coinbase, client->enonce1bin, wb, nonce2, ntime32, nonce, hash, swap, &cblen); + ret = share_diff(coinbase, client->enonce1bin, wb, nonce2, ntime32, version_mask, nonce, hash, swap, &cblen); /* Test we haven't solved a block regardless of share status */ - test_blocksolve(client, wb, swap, hash, ret, coinbase, cblen, nonce2, nonce, ntime32, stale); + test_blocksolve(client, wb, swap, hash, ret, coinbase, cblen, nonce2, nonce, ntime32, version_mask, stale); free(coinbase); @@ -5984,7 +6007,7 @@ static void update_client(const stratum_instance_t *client, const int64_t client /* Submit a share in proxy mode to the parent pool. workbase_lock is held. * Needs to be entered with client holding a ref count. */ static void submit_share(stratum_instance_t *client, const int64_t jobid, const char *nonce2, - const char *ntime, const char *nonce) + const char *ntime, const char *nonce, const char *version_mask) { ckpool_t *ckp = client->ckp; json_t *json_msg; @@ -5994,6 +6017,8 @@ static void submit_share(stratum_instance_t *client, const int64_t jobid, const JSON_CPACK(json_msg, "{sIsssssssIsIsi}", "jobid", jobid, "nonce2", enonce2, "ntime", ntime, "nonce", nonce, "client_id", client->id, "proxy", client->proxyid, "subproxy", client->subproxyid); + if (version_mask) + json_set_string(json_msg, "vmask", version_mask); generator_add_send(ckp, json_msg); } @@ -6024,17 +6049,17 @@ static json_t *parse_submit(stratum_instance_t *client, json_t *json_msg, const json_t *params_val, json_t **err_val) { bool share = false, result = false, invalid = true, submit = false, stale = false; + const char *workername, *job_id, *ntime, *nonce, *version_mask; double diff = client->diff, wdiff = 0, sdiff = -1; char hexhash[68] = {}, sharehash[32], cdfield[64]; - const char *workername, *job_id, *ntime, *nonce; user_instance_t *user = client->user_instance; + uint32_t ntime32, version_mask32 = 0; char *fname = NULL, *s, *nonce2; sdata_t *sdata = client->sdata; enum share_err err = SE_NONE; ckpool_t *ckp = client->ckp; char idstring[20] = {}; workbase_t *wb = NULL; - uint32_t ntime32; uchar hash[32]; int nlen, len; time_t now_t; @@ -6087,6 +6112,18 @@ static json_t *parse_submit(stratum_instance_t *client, json_t *json_msg, *err_val = JSON_ERR(err); goto out; } + + version_mask = json_string_value(json_array_get(params_val, 5)); + if (version_mask && strlen(version_mask) && validhex(version_mask)) { + sscanf(version_mask, "%x", &version_mask32); + // check version mask + if (version_mask32 && ((~client->version_mask) & version_mask32) != 0) { + // means client changed some bits which server doesn't allow to change + err = SE_INVALID_VERSION_MASK; + *err_val = JSON_ERR(err); + goto out; + } + } if (safecmp(workername, client->workername)) { err = SE_WORKER_MISMATCH; *err_val = JSON_ERR(err); @@ -6128,7 +6165,7 @@ static json_t *parse_submit(stratum_instance_t *client, json_t *json_msg, } if (id < sdata->blockchange_id) stale = true; - sdiff = submission_diff(client, wb, nonce2, ntime32, nonce, hash, stale); + sdiff = submission_diff(client, wb, nonce2, ntime32, version_mask32, nonce, hash, stale); if (sdiff > client->best_diff) { worker_instance_t *worker = client->worker_instance; @@ -6207,7 +6244,7 @@ out_nowb: * stale shares and filter out the rest. */ if (wb && wb->proxy && submit) { LOGINFO("Submitting share upstream: %s", hexhash); - submit_share(client, id, nonce2, ntime, nonce); + submit_share(client, id, nonce2, ntime, nonce, version_mask); } add_submit(ckp, client, diff, result, submit); @@ -6431,11 +6468,30 @@ static void suggest_diff(ckpool_t *ckp, stratum_instance_t *client, const char * stratum_send_diff(ckp->sdata, client); } +/* Needs to be entered with client holding a ref count */ +static void stratum_send_version_mask(sdata_t *sdata, stratum_instance_t *client) +{ + char version_str[12]; + json_t *json_msg; + + if (unlikely(!client->proxy)) { + LOGERR("stratum_send_version_mask called on a non proxied client"); + return; + } + client->version_mask = client->proxy->version_mask; + sprintf(version_str, "%08x", client->version_mask); + JSON_CPACK(json_msg, "{s[s]soss}", "params", version_str, "id", json_null(), + "method", "mining.set_version_mask"); + stratum_add_send(sdata, json_msg, client->id, SM_VERSIONMASK); +} + /* Send diff first when sending the first stratum template after subscribing */ -static void init_client(const stratum_instance_t *client, const int64_t client_id) +static void init_client(ckpool_t *ckp, stratum_instance_t *client, const int64_t client_id) { sdata_t *sdata = client->sdata; + if (ckp->proxy && client->vmask) + stratum_send_version_mask(client->sdata, client); stratum_send_diff(sdata, client); stratum_send_update(sdata, client_id, true); } @@ -6561,7 +6617,7 @@ static void parse_method(ckpool_t *ckp, sdata_t *sdata, stratum_instance_t *clie json_object_set_new_nocheck(val, "error", json_null()); stratum_add_send(sdata, val, client_id, SM_SUBSCRIBERESULT); if (likely(client->subscribed)) - init_client(client, client_id); + init_client(ckp, client, client_id); return; } @@ -6638,6 +6694,27 @@ static void parse_method(ckpool_t *ckp, sdata_t *sdata, stratum_instance_t *clie return; } + if (cmdmatch(method, "mining.configure")) { + json_t *val, *result_val; + char version_str[12]; + + LOGINFO("Mining configure requested from %s %s", client->identity, + client->address); + client->vmask = true; + /* Send a temporary vmask in proxy mode till we know what the + * real vmask will be for the upstream pool. */ + sprintf(version_str, "%08x", ckp->version_mask); + client->version_mask = ckp->version_mask; + val = json_object(); + JSON_CPACK(result_val, "{sbss}", "version-rolling", json_true(), + "version-rolling.mask", version_str); + json_object_set_new_nocheck(val, "result", result_val); + json_object_set_nocheck(val, "id", id_val); + json_object_set_new_nocheck(val, "error", json_null()); + stratum_add_send(sdata, val, client_id, SM_CONFIGURE); + return; + } + /* We should only accept requests from subscribed and authed users here * on */ if (!client->subscribed) { @@ -7682,13 +7759,14 @@ static void sauth_process(ckpool_t *ckp, json_params_t *jp) mindiff = client->suggest_diff; else mindiff = client->worker_instance->mindiff; - if (!mindiff) - goto out; - mindiff = MAX(ckp->mindiff, mindiff); - if (mindiff != client->diff) { - client->diff = mindiff; - stratum_send_diff(sdata, client); + if (mindiff) { + mindiff = MAX(ckp->mindiff, mindiff); + if (mindiff != client->diff) { + client->diff = mindiff; + stratum_send_diff(sdata, client); + } } + out: dec_instance_ref(sdata, client); out_noclient: diff --git a/src/stratifier.h b/src/stratifier.h index cc201890..1809c023 100644 --- a/src/stratifier.h +++ b/src/stratifier.h @@ -1,5 +1,5 @@ /* - * Copyright 2014-2017 Con Kolivas + * Copyright 2014-2018 Con Kolivas * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free @@ -86,6 +86,7 @@ struct genwork { json_t *json; /* getblocktemplate json */ }; +void stratum_set_proxy_vmask(ckpool_t *ckp, int id, int subid, uint32_t version_mask); void parse_remote_txns(ckpool_t *ckp, const json_t *val); #define parse_upstream_txns(ckp, val) parse_remote_txns(ckp, val) void parse_upstream_auth(ckpool_t *ckp, json_t *val);